Index: /tags/2.4.3/.editorconfig
===================================================================
--- /tags/2.4.3/.editorconfig	(revision 10410)
+++ /tags/2.4.3/.editorconfig	(revision 10410)
@@ -0,0 +1,18 @@
+# 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
+
+[{*.json,*.yml}]
+indent_style = space
+indent_size = 2
Index: /tags/2.4.3/.gitignore
===================================================================
--- /tags/2.4.3/.gitignore	(revision 10410)
+++ /tags/2.4.3/.gitignore	(revision 10410)
@@ -0,0 +1,23 @@
+.DS_Store
+phpunit.xml
+.idea
+
+lib-cov
+*.seed
+*.log
+*.csv
+*.dat
+*.out
+*.pid
+*.gz
+
+pids
+logs
+results
+src/vendor
+
+node_modules
+npm-debug.log
+
+# The build directory.
+build
Index: /tags/2.4.3/.jshintignore
===================================================================
--- /tags/2.4.3/.jshintignore	(revision 10410)
+++ /tags/2.4.3/.jshintignore	(revision 10410)
@@ -0,0 +1,9 @@
+// Deprecated
+src/bp-forums/bbpress/**/*
+src/bp-core/deprecated/js/**/*
+
+// 3rd party libraries
+src/bp-core/js/jquery.atwho.js
+src/bp-core/js/jquery.caret.js
+src/bp-core/js/jquery-cookie.js
+src/bp-core/js/jquery-scroll-to.js
Index: /tags/2.4.3/.jshintrc
===================================================================
--- /tags/2.4.3/.jshintrc	(revision 10410)
+++ /tags/2.4.3/.jshintrc	(revision 10410)
@@ -0,0 +1,23 @@
+{
+	"boss": true,
+	"curly": true,
+	"eqeqeq": true,
+	"eqnull": true,
+	"es3": true,
+	"expr": true,
+	"immed": true,
+	"noarg": true,
+	"quotmark": "single",
+	"undef": true,
+	"unused": true,
+
+	"browser": true,
+
+	"globals": {
+		"_": false,
+		"ajaxurl": false,
+		"Backbone": false,
+		"jQuery": false,
+		"wp": false
+	}
+}
Index: /tags/2.4.3/.scss-lint.yml
===================================================================
--- /tags/2.4.3/.scss-lint.yml	(revision 10410)
+++ /tags/2.4.3/.scss-lint.yml	(revision 10410)
@@ -0,0 +1,190 @@
+# Default application configuration that all configurations inherit from.
+
+scss_files: "**/*.scss"
+
+linters:
+  BangFormat:
+    enabled: true
+    space_before_bang: true
+    space_after_bang: false
+
+  BorderZero:
+    enabled: true
+    convention: zero # or `none`
+
+  ColorKeyword:
+    enabled: true
+
+  ColorVariable:
+    enabled: false
+
+  Comment:
+    enabled: false
+
+  DebugStatement:
+    enabled: true
+
+  DeclarationOrder:
+    enabled: false
+
+  DuplicateProperty:
+    enabled: false
+
+  ElsePlacement:
+    enabled: true
+    style: same_line # or 'new_line'
+
+  EmptyLineBetweenBlocks:
+    enabled: true
+    ignore_single_line_blocks: true
+
+  EmptyRule:
+    enabled: true
+
+  FinalNewline:
+    enabled: true
+    present: true
+
+  HexLength:
+    enabled: true
+    style: short # or 'long'
+
+  HexNotation:
+    enabled: true
+    style: lowercase # or 'uppercase'
+
+  HexValidation:
+    enabled: true
+
+  IdSelector:
+    enabled: false
+
+  ImportantRule:
+    enabled: false
+
+  ImportPath:
+    enabled: true
+    leading_underscore: false
+    filename_extension: false
+
+  Indentation:
+    enabled: true
+    allow_non_nested_indentation: false
+    character: tab # or 'space'
+    width: 1
+
+  LeadingZero:
+    enabled: true
+    style: include_zero # or 'include_zero'
+
+  MergeableSelector:
+    enabled: false
+    force_nesting: true
+
+  NameFormat:
+    enabled: true
+    allow_leading_underscore: true
+    convention: hyphenated_lowercase # or 'BEM', or a regex pattern
+
+  NestingDepth:
+    enabled: true
+    max_depth: 7
+
+  PlaceholderInExtend:
+    enabled: true
+
+  PropertyCount:
+    enabled: false
+    include_nested: false
+    max_properties: 10
+
+  PropertySortOrder:
+    enabled: true
+    ignore_unspecified: false
+    separate_groups: false
+
+  PropertySpelling:
+    enabled: true
+    extra_properties: []
+
+  QualifyingElement:
+    enabled: false
+    allow_element_with_attribute: false
+    allow_element_with_class: false
+    allow_element_with_id: false
+
+  SelectorDepth:
+    enabled: true
+    max_depth: 6
+
+  SelectorFormat:
+    enabled: true
+    convention: hyphenated_lowercase # or 'BEM', or 'hyphenated_BEM', or 'snake_case', or 'camel_case', or a regex pattern
+    ignored_names: 'signup_form'
+
+  Shorthand:
+    enabled: false
+
+  SingleLinePerProperty:
+    enabled: true
+    allow_single_line_rule_sets: true
+
+  SingleLinePerSelector:
+    enabled: true
+
+  SpaceAfterComma:
+    enabled: true
+
+  SpaceAfterPropertyColon:
+    enabled: true
+    style: one_space # or 'no_space', or 'at_least_one_space', or 'aligned'
+
+  SpaceAfterPropertyName:
+    enabled: true
+
+  SpaceBeforeBrace:
+    enabled: true
+    style: space # or 'new_line'
+    allow_single_line_padding: false
+
+  SpaceBetweenParens:
+    enabled: true
+    spaces: 0
+
+  StringQuotes:
+    enabled: true
+    style: double_quotes
+
+  TrailingSemicolon:
+    enabled: true
+
+  TrailingZero:
+    enabled: false
+
+  UnnecessaryMantissa:
+    enabled: true
+
+  UnnecessaryParentReference:
+    enabled: true
+
+  UrlFormat:
+    enabled: true
+
+  UrlQuotes:
+    enabled: true
+
+  VariableForProperty:
+    enabled: false
+    properties: []
+
+  VendorPrefix:
+    enabled: true
+    identifier_list: base
+    additional_identifiers: []
+    excluded_identifiers: []
+
+  ZeroUnit:
+    enabled: true
+
+  Compass::*:
+    enabled: false
Index: /tags/2.4.3/.travis.yml
===================================================================
--- /tags/2.4.3/.travis.yml	(revision 10410)
+++ /tags/2.4.3/.travis.yml	(revision 10410)
@@ -0,0 +1,75 @@
+# Travis CI Configuration File
+
+# Use new Travis container-based infrastructure
+# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
+sudo: false
+
+language: php
+
+php:
+    - 5.2
+    - 5.3
+    - 5.4
+    - 5.5
+    - 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=4.3
+    - WP_VERSION=4.2
+    - WP_VERSION=4.1
+    - WP_VERSION=4.0
+    - WP_VERSION=3.9
+    - WP_VERSION=3.8
+
+matrix:
+  fast_finish: true
+  include:
+    - php: nightly
+      env: WP_VERSION=master
+  allow_failures:
+    - php: nightly
+
+# before_install: Failures in this section will result in build status 'errored'
+before_install:
+    # set up WP install
+    - export WP_DEVELOP_DIR=/tmp/wordpress/
+    - mkdir -p $WP_DEVELOP_DIR
+    - git clone --depth=1 --branch="$WP_VERSION" git://develop.git.wordpress.org/ $WP_DEVELOP_DIR
+    - 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
+    - npm install -g grunt-cli
+    - gem install scss-lint
+
+# before_script: Failures in this section will result in build status 'errored'
+before_script:
+    - npm install
+
+# Run tests
+script: grunt travis
+
+notifications:
+    email: false
+
+    irc:
+        channels:
+            - secure: "Z+gAlyOafrw0Xg294np+7IpqOKTsswGjAWyOZsbzT7Xo8uY1nmz8tTuU50Gpo8jo5HQqG9G0QgI3zB3Cxy3M5dF+J5oLl1lBz1zL0mtHiq1HoXi2cbVnGCCpxtMvYII6UmPUGAg8zvNUQol+cw1jb3PzLwEDfGcDH64o/Z3j8Gg="
+        template:
+            - "Build %{build_number} (%{branch} - %{commit}): %{message} %{build_url}"
+    slack:
+        secure: JQT+HWoVhLzoG00HmJscYZO+Gy7ywnIlrUbCaI7DxFdFOIkKxsTLLq9cKE4y3VqgZAARTOKjP1eQ7a5OEg1vT11Ow+rB7iUAMNmITxXO4feTOx1i2673xRNBro/SZMhlLd7DY2erbMDZaG9hBQB4xLXRYOvP29pLxf5zTSyZMyE=
Index: /tags/2.4.3/Gruntfile.js
===================================================================
--- /tags/2.4.3/Gruntfile.js	(revision 10410)
+++ /tags/2.4.3/Gruntfile.js	(revision 10410)
@@ -0,0 +1,281 @@
+/* jshint node:true */
+/* global module */
+module.exports = function( grunt ) {
+	var SOURCE_DIR = 'src/',
+		BUILD_DIR = 'build/',
+
+		BP_CSS = [
+			'**/*.css'
+		],
+
+		// CSS exclusions, for excluding files from certain tasks, e.g. cssjanus
+		BP_EXCLUDED_CSS = [
+			'!**/*-rtl.css'
+		],
+
+		BP_JS = [
+			'**/*.js'
+		],
+
+		BP_EXCLUDED_MISC = [
+			'!bp-forums/bbpress/**/*'
+		];
+
+	require( 'matchdep' ).filterDev( ['grunt-*', '!grunt-legacy-util'] ).forEach( grunt.loadNpmTasks );
+	grunt.util = require( 'grunt-legacy-util' );
+
+	grunt.initConfig( {
+		pkg: grunt.file.readJSON( 'package.json' ),
+		jshint: {
+			options: grunt.file.readJSON( '.jshintrc' ),
+			grunt: {
+				src: ['Gruntfile.js']
+			},
+			core: {
+				expand: true,
+				cwd: SOURCE_DIR,
+				src: BP_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;
+				}
+			}
+		},
+		sass: {
+			styles: {
+				cwd: SOURCE_DIR,
+				extDot: 'last',
+				expand: true,
+				ext: '.css',
+				flatten: true,
+				src: ['bp-templates/bp-legacy/css/*.scss'],
+				dest: SOURCE_DIR + 'bp-templates/bp-legacy/css/',
+				options: {
+					outputStyle: 'expanded',
+					indentType: 'tab',
+					indentWidth: '1'
+				}
+			}
+		},
+		cssjanus: {
+			core: {
+				expand: true,
+				cwd: SOURCE_DIR,
+				dest: SOURCE_DIR,
+				extDot: 'last',
+				ext: '-rtl.css',
+				src: BP_CSS.concat( BP_EXCLUDED_CSS, BP_EXCLUDED_MISC ),
+				options: { generateExactDuplicates: true }
+			}
+		},
+		checktextdomain: {
+			options: {
+				correct_domain: false,
+				text_domain: 'buddypress',
+				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: {
+				cwd: SOURCE_DIR,
+				src: ['**/*.php'].concat( BP_EXCLUDED_MISC ),
+				expand: true
+			}
+		},
+		makepot: {
+			target: {
+				options: {
+					cwd: BUILD_DIR,
+					domainPath: '.',
+					mainFile: 'bp-loader.php',
+					potFilename: 'buddypress.pot',
+					processPot: function( pot ) {
+						pot.headers['report-msgid-bugs-to'] = 'https://buddypress.trac.wordpress.org';
+						pot.headers['last-translator'] = 'JOHN JAMES JACOBY <jjj@buddypress.org>';
+						pot.headers['language-team'] = 'ENGLISH <jjj@buddypress.org>';
+						return pot;
+					},
+					type: 'wp-plugin'
+				}
+			}
+		},
+		imagemin: {
+			core: {
+				expand: true,
+				cwd: SOURCE_DIR,
+				src: ['**/*.{gif,jpg,jpeg,png}'].concat( BP_EXCLUDED_MISC ),
+				dest: SOURCE_DIR
+			}
+		},
+		clean: {
+			all: [ BUILD_DIR ]
+		},
+		copy: {
+			files: {
+				files: [
+					{
+						cwd: SOURCE_DIR,
+						dest: BUILD_DIR,
+						dot: true,
+						expand: true,
+						src: ['**', '!**/.{svn,git}/**'].concat( BP_EXCLUDED_MISC )
+					}
+				]
+			}
+		},
+		uglify: {
+			core: {
+				cwd: BUILD_DIR,
+				dest: BUILD_DIR,
+				extDot: 'last',
+				expand: true,
+				ext: '.min.js',
+				src: BP_JS
+			},
+			options: {
+				banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
+				'<%= grunt.template.today("UTC:yyyy-mm-dd h:MM:ss TT Z") %> - ' +
+				'https://wordpress.org/plugins/buddypress/ */\n'
+			}
+		},
+		scsslint: {
+			options: {
+				bundleExec: false,
+				colorizeOutput: true,
+				config: '.scss-lint.yml'
+			},
+			core: [ SOURCE_DIR + 'bp-templates/bp-legacy/css/*.scss' ]
+		},
+		cssmin: {
+			minify: {
+				cwd: BUILD_DIR,
+				dest: BUILD_DIR,
+				extDot: 'last',
+				expand: true,
+				ext: '.min.css',
+				src: BP_CSS,
+				options: {
+					banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
+					'<%= grunt.template.today("UTC:yyyy-mm-dd h:MM:ss TT Z") %> - ' +
+					'https://wordpress.org/plugins/buddypress/ */'
+				}
+			}
+		},
+		phpunit: {
+			'default': {
+				cmd: 'phpunit',
+				args: ['-c', 'phpunit.xml.dist']
+			},
+			'multisite': {
+				cmd: 'phpunit',
+				args: ['-c', 'tests/phpunit/multisite.xml']
+			}
+		},
+		exec: {
+			bbpress: {
+				command: 'svn export --force https://bbpress.svn.wordpress.org/tags/1.2 bbpress',
+				cwd: BUILD_DIR + 'bp-forums',
+				stdout: false
+			},
+			bpdefault: {
+				command: 'svn export --force https://github.com/buddypress/BP-Default.git/trunk bp-themes/bp-default',
+				cwd: BUILD_DIR,
+				stdout: false
+			}
+		},
+		jsvalidate:{
+			options:{
+				globals: {},
+				esprimaOptions:{},
+				verbose: false
+			},
+			build: {
+				files: {
+					src: [BUILD_DIR + '/**/*.js']
+				}
+			},
+			src: {
+				files: {
+					src: [SOURCE_DIR + '/**/*.js'].concat( BP_EXCLUDED_MISC )
+				}
+			}
+		},
+		patch: {
+			options: {
+				tracUrl: 'buddypress.trac.wordpress.org'
+			}
+		}
+	});
+
+
+	/**
+	 * Register tasks.
+	 */
+	grunt.registerTask( 'src',     ['jsvalidate:src', 'jshint', 'scsslint', 'sass', 'cssjanus'] );
+	grunt.registerTask( 'commit',  ['src', 'checktextdomain', 'imagemin'] );
+	grunt.registerTask( 'build',   ['commit', 'clean:all', 'copy:files', 'uglify', 'jsvalidate:build', 'cssmin', 'makepot', 'exec:bpdefault'] );
+	grunt.registerTask( 'release', ['build', 'exec:bbpress'] );
+
+	// Testing tasks.
+	grunt.registerMultiTask( 'phpunit', 'Runs PHPUnit tests, including the ajax and multisite tests.', function() {
+		grunt.util.spawn( {
+			args: this.data.args,
+			cmd:  this.data.cmd,
+			opts: { stdio: 'inherit' }
+		}, this.async() );
+	});
+
+	grunt.registerTask( 'test', 'Run all unit test tasks.', ['phpunit'] );
+
+	grunt.registerTask( 'jstest', 'Runs all JavaScript tasks.', [ 'jsvalidate:src', 'jshint' ] );
+
+	// Travis CI Task
+	grunt.registerTask( 'travis', ['jsvalidate:src', 'jshint', 'checktextdomain', 'test'] );
+
+	// Patch task.
+	grunt.renameTask( 'patch_wordpress', 'patch' );
+
+	// Default task.
+	grunt.registerTask( 'default', ['src'] );
+};
Index: /tags/2.4.3/bp-loader.php
===================================================================
--- /tags/2.4.3/bp-loader.php	(revision 10410)
+++ /tags/2.4.3/bp-loader.php	(revision 10410)
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * The BuddyPress Plugin
+ *
+ * BuddyPress is social networking software with a twist from the creators of WordPress.
+ *
+ * @package BuddyPress
+ * @subpackage Main
+ */
+
+/**
+ * Plugin Name: BuddyPress
+ * Plugin URI:  https://buddypress.org
+ * Description: BuddyPress helps you run any kind of social network on your WordPress, with member profiles, activity streams, user groups, messaging, and more.
+ * Author:      The BuddyPress Community
+ * Author URI:  https://buddypress.org
+ * Version:     2.4.3
+ * Text Domain: buddypress
+ * Domain Path: /bp-languages/
+ * License:     GPLv2 or later (license.txt)
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+// Assume you want to load from build
+$bp_loader = dirname( __FILE__ ) . '/build/bp-loader.php';
+
+// Load from source if no build exists
+if ( ! file_exists( $bp_loader ) || defined( 'BP_LOAD_SOURCE' ) ) {
+	$bp_loader = dirname( __FILE__ ) . '/src/bp-loader.php';
+	$subdir = 'src';
+} else {
+	$subdir = 'build';
+}
+
+// Set source subdirectory
+define( 'BP_SOURCE_SUBDIRECTORY', $subdir );
+
+// Define overrides - only applicable to those running trunk
+if ( ! defined( 'BP_PLUGIN_DIR' ) ) {
+	define( 'BP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
+}
+if ( ! defined( 'BP_PLUGIN_URL' ) ) {
+	// Be nice to symlinked directories
+	define( 'BP_PLUGIN_URL', plugins_url( trailingslashit( basename( constant( 'BP_PLUGIN_DIR' ) ) ) ) );
+}
+
+// Include BuddyPress
+include( $bp_loader );
+
+// Unset the loader, since it's loaded in global scope
+unset( $bp_loader );
Index: /tags/2.4.3/package.json
===================================================================
--- /tags/2.4.3/package.json	(revision 10410)
+++ /tags/2.4.3/package.json	(revision 10410)
@@ -0,0 +1,46 @@
+{
+  "author": "The BuddyPress Contributors",
+  "bugs": {
+    "url": "https://buddypress.trac.wordpress.org/"
+  },
+  "description": "BuddyPress helps you run any kind of social network on your WordPress, with member profiles, activity streams, user groups, messaging, and more.",
+  "devDependencies": {
+    "grunt": "~0.4.5",
+    "grunt-checktextdomain": "~1.0.0",
+    "grunt-contrib-clean": "~0.6.0",
+    "grunt-contrib-copy": "~0.8.0",
+    "grunt-contrib-cssmin": "~0.11.0",
+    "grunt-contrib-imagemin": "~1.0.0",
+    "grunt-contrib-jshint": "~0.11.2",
+    "grunt-contrib-uglify": "~0.9.2",
+    "grunt-contrib-watch": "~0.6.1",
+    "grunt-cssjanus": "~0.2.3",
+    "grunt-exec": "~0.4.6",
+    "grunt-jsvalidate": "~0.2.2",
+    "grunt-legacy-util": "^0.2.0",
+    "grunt-patch-wordpress": "~0.3.0",
+    "grunt-sass": "~1.0.0",
+    "grunt-scss-lint": "~0.3.6",
+    "grunt-wp-i18n": "~0.5.2",
+    "matchdep": "~0.3.0"
+  },
+  "keywords": [
+    "activity",
+    "community",
+    "forum",
+    "friends",
+    "groups",
+    "messaging",
+    "notifications",
+    "profiles",
+    "social network"
+  ],
+  "homepage": "https://buddypress.org",
+  "license": "GPL-2.0+",
+  "name": "buddypress",
+  "repository": {
+    "type": "svn",
+    "url": "https://buddypress.svn.wordpress.org/trunk/"
+  },
+  "version": "2.4.3"
+}
Index: /tags/2.4.3/phpunit.xml.dist
===================================================================
--- /tags/2.4.3/phpunit.xml.dist	(revision 10410)
+++ /tags/2.4.3/phpunit.xml.dist	(revision 10410)
@@ -0,0 +1,14 @@
+<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>
+</phpunit>
Index: /tags/2.4.3/src/bp-activity/admin/css/admin-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-activity/admin/css/admin-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/admin/css/admin-rtl.css	(revision 10410)
@@ -0,0 +1,87 @@
+.akismet-status {
+	float: left;
+}
+.akismet-status a {
+	color: #AAA;
+	font-style: italic;
+}
+.akismet-history {
+	margin: 13px;
+}
+.akismet-history div {
+	margin-bottom: 13px;
+}
+.akismet-history span {
+	color: #999;
+}
+
+#wp-bp-activities-wrap {
+	padding: 5px 0;
+}
+#bp-activities {
+	height: 120px;
+}
+#bp-replyhead {
+	font-size: 1em;
+	line-height: 1.4em;
+	margin: 0;
+}
+#bp-replysubmit {
+	margin: 0;
+	padding: 0 0 3px;
+	text-align: center;
+}
+#bp-replysubmit .error {
+	color: red;
+	line-height: 21px;
+	text-align: center;
+	vertical-align: center;
+}
+#bp-replysubmit img.waiting {
+	float: left;
+	padding: 4px 10px 0;
+	vertical-align: top;
+}
+#bp-activities-form .column-response img {
+	float: right;
+	margin-bottom: 5px;
+	margin-left: 10px;
+	margin-top: 1px;
+}
+.activity-errors {
+	list-style-type: disc;
+	margin-right: 2em;
+}
+
+#bp_activity_action div.inside,
+#bp_activity_content div.inside {
+	line-height: 0;
+}
+#bp_activity_action h3,
+#bp_activity_content h3 {
+	cursor: auto;
+}
+#bp_activity_action td.mceIframeContainer,
+#bp_activity_content td.mceIframeContainer {
+	background-color: white;
+}
+#post-body #bp-activities-action_resize,
+#post-body #bp-activities-content_resize {
+	position: inherit;
+	margin-top: -2px;
+}
+#bp_activity_link input {
+	width: 99%;
+}
+#bp-activities-primaryid {
+	margin-bottom: 1em;
+}
+.column-action {
+	width: 12%;
+}
+
+@media screen and (max-width: 782px) {
+	body.toplevel_page_bp-activity .wp-list-table tr:not(.inline-edit-row):not(.no-items) td:not(.check-column) {
+		display: table-cell;
+	}
+}
Index: /tags/2.4.3/src/bp-activity/admin/css/admin.css
===================================================================
--- /tags/2.4.3/src/bp-activity/admin/css/admin.css	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/admin/css/admin.css	(revision 10410)
@@ -0,0 +1,87 @@
+.akismet-status {
+	float: right;
+}
+.akismet-status a {
+	color: #AAA;
+	font-style: italic;
+}
+.akismet-history {
+	margin: 13px;
+}
+.akismet-history div {
+	margin-bottom: 13px;
+}
+.akismet-history span {
+	color: #999;
+}
+
+#wp-bp-activities-wrap {
+	padding: 5px 0;
+}
+#bp-activities {
+	height: 120px;
+}
+#bp-replyhead {
+	font-size: 1em;
+	line-height: 1.4em;
+	margin: 0;
+}
+#bp-replysubmit {
+	margin: 0;
+	padding: 0 0 3px;
+	text-align: center;
+}
+#bp-replysubmit .error {
+	color: red;
+	line-height: 21px;
+	text-align: center;
+	vertical-align: center;
+}
+#bp-replysubmit img.waiting {
+	float: right;
+	padding: 4px 10px 0;
+	vertical-align: top;
+}
+#bp-activities-form .column-response img {
+	float: left;
+	margin-bottom: 5px;
+	margin-right: 10px;
+	margin-top: 1px;
+}
+.activity-errors {
+	list-style-type: disc;
+	margin-left: 2em;
+}
+
+#bp_activity_action div.inside,
+#bp_activity_content div.inside {
+	line-height: 0;
+}
+#bp_activity_action h3,
+#bp_activity_content h3 {
+	cursor: auto;
+}
+#bp_activity_action td.mceIframeContainer,
+#bp_activity_content td.mceIframeContainer {
+	background-color: white;
+}
+#post-body #bp-activities-action_resize,
+#post-body #bp-activities-content_resize {
+	position: inherit;
+	margin-top: -2px;
+}
+#bp_activity_link input {
+	width: 99%;
+}
+#bp-activities-primaryid {
+	margin-bottom: 1em;
+}
+.column-action {
+	width: 12%;
+}
+
+@media screen and (max-width: 782px) {
+	body.toplevel_page_bp-activity .wp-list-table tr:not(.inline-edit-row):not(.no-items) td:not(.check-column) {
+		display: table-cell;
+	}
+}
Index: /tags/2.4.3/src/bp-activity/admin/js/admin.js
===================================================================
--- /tags/2.4.3/src/bp-activity/admin/js/admin.js	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/admin/js/admin.js	(revision 10410)
@@ -0,0 +1,175 @@
+/* global bp_activity_admin_vars, postboxes, wpAjax */
+(function( $ ) {
+
+/**
+ * Activity reply object for the activity index screen
+ *
+ * @since 1.6.0
+ */
+var activityReply = {
+
+	/**
+	 * Attach event handler functions to the relevant elements.
+	 *
+	 * @since 1.6.0
+	 */
+	init : function() {
+		$(document).on( 'click', '.row-actions a.reply',              activityReply.open );
+		$(document).on( 'click', '#bp-activities-container a.cancel', activityReply.close );
+		$(document).on( 'click', '#bp-activities-container a.save',   activityReply.send );
+
+		// Close textarea on escape
+		$(document).on( 'keyup', '#bp-activities:visible', function( e ) {
+			if ( 27 === e.which ) {
+				activityReply.close();
+			}
+		});
+	},
+
+	/**
+	 * Reveals the entire row when "reply" is pressed.
+	 *
+	 * @since 1.6.0
+	 */
+	open : function() {
+		// Hide the container row, and move it to the new location
+		var box = $( '#bp-activities-container' ).hide();
+		$( this ).parents( 'tr' ).after( box );
+
+		// Fade the whole row in, and set focus on the text area.
+		box.fadeIn( '300' );
+		$( '#bp-activities' ).focus();
+
+		return false;
+	},
+
+	/**
+	 * Hide and reset the entire row when "cancel", or escape, are pressed.
+	 *
+	 * @since 1.6.0
+	 */
+	close : function() {
+		// Hide the container row
+		$('#bp-activities-container').fadeOut( '200', function () {
+
+			// Empty and unfocus the text area
+			$( '#bp-activities' ).val( '' ).blur();
+
+			// Remove any error message and disable the spinner
+			$( '#bp-replysubmit .error' ).html( '' ).hide();
+			$( '#bp-replysubmit .waiting' ).hide();
+		});
+
+		return false;
+	},
+
+	/**
+	 * Submits "form" via AJAX back to WordPress.
+	 *
+	 * @since 1.6.0
+	 */
+	send : function() {
+		// Hide any existing error message, and show the loading spinner
+		$( '#bp-replysubmit .error' ).hide();
+		$( '#bp-replysubmit .waiting' ).show();
+
+		// Grab the nonce
+		var reply = {};
+		reply['_ajax_nonce-bp-activity-admin-reply'] = $( '#bp-activities-container input[name="_ajax_nonce-bp-activity-admin-reply"]' ).val();
+
+		// Get the rest of the data
+		reply.action    = 'bp-activity-admin-reply';
+		reply.content   = $( '#bp-activities' ).val();
+		reply.parent_id = $( '#bp-activities-container' ).prev().data( 'parent_id' );
+		reply.root_id   = $( '#bp-activities-container' ).prev().data( 'root_id' );
+
+		// Make the AJAX call
+		$.ajax({
+			data    : reply,
+			type    : 'POST',
+			url     : ajaxurl,
+
+			// Callbacks
+			error   : function( r ) { activityReply.error( r ); },
+			success : function( r ) { activityReply.show( r ); }
+		});
+
+		return false;
+	},
+
+	/**
+	 * send() error message handler
+	 *
+	 * @since 1.6.0
+	 */
+	error : function( r ) {
+		var er = r.statusText;
+		$('#bp-replysubmit .waiting').hide();
+
+		if ( r.responseText ) {
+			er = r.responseText.replace( /<.[^<>]*?>/g, '' );
+		}
+
+		if ( er ) {
+			$('#bp-replysubmit .error').html( er ).show();
+		}
+	},
+
+	/**
+	 * send() success handler
+	 *
+	 * @since 1.6.0
+	 */
+	show : function ( xml ) {
+		var bg, id, response;
+
+		// Handle any errors in the response
+		if ( typeof( xml ) === 'string' ) {
+			activityReply.error( { 'responseText': xml } );
+			return false;
+		}
+
+		response = wpAjax.parseAjaxResponse( xml );
+		if ( response.errors ) {
+			activityReply.error( { 'responseText': wpAjax.broken } );
+			return false;
+		}
+		response = response.responses[0];
+
+		// Close and reset the reply row, and add the new Activity item into the list.
+		$('#bp-activities-container').fadeOut( '200', function () {
+
+			// Empty and unfocus the text area
+			$( '#bp-activities' ).val( '' ).blur();
+
+			// Remove any error message and disable the spinner
+			$( '#bp-replysubmit .error' ).html( '' ).hide();
+			$( '#bp-replysubmit .waiting' ).hide();
+
+			// Insert new activity item
+			$( '#bp-activities-container' ).before( response.data );
+
+			// Get background colour and animate the flash
+			id = $( '#activity-' + response.id );
+			bg = id.closest( '.widefat' ).css( 'backgroundColor' );
+			id.animate( { 'backgroundColor': '#CEB' }, 300 ).animate( { 'backgroundColor': bg }, 300 );
+		});
+	}
+};
+
+$(document).ready( function () {
+	// Create the Activity reply object after domready event
+	activityReply.init();
+
+	// On the edit screen, unload the close/open toggle js for the action & content metaboxes
+	$( '#bp_activity_action h3, #bp_activity_content h3' ).unbind( 'click' );
+
+	// redo the post box toggles to reset the one made by comment.js in favor
+	// of activity administration page id so that metaboxes are still collapsible
+	// in single Activity Administration screen.
+	if ( typeof postboxes !== 'undefined' ) {
+		postboxes.add_postbox_toggles( bp_activity_admin_vars.page );
+	}
+});
+
+})(jQuery);
Index: /tags/2.4.3/src/bp-activity/bp-activity-actions.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-actions.php	(revision 10410)
@@ -0,0 +1,832 @@
+<?php
+/**
+ * Action functions are exactly the same as screen functions, however they do
+ * not have a template screen associated with them. Usually they will send the
+ * user back to the default screen after execution.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityActions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Allow core components and dependent plugins to register activity actions.
+ *
+ * @since 1.2.0
+ *
+ * @uses do_action() To call 'bp_register_activity_actions' hook.
+ */
+function bp_register_activity_actions() {
+
+	/**
+	 * Fires on bp_init to allow core components and dependent plugins to register activity actions.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_register_activity_actions' );
+}
+add_action( 'bp_init', 'bp_register_activity_actions', 8 );
+
+/**
+ * Catch and route requests for single activity item permalinks.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_is_activity_component()
+ * @uses bp_is_current_action()
+ * @uses bp_action_variable()
+ * @uses bp_activity_get_specific()
+ * @uses bp_is_active()
+ * @uses bp_core_get_user_domain()
+ * @uses groups_get_group()
+ * @uses bp_get_group_permalink()
+ * @uses apply_filters_ref_array() To call the 'bp_activity_permalink_redirect_url' hook.
+ * @uses bp_core_redirect()
+ * @uses bp_get_root_domain()
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_permalink_router() {
+
+	// Not viewing activity.
+	if ( ! bp_is_activity_component() || ! bp_is_current_action( 'p' ) )
+		return false;
+
+	// No activity to display.
+	if ( ! bp_action_variable( 0 ) || ! is_numeric( bp_action_variable( 0 ) ) )
+		return false;
+
+	// Get the activity details.
+	$activity = bp_activity_get_specific( array( 'activity_ids' => bp_action_variable( 0 ), 'show_hidden' => true ) );
+
+	// 404 if activity does not exist
+	if ( empty( $activity['activities'][0] ) ) {
+		bp_do_404();
+		return;
+	} else {
+		$activity = $activity['activities'][0];
+	}
+
+	// Do not redirect at default.
+	$redirect = false;
+
+	// Redirect based on the type of activity.
+	if ( bp_is_active( 'groups' ) && $activity->component == buddypress()->groups->id ) {
+
+		// Activity is a user update.
+		if ( ! empty( $activity->user_id ) ) {
+			$redirect = bp_core_get_user_domain( $activity->user_id, $activity->user_nicename, $activity->user_login ) . bp_get_activity_slug() . '/' . $activity->id . '/';
+
+		// Activity is something else.
+		} else {
+
+			// Set redirect to group activity stream.
+			if ( $group = groups_get_group( array( 'group_id' => $activity->item_id ) ) ) {
+				$redirect = bp_get_group_permalink( $group ) . bp_get_activity_slug() . '/' . $activity->id . '/';
+			}
+		}
+
+	// Set redirect to users' activity stream.
+	} elseif ( ! empty( $activity->user_id ) ) {
+		$redirect = bp_core_get_user_domain( $activity->user_id, $activity->user_nicename, $activity->user_login ) . bp_get_activity_slug() . '/' . $activity->id . '/';
+	}
+
+	// If set, add the original query string back onto the redirect URL.
+	if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
+		$query_frags = array();
+		wp_parse_str( $_SERVER['QUERY_STRING'], $query_frags );
+		$redirect = add_query_arg( urlencode_deep( $query_frags ), $redirect );
+	}
+
+	/**
+	 * Filter the intended redirect url before the redirect occurs for the single activity item.
+	 *
+	 * @since 1.2.2
+	 *
+	 * @param array Array with url to redirect to and activity related to the redirect.
+	 */
+	if ( ! $redirect = apply_filters_ref_array( 'bp_activity_permalink_redirect_url', array( $redirect, &$activity ) ) ) {
+		bp_core_redirect( bp_get_root_domain() );
+	}
+
+	// Redirect to the actual activity permalink page.
+	bp_core_redirect( $redirect );
+}
+add_action( 'bp_actions', 'bp_activity_action_permalink_router' );
+
+/**
+ * Delete specific activity item and redirect to previous page.
+ *
+ * @since 1.1.0
+ *
+ * @uses bp_is_activity_component()
+ * @uses bp_is_current_action()
+ * @uses bp_action_variable()
+ * @uses check_admin_referer()
+ * @uses bp_activity_user_can_delete()
+ * @uses do_action() Calls 'bp_activity_before_action_delete_activity' hook to allow actions to be taken before the activity is deleted.
+ * @uses bp_activity_delete()
+ * @uses bp_core_add_message()
+ * @uses do_action() Calls 'bp_activity_action_delete_activity' hook to allow actions to be taken after the activity is deleted.
+ * @uses bp_core_redirect()
+ *
+ * @param int $activity_id Activity id to be deleted. Defaults to 0.
+ * @return bool False on failure.
+ */
+function bp_activity_action_delete_activity( $activity_id = 0 ) {
+
+	// Not viewing activity or action is not delete.
+	if ( !bp_is_activity_component() || !bp_is_current_action( 'delete' ) )
+		return false;
+
+	if ( empty( $activity_id ) && bp_action_variable( 0 ) )
+		$activity_id = (int) bp_action_variable( 0 );
+
+	// Not viewing a specific activity item.
+	if ( empty( $activity_id ) )
+		return false;
+
+	// Check the nonce.
+	check_admin_referer( 'bp_activity_delete_link' );
+
+	// Load up the activity item.
+	$activity = new BP_Activity_Activity( $activity_id );
+
+	// Check access.
+	if ( ! bp_activity_user_can_delete( $activity ) )
+		return false;
+
+	/**
+	 * Fires before the deletion so plugins can still fetch information about it.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $activity_id The activity ID.
+	 * @param int $user_id     The user associated with the activity.
+	 */
+	do_action( 'bp_activity_before_action_delete_activity', $activity_id, $activity->user_id );
+
+	// Delete the activity item and provide user feedback.
+	if ( bp_activity_delete( array( 'id' => $activity_id, 'user_id' => $activity->user_id ) ) )
+		bp_core_add_message( __( 'Activity deleted successfully', 'buddypress' ) );
+	else
+		bp_core_add_message( __( 'There was an error when deleting that activity', 'buddypress' ), 'error' );
+
+	/**
+	 * Fires after the deletion so plugins can act afterwards based on the activity.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param int $activity_id The activity ID.
+	 * @param int $user_id     The user associated with the activity.
+	 */
+	do_action( 'bp_activity_action_delete_activity', $activity_id, $activity->user_id );
+
+	// Check for the redirect query arg, otherwise let WP handle things.
+ 	if ( !empty( $_GET['redirect_to'] ) )
+		bp_core_redirect( esc_url( $_GET['redirect_to'] ) );
+	else
+		bp_core_redirect( wp_get_referer() );
+}
+add_action( 'bp_actions', 'bp_activity_action_delete_activity' );
+
+/**
+ * Mark specific activity item as spam and redirect to previous page.
+ *
+ * @since 1.6.0
+ *
+ * @param int $activity_id Activity id to be deleted. Defaults to 0.
+ * @return bool False on failure.
+ */
+function bp_activity_action_spam_activity( $activity_id = 0 ) {
+	$bp = buddypress();
+
+	// Not viewing activity, or action is not spam, or Akismet isn't present.
+	if ( !bp_is_activity_component() || !bp_is_current_action( 'spam' ) || empty( $bp->activity->akismet ) )
+		return false;
+
+	if ( empty( $activity_id ) && bp_action_variable( 0 ) )
+		$activity_id = (int) bp_action_variable( 0 );
+
+	// Not viewing a specific activity item.
+	if ( empty( $activity_id ) )
+		return false;
+
+	// Is the current user allowed to spam items?
+	if ( !bp_activity_user_can_mark_spam() )
+		return false;
+
+	// Load up the activity item.
+	$activity = new BP_Activity_Activity( $activity_id );
+	if ( empty( $activity->id ) )
+		return false;
+
+	// Check nonce.
+	check_admin_referer( 'bp_activity_akismet_spam_' . $activity->id );
+
+	/**
+	 * Fires before the marking activity as spam so plugins can modify things if they want to.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int    $activity_id Activity ID to be marked as spam.
+	 * @param object $activity    Activity object for the ID to be marked as spam.
+	 */
+	do_action( 'bp_activity_before_action_spam_activity', $activity->id, $activity );
+
+	// Mark as spam.
+	bp_activity_mark_as_spam( $activity );
+	$activity->save();
+
+	// Tell the user the spamming has been successful.
+	bp_core_add_message( __( 'The activity item has been marked as spam and is no longer visible.', 'buddypress' ) );
+
+	/**
+	 * Fires after the marking activity as spam so plugins can act afterwards based on the activity.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int $activity_id Activity ID that was marked as spam.
+	 * @param int $user_id     User ID associated with activity.
+	 */
+	do_action( 'bp_activity_action_spam_activity', $activity_id, $activity->user_id );
+
+	// Check for the redirect query arg, otherwise let WP handle things.
+ 	if ( !empty( $_GET['redirect_to'] ) )
+		bp_core_redirect( esc_url( $_GET['redirect_to'] ) );
+	else
+		bp_core_redirect( wp_get_referer() );
+}
+add_action( 'bp_actions', 'bp_activity_action_spam_activity' );
+
+/**
+ * Post user/group activity update.
+ *
+ * @since 1.2.0
+ *
+ * @uses is_user_logged_in()
+ * @uses bp_is_activity_component()
+ * @uses bp_is_current_action()
+ * @uses check_admin_referer()
+ * @uses apply_filters() To call 'bp_activity_post_update_content' hook.
+ * @uses apply_filters() To call 'bp_activity_post_update_object' hook.
+ * @uses apply_filters() To call 'bp_activity_post_update_item_id' hook.
+ * @uses bp_core_add_message()
+ * @uses bp_core_redirect()
+ * @uses bp_activity_post_update()
+ * @uses groups_post_update()
+ * @uses bp_core_redirect()
+ * @uses apply_filters() To call 'bp_activity_custom_update' hook.
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_post_update() {
+
+	// Do not proceed if user is not logged in, not viewing activity, or not posting.
+	if ( !is_user_logged_in() || !bp_is_activity_component() || !bp_is_current_action( 'post' ) )
+		return false;
+
+	// Check the nonce.
+	check_admin_referer( 'post_update', '_wpnonce_post_update' );
+
+	/**
+	 * Filters the content provided in the activity input field.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value Activity message being posted.
+	 */
+	$content = apply_filters( 'bp_activity_post_update_content', $_POST['whats-new'] );
+
+	if ( ! empty( $_POST['whats-new-post-object'] ) ) {
+
+		/**
+		 * Filters the item type that the activity update should be associated with.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Item type to associate with.
+		 */
+		$object = apply_filters( 'bp_activity_post_update_object', $_POST['whats-new-post-object'] );
+	}
+
+	if ( ! empty( $_POST['whats-new-post-in'] ) ) {
+
+		/**
+		 * Filters what component the activity is being to.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Chosen component to post activity to.
+		 */
+		$item_id = apply_filters( 'bp_activity_post_update_item_id', $_POST['whats-new-post-in'] );
+	}
+
+	// No activity content so provide feedback and redirect.
+	if ( empty( $content ) ) {
+		bp_core_add_message( __( 'Please enter some content to post.', 'buddypress' ), 'error' );
+		bp_core_redirect( wp_get_referer() );
+	}
+
+	// No existing item_id.
+	if ( empty( $item_id ) ) {
+		$activity_id = bp_activity_post_update( array( 'content' => $content ) );
+
+	// Post to groups object.
+	} elseif ( 'groups' == $object && bp_is_active( 'groups' ) ) {
+		if ( (int) $item_id ) {
+			$activity_id = groups_post_update( array( 'content' => $content, 'group_id' => $item_id ) );
+		}
+
+	} else {
+
+		/**
+		 * Filters activity object for BuddyPress core and plugin authors before posting activity update.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $object  Activity item being associated to.
+		 * @param string $item_id Component ID being posted to.
+		 * @param string $content Activity content being posted.
+		 */
+		$activity_id = apply_filters( 'bp_activity_custom_update', $object, $item_id, $content );
+	}
+
+	// Provide user feedback.
+	if ( !empty( $activity_id ) )
+		bp_core_add_message( __( 'Update Posted!', 'buddypress' ) );
+	else
+		bp_core_add_message( __( 'There was an error when posting your update. Please try again.', 'buddypress' ), 'error' );
+
+	// Redirect.
+	bp_core_redirect( wp_get_referer() );
+}
+add_action( 'bp_actions', 'bp_activity_action_post_update' );
+
+/**
+ * Post new activity comment.
+ *
+ * @since 1.2.0
+ *
+ * @uses is_user_logged_in()
+ * @uses bp_is_activity_component()
+ * @uses bp_is_current_action()
+ * @uses check_admin_referer()
+ * @uses apply_filters() To call 'bp_activity_post_comment_activity_id' hook.
+ * @uses apply_filters() To call 'bp_activity_post_comment_content' hook.
+ * @uses bp_core_add_message()
+ * @uses bp_core_redirect()
+ * @uses bp_activity_new_comment()
+ * @uses wp_get_referer()
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_post_comment() {
+
+	if ( !is_user_logged_in() || !bp_is_activity_component() || !bp_is_current_action( 'reply' ) )
+		return false;
+
+	// Check the nonce.
+	check_admin_referer( 'new_activity_comment', '_wpnonce_new_activity_comment' );
+
+	/**
+	 * Filters the activity ID a comment will be in reply to.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value ID of the activity being replied to.
+	 */
+	$activity_id = apply_filters( 'bp_activity_post_comment_activity_id', $_POST['comment_form_id'] );
+
+	/**
+	 * Filters the comment content for a comment reply.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value Comment content being posted.
+	 */
+	$content = apply_filters( 'bp_activity_post_comment_content', $_POST['ac_input_' . $activity_id] );
+
+	if ( empty( $content ) ) {
+		bp_core_add_message( __( 'Please do not leave the comment area blank.', 'buddypress' ), 'error' );
+		bp_core_redirect( wp_get_referer() . '#ac-form-' . $activity_id );
+	}
+
+	$comment_id = bp_activity_new_comment( array(
+		'content'     => $content,
+		'activity_id' => $activity_id,
+		'parent_id'   => false
+	));
+
+	if ( !empty( $comment_id ) )
+		bp_core_add_message( __( 'Reply Posted!', 'buddypress' ) );
+	else
+		bp_core_add_message( __( 'There was an error posting that reply. Please try again.', 'buddypress' ), 'error' );
+
+	bp_core_redirect( wp_get_referer() . '#ac-form-' . $activity_id );
+}
+add_action( 'bp_actions', 'bp_activity_action_post_comment' );
+
+/**
+ * Mark activity as favorite.
+ *
+ * @since 1.2.0
+ *
+ * @uses is_user_logged_in()
+ * @uses bp_is_activity_component()
+ * @uses bp_is_current_action()
+ * @uses check_admin_referer()
+ * @uses bp_activity_add_user_favorite()
+ * @uses bp_action_variable()
+ * @uses bp_core_add_message()
+ * @uses bp_core_redirect()
+ * @uses wp_get_referer()
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_mark_favorite() {
+
+	if ( !is_user_logged_in() || !bp_is_activity_component() || !bp_is_current_action( 'favorite' ) )
+		return false;
+
+	// Check the nonce.
+	check_admin_referer( 'mark_favorite' );
+
+	if ( bp_activity_add_user_favorite( bp_action_variable( 0 ) ) )
+		bp_core_add_message( __( 'Activity marked as favorite.', 'buddypress' ) );
+	else
+		bp_core_add_message( __( 'There was an error marking that activity as a favorite. Please try again.', 'buddypress' ), 'error' );
+
+	bp_core_redirect( wp_get_referer() . '#activity-' . bp_action_variable( 0 ) );
+}
+add_action( 'bp_actions', 'bp_activity_action_mark_favorite' );
+
+/**
+ * Remove activity from favorites.
+ *
+ * @since 1.2.0
+ *
+ * @uses is_user_logged_in()
+ * @uses bp_is_activity_component()
+ * @uses bp_is_current_action()
+ * @uses check_admin_referer()
+ * @uses bp_activity_remove_user_favorite()
+ * @uses bp_action_variable()
+ * @uses bp_core_add_message()
+ * @uses bp_core_redirect()
+ * @uses wp_get_referer()
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_remove_favorite() {
+
+	if ( ! is_user_logged_in() || ! bp_is_activity_component() || ! bp_is_current_action( 'unfavorite' ) )
+		return false;
+
+	// Check the nonce.
+	check_admin_referer( 'unmark_favorite' );
+
+	if ( bp_activity_remove_user_favorite( bp_action_variable( 0 ) ) )
+		bp_core_add_message( __( 'Activity removed as favorite.', 'buddypress' ) );
+	else
+		bp_core_add_message( __( 'There was an error removing that activity as a favorite. Please try again.', 'buddypress' ), 'error' );
+
+	bp_core_redirect( wp_get_referer() . '#activity-' . bp_action_variable( 0 ) );
+}
+add_action( 'bp_actions', 'bp_activity_action_remove_favorite' );
+
+/**
+ * Load the sitewide activity feed.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_is_activity_component()
+ * @uses bp_is_current_action()
+ * @uses bp_is_user()
+ * @uses status_header()
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_sitewide_feed() {
+	$bp = buddypress();
+
+	if ( ! bp_is_activity_component() || ! bp_is_current_action( 'feed' ) || bp_is_user() || ! empty( $bp->groups->current_group ) )
+		return false;
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'sitewide',
+
+		/* translators: Sitewide activity RSS title - "[Site Name] | Site Wide Activity" */
+		'title'         => sprintf( __( '%s | Site-Wide Activity', 'buddypress' ), bp_get_site_name() ),
+
+		'link'          => bp_get_activity_directory_permalink(),
+		'description'   => __( 'Activity feed for the entire site.', 'buddypress' ),
+		'activity_args' => 'display_comments=threaded'
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_sitewide_feed' );
+
+/**
+ * Load a user's personal activity feed.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_is_user_activity()
+ * @uses bp_is_current_action()
+ * @uses status_header()
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_personal_feed() {
+	if ( ! bp_is_user_activity() || ! bp_is_current_action( 'feed' ) ) {
+		return false;
+	}
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'personal',
+
+		/* translators: Personal activity RSS title - "[Site Name] | [User Display Name] | Activity" */
+		'title'         => sprintf( __( '%1$s | %2$s | Activity', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
+
+		'link'          => trailingslashit( bp_displayed_user_domain() . bp_get_activity_slug() ),
+		'description'   => sprintf( __( 'Activity feed for %s.', 'buddypress' ), bp_get_displayed_user_fullname() ),
+		'activity_args' => 'user_id=' . bp_displayed_user_id()
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_personal_feed' );
+
+/**
+ * Load a user's friends' activity feed.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_is_active()
+ * @uses bp_is_user_activity()
+ * @uses bp_is_current_action()
+ * @uses bp_get_friends_slug()
+ * @uses bp_is_action_variable()
+ * @uses status_header()
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_friends_feed() {
+	if ( ! bp_is_active( 'friends' ) || ! bp_is_user_activity() || ! bp_is_current_action( bp_get_friends_slug() ) || ! bp_is_action_variable( 'feed', 0 ) ) {
+		return false;
+	}
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'friends',
+
+		/* translators: Friends activity RSS title - "[Site Name] | [User Display Name] | Friends Activity" */
+		'title'         => sprintf( __( '%1$s | %2$s | Friends Activity', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
+
+		'link'          => trailingslashit( bp_displayed_user_domain() . bp_get_activity_slug() . '/' . bp_get_friends_slug() ),
+		'description'   => sprintf( __( "Activity feed for %s's friends.", 'buddypress' ), bp_get_displayed_user_fullname() ),
+		'activity_args' => 'scope=friends'
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_friends_feed' );
+
+/**
+ * Load the activity feed for a user's groups.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_is_active()
+ * @uses bp_is_user_activity()
+ * @uses bp_is_current_action()
+ * @uses bp_get_groups_slug()
+ * @uses bp_is_action_variable()
+ * @uses status_header()
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_my_groups_feed() {
+	if ( ! bp_is_active( 'groups' ) || ! bp_is_user_activity() || ! bp_is_current_action( bp_get_groups_slug() ) || ! bp_is_action_variable( 'feed', 0 ) ) {
+		return false;
+	}
+
+	// Get displayed user's group IDs.
+	$groups    = groups_get_user_groups();
+	$group_ids = implode( ',', $groups['groups'] );
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'mygroups',
+
+		/* translators: Member groups activity RSS title - "[Site Name] | [User Display Name] | Groups Activity" */
+		'title'         => sprintf( __( '%1$s | %2$s | Group Activity', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
+
+		'link'          => trailingslashit( bp_displayed_user_domain() . bp_get_activity_slug() . '/' . bp_get_groups_slug() ),
+		'description'   => sprintf( __( "Public group activity feed of which %s is a member.", 'buddypress' ), bp_get_displayed_user_fullname() ),
+		'activity_args' => array(
+			'object'           => buddypress()->groups->id,
+			'primary_id'       => $group_ids,
+			'display_comments' => 'threaded'
+		)
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_my_groups_feed' );
+
+/**
+ * Load a user's @mentions feed.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_is_user_activity()
+ * @uses bp_is_current_action()
+ * @uses bp_is_action_variable()
+ * @uses status_header()
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_mentions_feed() {
+	if ( ! bp_activity_do_mentions() ) {
+		return false;
+	}
+
+	if ( !bp_is_user_activity() || ! bp_is_current_action( 'mentions' ) || ! bp_is_action_variable( 'feed', 0 ) ) {
+		return false;
+	}
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'mentions',
+
+		/* translators: User mentions activity RSS title - "[Site Name] | [User Display Name] | Mentions" */
+		'title'         => sprintf( __( '%1$s | %2$s | Mentions', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
+
+		'link'          => bp_displayed_user_domain() . bp_get_activity_slug() . '/mentions/',
+		'description'   => sprintf( __( "Activity feed mentioning %s.", 'buddypress' ), bp_get_displayed_user_fullname() ),
+		'activity_args' => array(
+			'search_terms' => '@' . bp_core_get_username( bp_displayed_user_id() )
+		)
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_mentions_feed' );
+
+/**
+ * Load a user's favorites feed.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_is_user_activity()
+ * @uses bp_is_current_action()
+ * @uses bp_is_action_variable()
+ * @uses status_header()
+ *
+ * @return bool False on failure.
+ */
+function bp_activity_action_favorites_feed() {
+	if ( ! bp_is_user_activity() || ! bp_is_current_action( 'favorites' ) || ! bp_is_action_variable( 'feed', 0 ) ) {
+		return false;
+	}
+
+	// Get displayed user's favorite activity IDs.
+	$favs = bp_activity_get_user_favorites( bp_displayed_user_id() );
+	$fav_ids = implode( ',', (array) $favs );
+
+	// Setup the feed.
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'favorites',
+
+		/* translators: User activity favorites RSS title - "[Site Name] | [User Display Name] | Favorites" */
+		'title'         => sprintf( __( '%1$s | %2$s | Favorites', 'buddypress' ), bp_get_site_name(), bp_get_displayed_user_fullname() ),
+
+		'link'          => bp_displayed_user_domain() . bp_get_activity_slug() . '/favorites/',
+		'description'   => sprintf( __( "Activity feed of %s's favorites.", 'buddypress' ), bp_get_displayed_user_fullname() ),
+		'activity_args' => 'include=' . $fav_ids
+	) );
+}
+add_action( 'bp_actions', 'bp_activity_action_favorites_feed' );
+
+/**
+ * Loads Akismet filtering for activity.
+ *
+ * @since 1.6.0
+ * @since 2.3.0 We only support Akismet 3+.
+ */
+function bp_activity_setup_akismet() {
+	$bp = buddypress();
+
+	// Bail if Akismet is not active.
+	if ( ! defined( 'AKISMET_VERSION' ) ) {
+		return;
+	}
+
+	// Bail if older version of Akismet.
+	if ( ! class_exists( 'Akismet' ) ) {
+		return;
+	}
+
+	// Bail if no Akismet key is set.
+	if ( ! bp_get_option( 'wordpress_api_key' ) && ! defined( 'WPCOM_API_KEY' ) ) {
+		return;
+	}
+
+	/**
+	 * Filters if BuddyPress Activity Akismet support has been disabled by another plugin.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Return value of bp_is_akismet_active boolean function.
+	 */
+	if ( ! apply_filters( 'bp_activity_use_akismet', bp_is_akismet_active() ) ) {
+		return;
+	}
+
+	// Instantiate Akismet for BuddyPress.
+	$bp->activity->akismet = new BP_Akismet();
+}
+
+/**
+ * AJAX endpoint for Suggestions API lookups.
+ *
+ * @since 2.1.0
+ */
+function bp_ajax_get_suggestions() {
+	if ( ! bp_is_user_active() || empty( $_GET['term'] ) || empty( $_GET['type'] ) ) {
+		wp_send_json_error( 'missing_parameter' );
+		exit;
+	}
+
+	$args = array(
+		'term' => sanitize_text_field( $_GET['term'] ),
+		'type' => sanitize_text_field( $_GET['type'] ),
+	);
+
+	// Support per-Group suggestions.
+	if ( ! empty( $_GET['group-id'] ) ) {
+		$args['group_id'] = absint( $_GET['group-id'] );
+	}
+
+	$results = bp_core_get_suggestions( $args );
+
+	if ( is_wp_error( $results ) ) {
+		wp_send_json_error( $results->get_error_message() );
+		exit;
+	}
+
+	wp_send_json_success( $results );
+}
+add_action( 'wp_ajax_bp_get_suggestions', 'bp_ajax_get_suggestions' );
+
+/**
+ * Detect a change in post type status, and initiate an activity update if necessary.
+ *
+ * @since 2.2.0
+ *
+ * @todo Support untrashing better.
+ *
+ * @param string $new_status New status for the post.
+ * @param string $old_status Old status for the post.
+ * @param object $post       Post data.
+ */
+function bp_activity_catch_transition_post_type_status( $new_status, $old_status, $post ) {
+	if ( ! post_type_supports( $post->post_type, 'buddypress-activity' ) ) {
+		return;
+	}
+
+	// This is an edit.
+	if ( $new_status === $old_status ) {
+		// An edit of an existing post should update the existing activity item.
+		if ( $new_status == 'publish' ) {
+			bp_activity_post_type_update( $post );
+		}
+
+		return;
+	}
+
+	// Publishing a previously unpublished post.
+	if ( 'publish' === $new_status ) {
+		// Untrashing the post type - nothing here yet.
+		if ( 'trash' == $old_status ) {
+
+			/**
+			 * Fires if untrashing post in a post type.
+			 *
+			 * This is a variable filter that is dependent on the post type
+			 * being untrashed.
+			 *
+			 * @since 2.2.0
+			 *
+			 * @param WP_Post $post Post data.
+			 */
+			do_action( 'bp_activity_post_type_untrash_' . $post->post_type, $post );
+		} else {
+			// Record the post.
+			bp_activity_post_type_publish( $post->ID, $post );
+		}
+
+	// Unpublishing a previously published post.
+	} elseif ( 'publish' === $old_status ) {
+		// Some form of pending status - only remove the activity entry.
+		bp_activity_post_type_unpublish( $post->ID, $post );
+	}
+}
+add_action( 'transition_post_status', 'bp_activity_catch_transition_post_type_status', 10, 3 );
Index: /tags/2.4.3/src/bp-activity/bp-activity-admin.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-admin.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-admin.php	(revision 10410)
@@ -0,0 +1,1843 @@
+<?php
+/**
+ * BuddyPress Activity component admin screen.
+ *
+ * Props to WordPress core for the Comments admin screen, and its contextual
+ * help text, on which this implementation is heavily based.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityAdmin
+ * @since 1.6.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+// Include WP's list table class.
+if ( !class_exists( 'WP_List_Table' ) ) require( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
+
+// Per_page screen option. Has to be hooked in extremely early.
+if ( is_admin() && ! empty( $_REQUEST['page'] ) && 'bp-activity' == $_REQUEST['page'] )
+	add_filter( 'set-screen-option', 'bp_activity_admin_screen_options', 10, 3 );
+
+/**
+ * Register the Activity component admin screen.
+ *
+ * @since 1.6.0
+ */
+function bp_activity_add_admin_menu() {
+
+	// Add our screen.
+	$hook = add_menu_page(
+		_x( 'Activity', 'Admin Dashbord SWA page title', 'buddypress' ),
+		_x( 'Activity', 'Admin Dashbord SWA menu', 'buddypress' ),
+		'bp_moderate',
+		'bp-activity',
+		'bp_activity_admin',
+		'div'
+	);
+
+	// Hook into early actions to load custom CSS and our init handler.
+	add_action( "load-$hook", 'bp_activity_admin_load' );
+}
+add_action( bp_core_admin_hook(), 'bp_activity_add_admin_menu' );
+
+/**
+ * Add activity component to custom menus array.
+ *
+ * Several BuddyPress components have top-level menu items in the Dashboard,
+ * which all appear together in the middle of the Dashboard menu. This function
+ * adds the Activity page to the array of these menu items.
+ *
+ * @since 1.7.0
+ *
+ * @param array $custom_menus The list of top-level BP menu items.
+ * @return array $custom_menus List of top-level BP menu items, with Activity added.
+ */
+function bp_activity_admin_menu_order( $custom_menus = array() ) {
+	array_push( $custom_menus, 'bp-activity' );
+	return $custom_menus;
+}
+add_filter( 'bp_admin_menu_order', 'bp_activity_admin_menu_order' );
+
+/**
+ * AJAX receiver for Activity replies via the admin screen.
+ *
+ * Processes requests to add new activity comments, and echoes HTML for a new
+ * table row.
+ *
+ * @since 1.6.0
+ */
+function bp_activity_admin_reply() {
+	// Check nonce.
+	check_ajax_referer( 'bp-activity-admin-reply', '_ajax_nonce-bp-activity-admin-reply' );
+
+	$parent_id = ! empty( $_REQUEST['parent_id'] ) ? (int) $_REQUEST['parent_id'] : 0;
+	$root_id   = ! empty( $_REQUEST['root_id'] )   ? (int) $_REQUEST['root_id']   : 0;
+
+	// $parent_id is required
+	if ( empty( $parent_id ) )
+		die( '-1' );
+
+	// If $root_id not set (e.g. for root items), use $parent_id.
+	if ( empty( $root_id ) )
+		$root_id = $parent_id;
+
+	// Check that a reply has been entered.
+	if ( empty( $_REQUEST['content'] ) )
+		die( __( 'ERROR: Please type a reply.', 'buddypress' ) );
+
+	// Check parent activity exists.
+	$parent_activity = new BP_Activity_Activity( $parent_id );
+	if ( empty( $parent_activity->component ) )
+		die( __( 'ERROR: The item you are trying to reply to cannot be found, or it has been deleted.', 'buddypress' ) );
+
+	// @todo: Check if user is allowed to create new activity items
+	// if ( ! current_user_can( 'bp_new_activity' ) )
+	if ( ! current_user_can( 'bp_moderate' ) )
+		die( '-1' );
+
+	// Add new activity comment.
+	$new_activity_id = bp_activity_new_comment( array(
+		'activity_id' => $root_id,              // ID of the root activity item.
+		'content'     => $_REQUEST['content'],
+		'parent_id'   => $parent_id,            // ID of a parent comment.
+	) );
+
+	// Fetch the new activity item, as we need it to create table markup to return.
+	$new_activity = new BP_Activity_Activity( $new_activity_id );
+
+	// This needs to be set for the BP_Activity_List_Table constructor to work.
+	set_current_screen( 'toplevel_page_bp-activity' );
+
+	// Set up an output buffer.
+	ob_start();
+	$list_table = new BP_Activity_List_Table();
+	$list_table->single_row( (array) $new_activity );
+
+	// Get table markup.
+	$response =  array(
+		'data'     => ob_get_contents(),
+		'id'       => $new_activity_id,
+		'position' => -1,
+		'what'     => 'bp_activity',
+	);
+	ob_end_clean();
+
+	// Send response.
+	$r = new WP_Ajax_Response();
+	$r->add( $response );
+	$r->send();
+
+	exit();
+}
+add_action( 'wp_ajax_bp-activity-admin-reply', 'bp_activity_admin_reply' );
+
+/**
+ * Handle save/update of screen options for the Activity component admin screen.
+ *
+ * @since 1.6.0
+ *
+ * @param string $value     Will always be false unless another plugin filters it first.
+ * @param string $option    Screen option name.
+ * @param string $new_value Screen option form value.
+ * @return string Option value. False to abandon update.
+ */
+function bp_activity_admin_screen_options( $value, $option, $new_value ) {
+	if ( 'toplevel_page_bp_activity_per_page' != $option && 'toplevel_page_bp_activity_network_per_page' != $option )
+		return $value;
+
+	// Per page.
+	$new_value = (int) $new_value;
+	if ( $new_value < 1 || $new_value > 999 )
+		return $value;
+
+	return $new_value;
+}
+
+/**
+ * Hide the advanced edit meta boxes by default, so we don't clutter the screen.
+ *
+ * @since 1.6.0
+ *
+ * @param array     $hidden Array of items to hide.
+ * @param WP_Screen $screen Screen identifier.
+ * @return array Hidden Meta Boxes.
+ */
+function bp_activity_admin_edit_hidden_metaboxes( $hidden, $screen ) {
+	if ( empty( $screen->id ) || 'toplevel_page_bp-activity' != $screen->id && 'toplevel_page_bp-activity_network' != $screen->id )
+		return $hidden;
+
+	// Hide the primary link meta box by default.
+	$hidden  = array_merge( (array) $hidden, array( 'bp_activity_itemids', 'bp_activity_link', 'bp_activity_type', 'bp_activity_userid', ) );
+
+	/**
+	 * Filters default hidden metaboxes so plugins can alter list.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array     $hidden Default metaboxes to hide.
+	 * @param WP_Screen $screen Screen identifier.
+	 */
+	return apply_filters( 'bp_hide_meta_boxes', array_unique( $hidden ), $screen );
+}
+add_filter( 'default_hidden_meta_boxes', 'bp_activity_admin_edit_hidden_metaboxes', 10, 2 );
+
+/**
+ * Set up the Activity admin page.
+ *
+ * Does the following:
+ *   - Register contextual help and screen options for this admin page.
+ *   - Enqueues scripts and styles.
+ *   - Catches POST and GET requests related to Activity.
+ *
+ * @since 1.6.0
+ *
+ * @global object                 $bp                     BuddyPress global settings.
+ * @global BP_Activity_List_Table $bp_activity_list_table Activity screen list table.
+ */
+function bp_activity_admin_load() {
+	global $bp_activity_list_table;
+
+	$bp = buddypress();
+
+	// Decide whether to load the dev version of the CSS and JavaScript.
+	$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : 'min.';
+
+	$doaction = bp_admin_list_table_current_bulk_action();
+
+	/**
+	 * Fires at top of Activity admin page.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $doaction Current $_GET action being performed in admin screen.
+	 */
+	do_action( 'bp_activity_admin_load', $doaction );
+
+	// Edit screen.
+	if ( 'edit' == $doaction && ! empty( $_GET['aid'] ) ) {
+		// Columns screen option.
+		add_screen_option( 'layout_columns', array( 'default' => 2, 'max' => 2, ) );
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'bp-activity-edit-overview',
+			'title'   => __( 'Overview', 'buddypress' ),
+			'content' =>
+				'<p>' . __( 'You edit activities made on your site similar to the way you edit a comment. This is useful if you need to change which page the activity links to, or when you notice that the author has made a typographical error.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'The two big editing areas for the activity title and content 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 (Primary Item/Secondary Item, Link, Type, Author ID) or to choose a 1- or 2-column layout for this screen.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'You can also moderate the activity from this screen using the Status box, where you can also change the timestamp of the activity.', 'buddypress' ) . '</p>'
+		) );
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'bp-activity-edit-advanced',
+			'title'   => __( 'Item, Link, Type', 'buddypress' ),
+			'content' =>
+				'<p>' . __( '<strong>Primary Item/Secondary Item</strong> - These identify the object that created the activity. For example, the fields could reference a comment left on a specific site. Some types of activity may only use one, or none, of these fields.', 'buddypress' ) . '</p>' .
+				'<p>' . __( '<strong>Link</strong> - Used by some types of activity (e.g blog posts and comments, and forum topics and replies) to store a link back to the original content.', 'buddypress' ) . '</p>' .
+				'<p>' . __( '<strong>Type</strong> - Each distinct kind of activity has its own type. For example, <code>created_group</code> is used when a group is created and <code>joined_group</code> is used when a user joins a group.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'For information about when and how BuddyPress uses all of these settings, see the Managing Activity link in the panel to the side.', 'buddypress' ) . '</p>'
+		) );
+
+		// Help panel - sidebar links.
+		get_current_screen()->set_help_sidebar(
+			'<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
+			'<p>' . __( '<a href="https://codex.buddypress.org/administrator-guide/activity-stream-management-panels/">Managing Activity</a>', 'buddypress' ) . '</p>' .
+			'<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
+		);
+
+		// Register metaboxes for the edit screen.
+		add_meta_box( 'submitdiv',           _x( 'Status', 'activity admin edit screen', 'buddypress' ), 'bp_activity_admin_edit_metabox_status', get_current_screen()->id, 'side', 'core' );
+		add_meta_box( 'bp_activity_itemids', _x( 'Primary Item/Secondary Item', 'activity admin edit screen', 'buddypress' ), 'bp_activity_admin_edit_metabox_itemids', get_current_screen()->id, 'normal', 'core' );
+		add_meta_box( 'bp_activity_link',    _x( 'Link', 'activity admin edit screen', 'buddypress' ), 'bp_activity_admin_edit_metabox_link', get_current_screen()->id, 'normal', 'core' );
+		add_meta_box( 'bp_activity_type',    _x( 'Type', 'activity admin edit screen', 'buddypress' ), 'bp_activity_admin_edit_metabox_type', get_current_screen()->id, 'normal', 'core' );
+		add_meta_box( 'bp_activity_userid',  _x( 'Author ID', 'activity admin edit screen', 'buddypress' ), 'bp_activity_admin_edit_metabox_userid', get_current_screen()->id, 'normal', 'core' );
+
+		/**
+		 * Fires after the registration of all of the default activity meta boxes.
+		 *
+		 * @since 2.4.0
+		 */
+		do_action( 'bp_activity_admin_meta_boxes' );
+
+		// Enqueue JavaScript files.
+		wp_enqueue_script( 'postbox' );
+		wp_enqueue_script( 'dashboard' );
+		wp_enqueue_script( 'comment' );
+
+	// Index screen.
+	} else {
+		// Create the Activity screen list table.
+		$bp_activity_list_table = new BP_Activity_List_Table();
+
+		// The per_page screen option.
+		add_screen_option( 'per_page', array( 'label' => _x( 'Activity', 'Activity items per page (screen options)', 'buddypress' )) );
+
+		// Help panel - overview text.
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'bp-activity-overview',
+			'title'   => __( 'Overview', 'buddypress' ),
+			'content' =>
+				'<p>' . __( 'You can manage activities made on your site similar to the way you manage comments and other content. This screen is customizable in the same ways as other management screens, and you can act on activities using the on-hover action links or the Bulk Actions.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'There are many different types of activities. Some are generated automatically by BuddyPress and other plugins, and some are entered directly by a user in the form of status update. To help manage the different activity types, use the filter dropdown box to switch between them.', 'buddypress' ) . '</p>'
+		) );
+
+		// Help panel - moderation text.
+		get_current_screen()->add_help_tab( array(
+			'id'		=> 'bp-activity-moderating',
+			'title'		=> __( 'Moderating Activity', 'buddypress' ),
+			'content'	=>
+				'<p>' . __( 'In the <strong>Activity</strong> column, above each activity it says &#8220;Submitted on,&#8221; followed by the date and time the activity item was generated on your site. Clicking on the date/time link will take you to that activity on your live site. Hovering over any activity gives you options to reply, edit, spam mark, or delete that activity.', 'buddypress' ) . '</p>' .
+				'<p>' . __( "In the <strong>In Response To</strong> column, if the activity was in reply to another activity, it shows that activity's author's picture and name, and a link to that activity on your live site. If there is a small bubble, the number in it shows how many other activities are related to this one; these are usually comments. Clicking the bubble will filter the activity screen to show only related activity items.", 'buddypress' ) . '</p>'
+		) );
+
+		// Help panel - sidebar links.
+		get_current_screen()->set_help_sidebar(
+			'<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
+			'<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
+		);
+	}
+
+	// Enqueue CSS and JavaScript.
+	wp_enqueue_script( 'bp_activity_admin_js', $bp->plugin_url . "bp-activity/admin/js/admin.{$min}js",   array( 'jquery', 'wp-ajax-response' ), bp_get_version(), true );
+	wp_localize_script( 'bp_activity_admin_js', 'bp_activity_admin_vars', array(
+ 	  	'page'   => get_current_screen()->id
+ 	) );
+	wp_enqueue_style( 'bp_activity_admin_css', $bp->plugin_url . "bp-activity/admin/css/admin.{$min}css", array(),                               bp_get_version()       );
+
+	wp_style_add_data( 'bp_activity_admin_css', 'rtl', true );
+	if ( $min ) {
+		wp_style_add_data( 'bp_activity_admin_css', 'suffix', $min );
+	}
+
+	/**
+	 * Fires after the activity js and style has been enqueued.
+	 *
+	 * @since 2.4.0
+	 */
+	do_action( 'bp_activity_admin_enqueue_scripts' );
+
+	// Handle spam/un-spam/delete of activities.
+	if ( !empty( $doaction ) && ! in_array( $doaction, array( '-1', 'edit', 'save', ) ) ) {
+
+		// Build redirection URL.
+		$redirect_to = remove_query_arg( array( 'aid', 'deleted', 'error', 'spammed', 'unspammed', ), wp_get_referer() );
+		$redirect_to = add_query_arg( 'paged', $bp_activity_list_table->get_pagenum(), $redirect_to );
+
+		// Get activity IDs.
+		$activity_ids = array_map( 'absint', (array) $_REQUEST['aid'] );
+
+		/**
+		 * Filters list of IDs being spammed/un-spammed/deleted.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param array $activity_ids Activity IDs to spam/un-spam/delete.
+		 */
+		$activity_ids = apply_filters( 'bp_activity_admin_action_activity_ids', $activity_ids );
+
+		// Is this a bulk request?
+		if ( 'bulk_' == substr( $doaction, 0, 5 ) && ! empty( $_REQUEST['aid'] ) ) {
+			// Check this is a valid form submission.
+			check_admin_referer( 'bulk-activities' );
+
+			// Trim 'bulk_' off the action name to avoid duplicating a ton of code.
+			$doaction = substr( $doaction, 5 );
+
+		// This is a request to delete, spam, or un-spam, a single item.
+		} elseif ( !empty( $_REQUEST['aid'] ) ) {
+
+			// Check this is a valid form submission.
+			check_admin_referer( 'spam-activity_' . $activity_ids[0] );
+		}
+
+		// Initialise counters for how many of each type of item we perform an action on.
+		$deleted = $spammed = $unspammed = 0;
+
+		// Store any errors that occurs when updating the database items.
+		$errors = array();
+
+		// "We'd like to shoot the monster, could you move, please?"
+		foreach ( $activity_ids as $activity_id ) {
+			// @todo: Check the permissions on each
+			//if ( ! current_user_can( 'bp_edit_activity', $activity_id ) )
+			// continue;
+
+			// Get the activity from the database.
+			$activity = new BP_Activity_Activity( $activity_id );
+			if ( empty( $activity->component ) ) {
+				$errors[] = $activity_id;
+				continue;
+			}
+
+			switch ( $doaction ) {
+				case 'delete' :
+					if ( 'activity_comment' == $activity->type )
+						bp_activity_delete_comment( $activity->item_id, $activity->id );
+					else
+						bp_activity_delete( array( 'id' => $activity->id ) );
+
+					$deleted++;
+					break;
+
+				case 'ham' :
+					/**
+					 * Remove moderation and blacklist checks in case we want to ham an activity
+					 * which contains one of these listed keys.
+					 */
+					remove_action( 'bp_activity_before_save', 'bp_activity_check_moderation_keys', 2, 1 );
+					remove_action( 'bp_activity_before_save', 'bp_activity_check_blacklist_keys',  2, 1 );
+
+					bp_activity_mark_as_ham( $activity );
+					$result = $activity->save();
+
+					// Check for any error during activity save.
+					if ( ! $result )
+						$errors[] = $activity->id;
+					else
+						$unspammed++;
+					break;
+
+				case 'spam' :
+					bp_activity_mark_as_spam( $activity );
+					$result = $activity->save();
+
+					// Check for any error during activity save.
+					if ( ! $result )
+						$errors[] = $activity->id;
+					else
+						$spammed++;
+					break;
+
+				default:
+					break;
+			}
+
+			// Release memory.
+			unset( $activity );
+		}
+
+		/**
+		 * Fires before redirect for plugins to do something with activity.
+		 *
+		 * Passes an activity array counts how many were spam, not spam, deleted, and IDs that were errors.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param array  $value        Array holding spam, not spam, deleted counts, error IDs.
+		 * @param string $redirect_to  URL to redirect to.
+		 * @param array  $activity_ids Original array of activity IDs.
+		 */
+		do_action( 'bp_activity_admin_action_after', array( $spammed, $unspammed, $deleted, $errors ), $redirect_to, $activity_ids );
+
+		// Add arguments to the redirect URL so that on page reload, we can easily display what we've just done.
+		if ( $spammed )
+			$redirect_to = add_query_arg( 'spammed', $spammed, $redirect_to );
+
+		if ( $unspammed )
+			$redirect_to = add_query_arg( 'unspammed', $unspammed, $redirect_to );
+
+		if ( $deleted )
+			$redirect_to = add_query_arg( 'deleted', $deleted, $redirect_to );
+
+		// If an error occurred, pass back the activity ID that failed.
+		if ( ! empty( $errors ) )
+			$redirect_to = add_query_arg( 'error', implode ( ',', array_map( 'absint', $errors ) ), $redirect_to );
+
+		/**
+		 * Filters redirect URL after activity spamming/un-spamming/deletion occurs.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $redirect_to URL to redirect to.
+		 */
+		wp_redirect( apply_filters( 'bp_activity_admin_action_redirect', $redirect_to ) );
+		exit;
+
+
+	// Save the edit.
+	} elseif ( $doaction && 'save' == $doaction ) {
+		// Build redirection URL.
+		$redirect_to = remove_query_arg( array( 'action', 'aid', 'deleted', 'error', 'spammed', 'unspammed', ), $_SERVER['REQUEST_URI'] );
+
+		// Get activity ID.
+		$activity_id = (int) $_REQUEST['aid'];
+
+		// Check this is a valid form submission.
+		check_admin_referer( 'edit-activity_' . $activity_id );
+
+		// Get the activity from the database.
+		$activity = new BP_Activity_Activity( $activity_id );
+
+		// If the activity doesn't exist, just redirect back to the index.
+		if ( empty( $activity->component ) ) {
+			wp_redirect( $redirect_to );
+			exit;
+		}
+
+		// Check the form for the updated properties.
+		// Store any error that occurs when updating the database item.
+		$error = 0;
+
+		// Activity spam status.
+		$prev_spam_status = $new_spam_status = false;
+		if ( ! empty( $_POST['activity_status'] ) ) {
+			$prev_spam_status = $activity->is_spam;
+			$new_spam_status  = ( 'spam' == $_POST['activity_status'] ) ? true : false;
+		}
+
+		// Activity action.
+		if ( isset( $_POST['bp-activities-action'] ) )
+			$activity->action = $_POST['bp-activities-action'];
+
+		// Activity content.
+		if ( isset( $_POST['bp-activities-content'] ) )
+			$activity->content = $_POST['bp-activities-content'];
+
+		// Activity primary link.
+		if ( ! empty( $_POST['bp-activities-link'] ) )
+			$activity->primary_link = $_POST['bp-activities-link'];
+
+		// Activity user ID.
+		if ( ! empty( $_POST['bp-activities-userid'] ) )
+			$activity->user_id = (int) $_POST['bp-activities-userid'];
+
+		// Activity item primary ID.
+		if ( isset( $_POST['bp-activities-primaryid'] ) )
+			$activity->item_id = (int) $_POST['bp-activities-primaryid'];
+
+		// Activity item secondary ID.
+		if ( isset( $_POST['bp-activities-secondaryid'] ) )
+			$activity->secondary_item_id = (int) $_POST['bp-activities-secondaryid'];
+
+		// Activity type.
+		if ( ! empty( $_POST['bp-activities-type'] ) ) {
+			$actions = bp_activity_admin_get_activity_actions();
+
+			// Check that the new type is a registered activity type.
+			if ( in_array( $_POST['bp-activities-type'], $actions ) ) {
+				$activity->type = $_POST['bp-activities-type'];
+			}
+		}
+
+		// Activity timestamp.
+		if ( ! empty( $_POST['aa'] ) && ! empty( $_POST['mm'] ) && ! empty( $_POST['jj'] ) && ! empty( $_POST['hh'] ) && ! empty( $_POST['mn'] ) && ! empty( $_POST['ss'] ) ) {
+			$aa = $_POST['aa'];
+			$mm = $_POST['mm'];
+			$jj = $_POST['jj'];
+			$hh = $_POST['hh'];
+			$mn = $_POST['mn'];
+			$ss = $_POST['ss'];
+			$aa = ( $aa <= 0 ) ? date( 'Y' ) : $aa;
+			$mm = ( $mm <= 0 ) ? date( 'n' ) : $mm;
+			$jj = ( $jj > 31 ) ? 31 : $jj;
+			$jj = ( $jj <= 0 ) ? date( 'j' ) : $jj;
+			$hh = ( $hh > 23 ) ? $hh -24 : $hh;
+			$mn = ( $mn > 59 ) ? $mn -60 : $mn;
+			$ss = ( $ss > 59 ) ? $ss -60 : $ss;
+
+			// Reconstruct the date into a timestamp.
+			$gmt_date = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $aa, $mm, $jj, $hh, $mn, $ss );
+
+			$activity->date_recorded = $gmt_date;
+		}
+
+		// Has the spam status has changed?
+		if ( $new_spam_status != $prev_spam_status ) {
+			if ( $new_spam_status )
+				bp_activity_mark_as_spam( $activity );
+			else
+				bp_activity_mark_as_ham( $activity );
+		}
+
+		// Save.
+		$result = $activity->save();
+
+		// Clear the activity stream first page cache, in case this activity's timestamp was changed.
+		wp_cache_delete( 'bp_activity_sitewide_front', 'bp' );
+
+		// Check for any error during activity save.
+		if ( false === $result )
+			$error = $activity->id;
+
+		/**
+		 * Fires before redirect so plugins can do something first on save action.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param array Array holding activity object and ID that holds error.
+		 */
+		do_action_ref_array( 'bp_activity_admin_edit_after', array( &$activity, $error ) );
+
+		// If an error occurred, pass back the activity ID that failed.
+		if ( $error )
+			$redirect_to = add_query_arg( 'error', (int) $error, $redirect_to );
+		else
+			$redirect_to = add_query_arg( 'updated', (int) $activity->id, $redirect_to );
+
+		/**
+		 * Filters URL to redirect to after saving.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $redirect_to URL to redirect to.
+		 */
+		wp_redirect( apply_filters( 'bp_activity_admin_edit_redirect', $redirect_to ) );
+		exit;
+
+
+	// If a referrer and a nonce is supplied, but no action, redirect back.
+	} elseif ( ! empty( $_GET['_wp_http_referer'] ) ) {
+		wp_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), stripslashes( $_SERVER['REQUEST_URI'] ) ) );
+		exit;
+	}
+}
+
+/**
+ * Output the Activity component admin screens.
+ *
+ * @since 1.6.0
+ */
+function bp_activity_admin() {
+	// Decide whether to load the index or edit screen.
+	$doaction = ! empty( $_REQUEST['action'] ) ? $_REQUEST['action'] : '';
+
+	// Display the single activity edit screen.
+	if ( 'edit' == $doaction && ! empty( $_GET['aid'] ) )
+		bp_activity_admin_edit();
+
+	// Otherwise, display the Activity index screen.
+	else
+		bp_activity_admin_index();
+}
+
+/**
+ * Display the single activity edit screen.
+ *
+ * @since 1.6.0
+ */
+function bp_activity_admin_edit() {
+
+	// @todo: Check if user is allowed to edit activity items
+	// if ( ! current_user_can( 'bp_edit_activity' ) )
+	if ( ! is_super_admin() )
+		die( '-1' );
+
+	// Get the activity from the database.
+	$activity = bp_activity_get( array(
+		'in'               => ! empty( $_REQUEST['aid'] ) ? (int) $_REQUEST['aid'] : 0,
+		'max'              => 1,
+		'show_hidden'      => true,
+		'spam'             => 'all',
+		'display_comments' => 0
+	) );
+
+	if ( ! empty( $activity['activities'][0] ) ) {
+		$activity = $activity['activities'][0];
+
+		// Workaround to use WP's touch_time() without duplicating that function.
+		$GLOBALS['comment'] = new stdClass;
+		$GLOBALS['comment']->comment_date = $activity->date_recorded;
+	} else {
+		$activity = '';
+	}
+
+	// Construct URL for form.
+	$form_url = remove_query_arg( array( 'action', 'deleted', 'error', 'spammed', 'unspammed', ), $_SERVER['REQUEST_URI'] );
+	$form_url = add_query_arg( 'action', 'save', $form_url );
+
+	/**
+	 * Fires before activity edit form is displays so plugins can modify the activity.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $value Array holding single activity object that was passed by reference.
+	 */
+	do_action_ref_array( 'bp_activity_admin_edit', array( &$activity ) ); ?>
+
+	<div class="wrap">
+		<h2><?php printf( __( 'Editing Activity (ID #%s)', 'buddypress' ), number_format_i18n( (int) $_REQUEST['aid'] ) ); ?></h2>
+
+		<?php if ( ! empty( $activity ) ) : ?>
+
+			<form action="<?php echo esc_url( $form_url ); ?>" id="bp-activities-edit-form" method="post">
+				<div id="poststuff">
+
+					<div id="post-body" class="metabox-holder columns-<?php echo 1 == get_current_screen()->get_columns() ? '1' : '2'; ?>">
+						<div id="post-body-content">
+							<div id="postdiv">
+								<div id="bp_activity_action" class="postbox">
+									<h3><?php _e( 'Action', 'buddypress' ); ?></h3>
+									<div class="inside">
+										<?php wp_editor( stripslashes( $activity->action ), 'bp-activities-action', array( 'media_buttons' => false, 'textarea_rows' => 7, 'teeny' => true, 'quicktags' => array( 'buttons' => 'strong,em,link,block,del,ins,img,code,spell,close' ) ) ); ?>
+									</div>
+								</div>
+
+								<div id="bp_activity_content" class="postbox">
+									<h3><?php _e( 'Content', 'buddypress' ); ?></h3>
+									<div class="inside">
+										<?php wp_editor( stripslashes( $activity->content ), 'bp-activities-content', array( 'media_buttons' => false, 'teeny' => true, 'quicktags' => array( 'buttons' => 'strong,em,link,block,del,ins,img,code,spell,close' ) ) ); ?>
+									</div>
+								</div>
+							</div>
+						</div><!-- #post-body-content -->
+
+						<div id="postbox-container-1" class="postbox-container">
+							<?php do_meta_boxes( get_current_screen()->id, 'side', $activity ); ?>
+						</div>
+
+						<div id="postbox-container-2" class="postbox-container">
+							<?php do_meta_boxes( get_current_screen()->id, 'normal', $activity ); ?>
+							<?php do_meta_boxes( get_current_screen()->id, 'advanced', $activity ); ?>
+						</div>
+					</div><!-- #post-body -->
+
+				</div><!-- #poststuff -->
+				<?php wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); ?>
+				<?php wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); ?>
+				<?php wp_nonce_field( 'edit-activity_' . $activity->id ); ?>
+			</form>
+
+		<?php else : ?>
+			<p>
+				<?php _e( 'No activity found with this ID.', 'buddypress' ); ?>
+				<a href="<?php echo esc_url( bp_get_admin_url( 'admin.php?page=bp-activity' ) ); ?>"><?php _e( 'Go back and try again.', 'buddypress' ); ?></a>
+			</p>
+		<?php endif; ?>
+
+	</div><!-- .wrap -->
+
+<?php
+}
+
+/**
+ * Status metabox for the Activity admin edit screen.
+ *
+ * @since 1.6.0
+ *
+ * @param object $item Activity item.
+ */
+function bp_activity_admin_edit_metabox_status( $item ) {
+?>
+
+	<div class="submitbox" id="submitcomment">
+
+		<div id="minor-publishing">
+			<div id="minor-publishing-actions">
+				<div id="preview-action">
+					<a class="button preview" href="<?php echo esc_attr( bp_activity_get_permalink( $item->id, $item ) ); ?>" target="_blank"><?php _e( 'View Activity', 'buddypress' ); ?></a>
+				</div>
+
+				<div class="clear"></div>
+			</div><!-- #minor-publishing-actions -->
+
+			<div id="misc-publishing-actions">
+				<div class="misc-pub-section" id="comment-status-radio">
+					<label class="approved" for="activity-status-approved"><input type="radio" name="activity_status" id="activity-status-approved" value="ham" <?php checked( $item->is_spam, 0 ); ?>><?php _e( 'Approved', 'buddypress' ); ?></label><br />
+					<label class="spam" for="activity-status-spam"><input type="radio" name="activity_status" id="activity-status-spam" value="spam" <?php checked( $item->is_spam, 1 ); ?>><?php _e( 'Spam', 'buddypress' ); ?></label>
+				</div>
+
+				<div class="misc-pub-section curtime misc-pub-section-last">
+					<?php
+					// Translators: Publish box date format, see http://php.net/date.
+					$datef = __( 'M j, Y @ G:i', 'buddypress' );
+					$date  = date_i18n( $datef, strtotime( $item->date_recorded ) );
+					?>
+					<span id="timestamp"><?php printf( __( 'Submitted on: %s', 'buddypress' ), '<strong>' . $date . '</strong>' ); ?></span>&nbsp;<a href="#edit_timestamp" class="edit-timestamp hide-if-no-js" tabindex='4'><?php _e( 'Edit', 'buddypress' ); ?></a>
+
+					<div id='timestampdiv' class='hide-if-js'>
+						<?php touch_time( 1, 0, 5 ); ?>
+					</div><!-- #timestampdiv -->
+				</div>
+			</div> <!-- #misc-publishing-actions -->
+
+			<div class="clear"></div>
+		</div><!-- #minor-publishing -->
+
+		<div id="major-publishing-actions">
+			<div id="publishing-action">
+				<?php submit_button( __( 'Update', 'buddypress' ), 'primary', 'save', false ); ?>
+			</div>
+			<div class="clear"></div>
+		</div><!-- #major-publishing-actions -->
+
+	</div><!-- #submitcomment -->
+
+<?php
+}
+
+/**
+ * Primary link metabox for the Activity admin edit screen.
+ *
+ * @since 1.6.0
+ *
+ * @param object $item Activity item.
+ */
+function bp_activity_admin_edit_metabox_link( $item ) {
+?>
+
+	<label class="screen-reader-text" for="bp-activities-link"><?php _e( 'Link', 'buddypress' ); ?></label>
+	<input type="url" name="bp-activities-link" id="bp-activities-link" value="<?php echo esc_url( $item->primary_link ); ?>" aria-describedby="bp-activities-link-description" />
+	<p id="bp-activities-link-description"><?php _e( 'Activity generated by posts and comments, forum topics and replies, and some plugins, uses the link field for a permalink back to the content item.', 'buddypress' ); ?></p>
+
+<?php
+}
+
+/**
+ * User ID metabox for the Activity admin edit screen.
+ *
+ * @since 1.6.0
+ *
+ * @param object $item Activity item.
+ */
+function bp_activity_admin_edit_metabox_userid( $item ) {
+?>
+
+	<label class="screen-reader-text" for="bp-activities-userid"><?php _e( 'Author ID', 'buddypress' ); ?></label>
+	<input type="number" name="bp-activities-userid" id="bp-activities-userid" value="<?php echo esc_attr( $item->user_id ); ?>" min="1" />
+
+<?php
+}
+
+/**
+ * Get flattened array of all registered activity actions.
+ *
+ * Format is [activity_type] => Pretty name for activity type.
+ *
+ * @since 2.0.0
+ *
+ * @return array $actions
+ */
+function bp_activity_admin_get_activity_actions() {
+	$actions  = array();
+
+	// Walk through the registered actions, and build an array of actions/values.
+	foreach ( bp_activity_get_actions() as $action ) {
+		$action = array_values( (array) $action );
+
+		for ( $i = 0, $i_count = count( $action ); $i < $i_count; $i++ ) {
+			$actions[ $action[$i]['key'] ] = $action[$i]['value'];
+		}
+	}
+
+	// This was a mis-named activity type from before BP 1.6.
+	unset( $actions['friends_register_activity_action'] );
+
+	// Sort array by the human-readable value.
+	natsort( $actions );
+
+	return $actions;
+}
+
+/**
+ * Activity type metabox for the Activity admin edit screen.
+ *
+ * @since 1.6.0
+ *
+ * @param object $item Activity item.
+ */
+function bp_activity_admin_edit_metabox_type( $item ) {
+	$bp = buddypress();
+
+	$actions  = array();
+	$selected = $item->type;
+
+	// Walk through the registered actions, and build an array of actions/values.
+	foreach ( bp_activity_get_actions() as $action ) {
+		$action = array_values( (array) $action );
+
+		for ( $i = 0, $i_count = count( $action ); $i < $i_count; $i++ )
+			$actions[ $action[$i]['key'] ] = $action[$i]['value'];
+	}
+
+	// This was a mis-named activity type from before BP 1.6.
+	unset( $actions['friends_register_activity_action'] );
+
+	// Sort array by the human-readable value.
+	natsort( $actions );
+
+	/*
+	 * If the activity type is not registered properly (eg, a plugin has
+	 * not called bp_activity_set_action()), add the raw type to the end
+	 * of the list.
+	 */
+	if ( ! isset( $actions[ $selected ] ) ) {
+		_doing_it_wrong( __FUNCTION__, sprintf( __( 'This activity item has a type (%s) that is not registered using bp_activity_set_action(), so no label is available.', 'buddypress' ), $selected ), '2.0.0' );
+		$actions[ $selected ] = $selected;
+	}
+
+	?>
+
+	<label for="bp-activities-type" class="screen-reader-text"><?php esc_html_e( 'Select activity type', 'buddypress' ); ?></label>
+	<select name="bp-activities-type" id="bp-activities-type">
+		<?php foreach ( $actions as $k => $v ) : ?>
+			<option value="<?php echo esc_attr( $k ); ?>" <?php selected( $k,  $selected ); ?>><?php echo esc_html( $v ); ?></option>
+		<?php endforeach; ?>
+	</select>
+
+<?php
+}
+
+/**
+ * Primary item ID/Secondary item ID metabox for the Activity admin edit screen.
+ *
+ * @since 1.6.0
+ *
+ * @param object $item Activity item.
+ */
+function bp_activity_admin_edit_metabox_itemids( $item ) {
+?>
+
+	<label for="bp-activities-primaryid"><?php _e( 'Primary Item ID', 'buddypress' ); ?></label>
+	<input type="number" name="bp-activities-primaryid" id="bp-activities-primaryid" value="<?php echo esc_attr( $item->item_id ); ?>" min="0" />
+	<br />
+
+	<label for="bp-activities-secondaryid"><?php _e( 'Secondary Item ID', 'buddypress' ); ?></label>
+	<input type="number" name="bp-activities-secondaryid" id="bp-activities-secondaryid" value="<?php echo esc_attr( $item->secondary_item_id ); ?>" min="0" />
+
+	<p><?php _e( 'These identify the object that created this activity. For example, the fields could reference a pair of site and comment IDs.', 'buddypress' ); ?></p>
+
+<?php
+}
+
+/**
+ * Display the Activity admin index screen, which contains a list of all the activities.
+ *
+ * @since 1.6.0
+ *
+ * @global BP_Activity_List_Table $bp_activity_list_table Activity screen list table.
+ * @global string                 $plugin_page            The current plugin page.
+ */
+function bp_activity_admin_index() {
+	global $bp_activity_list_table, $plugin_page;
+
+	$messages = array();
+
+	// If the user has just made a change to an activity item, build status messages.
+	if ( ! empty( $_REQUEST['deleted'] ) || ! empty( $_REQUEST['spammed'] ) || ! empty( $_REQUEST['unspammed'] ) || ! empty( $_REQUEST['error'] ) || ! empty( $_REQUEST['updated'] ) ) {
+		$deleted   = ! empty( $_REQUEST['deleted']   ) ? (int) $_REQUEST['deleted']   : 0;
+		$errors    = ! empty( $_REQUEST['error']     ) ? $_REQUEST['error']           : '';
+		$spammed   = ! empty( $_REQUEST['spammed']   ) ? (int) $_REQUEST['spammed']   : 0;
+		$unspammed = ! empty( $_REQUEST['unspammed'] ) ? (int) $_REQUEST['unspammed'] : 0;
+		$updated   = ! empty( $_REQUEST['updated']   ) ? (int) $_REQUEST['updated']   : 0;
+
+		$errors = array_map( 'absint', explode( ',', $errors ) );
+
+		// Make sure we don't get any empty values in $errors.
+		for ( $i = 0, $errors_count = count( $errors ); $i < $errors_count; $i++ ) {
+			if ( 0 === $errors[$i] ) {
+				unset( $errors[$i] );
+			}
+		}
+
+		// Reindex array.
+		$errors = array_values( $errors );
+
+		if ( $deleted > 0 )
+			$messages[] = sprintf( _n( '%s activity item has been permanently deleted.', '%s activity items have been permanently deleted.', $deleted, 'buddypress' ), number_format_i18n( $deleted ) );
+
+		if ( ! empty( $errors ) ) {
+			if ( 1 == count( $errors ) ) {
+				$messages[] = sprintf( __( 'An error occurred when trying to update activity ID #%s.', 'buddypress' ), number_format_i18n( $errors[0] ) );
+
+			} else {
+				$error_msg  = __( 'Errors occurred when trying to update these activity items:', 'buddypress' );
+				$error_msg .= '<ul class="activity-errors">';
+
+				// Display each error as a list item.
+				foreach ( $errors as $error ) {
+					// Translators: This is a bulleted list of item IDs.
+					$error_msg .= '<li>' . sprintf( __( '#%s', 'buddypress' ), number_format_i18n( $error ) ) . '</li>';
+				}
+
+				$error_msg  .= '</ul>';
+				$messages[] = $error_msg;
+			}
+		}
+
+		if ( $spammed > 0 )
+			$messages[] = sprintf( _n( '%s activity item has been successfully spammed.', '%s activity items have been successfully spammed.', $spammed, 'buddypress' ), number_format_i18n( $spammed ) );
+
+		if ( $unspammed > 0 )
+			$messages[] = sprintf( _n( '%s activity item has been successfully unspammed.', '%s activity items have been successfully unspammed.', $unspammed, 'buddypress' ), number_format_i18n( $unspammed ) );
+
+		if ( $updated > 0 )
+			$messages[] = __( 'The activity item has been updated successfully.', 'buddypress' );
+	}
+
+	// Prepare the activity items for display.
+	$bp_activity_list_table->prepare_items();
+
+	/**
+	 * Fires before edit form is displayed so plugins can modify the activity messages.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $messages Array of messages to display at top of page.
+	 */
+	do_action( 'bp_activity_admin_index', $messages ); ?>
+
+	<div class="wrap">
+		<h2>
+			<?php if ( !empty( $_REQUEST['aid'] ) ) : ?>
+				<?php printf( __( 'Activity related to ID #%s', 'buddypress' ), number_format_i18n( (int) $_REQUEST['aid'] ) ); ?>
+			<?php else : ?>
+				<?php _ex( 'Activity', 'Admin SWA page', 'buddypress' ); ?>
+			<?php endif; ?>
+
+			<?php if ( !empty( $_REQUEST['s'] ) ) : ?>
+				<span class="subtitle"><?php printf( __( 'Search results for &#8220;%s&#8221;', 'buddypress' ), wp_html_excerpt( esc_html( stripslashes( $_REQUEST['s'] ) ), 50 ) ); ?></span>
+			<?php endif; ?>
+		</h2>
+
+		<?php // If the user has just made a change to an activity item, display the status messages. ?>
+		<?php if ( !empty( $messages ) ) : ?>
+			<div id="moderated" class="<?php echo ( ! empty( $_REQUEST['error'] ) ) ? 'error' : 'updated'; ?>"><p><?php echo implode( "<br/>\n", $messages ); ?></p></div>
+		<?php endif; ?>
+
+		<?php // Display each activity on its own row. ?>
+		<?php $bp_activity_list_table->views(); ?>
+
+		<form id="bp-activities-form" action="" method="get">
+			<?php $bp_activity_list_table->search_box( __( 'Search all Activity', 'buddypress' ), 'bp-activity' ); ?>
+			<input type="hidden" name="page" value="<?php echo esc_attr( $plugin_page ); ?>" />
+			<?php $bp_activity_list_table->display(); ?>
+		</form>
+
+		<?php // This markup is used for the reply form. ?>
+		<table style="display: none;">
+			<tr id="bp-activities-container" style="display: none;">
+				<td colspan="4">
+					<form method="get" action="">
+
+						<h5 id="bp-replyhead"><?php _e( 'Reply to Activity', 'buddypress' ); ?></h5>
+						<?php wp_editor( '', 'bp-activities', array( 'dfw' => false, 'media_buttons' => false, 'quicktags' => array( 'buttons' => 'strong,em,link,block,del,ins,img,code,spell,close' ), 'tinymce' => false, ) ); ?>
+
+						<p id="bp-replysubmit" class="submit">
+							<a href="#" class="cancel button-secondary alignleft"><?php _e( 'Cancel', 'buddypress' ); ?></a>
+							<a href="#" class="save button-primary alignright"><?php _e( 'Reply', 'buddypress' ); ?></a>
+
+							<img class="waiting" style="display:none;" src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" alt="" />
+							<span class="error" style="display:none;"></span>
+							<br class="clear" />
+						</p>
+
+						<?php wp_nonce_field( 'bp-activity-admin-reply', '_ajax_nonce-bp-activity-admin-reply', false ); ?>
+
+					</form>
+				</td>
+			</tr>
+		</table>
+	</div>
+
+<?php
+}
+
+/**
+ * List table class for the Activity component admin page.
+ *
+ * @since 1.6.0
+ */
+class BP_Activity_List_Table extends WP_List_Table {
+
+	/**
+	 * What type of view is being displayed?
+	 *
+	 * E.g. "all", "pending", "approved", "spam"...
+	 *
+	 * @since 1.6.0
+	 * @var string $view
+	 */
+	public $view = 'all';
+
+	/**
+	 * How many activity items have been marked as spam.
+	 *
+	 * @since 1.6.0
+	 * @var int $spam_count
+	 */
+	public $spam_count = 0;
+
+	/**
+	 * Store activity-to-user-ID mappings for use in the In Response To column.
+	 *
+	 * @since 1.6.0
+	 * @var array $activity_user_id
+	 */
+	protected $activity_user_id = array();
+
+	/**
+	 * If users can comment on blog & forum activity items.
+	 *
+	 * @link https://buddypress.trac.wordpress.org/ticket/6277
+	 *
+	 * @since 2.2.2
+	 * @var bool $disable_blogforum_comments
+	 */
+	public $disable_blogforum_comments = false;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 1.6.0
+	 */
+	public function __construct() {
+
+		// See if activity commenting is enabled for blog / forum activity items.
+		$this->disable_blogforum_comments = bp_disable_blogforum_comments();
+
+		// Define singular and plural labels, as well as whether we support AJAX.
+		parent::__construct( array(
+			'ajax'     => false,
+			'plural'   => 'activities',
+			'singular' => 'activity',
+			'screen'   => get_current_screen(),
+		) );
+	}
+
+	/**
+	 * Handle filtering of data, sorting, pagination, and any other data manipulation prior to rendering.
+	 *
+	 * @since 1.6.0
+	 */
+	function prepare_items() {
+
+		// Option defaults.
+		$filter           = array();
+		$include_id       = false;
+		$search_terms     = false;
+		$sort             = 'DESC';
+		$spam             = 'ham_only';
+
+		// Set current page.
+		$page = $this->get_pagenum();
+
+		// Set per page from the screen options.
+		$per_page = $this->get_items_per_page( str_replace( '-', '_', "{$this->screen->id}_per_page" ) );
+
+		// Check if we're on the "Spam" view.
+		if ( !empty( $_REQUEST['activity_status'] ) && 'spam' == $_REQUEST['activity_status'] ) {
+			$spam       = 'spam_only';
+			$this->view = 'spam';
+		}
+
+		// Sort order.
+		if ( !empty( $_REQUEST['order'] ) && 'desc' != $_REQUEST['order'] )
+			$sort = 'ASC';
+
+		// Order by.
+		/*if ( !empty( $_REQUEST['orderby'] ) ) {
+		}*/
+
+		// Filter.
+		if ( !empty( $_REQUEST['activity_type'] ) )
+			$filter = array( 'action' => $_REQUEST['activity_type'] );
+
+		// Are we doing a search?
+		if ( !empty( $_REQUEST['s'] ) )
+			$search_terms = $_REQUEST['s'];
+
+		// Check if user has clicked on a specific activity (if so, fetch only that, and any related, activity).
+		if ( !empty( $_REQUEST['aid'] ) )
+			$include_id = (int) $_REQUEST['aid'];
+
+		// Get the spam total (ignoring any search query or filter).
+		$spams = bp_activity_get( array(
+			'display_comments' => 'stream',
+			'show_hidden'      => true,
+			'spam'             => 'spam_only',
+			'count_total'      => 'count_query',
+		) );
+		$this->spam_count = $spams['total'];
+		unset( $spams );
+
+		// Get the activities from the database.
+		$activities = bp_activity_get( array(
+			'display_comments' => 'stream',
+			'filter'           => $filter,
+			'in'               => $include_id,
+			'page'             => $page,
+			'per_page'         => $per_page,
+			'search_terms'     => $search_terms,
+			'show_hidden'      => true,
+			// 'sort'             => $sort,
+			'spam'             => $spam,
+			'count_total'      => 'count_query',
+		) );
+
+		// If we're viewing a specific activity, flatten all activities into a single array.
+		if ( $include_id ) {
+			$activities['activities'] = BP_Activity_List_Table::flatten_activity_array( $activities['activities'] );
+			$activities['total']      = count( $activities['activities'] );
+
+			// Sort the array by the activity object's date_recorded value.
+			usort( $activities['activities'], create_function( '$a, $b', 'return $a->date_recorded > $b->date_recorded;' ) );
+		}
+
+		// The bp_activity_get function returns an array of objects; cast these to arrays for WP_List_Table.
+		$new_activities = array();
+		foreach ( $activities['activities'] as $activity_item ) {
+			$new_activities[] = (array) $activity_item;
+
+			// Build an array of activity-to-user ID mappings for better efficiency in the In Response To column.
+			$this->activity_user_id[$activity_item->id] = $activity_item->user_id;
+		}
+
+		// Set raw data to display.
+		$this->items       = $new_activities;
+
+		// Store information needed for handling table pagination.
+		$this->set_pagination_args( array(
+			'per_page'    => $per_page,
+			'total_items' => $activities['total'],
+			'total_pages' => ceil( $activities['total'] / $per_page )
+		) );
+
+		// Don't truncate activity items; bp_activity_truncate_entry() needs to be used inside a BP_Activity_Template loop.
+		remove_filter( 'bp_get_activity_content_body', 'bp_activity_truncate_entry', 5 );
+	}
+
+	/**
+	 * Get an array of all the columns on the page.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @return array Column headers.
+	 */
+	function get_column_info() {
+		$this->_column_headers = array(
+			$this->get_columns(),
+			array(),
+			$this->get_sortable_columns(),
+			$this->get_default_primary_column_name(),
+		);
+
+		return $this->_column_headers;
+	}
+
+	/**
+	 * Get name of default primary column
+	 *
+	 * @since 2.3.3
+	 *
+	 * @return string
+	 */
+	protected function get_default_primary_column_name() {
+		return 'author';
+	}
+
+	/**
+	 * Display a message on screen when no items are found (e.g. no search matches).
+	 *
+	 * @since 1.6.0
+	 */
+	function no_items() {
+		_e( 'No activities found.', 'buddypress' );
+	}
+
+	/**
+	 * Output the Activity data table.
+	 *
+	 * @since 1.6.0
+	 */
+	function display() {
+		$this->display_tablenav( 'top' ); ?>
+
+		<table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>" cellspacing="0">
+			<thead>
+				<tr>
+					<?php $this->print_column_headers(); ?>
+				</tr>
+			</thead>
+
+			<tfoot>
+				<tr>
+					<?php $this->print_column_headers( false ); ?>
+				</tr>
+			</tfoot>
+
+			<tbody id="the-comment-list">
+				<?php $this->display_rows_or_placeholder(); ?>
+			</tbody>
+		</table>
+		<?php
+
+		$this->display_tablenav( 'bottom' );
+	}
+
+	/**
+	 * Generate content for a single row of the table.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param object $item The current item.
+	 */
+	function single_row( $item ) {
+		static $even = false;
+
+		if ( $even ) {
+			$row_class = ' class="even"';
+		} else {
+			$row_class = ' class="alternate odd"';
+		}
+
+		if ( 'activity_comment' === $item['type'] ) {
+			$root_id = $item['item_id'];
+		} else {
+			$root_id = $item['id'];
+		}
+
+		echo '<tr' . $row_class . ' id="activity-' . esc_attr( $item['id'] ) . '" data-parent_id="' . esc_attr( $item['id'] ) . '" data-root_id="' . esc_attr( $root_id ) . '">';
+		echo $this->single_row_columns( $item );
+		echo '</tr>';
+
+		$even = ! $even;
+	}
+
+	/**
+	 * Get the list of views available on this table (e.g. "all", "spam").
+	 *
+	 * @since 1.6.0
+	 */
+	function get_views() {
+		$url_base = add_query_arg( array( 'page' => 'bp-activity' ), bp_get_admin_url( 'admin.php' ) ); ?>
+
+		<ul class="subsubsub">
+			<li class="all"><a href="<?php echo esc_url( $url_base ); ?>" class="<?php if ( 'spam' != $this->view ) echo 'current'; ?>"><?php _e( 'All', 'buddypress' ); ?></a> |</li>
+			<li class="spam"><a href="<?php echo esc_url( add_query_arg( array( 'activity_status' => 'spam' ), $url_base ) ); ?>" class="<?php if ( 'spam' == $this->view ) echo 'current'; ?>"><?php printf( __( 'Spam <span class="count">(%s)</span>', 'buddypress' ), number_format_i18n( $this->spam_count ) ); ?></a></li>
+
+			<?php
+
+			/**
+			 * Fires inside listing of views so plugins can add their own.
+			 *
+			 * @since 1.6.0
+			 *
+			 * @param string $url_base Current URL base for view.
+			 * @param string $view     Current view being displayed.
+			 */
+			do_action( 'bp_activity_list_table_get_views', $url_base, $this->view ); ?>
+		</ul>
+	<?php
+	}
+
+	/**
+	 * Get bulk actions.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @return array Key/value pairs for the bulk actions dropdown.
+	 */
+	function get_bulk_actions() {
+		$actions = array();
+		$actions['bulk_spam']   = __( 'Mark as Spam', 'buddypress' );
+		$actions['bulk_ham']    = __( 'Not Spam', 'buddypress' );
+		$actions['bulk_delete'] = __( 'Delete Permanently', 'buddypress' );
+
+		/**
+		 * Filters the default bulk actions so plugins can add custom actions.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param array $actions Default available actions for bulk operations.
+		 */
+		return apply_filters( 'bp_activity_list_table_get_bulk_actions', $actions );
+	}
+
+	/**
+	 * Get the table column titles.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @see WP_List_Table::single_row_columns()
+	 *
+	 * @return array The columns to appear in the Activity list table.
+	 */
+	function get_columns() {
+		/**
+		 * Filters the titles for the columns for the activity list table.
+		 *
+		 * @since 2.4.0
+		 *
+		 * @param array $value Array of slugs and titles for the columns.
+		 */
+		return apply_filters( 'bp_activity_list_table_get_columns', array(
+			'cb'       => '<input name type="checkbox" />',
+			'author'   => _x('Author', 'Admin SWA column header', 'buddypress' ),
+			'comment'  => _x( 'Activity', 'Admin SWA column header', 'buddypress' ),
+			'action'   => _x( 'Action', 'Admin SWA column header', 'buddypress' ),
+			'response' => _x( 'In Response To', 'Admin SWA column header', 'buddypress' ),
+		) );
+	}
+
+	/**
+	 * Get the column names for sortable columns.
+	 *
+	 * Currently, returns an empty array (no columns are sortable).
+	 *
+	 * @since 1.6.0
+	 * @todo For this to work, BP_Activity_Activity::get() needs updating
+	 *       to support ordering by specific fields.
+	 *
+	 * @return array The columns that can be sorted on the Activity screen.
+	 */
+	function get_sortable_columns() {
+		return array();
+
+		/*return array(
+			'author' => array( 'activity_author', false ),  // Intentionally not using "=>"
+		);*/
+	}
+
+	/**
+	 * Markup for the "filter" part of the form (i.e. which activity type to display).
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $which 'top' or 'bottom'.
+	 */
+	function extra_tablenav( $which ) {
+
+		// Bail on bottom table nav.
+		if ( 'bottom' === $which ) {
+			return;
+		}
+
+		// Is any filter currently selected?
+		$selected = ( ! empty( $_REQUEST['activity_type'] ) ) ? $_REQUEST['activity_type'] : '';
+
+		// Get the actions.
+		$activity_actions = bp_activity_get_actions(); ?>
+
+		<div class="alignleft actions">
+			<label for="activity-type" class="screen-reader-text"><?php _e( 'Filter by activity type', 'buddypress' ); ?></label>
+			<select name="activity_type" id="activity-type">
+				<option value="" <?php selected( ! $selected ); ?>><?php _e( 'View all actions', 'buddypress' ); ?></option>
+
+				<?php foreach ( $activity_actions as $component => $actions ) : ?>
+
+					<optgroup label="<?php echo ucfirst( $component ); ?>">
+
+						<?php foreach ( $actions as $action_key => $action_values ) : ?>
+
+							<?php
+
+							// Skip the incorrectly named pre-1.6 action.
+							if ( 'friends_register_activity_action' !== $action_key ) : ?>
+
+								<option value="<?php echo esc_attr( $action_key ); ?>" <?php selected( $action_key,  $selected ); ?>><?php echo esc_html( $action_values[ 'value' ] ); ?></option>
+
+							<?php endif; ?>
+
+						<?php endforeach; ?>
+
+					</optgroup>
+
+				<?php endforeach; ?>
+
+			</select>
+
+			<?php submit_button( __( 'Filter', 'buddypress' ), 'secondary', false, false, array( 'id' => 'post-query-submit' ) ); ?>
+		</div>
+
+	<?php
+	}
+
+	/**
+	 * Override WP_List_Table::row_actions().
+	 *
+	 * Basically a duplicate of the row_actions() method, but removes the
+	 * unnecessary <button> addition.
+	 *
+	 * @since 2.3.3
+	 * @since 2.3.4 Visibility set to public for compatibility with WP < 4.0.0.
+	 *
+	 * @param array $actions The list of actions.
+	 * @param bool  $always_visible Whether the actions should be always visible.
+	 * @return string
+	 */
+	public function row_actions( $actions, $always_visible = false ) {
+		$action_count = count( $actions );
+		$i = 0;
+
+		if ( !$action_count )
+			return '';
+
+		$out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
+		foreach ( $actions as $action => $link ) {
+			++$i;
+			( $i == $action_count ) ? $sep = '' : $sep = ' | ';
+			$out .= "<span class='$action'>$link$sep</span>";
+		}
+		$out .= '</div>';
+
+		return $out;
+	}
+
+	/**
+	 * Checkbox column markup.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @see WP_List_Table::single_row_columns()
+	 *
+	 * @param array $item A singular item (one full row).
+	 */
+	function column_cb( $item ) {
+		printf( '<label class="screen-reader-text" for="aid-%1$d">' . __( 'Select activity item %1$d', 'buddypress' ) . '</label><input type="checkbox" name="aid[]" value="%1$d" id="aid-%1$d" />', $item['id'] );
+	}
+
+	/**
+	 * Author column markup.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @see WP_List_Table::single_row_columns()
+	 *
+	 * @param array $item A singular item (one full row).
+	 */
+	function column_author( $item ) {
+		echo '<strong>' . get_avatar( $item['user_id'], '32' ) . ' ' . bp_core_get_userlink( $item['user_id'] ) . '</strong>';
+	}
+
+	/**
+	 * Action column markup.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @see WP_List_Table::single_row_columns()
+	 *
+	 * @param array $item A singular item (one full row).
+	 */
+	function column_action( $item ) {
+		$actions = bp_activity_admin_get_activity_actions();
+
+		if ( isset( $actions[ $item['type'] ] ) ) {
+			echo $actions[ $item['type'] ];
+		} else {
+			printf( __( 'Unregistered action - %s', 'buddypress' ), $item['type'] );
+		}
+	}
+
+	/**
+	 * Content column, and "quick admin" rollover actions.
+	 *
+	 * Called "comment" in the CSS so we can re-use some WP core CSS.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @see WP_List_Table::single_row_columns()
+	 *
+	 * @param array $item A singular item (one full row).
+	 */
+	function column_comment( $item ) {
+		// Determine what type of item (row) we're dealing with.
+		if ( $item['is_spam'] )
+			$item_status = 'spam';
+		else
+			$item_status = 'all';
+
+		// Preorder items: Reply | Edit | Spam | Delete Permanently.
+		$actions = array(
+			'reply'  => '',
+			'edit'   => '',
+			'spam'   => '', 'unspam' => '',
+			'delete' => '',
+		);
+
+		// Build actions URLs.
+		$base_url   = bp_get_admin_url( 'admin.php?page=bp-activity&amp;aid=' . $item['id'] );
+		$spam_nonce = esc_html( '_wpnonce=' . wp_create_nonce( 'spam-activity_' . $item['id'] ) );
+
+		$delete_url = $base_url . "&amp;action=delete&amp;$spam_nonce";
+		$edit_url   = $base_url . '&amp;action=edit';
+		$ham_url    = $base_url . "&amp;action=ham&amp;$spam_nonce";
+		$spam_url   = $base_url . "&amp;action=spam&amp;$spam_nonce";
+
+		// Rollover actions.
+		// Reply - JavaScript only; implemented by AJAX.
+		if ( 'spam' != $item_status ) {
+			if ( $this->can_comment( $item ) ) {
+				$actions['reply'] = sprintf( '<a href="#" class="reply hide-if-no-js">%s</a>', __( 'Reply', 'buddypress' ) );
+			} else {
+				$actions['reply'] = sprintf( '<span class="form-input-tip" title="%s">%s</span>', __( 'Replies are disabled for this activity item', 'buddypress' ), __( 'Replies disabled', 'buddypress' ) );
+			}
+
+			// Edit.
+			$actions['edit'] = sprintf( '<a href="%s">%s</a>', $edit_url, __( 'Edit', 'buddypress' ) );
+		}
+
+		// Spam/unspam.
+		if ( 'spam' == $item_status )
+			$actions['unspam'] = sprintf( '<a href="%s">%s</a>', $ham_url, __( 'Not Spam', 'buddypress' ) );
+		else
+			$actions['spam'] = sprintf( '<a href="%s">%s</a>', $spam_url, __( 'Spam', 'buddypress' ) );
+
+		// Delete.
+		$actions['delete'] = sprintf( '<a href="%s" onclick="%s">%s</a>', $delete_url, "javascript:return confirm('" . esc_js( __( 'Are you sure?', 'buddypress' ) ) . "'); ", __( 'Delete Permanently', 'buddypress' ) );
+
+		// Start timestamp.
+		echo '<div class="submitted-on">';
+
+		/**
+		 * Filters available actions for plugins to alter.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param array $actions Array of available actions user could use.
+		 * @param array $item    Current item being added to page.
+		 */
+		$actions = apply_filters( 'bp_activity_admin_comment_row_actions', array_filter( $actions ), $item );
+
+		/* translators: 2: activity admin ui date/time */
+		printf(
+			__( 'Submitted on <a href="%1$s">%2$s at %3$s</a>', 'buddypress' ),
+			bp_activity_get_permalink( $item['id'] ),
+			date_i18n( bp_get_option( 'date_format' ), strtotime( $item['date_recorded'] ) ),
+			get_date_from_gmt( $item['date_recorded'], bp_get_option( 'time_format' ) )
+		);
+
+		// End timestamp.
+		echo '</div>';
+
+		// Get activity content - if not set, use the action.
+		if ( ! empty( $item['content'] ) ) {
+
+			/**
+			 * Filters current activity item content.
+			 *
+			 * @since 1.2.0
+			 *
+			 * @param array $item Array index holding current activity item content.
+			 */
+			$content = apply_filters_ref_array( 'bp_get_activity_content_body', array( $item['content'] ) );
+		} else {
+			/**
+			 * Filters current activity item action.
+			 *
+			 * @since 1.2.0
+			 *
+			 * @var array $item Array index holding current activity item action.
+			 */
+			$content = apply_filters_ref_array( 'bp_get_activity_action', array( $item['action'] ) );
+		}
+
+		/**
+		 * Filter here to add extra output to the activity content into the Administration.
+		 *
+		 * @since  2.4.0
+		 *
+		 * @param  string $content The activity content.
+		 * @param  array  $item    The activity object converted into an array.
+		 */
+		echo apply_filters( 'bp_activity_admin_comment_content', $content, $item ) . ' ' . $this->row_actions( $actions );
+	}
+
+	/**
+	 * "In response to" column markup.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @see WP_List_Table::single_row_columns()
+	 *
+	 * @param array $item A singular item (one full row).
+	 */
+	function column_response( $item ) {
+
+		// Is $item is a root activity?
+		?>
+
+		<div class="response-links">
+
+		<?php
+		// Activity permalink
+		$activity_permalink = '';
+		if ( ! $item['is_spam'] ) {
+			$activity_permalink = '<a href="' . bp_activity_get_permalink( $item['id'], (object) $item ) . '" class="comments-view-item-link">' . __( 'View Activity', 'buddypress' ) . '</a>';
+		}
+
+		/**
+		 * Filters default list of default root activity types.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param array $value Array of default activity types.
+		 * @param array $item  Current item being displayed.
+		 */
+		if ( empty( $item['item_id'] ) || ! in_array( $item['type'], apply_filters( 'bp_activity_admin_root_activity_types', array( 'activity_comment' ), $item ) ) ) {
+			echo $activity_permalink;
+
+			$comment_count     = !empty( $item['children'] ) ? bp_activity_recurse_comment_count( (object) $item ) : 0;
+			$root_activity_url = bp_get_admin_url( 'admin.php?page=bp-activity&amp;aid=' . $item['id'] );
+
+			// If the activity has comments, display a link to the activity's permalink, with its comment count in a speech bubble.
+			if ( $comment_count ) {
+				$title_attr = sprintf( _n( '%s related activity', '%s related activities', $comment_count, 'buddypress' ), number_format_i18n( $comment_count ) );
+				printf( '<a href="%1$s" title="%2$s" class="post-com-count post-com-count-approved"><span class="comment-count comment-count-approved">%3$s</span></a>', esc_url( $root_activity_url ), esc_attr( $title_attr ), number_format_i18n( $comment_count ) );
+			}
+
+		// For non-root activities, display a link to the replied-to activity's author's profile.
+		} else {
+			echo '<strong>' . get_avatar( $this->get_activity_user_id( $item['item_id'] ), '32' ) . ' ' . bp_core_get_userlink( $this->get_activity_user_id( $item['item_id'] ) ) . '</strong><br />';
+			echo $activity_permalink;
+		}
+		?>
+
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Allow plugins to add their custom column.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param array  $item        Information about the current row.
+	 * @param string $column_name The column name.
+	 * @return string
+	 */
+	public function column_default( $item = array(), $column_name = '' ) {
+
+		/**
+		 * Filters a string to allow plugins to add custom column content.
+		 *
+		 * @since 2.4.0
+		 *
+		 * @param string $value       Empty string.
+		 * @param string $column_name Name of the column being rendered.
+		 * @param array  $item        The current activity item in the loop.
+		 */
+		return apply_filters( 'bp_activity_admin_get_custom_column', '', $column_name, $item );
+	}
+
+	/**
+	 * Get the user id associated with a given activity item.
+	 *
+	 * Wraps bp_activity_get_specific(), with some additional logic for
+	 * avoiding duplicate queries.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int $activity_id Activity ID to retrieve User ID for.
+	 * @return int User ID of the activity item in question.
+	 */
+	protected function get_activity_user_id( $activity_id ) {
+		// If there is an existing activity/user ID mapping, just return the user ID.
+		if ( ! empty( $this->activity_user_id[$activity_id] ) ) {
+			return $this->activity_user_id[$activity_id];
+
+		/*
+		 * We don't have a mapping. This means the $activity_id is not on the current
+		 * page of results, so fetch its details from the database.
+		 */
+		} else {
+			$activity = bp_activity_get_specific( array( 'activity_ids' => $activity_id, 'show_hidden' => true, 'spam' => 'all', ) );
+
+			/*
+			 * If, somehow, the referenced activity has been deleted, leaving its associated
+			 * activities as orphans, use the logged in user's ID to avoid errors.
+			 */
+			if ( empty( $activity['activities'] ) )
+				return bp_loggedin_user_id();
+
+			// Store the new activity/user ID mapping for any later re-use.
+			$this->activity_user_id[ $activity['activities'][0]->id ] = $activity['activities'][0]->user_id;
+
+			// Return the user ID.
+			return $activity['activities'][0]->user_id;
+		}
+	}
+
+	/**
+	 * Checks if an activity item can be replied to.
+	 *
+	 * This method merges functionality from {@link bp_activity_can_comment()} and
+	 * {@link bp_blogs_disable_activity_commenting()}. This is done because the activity
+	 * list table doesn't use a BuddyPress activity loop, which prevents those
+	 * functions from working as intended.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $item An array version of the BP_Activity_Activity object.
+	 * @return bool $can_comment
+	 */
+	protected function can_comment( $item  ) {
+		$can_comment = true;
+
+		if ( $this->disable_blogforum_comments ) {
+			switch ( $item['type'] ) {
+				case 'new_blog_post' :
+				case 'new_blog_comment' :
+				case 'new_forum_topic' :
+				case 'new_forum_post' :
+					$can_comment = false;
+					break;
+			}
+
+		// Activity comments supported.
+		} else {
+			// Activity comment.
+			if ( 'activity_comment' == $item['type'] ) {
+				// Blogs.
+				if ( bp_is_active( 'blogs' ) ) {
+					// Grab the parent activity entry.
+					$parent_activity = new BP_Activity_Activity( $item['item_id'] );
+
+					// Fetch blog post comment depth and if the blog post's comments are open.
+					bp_blogs_setup_activity_loop_globals( $parent_activity );
+
+					// Check if the activity item can be replied to.
+					if ( false === bp_blogs_can_comment_reply( true, $item ) ) {
+						$can_comment = false;
+					}
+				}
+
+			// Blog post.
+			} elseif ( 'new_blog_post' == $item['type'] ) {
+				if ( bp_is_active( 'blogs' ) ) {
+					bp_blogs_setup_activity_loop_globals( (object) $item );
+
+					if ( empty( buddypress()->blogs->allow_comments[$item['id']] ) ) {
+						$can_comment = false;
+					}
+				}
+			}
+		}
+
+		/**
+		 * Filters if an activity item can be commented on or not.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param bool $can_comment Whether an activity item can be commented on or not.
+		 */
+		return apply_filters( 'bp_activity_list_table_can_comment', $can_comment );
+	}
+
+	/**
+	 * Flatten the activity array.
+	 *
+	 * In some cases, BuddyPress gives us a structured tree of activity
+	 * items plus their comments. This method converts it to a flat array.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $tree Source array.
+	 * @return array Flattened array.
+	 */
+	public static function flatten_activity_array( $tree ){
+		foreach ( (array) $tree as $node ) {
+			if ( isset( $node->children ) ) {
+
+				foreach ( BP_Activity_List_Table::flatten_activity_array( $node->children ) as $child ) {
+					$tree[] = $child;
+				}
+
+				unset( $node->children );
+			}
+		}
+
+		return $tree;
+	}
+}
Index: /tags/2.4.3/src/bp-activity/bp-activity-akismet.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-akismet.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-akismet.php	(revision 10410)
@@ -0,0 +1,690 @@
+<?php
+/**
+ * Akismet support for BuddyPress' Activity Stream.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityAkismet
+ * @since 1.6.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Akismet support for the Activity component.
+ *
+ * @since 1.6.0
+ * @since 2.3.0 We only support Akismet 3+.
+ */
+class BP_Akismet {
+
+	/**
+	 * The activity last marked as spam.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @var BP_Activity_Activity
+	 */
+	protected $last_activity = null;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 1.6.0
+	 */
+	public function __construct() {
+		$this->setup_actions();
+	}
+
+	/**
+	 * Hook Akismet into the activity stream.
+	 *
+	 * @since 1.6.0
+	 */
+	protected function setup_actions() {
+		// Add nonces to activity stream lists.
+		add_action( 'bp_after_activity_post_form', array( $this, 'add_activity_stream_nonce' ) );
+		add_action( 'bp_activity_entry_comments',  array( $this, 'add_activity_stream_nonce' ) );
+
+		// Add a "mark as spam" button to individual activity items.
+		add_action( 'bp_activity_entry_meta',      array( $this, 'add_activity_spam_button' ) );
+		add_action( 'bp_activity_comment_options', array( $this, 'add_activity_comment_spam_button' ) );
+
+		// Check activity for spam.
+		add_action( 'bp_activity_before_save',     array( $this, 'check_activity' ), 4, 1 );
+
+		// Tidy up member's latest (activity) update.
+		add_action( 'bp_activity_posted_update',   array( $this, 'check_member_activity_update' ), 1, 3 );
+
+		// Hooks to extend Activity core spam/ham functions for Akismet.
+		add_action( 'bp_activity_mark_as_spam',    array( $this, 'mark_as_spam' ), 10, 2 );
+		add_action( 'bp_activity_mark_as_ham',     array( $this, 'mark_as_ham' ),  10, 2 );
+
+		// Hook into the Activity wp-admin screen.
+		add_action( 'bp_activity_admin_comment_row_actions', array( $this, 'comment_row_action' ), 10, 2 );
+		add_action( 'bp_activity_admin_load',                array( $this, 'add_history_metabox' ) );
+	}
+
+	/**
+	 * Add a history item to the hover links in an activity's row.
+	 *
+	 * This function lifted with love from the Akismet WordPress plugin's
+	 * akismet_comment_row_action() function. Thanks!
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $actions  The hover links.
+	 * @param array $activity The activity for the current row being processed.
+	 * @return array The hover links.
+	 */
+	function comment_row_action( $actions, $activity ) {
+		$akismet_result = bp_activity_get_meta( $activity['id'], '_bp_akismet_result' );
+		$user_result    = bp_activity_get_meta( $activity['id'], '_bp_akismet_user_result' );
+		$desc           = '';
+
+		if ( !$user_result || $user_result == $akismet_result ) {
+			// Show the original Akismet result if the user hasn't overridden it, or if their decision was the same.
+			if ( 'true' == $akismet_result && $activity['is_spam'] )
+				$desc = __( 'Flagged as spam by Akismet', 'buddypress' );
+
+			elseif ( 'false' == $akismet_result && !$activity['is_spam'] )
+				$desc = __( 'Cleared by Akismet', 'buddypress' );
+
+		} else {
+			$who = bp_activity_get_meta( $activity['id'], '_bp_akismet_user' );
+
+			if ( 'true' == $user_result )
+				$desc = sprintf( __( 'Flagged as spam by %s', 'buddypress' ), $who );
+			else
+				$desc = sprintf( __( 'Un-spammed by %s', 'buddypress' ), $who );
+		}
+
+		// Add a History item to the hover links, just after Edit.
+		if ( $akismet_result ) {
+			$b = array();
+			foreach ( $actions as $k => $item ) {
+				$b[ $k ] = $item;
+				if ( $k == 'edit' )
+					$b['history'] = '<a href="' . esc_url( bp_get_admin_url( 'admin.php?page=bp-activity&amp;action=edit&aid=' . $activity['id'] ) ) . '#bp_activity_history"> '. __( 'History', 'buddypress' ) . '</a>';
+			}
+
+			$actions = $b;
+		}
+
+		if ( $desc )
+			echo '<span class="akismet-status"><a href="' . esc_url( bp_get_admin_url( 'admin.php?page=bp-activity&amp;action=edit&aid=' . $activity['id'] ) ) . '#bp_activity_history">' . htmlspecialchars( $desc ) . '</a></span>';
+
+		/**
+		 * Filters the list of actions for the current activity's row.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param array $actions Array of available actions for the current activity item's row.
+		 */
+		return apply_filters( 'bp_akismet_comment_row_action', $actions );
+	}
+
+	/**
+	 * Generate nonces for activity forms.
+	 *
+	 * These nonces appear in the member profile status form, as well as in
+	 * the reply form of each activity item. The nonces are, in turn, used
+	 * by Akismet to help detect spam activity.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @see https://plugins.trac.wordpress.org/ticket/1232
+	 */
+	public function add_activity_stream_nonce() {
+		$form_id = '_bp_as_nonce';
+		$value   = '_bp_as_nonce_' . bp_loggedin_user_id();
+
+		// If we're in the activity stream loop, we can use the current item's ID to make the nonce unique.
+		if ( 'bp_activity_entry_comments' == current_filter() ) {
+			$form_id .= '_' . bp_get_activity_id();
+			$value   .= '_' . bp_get_activity_id();
+		}
+
+		wp_nonce_field( $value, $form_id, false );
+	}
+
+	/**
+	 * Clean up the bp_latest_update usermeta in case of spamming.
+	 *
+	 * Run just after an update is posted, this method check to see whether
+	 * the newly created update has been marked as spam by Akismet. If so,
+	 * the cached update is cleared from the user's 'bp_latest_update'
+	 * usermeta, ensuring that it won't appear in the member header and
+	 * elsewhere in the theme.
+	 *
+	 * This can't be done in BP_Akismet::check_activity() due to the
+	 * default AJAX implementation; see bp_dtheme_post_update().
+	 *
+	 * @since 1.6.0
+	 *
+	 * @see bp_dtheme_post_update()
+	 *
+	 * @param string $content     Activity update text.
+	 * @param int    $user_id     User ID.
+	 * @param int    $activity_id Activity ID.
+	 */
+	public function check_member_activity_update( $content, $user_id, $activity_id ) {
+		// By default, only handle activity updates and activity comments.
+		if ( empty( $this->last_activity ) || !in_array( $this->last_activity->type, BP_Akismet::get_activity_types() ) )
+			return;
+
+		// Was this $activity_id just marked as spam? If not, bail out.
+		if ( !$this->last_activity->id || $activity_id != $this->last_activity->id || 'false' == $this->last_activity->akismet_submission['bp_as_result'] )
+			return;
+
+		// It was, so delete the member's latest activity update.
+		bp_delete_user_meta( $user_id, 'bp_latest_update' );
+	}
+
+	/**
+	 * Adds a "mark as spam" button to each activity item for site admins.
+	 *
+	 * This function is intended to be used inside the activity stream loop.
+	 *
+	 * @since 1.6.0
+	 */
+	public function add_activity_spam_button() {
+		if ( !bp_activity_user_can_mark_spam() )
+			return;
+
+		// By default, only handle activity updates and activity comments.
+		if ( !in_array( bp_get_activity_type(), BP_Akismet::get_activity_types() ) )
+			return;
+
+		bp_button(
+			array(
+				'block_self' => false,
+				'component'  => 'activity',
+				'id'         => 'activity_make_spam_' . bp_get_activity_id(),
+				'link_class' => 'bp-secondary-action spam-activity confirm button item-button',
+				'link_href'  => wp_nonce_url( bp_get_root_domain() . '/' . bp_get_activity_slug() . '/spam/' . bp_get_activity_id() . '/', 'bp_activity_akismet_spam_' . bp_get_activity_id() ),
+				'link_text'  => __( 'Spam', 'buddypress' ),
+				'wrapper'    => false,
+			)
+		);
+	}
+
+	/**
+	 * Adds a "mark as spam" button to each activity COMMENT item for site admins.
+	 *
+	 * This function is intended to be used inside the activity stream loop.
+	 *
+	 * @since 1.6.0
+	 */
+	public function add_activity_comment_spam_button() {
+		if ( !bp_activity_user_can_mark_spam() )
+			return;
+
+		// By default, only handle activity updates and activity comments.
+		$current_comment = bp_activity_current_comment();
+		if ( empty( $current_comment ) || !in_array( $current_comment->type, BP_Akismet::get_activity_types() ) )
+			return;
+
+		bp_button(
+			array(
+				'block_self' => false,
+				'component'  => 'activity',
+				'id'         => 'activity_make_spam_' . bp_get_activity_comment_id(),
+				'link_class' => 'bp-secondary-action spam-activity-comment confirm',
+				'link_href'  => wp_nonce_url( bp_get_root_domain() . '/' . bp_get_activity_slug() . '/spam/' . bp_get_activity_comment_id() . '/?cid=' . bp_get_activity_comment_id(), 'bp_activity_akismet_spam_' . bp_get_activity_comment_id() ),
+				'link_text'  => __( 'Spam', 'buddypress' ),
+				'wrapper'    => false,
+			)
+		);
+	}
+
+	/**
+	 * Get a filterable list of activity types that Akismet should automatically check for spam.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @static
+	 *
+	 * @return array $value List of activity types.
+	 */
+	public static function get_activity_types() {
+
+		/**
+		 * Filters the list of activity types that Akismet should automatically check for spam.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param array Array of default activity types for Akismet to check.
+		 */
+		return apply_filters( 'bp_akismet_get_activity_types', array( 'activity_comment', 'activity_update' ) );
+	}
+
+	/**
+	 * Mark activity item as spam.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param BP_Activity_Activity $activity Activity item being spammed.
+	 * @param string               $source   Either "by_a_person" (e.g. a person has
+	 *                                       manually marked the activity as spam) or
+	 *                                       "by_akismet" (automatically spammed).
+	 */
+	public function mark_as_spam( $activity, $source ) {
+		// Record this item so we can do some tidyup in BP_Akismet::check_member_activity_update().
+		$this->last_activity = $activity;
+
+		/**
+		 * Fires after marking an activity item has been marked as spam.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param BP_Activity_Activity $activity Activity object being marked as spam.
+		 * @param string               $source   Source of the whom marked as spam.
+		 *                                       Either "by_a_person" (e.g. a person has
+		 *                                       manually marked the activity as spam)
+		 *                                       or "by_akismet".
+		 */
+		do_action( 'bp_activity_akismet_mark_as_spam', $activity, $source );
+	}
+
+	/**
+	 * Mark activity item as ham.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param BP_Activity_Activity $activity Activity item being hammed.
+	 * @param string               $source   Either "by_a_person" (e.g. a person has
+	 *                                       manually marked the activity as ham) or
+	 *                                       "by_akismet" (automatically hammed).
+	 */
+	public function mark_as_ham( $activity, $source ) {
+		// If the activity was, originally, automatically marked as spam by Akismet, run the @mentions filter as it would have been skipped.
+		if ( 'true' == bp_activity_get_meta( $activity->id, '_bp_akismet_result' ) && !bp_activity_get_meta( $activity->id, '_bp_akismet_user_result' ) )
+			$activity->content = bp_activity_at_name_filter( $activity->content, $activity->id );
+
+		/**
+		 * Fires after marking an activity item has been marked as ham.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param BP_Activity_Activity $activity Activity object being marked as ham.
+		 * @param string               $source   Source of the whom marked as ham.
+		 *                                       Either "by_a_person" (e.g. a person has
+		 *                                       manually marked the activity as ham) or
+		 *                                       "by_akismet" (automatically hammed).
+		 */
+		do_action( 'bp_activity_akismet_mark_as_ham', $activity, $source );
+	}
+
+	/**
+	 * Build a data package for the Akismet service to inspect.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @see http://akismet.com/development/api/#comment-check
+	 * @static
+	 *
+	 * @param BP_Activity_Activity $activity Activity item data.
+	 * @return array $activity_data
+	 */
+	public static function build_akismet_data_package( $activity ) {
+		$userdata = get_userdata( $activity->user_id );
+
+		$activity_data                          = array();
+		$activity_data['akismet_comment_nonce'] = 'inactive';
+		$activity_data['comment_author']        = $userdata->display_name;
+		$activity_data['comment_author_email']  = $userdata->user_email;
+		$activity_data['comment_author_url']    = bp_core_get_userlink( $userdata->ID, false, true);
+		$activity_data['comment_content']       = $activity->content;
+		$activity_data['comment_type']          = $activity->type;
+		$activity_data['permalink']             = bp_activity_get_permalink( $activity->id, $activity );
+		$activity_data['user_ID']               = $userdata->ID;
+		$activity_data['user_role']             = Akismet::get_user_roles( $userdata->ID );
+
+		/**
+		 * Get the nonce if the new activity was submitted through the "what's up, Paul?" form.
+		 * This helps Akismet ensure that the update was a valid form submission.
+		 */
+		if ( !empty( $_POST['_bp_as_nonce'] ) )
+			$activity_data['akismet_comment_nonce'] = wp_verify_nonce( $_POST['_bp_as_nonce'], "_bp_as_nonce_{$userdata->ID}" ) ? 'passed' : 'failed';
+
+		/**
+		 * If the new activity was a reply to an existing item, check the nonce with the activity parent ID.
+		 * This helps Akismet ensure that the update was a valid form submission.
+		 */
+		elseif ( !empty( $activity->secondary_item_id ) && !empty( $_POST['_bp_as_nonce_' . $activity->secondary_item_id] ) )
+			$activity_data['akismet_comment_nonce'] = wp_verify_nonce( $_POST["_bp_as_nonce_{$activity->secondary_item_id}"], "_bp_as_nonce_{$userdata->ID}_{$activity->secondary_item_id}" ) ? 'passed' : 'failed';
+
+		/**
+		 * Filters activity data before being sent to Akismet to inspect.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param array                $activity_data Array of activity data for Akismet to inspect.
+		 * @param BP_Activity_Activity $activity      Activity item data.
+		 */
+		return apply_filters( 'bp_akismet_build_akismet_data_package', $activity_data, $activity );
+	}
+
+	/**
+	 * Check if the activity item is spam or ham.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @see http://akismet.com/development/api/
+	 * @todo Spam counter?
+	 * @todo Auto-delete old spam?
+	 *
+	 * @param BP_Activity_Activity $activity The activity item to check.
+	 */
+	public function check_activity( $activity ) {
+		// By default, only handle activity updates and activity comments.
+		if ( !in_array( $activity->type, BP_Akismet::get_activity_types() ) )
+			return;
+
+		// Make sure last_activity is clear to avoid any confusion.
+		$this->last_activity = null;
+
+		// Build data package for Akismet.
+		$activity_data = BP_Akismet::build_akismet_data_package( $activity );
+
+		// Check with Akismet to see if this is spam.
+		$activity_data = $this->send_akismet_request( $activity_data, 'check', 'spam' );
+
+		// Record this item.
+		$this->last_activity = $activity;
+
+		// Store a copy of the data that was submitted to Akismet.
+		$this->last_activity->akismet_submission = $activity_data;
+
+		// Spam.
+		if ( 'true' == $activity_data['bp_as_result'] ) {
+			/**
+			 * Fires after an activity item has been proven to be spam, but before officially being marked as spam.
+			 *
+			 * @since 1.6.0
+			 *
+			 * @param BP_Activity_Activity $activity      The activity item proven to be spam.
+			 * @param array                $activity_data Array of activity data for item including
+			 *                                            Akismet check results data.
+			 */
+			do_action_ref_array( 'bp_activity_akismet_spam_caught', array( &$activity, $activity_data ) );
+
+			// Mark as spam.
+			bp_activity_mark_as_spam( $activity, 'by_akismet' );
+		}
+
+		// Update activity meta after a spam check.
+		add_action( 'bp_activity_after_save', array( $this, 'update_activity_akismet_meta' ), 1, 1 );
+	}
+
+	/**
+	 * Update activity meta after a manual spam change (user-initiated).
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param BP_Activity_Activity $activity The activity to check.
+	 */
+	public function update_activity_spam_meta( $activity ) {
+		// By default, only handle activity updates and activity comments.
+		if ( !in_array( $activity->type, BP_Akismet::get_activity_types() ) )
+			return;
+
+		$this->update_activity_history( $activity->id, sprintf( __( '%s reported this activity as spam', 'buddypress' ), bp_get_loggedin_user_username() ), 'report-spam' );
+		bp_activity_update_meta( $activity->id, '_bp_akismet_user_result', 'true' );
+		bp_activity_update_meta( $activity->id, '_bp_akismet_user', bp_get_loggedin_user_username() );
+	}
+
+	/**
+	 * Update activity meta after a manual ham change (user-initiated).
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param BP_Activity_Activity $activity The activity to check.
+	 */
+	public function update_activity_ham_meta( $activity ) {
+		// By default, only handle activity updates and activity comments.
+		if ( !in_array( $activity->type, BP_Akismet::get_activity_types() ) )
+			return;
+
+		$this->update_activity_history( $activity->id, sprintf( __( '%s reported this activity as not spam', 'buddypress' ), bp_get_loggedin_user_username() ), 'report-ham' );
+		bp_activity_update_meta( $activity->id, '_bp_akismet_user_result', 'false' );
+		bp_activity_update_meta( $activity->id, '_bp_akismet_user', bp_get_loggedin_user_username() );
+	}
+
+	/**
+	 * Update activity meta after an automatic spam check (not user-initiated).
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param BP_Activity_Activity $activity The activity to check.
+	 */
+	public function update_activity_akismet_meta( $activity ) {
+		// Check we're dealing with what was last updated by Akismet.
+		if ( empty( $this->last_activity ) || !empty( $this->last_activity ) && $activity->id != $this->last_activity->id )
+			return;
+
+		// By default, only handle activity updates and activity comments.
+		if ( !in_array( $this->last_activity->type, BP_Akismet::get_activity_types() ) )
+			return;
+
+		// Spam.
+		if ( 'true' == $this->last_activity->akismet_submission['bp_as_result'] ) {
+			bp_activity_update_meta( $activity->id, '_bp_akismet_result', 'true' );
+			$this->update_activity_history( $activity->id, __( 'Akismet caught this item as spam', 'buddypress' ), 'check-spam' );
+
+		// Not spam.
+		} elseif ( 'false' == $this->last_activity->akismet_submission['bp_as_result'] ) {
+			bp_activity_update_meta( $activity->id, '_bp_akismet_result', 'false' );
+			$this->update_activity_history( $activity->id, __( 'Akismet cleared this item', 'buddypress' ), 'check-ham' );
+
+		// Uh oh, something's gone horribly wrong. Unexpected result.
+		} else {
+			bp_activity_update_meta( $activity->id, '_bp_akismet_error', bp_core_current_time() );
+			$this->update_activity_history( $activity->id, sprintf( __( 'Akismet was unable to check this item (response: %s), will automatically retry again later.', 'buddypress' ), $this->last_activity->akismet_submission['bp_as_result'] ), 'check-error' );
+		}
+
+		// Record the original data which was submitted to Akismet for checking.
+		bp_activity_update_meta( $activity->id, '_bp_akismet_submission', $this->last_activity->akismet_submission );
+	}
+
+	/**
+	 * Contact Akismet to check if this is spam or ham.
+	 *
+	 * Props to WordPress core Akismet plugin for a lot of this.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array  $activity_data Packet of information to submit to Akismet.
+	 * @param string $check         "check" or "submit".
+	 * @param string $spam          "spam" or "ham".
+	 * @return array $activity_data Activity data, with Akismet data added.
+	 */
+	public function send_akismet_request( $activity_data, $check = 'check', $spam = 'spam' ) {
+		$query_string = $path = '';
+
+		$activity_data['blog']         = bp_get_option( 'home' );
+		$activity_data['blog_charset'] = bp_get_option( 'blog_charset' );
+		$activity_data['blog_lang']    = get_locale();
+		$activity_data['referrer']     = $_SERVER['HTTP_REFERER'];
+		$activity_data['user_agent']   = bp_core_current_user_ua();
+		$activity_data['user_ip']      = bp_core_current_user_ip();
+
+		if ( Akismet::is_test_mode() )
+			$activity_data['is_test'] = 'true';
+
+		// Loop through _POST args and rekey strings.
+		foreach ( $_POST as $key => $value )
+			if ( is_string( $value ) && 'cookie' != $key )
+				$activity_data['POST_' . $key] = $value;
+
+		// Keys to ignore.
+		$ignore = array( 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' );
+
+		// Loop through _SERVER args and remove whitelisted keys.
+		foreach ( $_SERVER as $key => $value ) {
+
+			// Key should not be ignored.
+			if ( !in_array( $key, $ignore ) && is_string( $value ) ) {
+				$activity_data[$key] = $value;
+
+			// Key should be ignored.
+			} else {
+				$activity_data[$key] = '';
+			}
+		}
+
+		foreach ( $activity_data as $key => $data )
+			$query_string .= $key . '=' . urlencode( stripslashes( $data ) ) . '&';
+
+		if ( 'check' == $check )
+			$path = 'comment-check';
+		elseif ( 'submit' == $check )
+			$path = 'submit-' . $spam;
+
+		// Send to Akismet.
+		add_filter( 'akismet_ua', array( $this, 'buddypress_ua' ) );
+		$response = Akismet::http_post( $query_string, $path );
+		remove_filter( 'akismet_ua', array( $this, 'buddypress_ua' ) );
+
+		// Get the response.
+		if ( ! empty( $response[1] ) && ! is_wp_error( $response[1] ) )
+			$activity_data['bp_as_result'] = $response[1];
+		else
+			$activity_data['bp_as_result'] = false;
+
+		// Perform a daily tidy up.
+		if ( ! wp_next_scheduled( 'bp_activity_akismet_delete_old_metadata' ) )
+			wp_schedule_event( time(), 'daily', 'bp_activity_akismet_delete_old_metadata' );
+
+		return $activity_data;
+	}
+
+	/**
+	 * Filters user agent when sending to Akismet to add BuddyPress info.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $user_agent User agent string, as generated by Akismet.
+	 * @return string $user_agent Modified user agent string.
+	 */
+	public function buddypress_ua( $user_agent ) {
+		$user_agent = 'BuddyPress/' . bp_get_version() . ' | Akismet/'. constant( 'AKISMET_VERSION' );
+		return $user_agent;
+	}
+
+	/**
+	 * Adds a "History" meta box to the activity edit screen.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $screen_action The type of screen that has been requested.
+	 */
+	function add_history_metabox( $screen_action ) {
+		// Only proceed if we're on the edit screen.
+		if ( 'edit' != $screen_action )
+			return;
+
+		// Display meta box with a low priority (low position on screen by default).
+		add_meta_box( 'bp_activity_history',  __( 'Activity History', 'buddypress' ), array( $this, 'history_metabox' ), get_current_screen()->id, 'normal', 'low' );
+	}
+
+	/**
+	 * History meta box for the Activity admin edit screen.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @see https://buddypress.trac.wordpress.org/ticket/3907
+	 * @todo Update activity meta to allow >1 record with the same key (iterate through $history).
+	 *
+	 * @param object $item Activity item.
+	 */
+	function history_metabox( $item ) {
+		$history = BP_Akismet::get_activity_history( $item->id );
+
+		if ( empty( $history ) )
+			return;
+
+		echo '<div class="akismet-history"><div>';
+		printf( _x( '%1$s &mdash; %2$s', 'x hours ago - akismet cleared this item', 'buddypress' ), '<span>' . bp_core_time_since( $history[2] ) . '</span>', esc_html( $history[1] ) );
+		echo '</div></div>';
+	}
+
+	/**
+	 * Update an activity item's Akismet history.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int    $activity_id Activity item ID.
+	 * @param string $message     Human-readable description of what's changed.
+	 * @param string $event       The type of check we were carrying out.
+	 */
+	public function update_activity_history( $activity_id = 0, $message = '', $event = '' ) {
+		$event = array(
+			'event'   => $event,
+			'message' => $message,
+			'time'    => Akismet::_get_microtime(),
+			'user'    => bp_loggedin_user_id(),
+		);
+
+		// Save the history data.
+		bp_activity_update_meta( $activity_id, '_bp_akismet_history', $event );
+	}
+
+	/**
+	 * Get an activity item's Akismet history.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int $activity_id Activity item ID.
+	 * @return array The activity item's Akismet history.
+	 */
+	public function get_activity_history( $activity_id = 0 ) {
+		$history = bp_activity_get_meta( $activity_id, '_bp_akismet_history' );
+		if ( $history === false )
+			$history = array();
+
+		// Sort it by the time recorded.
+		usort( $history, 'akismet_cmp_time' );
+
+		return $history;
+	}
+}
+
+/**
+ * Delete old spam activity meta data.
+ *
+ * This is done as a clean-up mechanism, as _bp_akismet_submission meta can
+ * grow to be quite large.
+ *
+ * @since 1.6.0
+ *
+ * @global wpdb $wpdb WordPress database object.
+ */
+function bp_activity_akismet_delete_old_metadata() {
+	global $wpdb;
+
+	$bp = buddypress();
+
+	/**
+	 * Filters the threshold for how many days old Akismet metadata needs to be before being automatically deleted.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param integer 15 How many days old metadata needs to be.
+	 */
+	$interval = apply_filters( 'bp_activity_akismet_delete_meta_interval', 15 );
+
+	// Enforce a minimum of 1 day.
+	$interval = max( 1, absint( $interval ) );
+
+	// _bp_akismet_submission meta values are large, so expire them after $interval days regardless of the activity status
+	$sql          = $wpdb->prepare( "SELECT a.id FROM {$bp->activity->table_name} a LEFT JOIN {$bp->activity->table_name_meta} m ON a.id = m.activity_id WHERE m.meta_key = %s AND DATE_SUB(%s, INTERVAL {$interval} DAY) > a.date_recorded LIMIT 10000", '_bp_akismet_submission', current_time( 'mysql', 1 ) );
+	$activity_ids = $wpdb->get_col( $sql );
+
+	if ( ! empty( $activity_ids ) ) {
+		foreach ( $activity_ids as $activity_id )
+			bp_activity_delete_meta( $activity_id, '_bp_akismet_submission' );
+	}
+}
+add_action( 'bp_activity_akismet_delete_old_metadata', 'bp_activity_akismet_delete_old_metadata' );
Index: /tags/2.4.3/src/bp-activity/bp-activity-cache.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-cache.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-cache.php	(revision 10410)
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Functions related to the BuddyPress Activity component and the WP Cache.
+ *
+ * @since 1.6.0
+ * @package    BuddyPress
+ * @subpackage ActivityCache
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Slurp up activitymeta for a specified set of activity items.
+ *
+ * It grabs all activitymeta associated with all of the activity items passed
+ * in $activity_ids and adds it to the WP cache. This improves efficiency when
+ * using querying activitymeta inline.
+ *
+ * @param int|string|array|bool $activity_ids Accepts a single activity ID, or a comma-
+ *                                            separated list or array of activity ids.
+ */
+function bp_activity_update_meta_cache( $activity_ids = false ) {
+	$bp = buddypress();
+
+	$cache_args = array(
+		'object_ids' 	   => $activity_ids,
+		'object_type' 	   => $bp->activity->id,
+		'object_column'    => 'activity_id',
+		'cache_group'      => 'activity_meta',
+		'meta_table' 	   => $bp->activity->table_name_meta,
+		'cache_key_prefix' => 'bp_activity_meta'
+	);
+
+	bp_update_meta_cache( $cache_args );
+}
+
+/**
+ * Clear a cached activity item when that item is updated.
+ *
+ * @since 2.0.0
+ *
+ * @param BP_Activity_Activity $activity Activity object.
+ */
+function bp_activity_clear_cache_for_activity( $activity ) {
+	wp_cache_delete( $activity->id, 'bp_activity' );
+	wp_cache_delete( 'bp_activity_sitewide_front', 'bp' );
+}
+add_action( 'bp_activity_after_save', 'bp_activity_clear_cache_for_activity' );
+
+/**
+ * Clear cached data for deleted activity items.
+ *
+ * @since 2.0.0
+ *
+ * @param array $deleted_ids IDs of deleted activity items.
+ */
+function bp_activity_clear_cache_for_deleted_activity( $deleted_ids ) {
+	foreach ( (array) $deleted_ids as $deleted_id ) {
+		wp_cache_delete( $deleted_id, 'bp_activity' );
+	}
+}
+add_action( 'bp_activity_deleted_activities', 'bp_activity_clear_cache_for_deleted_activity' );
Index: /tags/2.4.3/src/bp-activity/bp-activity-classes.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-classes.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-classes.php	(revision 10410)
@@ -0,0 +1,14 @@
+<?php
+/**
+ * BuddyPress Activity Classes.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+require dirname( __FILE__ ) . '/classes/class-bp-activity-activity.php';
+require dirname( __FILE__ ) . '/classes/class-bp-activity-feed.php';
+require dirname( __FILE__ ) . '/classes/class-bp-activity-query.php';
Index: /tags/2.4.3/src/bp-activity/bp-activity-cssjs.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-cssjs.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-cssjs.php	(revision 10410)
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Activity component CSS/JS
+ *
+ * @package BuddyPress
+ * @subpackage ActivityScripts
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Enqueue @mentions JS.
+ *
+ * @since 2.1.0
+ */
+function bp_activity_mentions_script() {
+	if ( ! bp_activity_maybe_load_mentions_scripts() ) {
+		return;
+	}
+
+	// Special handling for New/Edit screens in wp-admin.
+	if ( is_admin() ) {
+		if (
+			! get_current_screen() ||
+			! in_array( get_current_screen()->base, array( 'page', 'post' ) ) ||
+			! post_type_supports( get_current_screen()->post_type, 'editor' ) ) {
+			return;
+		}
+	}
+
+
+	$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+
+	wp_enqueue_script( 'bp-mentions', buddypress()->plugin_url . "bp-activity/js/mentions{$min}.js", array( 'jquery', 'jquery-atwho' ), bp_get_version(), true );
+	wp_enqueue_style( 'bp-mentions-css', buddypress()->plugin_url . "bp-activity/css/mentions{$min}.css", array(), bp_get_version() );
+
+	wp_style_add_data( 'bp-mentions-css', 'rtl', true );
+	if ( $min ) {
+		wp_style_add_data( 'bp-mentions-css', 'suffix', $min );
+	}
+
+	// If the script has been enqueued, let's attach our mentions TinyMCE init callback.
+	add_filter( 'tiny_mce_before_init', 'bp_add_mentions_on_tinymce_init', 10, 2 );
+
+	/**
+	 * Fires at the end of the Activity Mentions script.
+	 *
+	 * This is the hook where BP components can add their own prefetched results
+	 * friends to the page for quicker @mentions lookups.
+	 *
+	 * @since 2.1.0
+	 */
+	do_action( 'bp_activity_mentions_prime_results' );
+}
+add_action( 'bp_enqueue_scripts', 'bp_activity_mentions_script' );
+add_action( 'bp_admin_enqueue_scripts', 'bp_activity_mentions_script' );
+
+/**
+ * Bind the mentions listener to a wp_editor instance when TinyMCE initializes.
+ *
+ * @since 2.3.3
+ *
+ * @param array  $settings   An array with TinyMCE config.
+ * @param string $editor_id Unique editor identifier, e.g. 'content'.
+ * @return array  $mceInit   An array with TinyMCE config.
+ */
+function bp_add_mentions_on_tinymce_init( $settings, $editor_id ) {
+	// We only apply the mentions init to the visual post editor in the WP dashboard.
+	if ( 'content' === $editor_id ) {
+		$settings['init_instance_callback'] = 'window.bp.mentions.tinyMCEinit';
+	}
+
+	return $settings;
+}
Index: /tags/2.4.3/src/bp-activity/bp-activity-filters.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-filters.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-filters.php	(revision 10410)
@@ -0,0 +1,823 @@
+<?php
+/**
+ * Filters related to the Activity component.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityFilters
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/** Filters *******************************************************************/
+
+// Apply WordPress defined filters.
+add_filter( 'bp_get_activity_action',                'bp_activity_filter_kses', 1 );
+add_filter( 'bp_get_activity_content_body',          'bp_activity_filter_kses', 1 );
+add_filter( 'bp_get_activity_content',               'bp_activity_filter_kses', 1 );
+add_filter( 'bp_get_activity_parent_content',        'bp_activity_filter_kses', 1 );
+add_filter( 'bp_get_activity_latest_update',         'bp_activity_filter_kses', 1 );
+add_filter( 'bp_get_activity_latest_update_excerpt', 'bp_activity_filter_kses', 1 );
+add_filter( 'bp_get_activity_feed_item_description', 'bp_activity_filter_kses', 1 );
+add_filter( 'bp_activity_content_before_save',       'bp_activity_filter_kses', 1 );
+add_filter( 'bp_activity_action_before_save',        'bp_activity_filter_kses', 1 );
+add_filter( 'bp_activity_latest_update_content',     'bp_activity_filter_kses', 1 );
+
+add_filter( 'bp_get_activity_action',                'force_balance_tags' );
+add_filter( 'bp_get_activity_content_body',          'force_balance_tags' );
+add_filter( 'bp_get_activity_content',               'force_balance_tags' );
+add_filter( 'bp_get_activity_latest_update',         'force_balance_tags' );
+add_filter( 'bp_get_activity_latest_update_excerpt', 'force_balance_tags' );
+add_filter( 'bp_get_activity_feed_item_description', 'force_balance_tags' );
+add_filter( 'bp_activity_content_before_save',       'force_balance_tags' );
+add_filter( 'bp_activity_action_before_save',        'force_balance_tags' );
+
+add_filter( 'bp_get_activity_action',                'wptexturize' );
+add_filter( 'bp_get_activity_content_body',          'wptexturize' );
+add_filter( 'bp_get_activity_content',               'wptexturize' );
+add_filter( 'bp_get_activity_parent_content',        'wptexturize' );
+add_filter( 'bp_get_activity_latest_update',         'wptexturize' );
+add_filter( 'bp_get_activity_latest_update_excerpt', 'wptexturize' );
+
+add_filter( 'bp_get_activity_action',                'convert_smilies' );
+add_filter( 'bp_get_activity_content_body',          'convert_smilies' );
+add_filter( 'bp_get_activity_content',               'convert_smilies' );
+add_filter( 'bp_get_activity_parent_content',        'convert_smilies' );
+add_filter( 'bp_get_activity_latest_update',         'convert_smilies' );
+add_filter( 'bp_get_activity_latest_update_excerpt', 'convert_smilies' );
+
+add_filter( 'bp_get_activity_action',                'convert_chars' );
+add_filter( 'bp_get_activity_content_body',          'convert_chars' );
+add_filter( 'bp_get_activity_content',               'convert_chars' );
+add_filter( 'bp_get_activity_parent_content',        'convert_chars' );
+add_filter( 'bp_get_activity_latest_update',         'convert_chars' );
+add_filter( 'bp_get_activity_latest_update_excerpt', 'convert_chars' );
+
+add_filter( 'bp_get_activity_action',                'wpautop' );
+add_filter( 'bp_get_activity_content_body',          'wpautop' );
+add_filter( 'bp_get_activity_content',               'wpautop' );
+add_filter( 'bp_get_activity_feed_item_description', 'wpautop' );
+
+add_filter( 'bp_get_activity_action',                'make_clickable', 9 );
+add_filter( 'bp_get_activity_content_body',          'make_clickable', 9 );
+add_filter( 'bp_get_activity_content',               'make_clickable', 9 );
+add_filter( 'bp_get_activity_parent_content',        'make_clickable', 9 );
+add_filter( 'bp_get_activity_latest_update',         'make_clickable', 9 );
+add_filter( 'bp_get_activity_latest_update_excerpt', 'make_clickable', 9 );
+add_filter( 'bp_get_activity_feed_item_description', 'make_clickable', 9 );
+
+add_filter( 'bp_acomment_name',                      'stripslashes_deep', 5 );
+add_filter( 'bp_get_activity_action',                'stripslashes_deep', 5 );
+add_filter( 'bp_get_activity_content',               'stripslashes_deep', 5 );
+add_filter( 'bp_get_activity_content_body',          'stripslashes_deep', 5 );
+add_filter( 'bp_get_activity_parent_content',        'stripslashes_deep', 5 );
+add_filter( 'bp_get_activity_latest_update',         'stripslashes_deep', 5 );
+add_filter( 'bp_get_activity_latest_update_excerpt', 'stripslashes_deep', 5 );
+add_filter( 'bp_get_activity_feed_item_description', 'stripslashes_deep', 5 );
+
+add_filter( 'bp_activity_primary_link_before_save',  'esc_url_raw' );
+
+// Apply BuddyPress-defined filters.
+add_filter( 'bp_get_activity_content',               'bp_activity_make_nofollow_filter' );
+add_filter( 'bp_get_activity_content_body',          'bp_activity_make_nofollow_filter' );
+add_filter( 'bp_get_activity_parent_content',        'bp_activity_make_nofollow_filter' );
+add_filter( 'bp_get_activity_latest_update',         'bp_activity_make_nofollow_filter' );
+add_filter( 'bp_get_activity_latest_update_excerpt', 'bp_activity_make_nofollow_filter' );
+add_filter( 'bp_get_activity_feed_item_description', 'bp_activity_make_nofollow_filter' );
+
+add_filter( 'pre_comment_content',                   'bp_activity_at_name_filter' );
+add_filter( 'group_forum_topic_text_before_save',    'bp_activity_at_name_filter' );
+add_filter( 'group_forum_post_text_before_save',     'bp_activity_at_name_filter' );
+add_filter( 'the_content',                           'bp_activity_at_name_filter' );
+
+add_filter( 'bp_get_activity_parent_content',        'bp_create_excerpt' );
+
+add_filter( 'bp_get_activity_content_body', 'bp_activity_truncate_entry', 5 );
+add_filter( 'bp_get_activity_content',      'bp_activity_truncate_entry', 5 );
+
+add_filter( 'bp_get_total_favorite_count_for_user', 'bp_core_number_format' );
+add_filter( 'bp_get_total_mention_count_for_user',  'bp_core_number_format' );
+
+/** Actions *******************************************************************/
+
+// At-name filter.
+add_action( 'bp_activity_before_save', 'bp_activity_at_name_filter_updates' );
+
+// Activity stream moderation.
+add_action( 'bp_activity_before_save', 'bp_activity_check_moderation_keys', 2, 1 );
+add_action( 'bp_activity_before_save', 'bp_activity_check_blacklist_keys',  2, 1 );
+
+/** Functions *****************************************************************/
+
+/**
+ * Types of activity stream items to moderate.
+ *
+ * @since 1.6.0
+ *
+ * @return array $types List of the activity types to moderate.
+ */
+function bp_activity_get_moderated_activity_types() {
+	$types = array(
+		'activity_comment',
+		'activity_update'
+	);
+
+	/**
+	 * Filters the default activity types that BuddyPress should moderate.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $types Default activity types to moderate.
+	 */
+	return apply_filters( 'bp_activity_check_activity_types', $types );
+}
+
+/**
+ * Moderate the posted activity item, if it contains moderate keywords.
+ *
+ * @since 1.6.0
+ *
+ * @param BP_Activity_Activity $activity The activity object to check.
+ */
+function bp_activity_check_moderation_keys( $activity ) {
+
+	// Only check specific types of activity updates.
+	if ( !in_array( $activity->type, bp_activity_get_moderated_activity_types() ) )
+		return;
+
+	// Unset the activity component so activity stream update fails
+	// @todo This is temporary until some kind of moderation is built.
+	if ( !bp_core_check_for_moderation( $activity->user_id, '', $activity->content ) )
+		$activity->component = false;
+}
+
+/**
+ * Mark the posted activity as spam, if it contains blacklist keywords.
+ *
+ * @since 1.6.0
+ *
+ * @param BP_Activity_Activity $activity The activity object to check.
+ */
+function bp_activity_check_blacklist_keys( $activity ) {
+
+	// Only check specific types of activity updates.
+	if ( ! in_array( $activity->type, bp_activity_get_moderated_activity_types() ) )
+		return;
+
+	// Mark as spam.
+	if ( ! bp_core_check_for_blacklist( $activity->user_id, '', $activity->content ) )
+		bp_activity_mark_as_spam( $activity, 'by_blacklist' );
+}
+
+/**
+ * Custom kses filtering for activity content.
+ *
+ * @since 1.1.0
+ *
+ * @uses apply_filters() To call the 'bp_activity_allowed_tags' hook.
+ * @uses wp_kses()
+ *
+ * @param string $content The activity content.
+ * @return string $content Filtered activity content.
+ */
+function bp_activity_filter_kses( $content ) {
+	global $allowedtags;
+
+	$activity_allowedtags = $allowedtags;
+	$activity_allowedtags['a']['class']    = array();
+	$activity_allowedtags['a']['id']       = array();
+	$activity_allowedtags['a']['rel']      = array();
+	$activity_allowedtags['a']['title']    = array();
+	$activity_allowedtags['b']             = array();
+	$activity_allowedtags['code']          = array();
+	$activity_allowedtags['i']             = array();
+	$activity_allowedtags['img']           = array();
+	$activity_allowedtags['img']['src']    = array();
+	$activity_allowedtags['img']['alt']    = array();
+	$activity_allowedtags['img']['width']  = array();
+	$activity_allowedtags['img']['height'] = array();
+	$activity_allowedtags['img']['class']  = array();
+	$activity_allowedtags['img']['id']     = array();
+	$activity_allowedtags['img']['title']  = array();
+	$activity_allowedtags['span']          = array();
+	$activity_allowedtags['span']['class'] = array();
+
+
+	/**
+	 * Filters the allowed HTML tags for BuddyPress Activity content.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $value Array of allowed HTML tags and attributes.
+	 */
+	$activity_allowedtags = apply_filters( 'bp_activity_allowed_tags', $activity_allowedtags );
+	return wp_kses( $content, $activity_allowedtags );
+}
+
+/**
+ * Find and link @-mentioned users in the contents of a given item.
+ *
+ * @since 1.2.0
+ *
+ * @param string $content     The contents of a given item.
+ * @param int    $activity_id The activity id. Deprecated.
+ * @return string $content Content filtered for mentions.
+ */
+function bp_activity_at_name_filter( $content, $activity_id = 0 ) {
+
+	// Are mentions disabled?
+	if ( ! bp_activity_do_mentions() ) {
+		return $content;
+	}
+
+	// Try to find mentions.
+	$usernames = bp_activity_find_mentions( $content );
+
+	// No mentions? Stop now!
+	if ( empty( $usernames ) )
+		return $content;
+
+	// We don't want to link @mentions that are inside of links, so we
+	// temporarily remove them.
+	$replace_count = 0;
+	$replacements = array();
+	foreach ( $usernames as $username ) {
+		// Prevent @ name linking inside <a> tags.
+		preg_match_all( '/(<a.*?(?!<\/a>)@' . $username . '.*?<\/a>)/', $content, $content_matches );
+		if ( ! empty( $content_matches[1] ) ) {
+			foreach ( $content_matches[1] as $replacement ) {
+				$replacements[ '#BPAN' . $replace_count ] = $replacement;
+				$content = str_replace( $replacement, '#BPAN' . $replace_count, $content );
+				$replace_count++;
+			}
+		}
+	}
+
+	// Linkify the mentions with the username.
+	foreach ( (array) $usernames as $user_id => $username ) {
+		$content = preg_replace( '/(@' . $username . '\b)/', "<a href='" . bp_core_get_user_domain( $user_id ) . "' rel='nofollow'>@$username</a>", $content );
+	}
+
+	// Put everything back.
+	if ( ! empty( $replacements ) ) {
+		foreach ( $replacements as $placeholder => $original ) {
+			$content = str_replace( $placeholder, $original, $content );
+		}
+	}
+
+	// Return the content.
+	return $content;
+}
+
+/**
+ * Catch mentions in an activity item before it is saved into the database.
+ *
+ * If mentions are found, replace @mention text with user links and add our
+ * hook to send mention notifications after the activity item is saved.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_activity_find_mentions()
+ *
+ * @param BP_Activity_Activity $activity Activity Object.
+ */
+function bp_activity_at_name_filter_updates( $activity ) {
+	// Are mentions disabled?
+	if ( ! bp_activity_do_mentions() ) {
+		return;
+	}
+
+	// If activity was marked as spam, stop the rest of this function.
+	if ( ! empty( $activity->is_spam ) )
+		return;
+
+	// Try to find mentions.
+	$usernames = bp_activity_find_mentions( $activity->content );
+
+	// We have mentions!
+	if ( ! empty( $usernames ) ) {
+		// Replace @mention text with userlinks.
+		foreach( (array) $usernames as $user_id => $username ) {
+			$activity->content = preg_replace( '/(@' . $username . '\b)/', "<a href='" . bp_core_get_user_domain( $user_id ) . "' rel='nofollow'>@$username</a>", $activity->content );
+		}
+
+		// Add our hook to send @mention emails after the activity item is saved.
+		add_action( 'bp_activity_after_save', 'bp_activity_at_name_send_emails' );
+
+		// Temporary variable to avoid having to run bp_activity_find_mentions() again.
+		buddypress()->activity->mentioned_users = $usernames;
+	}
+}
+
+/**
+ * Sends emails and BP notifications for users @-mentioned in an activity item.
+ *
+ * @since 1.7.0
+ *
+ * @uses bp_activity_at_message_notification()
+ * @uses bp_activity_update_mention_count_for_user()
+ *
+ * @param BP_Activity_Activity $activity The BP_Activity_Activity object.
+ */
+function bp_activity_at_name_send_emails( $activity ) {
+	// Are mentions disabled?
+	if ( ! bp_activity_do_mentions() ) {
+		return;
+	}
+
+	// If our temporary variable doesn't exist, stop now.
+	if ( empty( buddypress()->activity->mentioned_users ) )
+		return;
+
+	// Grab our temporary variable from bp_activity_at_name_filter_updates().
+	$usernames = buddypress()->activity->mentioned_users;
+
+	// Get rid of temporary variable.
+	unset( buddypress()->activity->mentioned_users );
+
+	// Send @mentions and setup BP notifications.
+	foreach( (array) $usernames as $user_id => $username ) {
+
+		/**
+		 * Filters BuddyPress' ability to send email notifications for @mentions.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param bool  $value     Whether or not BuddyPress should send a notification to the mentioned users.
+		 * @param array $usernames Array of users potentially notified.
+		 */
+		if ( apply_filters( 'bp_activity_at_name_do_notifications', true, $usernames ) ) {
+			bp_activity_at_message_notification( $activity->id, $user_id );
+		}
+
+		// Updates mention count for the user.
+		bp_activity_update_mention_count_for_user( $user_id, $activity->id );
+	}
+}
+
+/**
+ * Catch links in activity text so rel=nofollow can be added.
+ *
+ * @since 1.2.0
+ *
+ * @param string $text Activity text.
+ * @return string $text Text with rel=nofollow added to any links.
+ */
+function bp_activity_make_nofollow_filter( $text ) {
+	return preg_replace_callback( '|<a (.+?)>|i', 'bp_activity_make_nofollow_filter_callback', $text );
+}
+
+	/**
+	 * Add rel=nofollow to a link.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $matches Items matched by preg_replace_callback() in bp_activity_make_nofollow_filter().
+	 * @return string $text Link with rel=nofollow added.
+	 */
+	function bp_activity_make_nofollow_filter_callback( $matches ) {
+		$text = $matches[1];
+		$text = str_replace( array( ' rel="nofollow"', " rel='nofollow'"), '', $text );
+		return "<a $text rel=\"nofollow\">";
+	}
+
+/**
+ * Truncate long activity entries when viewed in activity streams.
+ *
+ * This method can only be used inside the Activity loop.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_is_single_activity()
+ * @uses apply_filters() To call the 'bp_activity_excerpt_append_text' hook.
+ * @uses apply_filters() To call the 'bp_activity_excerpt_length' hook.
+ * @uses bp_create_excerpt()
+ * @uses bp_get_activity_id()
+ * @uses bp_get_activity_thread_permalink()
+ * @uses apply_filters() To call the 'bp_activity_truncate_entry' hook.
+ *
+ * @param string $text The original activity entry text.
+ * @return string $excerpt The truncated text.
+ */
+function bp_activity_truncate_entry( $text ) {
+	global $activities_template;
+
+	/**
+	 * Provides a filter that lets you choose whether to skip this filter on a per-activity basis.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param bool $value If true, text should be checked to see if it needs truncating.
+	 */
+	$maybe_truncate_text = apply_filters(
+		'bp_activity_maybe_truncate_entry',
+		isset( $activities_template->activity->type ) && ! in_array( $activities_template->activity->type, array( 'new_blog_post', ), true )
+	);
+
+	// The full text of the activity update should always show on the single activity screen.
+	if ( ! $maybe_truncate_text || bp_is_single_activity() ) {
+		return $text;
+	}
+
+	/**
+	 * Filters the appended text for the activity excerpt.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $value Internationalized "Read more" text.
+	 */
+	$append_text    = apply_filters( 'bp_activity_excerpt_append_text', __( '[Read more]', 'buddypress' ) );
+
+	/**
+	 * Filters the excerpt length for the activity excerpt.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $value Number indicating how many words to trim the excerpt down to.
+	 */
+	$excerpt_length = apply_filters( 'bp_activity_excerpt_length', 358 );
+
+	// Run the text through the excerpt function. If it's too short, the original text will be returned.
+	$excerpt        = bp_create_excerpt( $text, $excerpt_length, array( 'ending' => __( '&hellip;', 'buddypress' ) ) );
+
+	/*
+	 * If the text returned by bp_create_excerpt() is different from the original text (ie it's
+	 * been truncated), add the "Read More" link. Note that bp_create_excerpt() is stripping
+	 * shortcodes, so we have strip them from the $text before the comparison.
+	 */
+	if ( $excerpt != strip_shortcodes( $text ) ) {
+		$id = !empty( $activities_template->activity->current_comment->id ) ? 'acomment-read-more-' . $activities_template->activity->current_comment->id : 'activity-read-more-' . bp_get_activity_id();
+
+		$excerpt = sprintf( '%1$s<span class="activity-read-more" id="%2$s"><a href="%3$s" rel="nofollow">%4$s</a></span>', $excerpt, $id, bp_get_activity_thread_permalink(), $append_text );
+	}
+
+	/**
+	 * Filters the composite activity excerpt entry.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $excerpt     Excerpt text and markup to be displayed.
+	 * @param string $text        The original activity entry text.
+	 * @param string $append_text The final append text applied.
+	 */
+	return apply_filters( 'bp_activity_truncate_entry', $excerpt, $text, $append_text );
+}
+
+/**
+ * Include extra JavaScript dependencies for activity component.
+ *
+ * @since 2.0.0
+ *
+ * @uses bp_activity_do_heartbeat() to check if heartbeat is required.
+ *
+ * @param array $js_handles The original dependencies.
+ * @return array $js_handles The new dependencies.
+ */
+function bp_activity_get_js_dependencies( $js_handles = array() ) {
+	if ( bp_activity_do_heartbeat() ) {
+		$js_handles[] = 'heartbeat';
+	}
+
+	return $js_handles;
+}
+add_filter( 'bp_core_get_js_dependencies', 'bp_activity_get_js_dependencies', 10, 1 );
+
+/**
+ * Add a just-posted classes to the most recent activity item.
+ *
+ * We use these classes to avoid pagination issues when items are loaded
+ * dynamically into the activity stream.
+ *
+ * @since 2.0.0
+ *
+ * @param string $classes Array of classes for most recent activity item.
+ * @return string $classes
+ */
+function bp_activity_newest_class( $classes = '' ) {
+	$bp = buddypress();
+
+	if ( ! empty( $bp->activity->last_recorded ) && $bp->activity->last_recorded == bp_get_activity_date_recorded() ) {
+		$classes .= ' new-update';
+	}
+
+	$classes .= ' just-posted';
+	return $classes;
+}
+
+/**
+ * Check if Activity Heartbeat feature i on to add a timestamp class.
+ *
+ * @since 2.0.0
+ *
+ * @param string $classes Array of classes for timestamp.
+ * @return string $classes
+ */
+function bp_activity_timestamp_class( $classes = '' ) {
+
+	if ( ! bp_activity_do_heartbeat() ) {
+		return $classes;
+	}
+
+	$activity_date = bp_get_activity_date_recorded();
+
+	if ( empty( $activity_date ) ) {
+		return $classes;
+	}
+
+	$classes .= ' date-recorded-' . strtotime( $activity_date );
+
+	return $classes;
+}
+add_filter( 'bp_get_activity_css_class', 'bp_activity_timestamp_class', 9, 1 );
+
+/**
+ * Use WordPress Heartbeat API to check for latest activity update.
+ *
+ * @since 2.0.0
+ *
+ * @uses bp_activity_get_last_updated() to get the recorded date of the last activity.
+ *
+ * @param array $response Array containing Heartbeat API response.
+ * @param array $data     Array containing data for Heartbeat API response.
+ * @return array $response
+ */
+function bp_activity_heartbeat_last_recorded( $response = array(), $data = array() ) {
+	if ( empty( $data['bp_activity_last_recorded'] ) ) {
+		return $response;
+	}
+
+	// Use the querystring argument stored in the cookie (to preserve
+	// filters), but force the offset to get only new items.
+	$activity_latest_args = bp_parse_args(
+		bp_ajax_querystring( 'activity' ),
+		array( 'since' => date( 'Y-m-d H:i:s', $data['bp_activity_last_recorded'] ) ),
+		'activity_latest_args'
+	);
+
+	if ( ! empty( $data['bp_activity_last_recorded_search_terms'] ) && empty( $activity_latest_args['search_terms'] ) ) {
+		$activity_latest_args['search_terms'] = addslashes( $data['bp_activity_last_recorded_search_terms'] );
+	}
+
+	$newest_activities = array();
+	$last_activity_recorded = 0;
+
+	// Temporarily add a just-posted class for new activity items.
+	add_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10, 1 );
+
+	ob_start();
+	if ( bp_has_activities( $activity_latest_args ) ) {
+		while ( bp_activities() ) {
+			bp_the_activity();
+
+			$atime = strtotime( bp_get_activity_date_recorded() );
+			if ( $last_activity_recorded < $atime ) {
+				$last_activity_recorded = $atime;
+			}
+
+			bp_get_template_part( 'activity/entry' );
+		}
+	}
+
+	$newest_activities['activities']    = ob_get_contents();
+	$newest_activities['last_recorded'] = $last_activity_recorded;
+	ob_end_clean();
+
+	// Remove the temporary filter.
+	remove_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10, 1 );
+
+	if ( ! empty( $newest_activities['last_recorded'] ) ) {
+		$response['bp_activity_newest_activities'] = $newest_activities;
+	}
+
+	return $response;
+}
+add_filter( 'heartbeat_received', 'bp_activity_heartbeat_last_recorded', 10, 2 );
+add_filter( 'heartbeat_nopriv_received', 'bp_activity_heartbeat_last_recorded', 10, 2 );
+
+/**
+ * Set the strings for WP HeartBeat API where needed.
+ *
+ * @since 2.0.0
+ *
+ * @param array $strings Localized strings.
+ * @return array $strings
+ */
+function bp_activity_heartbeat_strings( $strings = array() ) {
+
+	if ( ! bp_activity_do_heartbeat() ) {
+		return $strings;
+	}
+
+	$global_pulse = 0;
+
+	/**
+	 * Filter that checks whether the global heartbeat settings already exist.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $value Heartbeat settings array.
+	 */
+	$heartbeat_settings = apply_filters( 'heartbeat_settings', array() );
+	if ( ! empty( $heartbeat_settings['interval'] ) ) {
+		// 'Fast' is 5
+		$global_pulse = is_numeric( $heartbeat_settings['interval'] ) ? absint( $heartbeat_settings['interval'] ) : 5;
+	}
+
+	/**
+	 * Filters the pulse frequency to be used for the BuddyPress Activity heartbeat.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param int $value The frequency in seconds between pulses.
+	 */
+	$bp_activity_pulse = apply_filters( 'bp_activity_heartbeat_pulse', 15 );
+
+	/**
+	 * Use the global pulse value unless:
+	 * a. the BP-specific value has been specifically filtered, or
+	 * b. it doesn't exist (ie, BP will be the only one using the heartbeat,
+	 *    so we're responsible for enabling it)
+	 */
+	if ( has_filter( 'bp_activity_heartbeat_pulse' ) || empty( $global_pulse ) ) {
+		$pulse = $bp_activity_pulse;
+	} else {
+		$pulse = $global_pulse;
+	}
+
+	$strings = array_merge( $strings, array(
+		'newest' => __( 'Load Newest', 'buddypress' ),
+		'pulse'  => absint( $pulse ),
+	) );
+
+	return $strings;
+}
+add_filter( 'bp_core_get_js_strings', 'bp_activity_heartbeat_strings', 10, 1 );
+
+/** Scopes ********************************************************************/
+
+/**
+ * Set up activity arguments for use with the 'just-me' scope.
+ *
+ * @since 2.2.0
+ *
+ * @param array $retval Empty array by default.
+ * @param array $filter Current activity arguments.
+ * @return array $retval
+ */
+function bp_activity_filter_just_me_scope( $retval = array(), $filter = array() ) {
+
+	// Determine the user_id.
+	if ( ! empty( $filter['user_id'] ) ) {
+		$user_id = $filter['user_id'];
+	} else {
+		$user_id = bp_displayed_user_id()
+			? bp_displayed_user_id()
+			: bp_loggedin_user_id();
+	}
+
+	// Should we show all items regardless of sitewide visibility?
+	$show_hidden = array();
+	if ( ! empty( $user_id ) && $user_id !== bp_loggedin_user_id() ) {
+		$show_hidden = array(
+			'column' => 'hide_sitewide',
+			'value'  => 0
+		);
+	}
+
+	$retval = array(
+		'relation' => 'AND',
+		array(
+			'column' => 'user_id',
+			'value'  => $user_id
+		),
+		$show_hidden,
+
+		// Overrides.
+		'override' => array(
+			'display_comments' => 'stream',
+			'filter'           => array( 'user_id' => 0 ),
+			'show_hidden'      => true
+		),
+	);
+
+	return $retval;
+}
+add_filter( 'bp_activity_set_just-me_scope_args', 'bp_activity_filter_just_me_scope', 10, 2 );
+
+/**
+ * Set up activity arguments for use with the 'favorites' scope.
+ *
+ * @since 2.2.0
+ *
+ * @param array $retval Empty array by default.
+ * @param array $filter Current activity arguments.
+ * @return array $retval
+ */
+function bp_activity_filter_favorites_scope( $retval = array(), $filter = array() ) {
+
+	// Determine the user_id.
+	if ( ! empty( $filter['user_id'] ) ) {
+		$user_id = $filter['user_id'];
+	} else {
+		$user_id = bp_displayed_user_id()
+			? bp_displayed_user_id()
+			: bp_loggedin_user_id();
+	}
+
+	// Determine the favorites.
+	$favs = bp_activity_get_user_favorites( $user_id );
+	if ( empty( $favs ) ) {
+		$favs = array( 0 );
+	}
+
+	// Should we show all items regardless of sitewide visibility?
+	$show_hidden = array();
+	if ( ! empty( $user_id ) && ( $user_id !== bp_loggedin_user_id() ) ) {
+		$show_hidden = array(
+			'column' => 'hide_sitewide',
+			'value'  => 0
+		);
+	}
+
+	$retval = array(
+		'relation' => 'AND',
+		array(
+			'column'  => 'id',
+			'compare' => 'IN',
+			'value'   => (array) $favs
+		),
+		$show_hidden,
+
+		// Overrides.
+		'override' => array(
+			'display_comments' => true,
+			'filter'           => array( 'user_id' => 0 ),
+			'show_hidden'      => true
+		),
+	);
+
+	return $retval;
+}
+add_filter( 'bp_activity_set_favorites_scope_args', 'bp_activity_filter_favorites_scope', 10, 2 );
+
+
+/**
+ * Set up activity arguments for use with the 'favorites' scope.
+ *
+ * @since 2.2.0
+ *
+ * @param array $retval Empty array by default.
+ * @param array $filter Current activity arguments.
+ * @return array $retval
+ */
+function bp_activity_filter_mentions_scope( $retval = array(), $filter = array() ) {
+
+	// Are mentions disabled?
+	if ( ! bp_activity_do_mentions() ) {
+		return $retval;
+	}
+
+	// Determine the user_id.
+	if ( ! empty( $filter['user_id'] ) ) {
+		$user_id = $filter['user_id'];
+	} else {
+		$user_id = bp_displayed_user_id()
+			? bp_displayed_user_id()
+			: bp_loggedin_user_id();
+	}
+
+	// Should we show all items regardless of sitewide visibility?
+	$show_hidden = array();
+	if ( ! empty( $user_id ) && $user_id !== bp_loggedin_user_id() ) {
+		$show_hidden = array(
+			'column' => 'hide_sitewide',
+			'value'  => 0
+		);
+	}
+
+	$retval = array(
+		'relation' => 'AND',
+		array(
+			'column'  => 'content',
+			'compare' => 'LIKE',
+
+			// Start search at @ symbol and stop search at closing tag delimiter.
+			'value'   => '@' . bp_activity_get_user_mentionname( $user_id ) . '<'
+		),
+		$show_hidden,
+
+		// Overrides.
+		'override' => array(
+
+			// Clear search terms so 'mentions' scope works with other scopes.
+			'search_terms' => false,
+
+			'display_comments' => 'stream',
+			'filter'           => array( 'user_id' => 0 ),
+			'show_hidden'      => true
+		),
+	);
+
+	return $retval;
+}
+add_filter( 'bp_activity_set_mentions_scope_args', 'bp_activity_filter_mentions_scope', 10, 2 );
Index: /tags/2.4.3/src/bp-activity/bp-activity-functions.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-functions.php	(revision 10410)
@@ -0,0 +1,3086 @@
+<?php
+/**
+ * BuddyPress Activity Functions.
+ *
+ * Functions for the Activity Streams component.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Check whether the $bp global lists an activity directory page.
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if activity directory page is found, otherwise false.
+ */
+function bp_activity_has_directory() {
+	return (bool) !empty( buddypress()->pages->activity->id );
+}
+
+/**
+ * Are mentions enabled or disabled?
+ *
+ * The Mentions feature does a number of things, all of which will be turned
+ * off if you disable mentions:
+ *   - Detecting and auto-linking @username in all BP/WP content.
+ *   - Sending BP notifications and emails to users when they are mentioned
+ *     using the @username syntax.
+ *   - The Public Message button on user profiles.
+ *
+ * Mentions are enabled by default. To disable, put the following line in
+ * bp-custom.php or your theme's functions.php file:
+ *
+ *   add_filter( 'bp_activity_do_mentions', '__return_false' );
+ *
+ * @since 1.8.0
+ *
+ * @uses apply_filters() To call 'bp_activity_do_mentions' hook.
+ *
+ * @return bool $retval True to enable mentions, false to disable.
+ */
+function bp_activity_do_mentions() {
+
+	/**
+	 * Filters whether or not mentions are enabled.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param bool $enabled True to enable mentions, false to disable.
+	 */
+	return (bool) apply_filters( 'bp_activity_do_mentions', true );
+}
+
+/**
+ * Should BuddyPress load the mentions scripts and related assets, including results to prime the
+ * mentions suggestions?
+ *
+ * @since 2.1.0
+ *
+ * @return bool True if mentions scripts should be loaded.
+ */
+function bp_activity_maybe_load_mentions_scripts() {
+	$mentions_enabled = bp_activity_do_mentions() && bp_is_user_active();
+	$load_mentions    = $mentions_enabled && ( bp_is_activity_component() || is_admin() );
+
+	/**
+	 * Filters whether or not BuddyPress should load mentions scripts and assets.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $load_mentions    True to load mentions assets, false otherwise.
+	 * @param bool $mentions_enabled True if mentions are enabled.
+	 */
+	return (bool) apply_filters( 'bp_activity_maybe_load_mentions_scripts', $load_mentions, $mentions_enabled );
+}
+
+/**
+ * Locate usernames in an activity content string, as designated by an @ sign.
+ *
+ * @since 1.5.0
+ *
+ * @param string $content The content of the activity, usually found in
+ *                        $activity->content.
+ * @return array|bool Associative array with user ID as key and username as
+ *                    value. Boolean false if no mentions found.
+ */
+function bp_activity_find_mentions( $content ) {
+
+	$pattern = '/[@]+([A-Za-z0-9-_\.@]+)\b/';
+	preg_match_all( $pattern, $content, $usernames );
+
+	// Make sure there's only one instance of each username.
+	$usernames = array_unique( $usernames[1] );
+
+	// Bail if no usernames.
+	if ( empty( $usernames ) ) {
+		return false;
+	}
+
+	$mentioned_users = array();
+
+	// We've found some mentions! Check to see if users exist.
+	foreach( (array) array_values( $usernames ) as $username ) {
+		$user_id = bp_activity_get_userid_from_mentionname( $username );
+
+		// The user ID exists, so let's add it to our array.
+		if ( ! empty( $user_id ) ) {
+			$mentioned_users[ $user_id ] = $username;
+		}
+	}
+
+	if ( empty( $mentioned_users ) ) {
+		return false;
+	}
+
+	return $mentioned_users;
+}
+
+/**
+ * Reset a user's unread mentions list and count.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_delete_user_meta()
+ *
+ * @param int $user_id The id of the user whose unread mentions are being reset.
+ */
+function bp_activity_clear_new_mentions( $user_id ) {
+	bp_delete_user_meta( $user_id, 'bp_new_mention_count' );
+	bp_delete_user_meta( $user_id, 'bp_new_mentions'      );
+}
+
+/**
+ * Adjusts mention count for mentioned users in activity items.
+ *
+ * This function is useful if you only have the activity ID handy and you
+ * haven't parsed an activity item for @mentions yet.
+ *
+ * Currently, only used in {@link bp_activity_delete()}.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_activity_find_mentions()
+ * @uses bp_activity_update_mention_count_for_user()
+ *
+ * @param int    $activity_id The unique id for the activity item.
+ * @param string $action      Can be 'delete' or 'add'. Defaults to 'add'.
+ * @return bool
+ */
+function bp_activity_adjust_mention_count( $activity_id = 0, $action = 'add' ) {
+
+	// Bail if no activity ID passed.
+	if ( empty( $activity_id ) ) {
+		return false;
+	}
+
+	// Get activity object.
+	$activity  = new BP_Activity_Activity( (int) $activity_id );
+
+	// Try to find mentions.
+	$usernames = bp_activity_find_mentions( strip_tags( $activity->content ) );
+
+	// Still empty? Stop now.
+	if ( empty( $usernames ) ) {
+		return false;
+	}
+
+	// Increment mention count foreach mentioned user.
+	foreach( (array) array_keys( $usernames ) as $user_id ) {
+		bp_activity_update_mention_count_for_user( $user_id, $activity_id, $action );
+	}
+}
+
+/**
+ * Update the mention count for a given user.
+ *
+ * This function should be used when you've already parsed your activity item
+ * for @mentions.
+ *
+ * @since 1.7.0
+ *
+ * @uses bp_get_user_meta()
+ * @uses bp_update_user_meta()
+ *
+ * @param int    $user_id     The user ID.
+ * @param int    $activity_id The unique ID for the activity item.
+ * @param string $action      'delete' or 'add'. Default: 'add'.
+ * @return bool
+ */
+function bp_activity_update_mention_count_for_user( $user_id, $activity_id, $action = 'add' ) {
+
+	if ( empty( $user_id ) || empty( $activity_id ) ) {
+		return false;
+	}
+
+	// Adjust the mention list and count for the member.
+	$new_mention_count = (int) bp_get_user_meta( $user_id, 'bp_new_mention_count', true );
+	$new_mentions      =       bp_get_user_meta( $user_id, 'bp_new_mentions',      true );
+
+	// Make sure new mentions is an array.
+	if ( empty( $new_mentions ) ) {
+		$new_mentions = array();
+	}
+
+	switch ( $action ) {
+		case 'delete' :
+			$key = array_search( $activity_id, $new_mentions );
+
+			if ( $key !== false ) {
+				unset( $new_mentions[$key] );
+			}
+
+			break;
+
+		case 'add' :
+		default :
+			if ( !in_array( $activity_id, $new_mentions ) ) {
+				$new_mentions[] = (int) $activity_id;
+			}
+
+			break;
+	}
+
+	// Get an updated mention count.
+	$new_mention_count = count( $new_mentions );
+
+	// Resave the user_meta.
+	bp_update_user_meta( $user_id, 'bp_new_mention_count', $new_mention_count );
+	bp_update_user_meta( $user_id, 'bp_new_mentions',      $new_mentions );
+
+	return true;
+}
+
+/**
+ * Determine a user's "mentionname", the name used for that user in @-mentions.
+ *
+ * @since 1.9.0
+ *
+ * @param int|string $user_id ID of the user to get @-mention name for.
+ * @return string $mentionname User name appropriate for @-mentions.
+ */
+function bp_activity_get_user_mentionname( $user_id ) {
+	$mentionname = '';
+
+	$userdata = bp_core_get_core_userdata( $user_id );
+
+	if ( $userdata ) {
+		if ( bp_is_username_compatibility_mode() ) {
+			$mentionname = str_replace( ' ', '-', $userdata->user_login );
+		} else {
+			$mentionname = $userdata->user_nicename;
+		}
+	}
+
+	return $mentionname;
+}
+
+/**
+ * Get a user ID from a "mentionname", the name used for a user in @-mentions.
+ *
+ * @since 1.9.0
+ *
+ * @param string $mentionname Username of user in @-mentions.
+ * @return int|bool ID of the user, if one is found. Otherwise false.
+ */
+function bp_activity_get_userid_from_mentionname( $mentionname ) {
+	$user_id = false;
+
+	/*
+	 * In username compatibility mode, hyphens are ambiguous between
+	 * actual hyphens and converted spaces.
+	 *
+	 * @todo There is the potential for username clashes between 'foo bar'
+	 * and 'foo-bar' in compatibility mode. Come up with a system for
+	 * unique mentionnames.
+	 */
+	if ( bp_is_username_compatibility_mode() ) {
+		// First, try the raw username.
+		$userdata = get_user_by( 'login', $mentionname );
+
+		// Doing a direct query to use proper regex. Necessary to
+		// account for hyphens + spaces in the same user_login.
+		if ( empty( $userdata ) || ! is_a( $userdata, 'WP_User' ) ) {
+			global $wpdb;
+			$regex   = esc_sql( str_replace( '-', '[ \-]', $mentionname ) );
+			$user_id = $wpdb->get_var( "SELECT ID FROM {$wpdb->users} WHERE user_login REGEXP '{$regex}'" );
+		} else {
+			$user_id = $userdata->ID;
+		}
+
+	// When username compatibility mode is disabled, the mentionname is
+	// the same as the nicename.
+	} else {
+		$user_id = bp_core_get_userid_from_nicename( $mentionname );
+	}
+
+
+	return $user_id;
+}
+
+/** Actions ******************************************************************/
+
+/**
+ * Register an activity 'type' and its action description/callback.
+ *
+ * Activity actions are strings used to describe items in the activity stream,
+ * such as 'Joe became a registered member' or 'Bill and Susie are now
+ * friends'. Each activity type (such as 'new_member' or 'friendship_created')
+ * used by a component should be registered using this function.
+ *
+ * While it's possible to post items to the activity stream whose types are
+ * not registered using bp_activity_set_action(), it is not recommended;
+ * unregistered types will not be displayed properly in the activity admin
+ * panel, and dynamic action generation (which is essential for multilingual
+ * sites, etc) will not work.
+ *
+ * @since 1.1.0
+ *
+ * @param  string        $component_id    The unique string ID of the component.
+ * @param  string        $type            The action type.
+ * @param  string        $description     The action description.
+ * @param  callable|bool $format_callback Callback for formatting the action string.
+ * @param  string|bool   $label           String to describe this action in the activity stream filter dropdown.
+ * @param  array         $context         Optional. Activity stream contexts where the filter should appear.
+ *                                        Values: 'activity', 'member', 'member_groups', 'group'.
+ * @param  int           $position        Optional. The position of the action when listed in dropdowns.
+ * @return bool False if any param is empty, otherwise true.
+ */
+function bp_activity_set_action( $component_id, $type, $description, $format_callback = false, $label = false, $context = array(), $position = 0 ) {
+	$bp = buddypress();
+
+	// Return false if any of the above values are not set.
+	if ( empty( $component_id ) || empty( $type ) || empty( $description ) ) {
+		return false;
+	}
+
+	// Set activity action.
+	if ( ! isset( $bp->activity->actions ) || ! is_object( $bp->activity->actions ) ) {
+		$bp->activity->actions = new stdClass;
+	}
+
+	// Verify callback.
+	if ( ! is_callable( $format_callback ) ) {
+		$format_callback = '';
+	}
+
+	if ( ! isset( $bp->activity->actions->{$component_id} ) || ! is_object( $bp->activity->actions->{$component_id} ) ) {
+		$bp->activity->actions->{$component_id} = new stdClass;
+	}
+
+	/**
+	 * Filters the action type being set for the current activity item.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param array    $array           Array of arguments for action type being set.
+	 * @param string   $component_id    ID of the current component being set.
+	 * @param string   $type            Action type being set.
+	 * @param string   $description     Action description for action being set.
+	 * @param callable $format_callback Callback for formatting the action string.
+	 * @param string   $label           String to describe this action in the activity stream filter dropdown.
+	 * @param array    $context         Activity stream contexts where the filter should appear. 'activity', 'member',
+	 *                                  'member_groups', 'group'.
+	 */
+	$bp->activity->actions->{$component_id}->{$type} = apply_filters( 'bp_activity_set_action', array(
+		'key'             => $type,
+		'value'           => $description,
+		'format_callback' => $format_callback,
+		'label'           => $label,
+		'context'         => $context,
+		'position'        => $position,
+	), $component_id, $type, $description, $format_callback, $label, $context );
+
+	return true;
+}
+
+/**
+ * Set tracking arguments for a given post type.
+ *
+ * @since 2.2.0
+ *
+ * @global $wp_post_types
+ *
+ * @param string $post_type The name of the post type, as registered with WordPress. Eg 'post' or 'page'.
+ * @param array  $args {
+ *     An associative array of tracking parameters. All items are optional.
+ *     @type string   $bp_activity_admin_filter String to use in the Dashboard > Activity dropdown.
+ *     @type string   $bp_activity_front_filter String to use in frontend dropdown.
+ *     @type string   $bp_activity_new_post     String format to use for generating the activity action. Should be a
+ *                                              translatable string where %1$s is replaced by a user link and %2$s is
+ *                                              the URL of the newly created post.
+ *     @type string   $bp_activity_new_post_ms  String format to use for generating the activity action on Multisite.
+ *                                              Should be a translatable string where %1$s is replaced by a user link,
+ *                                              %2$s is the URL of the newly created post, and %3$s is a link to
+ *                                              the site.
+ *     @type string   $component_id             ID of the BuddyPress component to associate the activity item.
+ *     @type string   $action_id                Value for the 'type' param of the new activity item.
+ *     @type callable $format_callback          Callback for formatting the activity action string.
+ *                                              Default: 'bp_activity_format_activity_action_custom_post_type_post'.
+ *     @type array    $contexts                 The directory contexts in which the filter will show.
+ *                                              Default: array( 'activity' ).
+ *     @type array    $position                 Position of the item in filter dropdowns.
+ *     @type string   $singular                 Singular, translatable name of the post type item. If no value is
+ *                                              provided, it's pulled from the 'singular_name' of the post type.
+ *     @type bool     $activity_comment         Whether to allow comments on the activity items. Defaults to true if
+ *                                              the post type does not natively support comments, otherwise false.
+ * }
+ * @return bool
+ */
+function bp_activity_set_post_type_tracking_args( $post_type = '', $args = array() ) {
+	global $wp_post_types;
+
+	if ( empty( $wp_post_types[ $post_type ] ) || ! post_type_supports( $post_type, 'buddypress-activity' ) || ! is_array( $args ) ) {
+		return false;
+	}
+
+	// Labels are loaded into the post type object.
+	foreach ( array( 'bp_activity_admin_filter', 'bp_activity_front_filter', 'bp_activity_new_post', 'bp_activity_new_post_ms' ) as $label_type ) {
+		if ( ! empty( $args[ $label_type ] ) ) {
+			$wp_post_types[ $post_type ]->labels->{$label_type} = $args[ $label_type ];
+			unset( $args[ $post_type ] );
+		}
+	}
+
+	// If there are any additional args, put them in the bp_activity attribute of the post type.
+	if ( ! empty( $args ) ) {
+		$wp_post_types[ $post_type ]->bp_activity = $args;
+	}
+}
+
+/**
+ * Get tracking arguments for a specific post type.
+ *
+ * @since 2.2.0
+ *
+ * @param  string $post_type Name of the post type.
+ * @return object The tracking arguments of the post type.
+ */
+function bp_activity_get_post_type_tracking_args( $post_type ) {
+	if ( ! post_type_supports( $post_type, 'buddypress-activity' ) ) {
+		return false;
+	}
+
+	$post_type_object = get_post_type_object( $post_type );
+
+	$post_type_activity = array(
+		'component_id'      => buddypress()->activity->id,
+		'action_id'         => 'new_' . $post_type,
+		'format_callback'   => 'bp_activity_format_activity_action_custom_post_type_post',
+		'front_filter'      => $post_type_object->labels->name,
+		'contexts'          => array( 'activity' ),
+		'position'          => 0,
+		'singular'          => strtolower( $post_type_object->labels->singular_name ),
+		'activity_comment' => ! post_type_supports( $post_type, 'comments' ),
+	);
+
+	if ( ! empty( $post_type_object->bp_activity ) ) {
+		$post_type_activity = bp_parse_args( (array) $post_type_object->bp_activity, $post_type_activity, $post_type . '_tracking_args' );
+	}
+
+	$post_type_activity = (object) $post_type_activity;
+
+	// Try to get the admin filter from the post type labels.
+	if ( ! empty( $post_type_object->labels->bp_activity_admin_filter ) ) {
+		$post_type_activity->admin_filter = $post_type_object->labels->bp_activity_admin_filter;
+
+	// Fall back to a generic name.
+	} else {
+		$post_type_activity->admin_filter = _x( 'New item published', 'Post Type generic activity post admin filter', 'buddypress' );
+	}
+
+	// Check for the front filter in the post type labels.
+	if ( ! empty( $post_type_object->labels->bp_activity_front_filter ) ) {
+		$post_type_activity->front_filter = $post_type_object->labels->bp_activity_front_filter;
+	}
+
+	// Try to get the action for new post type action on non-multisite installations.
+	if ( ! empty( $post_type_object->labels->bp_activity_new_post ) ) {
+		$post_type_activity->new_post_type_action = $post_type_object->labels->bp_activity_new_post;
+	}
+
+	// Try to get the action for new post type action on multisite installations.
+	if ( ! empty( $post_type_object->labels->bp_activity_new_post_ms ) ) {
+		$post_type_activity->new_post_type_action_ms = $post_type_object->labels->bp_activity_new_post_ms;
+	}
+
+	/**
+	 * Filters tracking arguments for a specific post type.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param object $post_type_activity The tracking arguments of the post type.
+	 * @param string $post_type          Name of the post type.
+	 */
+	return apply_filters( 'bp_activity_get_post_type_tracking_args', $post_type_activity, $post_type );
+}
+
+/**
+ * Get tracking arguments for all post types.
+ *
+ * @since 2.2.0
+ *
+ * @return array List of post types with their tracking arguments.
+ */
+function bp_activity_get_post_types_tracking_args() {
+	// Fetch all public post types.
+	$post_types = get_post_types( array( 'public' => true ), 'names' );
+
+	$post_types_tracking_args = array();
+
+	foreach ( $post_types as $post_type ) {
+		$track_post_type = bp_activity_get_post_type_tracking_args( $post_type );
+
+		if ( ! empty( $track_post_type ) ) {
+			$post_types_tracking_args[ $track_post_type->action_id ] = $track_post_type;
+		}
+
+	}
+
+	/**
+	 * Filters tracking arguments for all post types.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $post_types_tracking_args Array of post types with
+	 *                                        their tracking arguments.
+	 */
+	return apply_filters( 'bp_activity_get_post_types_tracking_args', $post_types_tracking_args );
+}
+
+/**
+ * Get all components' activity actions, sorted by their position attribute.
+ *
+ * @since 2.2.0
+ *
+ * @return object Actions ordered by their position.
+ */
+function bp_activity_get_actions() {
+	$bp = buddypress();
+
+	$post_types = bp_activity_get_post_types_tracking_args();
+
+	// Create the actions for the post types, if they haven't already been created.
+	if ( ! empty( $post_types ) ) {
+		foreach ( $post_types as $post_type ) {
+			if ( isset( $bp->activity->actions->{$post_type->component_id}->{$post_type->action_id} ) ) {
+				continue;
+			}
+
+			bp_activity_set_action(
+				$post_type->component_id,
+				$post_type->action_id,
+				$post_type->admin_filter,
+				$post_type->format_callback,
+				$post_type->front_filter,
+				$post_type->contexts,
+				$post_type->position
+			);
+		}
+	}
+
+	// Sort the actions by their position within each component.
+	foreach ( $bp->activity->actions as $component => $actions ) {
+		$actions = (array) $actions;
+		$temp = bp_sort_by_key( $actions, 'position', 'num' );
+
+		// Restore keys.
+		$bp->activity->actions->{$component} = new stdClass;
+		foreach ( $temp as $key_ordered ) {
+			$bp->activity->actions->{$component}->{$key_ordered['key']} = $key_ordered;
+		}
+	}
+
+	return $bp->activity->actions;
+}
+
+/**
+ * Retrieve the current action from a component and key.
+ *
+ * @since 1.1.0
+ *
+ * @uses apply_filters() To call the 'bp_activity_get_action' hook.
+ *
+ * @param string $component_id The unique string ID of the component.
+ * @param string $key          The action key.
+ * @return string|bool Action value if found, otherwise false.
+ */
+function bp_activity_get_action( $component_id, $key ) {
+
+	// Return false if any of the above values are not set.
+	if ( empty( $component_id ) || empty( $key ) ) {
+		return false;
+	}
+
+	$actions = bp_activity_get_actions();
+	$retval  = false;
+
+	if ( isset( $actions->{$component_id}->{$key} ) ) {
+		$retval = $actions->{$component_id}->{$key};
+	}
+
+	/**
+	 * Filters the current action by component and key.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string|bool $retval       The action key.
+	 * @param string      $component_id The unique string ID of the component.
+	 * @param string      $key          The action key.
+	 */
+	return apply_filters( 'bp_activity_get_action', $retval, $component_id, $key );
+}
+
+/**
+ * Fetch details of all registered activity types.
+ *
+ * @since 1.7.0
+ *
+ * @return array array( type => description ), ...
+ */
+function bp_activity_get_types() {
+	$actions  = array();
+
+	// Walk through the registered actions, and build an array of actions/values.
+	foreach ( bp_activity_get_actions() as $action ) {
+		$action = array_values( (array) $action );
+
+		for ( $i = 0, $i_count = count( $action ); $i < $i_count; $i++ ) {
+			$actions[ $action[$i]['key'] ] = $action[$i]['value'];
+		}
+	}
+
+	// This was a mis-named activity type from before BP 1.6.
+	unset( $actions['friends_register_activity_action'] );
+
+	/**
+	 * Filters the available activity types.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $actions Array of registered activity types.
+	 */
+	return apply_filters( 'bp_activity_get_types', $actions );
+}
+
+/** Favorites ****************************************************************/
+
+/**
+ * Get a users favorite activity stream items.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_user_meta()
+ * @uses apply_filters() To call the 'bp_activity_get_user_favorites' hook.
+ *
+ * @param int $user_id ID of the user whose favorites are being queried.
+ * @return array IDs of the user's favorite activity items.
+ */
+function bp_activity_get_user_favorites( $user_id = 0 ) {
+
+	// Fallback to logged in user if no user_id is passed.
+	if ( empty( $user_id ) ) {
+		$user_id = bp_displayed_user_id();
+	}
+
+	// Get favorites for user.
+	$favs = bp_get_user_meta( $user_id, 'bp_favorite_activities', true );
+
+	/**
+	 * Filters the favorited activity items for a specified user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $favs Array of user's favorited activity items.
+	 */
+	return apply_filters( 'bp_activity_get_user_favorites', $favs );
+}
+
+/**
+ * Add an activity stream item as a favorite for a user.
+ *
+ * @since 1.2.0
+ *
+ * @uses is_user_logged_in()
+ * @uses bp_get_user_meta()
+ * @uses bp_activity_get_meta()
+ * @uses bp_update_user_meta()
+ * @uses bp_activity_update_meta()
+ * @uses do_action() To call the 'bp_activity_add_user_favorite' hook.
+ * @uses do_action() To call the 'bp_activity_add_user_favorite_fail' hook.
+ *
+ * @param int $activity_id ID of the activity item being favorited.
+ * @param int $user_id     ID of the user favoriting the activity item.
+ * @return bool True on success, false on failure.
+ */
+function bp_activity_add_user_favorite( $activity_id, $user_id = 0 ) {
+
+	// Favorite activity stream items are for logged in users only.
+	if ( ! is_user_logged_in() ) {
+		return false;
+	}
+
+	// Fallback to logged in user if no user_id is passed.
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	$my_favs = bp_get_user_meta( $user_id, 'bp_favorite_activities', true );
+	if ( empty( $my_favs ) || ! is_array( $my_favs ) ) {
+		$my_favs = array();
+	}
+
+	// Bail if the user has already favorited this activity item.
+	if ( in_array( $activity_id, $my_favs ) ) {
+		return false;
+	}
+
+	// Add to user's favorites.
+	$my_favs[] = $activity_id;
+
+	// Update the total number of users who have favorited this activity.
+	$fav_count = bp_activity_get_meta( $activity_id, 'favorite_count' );
+	$fav_count = !empty( $fav_count ) ? (int) $fav_count + 1 : 1;
+
+	// Update user meta.
+	bp_update_user_meta( $user_id, 'bp_favorite_activities', $my_favs );
+
+	// Update activity meta counts.
+	if ( bp_activity_update_meta( $activity_id, 'favorite_count', $fav_count ) ) {
+
+		/**
+		 * Fires if bp_activity_update_meta() for favorite_count is successful and before returning a true value for success.
+		 *
+		 * @since 1.2.1
+		 *
+		 * @param int $activity_id ID of the activity item being favorited.
+		 * @param int $user_id     ID of the user doing the favoriting.
+		 */
+		do_action( 'bp_activity_add_user_favorite', $activity_id, $user_id );
+
+		// Success.
+		return true;
+
+	// Saving meta was unsuccessful for an unknown reason.
+	} else {
+
+		/**
+		 * Fires if bp_activity_update_meta() for favorite_count is unsuccessful and before returning a false value for failure.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int $activity_id ID of the activity item being favorited.
+		 * @param int $user_id     ID of the user doing the favoriting.
+		 */
+		do_action( 'bp_activity_add_user_favorite_fail', $activity_id, $user_id );
+
+		return false;
+	}
+}
+
+/**
+ * Remove an activity stream item as a favorite for a user.
+ *
+ * @since 1.2.0
+ *
+ * @uses is_user_logged_in()
+ * @uses bp_get_user_meta()
+ * @uses bp_activity_get_meta()
+ * @uses bp_activity_update_meta()
+ * @uses bp_update_user_meta()
+ * @uses do_action() To call the 'bp_activity_remove_user_favorite' hook.
+ *
+ * @param int $activity_id ID of the activity item being unfavorited.
+ * @param int $user_id     ID of the user unfavoriting the activity item.
+ * @return bool True on success, false on failure.
+ */
+function bp_activity_remove_user_favorite( $activity_id, $user_id = 0 ) {
+
+	// Favorite activity stream items are for logged in users only.
+	if ( ! is_user_logged_in() ) {
+		return false;
+	}
+
+	// Fallback to logged in user if no user_id is passed.
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	$my_favs = bp_get_user_meta( $user_id, 'bp_favorite_activities', true );
+	$my_favs = array_flip( (array) $my_favs );
+
+	// Bail if the user has not previously favorited the item.
+	if ( ! isset( $my_favs[ $activity_id ] ) ) {
+		return false;
+	}
+
+	// Remove the fav from the user's favs.
+	unset( $my_favs[$activity_id] );
+	$my_favs = array_unique( array_flip( $my_favs ) );
+
+	// Update the total number of users who have favorited this activity.
+	$fav_count = bp_activity_get_meta( $activity_id, 'favorite_count' );
+	if ( ! empty( $fav_count ) ) {
+
+		// Deduct from total favorites.
+		if ( bp_activity_update_meta( $activity_id, 'favorite_count', (int) $fav_count - 1 ) ) {
+
+			// Update users favorites.
+			if ( bp_update_user_meta( $user_id, 'bp_favorite_activities', $my_favs ) ) {
+
+				/**
+				 * Fires if bp_update_user_meta() is successful and before returning a true value for success.
+				 *
+				 * @since 1.2.1
+				 *
+				 * @param int $activity_id ID of the activity item being unfavorited.
+				 * @param int $user_id     ID of the user doing the unfavoriting.
+				 */
+				do_action( 'bp_activity_remove_user_favorite', $activity_id, $user_id );
+
+				// Success.
+				return true;
+
+			// Error updating.
+			} else {
+				return false;
+			}
+
+		// Error updating favorite count.
+		} else {
+			return false;
+		}
+
+	// Error getting favorite count.
+	} else {
+		return false;
+	}
+}
+
+/**
+ * Check whether an activity item exists with a given content string.
+ *
+ * @since 1.1.0
+ *
+ * @uses BP_Activity_Activity::check_exists_by_content() {@link BP_Activity_Activity}
+ * @uses apply_filters() To call the 'bp_activity_check_exists_by_content' hook.
+ *
+ * @param string $content The content to filter by.
+ * @return int|null The ID of the located activity item. Null if none is found.
+ */
+function bp_activity_check_exists_by_content( $content ) {
+
+	/**
+	 * Filters the results of the check for whether an activity item exists by specified content.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param BP_Activity_Activity $content_exists ID of the activity if found, else null.
+	 */
+	return apply_filters( 'bp_activity_check_exists_by_content', BP_Activity_Activity::check_exists_by_content( $content ) );
+}
+
+/**
+ * Retrieve the last time activity was updated.
+ *
+ * @since 1.0.0
+ *
+ * @uses BP_Activity_Activity::get_last_updated() {@link BP_Activity_Activity}
+ * @uses apply_filters() To call the 'bp_activity_get_last_updated' hook.
+ *
+ * @return string Date last updated.
+ */
+function bp_activity_get_last_updated() {
+
+	/**
+	 * Filters the value for the last updated time for an activity item.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param BP_Activity_Activity $last_updated Date last updated.
+	 */
+	return apply_filters( 'bp_activity_get_last_updated', BP_Activity_Activity::get_last_updated() );
+}
+
+/**
+ * Retrieve the number of favorite activity stream items a user has.
+ *
+ * @since 1.2.0
+ *
+ * @uses BP_Activity_Activity::total_favorite_count() {@link BP_Activity_Activity}
+ *
+ * @param int $user_id ID of the user whose favorite count is being requested.
+ * @return int Total favorite count for the user.
+ */
+function bp_activity_total_favorites_for_user( $user_id = 0 ) {
+
+	// Fallback on displayed user, and then logged in user.
+	if ( empty( $user_id ) ) {
+		$user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+	}
+
+	return BP_Activity_Activity::total_favorite_count( $user_id );
+}
+
+/** Meta *********************************************************************/
+
+/**
+ * Delete a meta entry from the DB for an activity stream item.
+ *
+ * @since 1.2.0
+ *
+ * @global object $wpdb WordPress database access object.
+ *
+ * @param int    $activity_id ID of the activity item whose metadata is being deleted.
+ * @param string $meta_key    Optional. The key of the metadata being deleted. If
+ *                            omitted, all metadata associated with the activity
+ *                            item will be deleted.
+ * @param string $meta_value  Optional. If present, the metadata will only be
+ *                            deleted if the meta_value matches this parameter.
+ * @param bool   $delete_all  Optional. If true, delete matching metadata entries
+ *                            for all objects, ignoring the specified object_id. Otherwise,
+ *                            only delete matching metadata entries for the specified
+ *                            activity item. Default: false.
+ * @return bool True on success, false on failure.
+ */
+function bp_activity_delete_meta( $activity_id, $meta_key = '', $meta_value = '', $delete_all = false ) {
+
+	// Legacy - if no meta_key is passed, delete all for the item.
+	if ( empty( $meta_key ) ) {
+		$all_meta = bp_activity_get_meta( $activity_id );
+		$keys     = ! empty( $all_meta ) ? array_keys( $all_meta ) : array();
+
+		// With no meta_key, ignore $delete_all.
+		$delete_all = false;
+	} else {
+		$keys = array( $meta_key );
+	}
+
+	$retval = true;
+
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	foreach ( $keys as $key ) {
+		$retval = delete_metadata( 'activity', $activity_id, $key, $meta_value, $delete_all );
+	}
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Get metadata for a given activity item.
+ *
+ * @since 1.2.0
+ *
+ * @uses apply_filters() To call the 'bp_activity_get_meta' hook.
+ *
+ * @param int    $activity_id ID of the activity item whose metadata is being requested.
+ * @param string $meta_key    Optional. If present, only the metadata matching
+ *                            that meta key will be returned. Otherwise, all metadata for the
+ *                            activity item will be fetched.
+ * @param bool   $single      Optional. If true, return only the first value of the
+ *                            specified meta_key. This parameter has no effect if meta_key is not
+ *                            specified. Default: true.
+ * @return mixed The meta value(s) being requested.
+ */
+function bp_activity_get_meta( $activity_id = 0, $meta_key = '', $single = true ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = get_metadata( 'activity', $activity_id, $meta_key, $single );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	/**
+	 * Filters the metadata for a specified activity item.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param mixed  $retval      The meta values for the activity item.
+	 * @param int    $activity_id ID of the activity item.
+	 * @param string $meta_key    Meta key for the value being requested.
+	 * @param bool   $single      Whether to return one matched meta key row or all.
+	 */
+	return apply_filters( 'bp_activity_get_meta', $retval, $activity_id, $meta_key, $single );
+}
+
+/**
+ * Update a piece of activity meta.
+ *
+ * @since 1.2.0
+ *
+ * @param int    $activity_id ID of the activity item whose metadata is being updated.
+ * @param string $meta_key    Key of the metadata being updated.
+ * @param mixed  $meta_value  Value to be set.
+ * @param mixed  $prev_value  Optional. If specified, only update existing metadata entries
+ *                            with the specified value. Otherwise, update all entries.
+ * @return bool|int Returns false on failure. On successful update of existing
+ *                  metadata, returns true. On successful creation of new metadata,
+ *                  returns the integer ID of the new metadata row.
+ */
+function bp_activity_update_meta( $activity_id, $meta_key, $meta_value, $prev_value = '' ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = update_metadata( 'activity', $activity_id, $meta_key, $meta_value, $prev_value );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Add a piece of activity metadata.
+ *
+ * @since 2.0.0
+ *
+ * @param int    $activity_id ID of the activity item.
+ * @param string $meta_key    Metadata key.
+ * @param mixed  $meta_value  Metadata value.
+ * @param bool   $unique      Optional. Whether to enforce a single metadata value for the
+ *                            given key. If true, and the object already has a value for
+ *                            the key, no change will be made. Default: false.
+ * @return int|bool The meta ID on successful update, false on failure.
+ */
+function bp_activity_add_meta( $activity_id, $meta_key, $meta_value, $unique = false ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = add_metadata( 'activity', $activity_id, $meta_key, $meta_value, $unique );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/** Clean up *****************************************************************/
+
+/**
+ * Completely remove a user's activity data.
+ *
+ * @since 1.5.0
+ *
+ * @uses is_user_logged_in()
+ * @uses bp_activity_delete()
+ * @uses bp_delete_user_meta()
+ * @uses do_action() To call the 'bp_activity_remove_data' hook.
+ * @uses do_action() To call the 'bp_activity_remove_all_user_data' hook.
+ *
+ * @param int $user_id ID of the user whose activity is being deleted.
+ * @return bool
+ */
+function bp_activity_remove_all_user_data( $user_id = 0 ) {
+
+	// Do not delete user data unless a logged in user says so.
+	if ( empty( $user_id ) || ! is_user_logged_in() ) {
+		return false;
+	}
+
+	// Clear the user's activity from the sitewide stream and clear their activity tables.
+	bp_activity_delete( array( 'user_id' => $user_id ) );
+
+	// Remove any usermeta.
+	bp_delete_user_meta( $user_id, 'bp_latest_update'       );
+	bp_delete_user_meta( $user_id, 'bp_favorite_activities' );
+
+	// Execute additional code
+	do_action( 'bp_activity_remove_data', $user_id ); // Deprecated! Do not use!
+
+	/**
+	 * Fires after the removal of all of a user's activity data.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $user_id ID of the user being deleted.
+	 */
+	do_action( 'bp_activity_remove_all_user_data', $user_id );
+}
+add_action( 'wpmu_delete_user',  'bp_activity_remove_all_user_data' );
+add_action( 'delete_user',       'bp_activity_remove_all_user_data' );
+
+/**
+ * Mark all of the user's activity as spam.
+ *
+ * @since 1.6.0
+ *
+ * @global object $wpdb WordPress database access object.
+ *
+ * @param int $user_id ID of the user whose activity is being spammed.
+ * @return bool
+ */
+function bp_activity_spam_all_user_data( $user_id = 0 ) {
+	global $wpdb;
+
+	// Do not delete user data unless a logged in user says so.
+	if ( empty( $user_id ) || ! is_user_logged_in() ) {
+		return false;
+	}
+
+	// Get all the user's activities.
+	$activities = bp_activity_get( array(
+		'display_comments' => 'stream',
+		'filter'           => array( 'user_id' => $user_id ),
+		'show_hidden'      => true
+	) );
+
+	$bp = buddypress();
+
+	// Mark each as spam.
+	foreach ( (array) $activities['activities'] as $activity ) {
+
+		// Create an activity object.
+		$activity_obj = new BP_Activity_Activity;
+		foreach ( $activity as $k => $v ) {
+			$activity_obj->$k = $v;
+		}
+
+		// Mark as spam.
+		bp_activity_mark_as_spam( $activity_obj );
+
+		/*
+		 * If Akismet is present, update the activity history meta.
+		 *
+		 * This is usually taken care of when BP_Activity_Activity::save() happens, but
+		 * as we're going to be updating all the activity statuses directly, for efficiency,
+		 * we need to update manually.
+		 */
+		if ( ! empty( $bp->activity->akismet ) ) {
+			$bp->activity->akismet->update_activity_spam_meta( $activity_obj );
+		}
+
+		// Tidy up.
+		unset( $activity_obj );
+	}
+
+	// Mark all of this user's activities as spam.
+	$wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET is_spam = 1 WHERE user_id = %d", $user_id ) );
+
+	/**
+	 * Fires after all activity data from a user has been marked as spam.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int   $user_id    ID of the user whose activity is being marked as spam.
+	 * @param array $activities Array of activity items being marked as spam.
+	 */
+	do_action( 'bp_activity_spam_all_user_data', $user_id, $activities['activities'] );
+}
+add_action( 'bp_make_spam_user', 'bp_activity_spam_all_user_data' );
+
+/**
+ * Mark all of the user's activity as ham (not spam).
+ *
+ * @since 1.6.0
+ *
+ * @global object $wpdb WordPress database access object.
+ *
+ * @param int $user_id ID of the user whose activity is being hammed.
+ * @return bool
+ */
+function bp_activity_ham_all_user_data( $user_id = 0 ) {
+	global $wpdb;
+
+	// Do not delete user data unless a logged in user says so.
+	if ( empty( $user_id ) || ! is_user_logged_in() ) {
+		return false;
+	}
+
+	// Get all the user's activities.
+	$activities = bp_activity_get( array(
+		'display_comments' => 'stream',
+		'filter'           => array( 'user_id' => $user_id ),
+		'show_hidden'      => true,
+		'spam'             => 'all'
+	) );
+
+	$bp = buddypress();
+
+	// Mark each as not spam.
+	foreach ( (array) $activities['activities'] as $activity ) {
+
+		// Create an activity object.
+		$activity_obj = new BP_Activity_Activity;
+		foreach ( $activity as $k => $v ) {
+			$activity_obj->$k = $v;
+		}
+
+		// Mark as not spam.
+		bp_activity_mark_as_ham( $activity_obj );
+
+		/*
+		 * If Akismet is present, update the activity history meta.
+		 *
+		 * This is usually taken care of when BP_Activity_Activity::save() happens, but
+		 * as we're going to be updating all the activity statuses directly, for efficiency,
+		 * we need to update manually.
+		 */
+		if ( ! empty( $bp->activity->akismet ) ) {
+			$bp->activity->akismet->update_activity_ham_meta( $activity_obj );
+		}
+
+		// Tidy up.
+		unset( $activity_obj );
+	}
+
+	// Mark all of this user's activities as not spam.
+	$wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET is_spam = 0 WHERE user_id = %d", $user_id ) );
+
+	/**
+	 * Fires after all activity data from a user has been marked as ham.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int   $user_id    ID of the user whose activity is being marked as ham.
+	 * @param array $activities Array of activity items being marked as ham.
+	 */
+	do_action( 'bp_activity_ham_all_user_data', $user_id, $activities['activities'] );
+}
+add_action( 'bp_make_ham_user', 'bp_activity_ham_all_user_data' );
+
+/**
+ * Register the activity stream actions for updates.
+ *
+ * @since 1.6.0
+ */
+function bp_activity_register_activity_actions() {
+	$bp = buddypress();
+
+	bp_activity_set_action(
+		$bp->activity->id,
+		'activity_update',
+		__( 'Posted a status update', 'buddypress' ),
+		'bp_activity_format_activity_action_activity_update',
+		__( 'Updates', 'buddypress' ),
+		array( 'activity', 'group', 'member', 'member_groups' )
+	);
+
+	bp_activity_set_action(
+		$bp->activity->id,
+		'activity_comment',
+		__( 'Replied to a status update', 'buddypress' ),
+		'bp_activity_format_activity_action_activity_comment',
+		__( 'Activity Comments', 'buddypress' )
+	);
+
+	/**
+	 * Fires at the end of the activity actions registration.
+	 *
+	 * Allows plugin authors to add their own activity actions alongside the core actions.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_activity_register_activity_actions' );
+
+	// Backpat. Don't use this.
+	do_action( 'updates_register_activity_actions' );
+}
+add_action( 'bp_register_activity_actions', 'bp_activity_register_activity_actions' );
+
+/**
+ * Generate an activity action string for an activity item.
+ *
+ * @param object $activity Activity data object.
+ * @return string|bool Returns false if no callback is found, otherwise returns
+ *                     the formatted action string.
+ */
+function bp_activity_generate_action_string( $activity ) {
+
+	// Check for valid input.
+	if ( empty( $activity->component ) || empty( $activity->type ) ) {
+		return false;
+	}
+
+	// Check for registered format callback.
+	$actions = bp_activity_get_actions();
+	if ( empty( $actions->{$activity->component}->{$activity->type}['format_callback'] ) ) {
+		return false;
+	}
+
+	// We apply the format_callback as a filter.
+	add_filter( 'bp_activity_generate_action_string', $actions->{$activity->component}->{$activity->type}['format_callback'], 10, 2 );
+
+	/**
+	 * Filters the string for the activity action being returned.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param BP_Activity_Activity $action   Action string being requested.
+	 * @param BP_Activity_Activity $activity Activity item object.
+	 */
+	$action = apply_filters( 'bp_activity_generate_action_string', $activity->action, $activity );
+
+	// Remove the filter for future activity items.
+	remove_filter( 'bp_activity_generate_action_string', $actions->{$activity->component}->{$activity->type}['format_callback'], 10, 2 );
+
+	return $action;
+}
+
+/**
+ * Format 'activity_update' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity data object.
+ * @return string $action
+ */
+function bp_activity_format_activity_action_activity_update( $action, $activity ) {
+	$action = sprintf( __( '%s posted an update', 'buddypress' ), bp_core_get_userlink( $activity->user_id ) );
+
+	/**
+	 * Filters the formatted activity action update string.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string               $action   Activity action string value.
+	 * @param BP_Activity_Activity $activity Activity item object.
+	 */
+	return apply_filters( 'bp_activity_new_update_action', $action, $activity );
+}
+
+/**
+ * Format 'activity_comment' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity data object.
+ * @return string $action
+ */
+function bp_activity_format_activity_action_activity_comment( $action, $activity ) {
+	$action = sprintf( __( '%s posted a new activity comment', 'buddypress' ), bp_core_get_userlink( $activity->user_id ) );
+
+	/**
+	 * Filters the formatted activity action comment string.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string               $action   Activity action string value.
+	 * @param BP_Activity_Activity $activity Activity item object.
+	 */
+	return apply_filters( 'bp_activity_comment_action', $action, $activity );
+}
+
+/**
+ * Format activity action strings for custom post types.
+ *
+ * @since 2.2.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity data object.
+ * @return string $action
+ */
+function bp_activity_format_activity_action_custom_post_type_post( $action, $activity ) {
+	$bp = buddypress();
+
+	// Fetch all the tracked post types once.
+	if ( empty( $bp->activity->track ) ) {
+		$bp->activity->track = bp_activity_get_post_types_tracking_args();
+	}
+
+	if ( empty( $activity->type ) || empty( $bp->activity->track[ $activity->type ] ) ) {
+		return $action;
+	}
+
+	$user_link = bp_core_get_userlink( $activity->user_id );
+	$blog_url  = get_home_url( $activity->item_id );
+
+	if ( empty( $activity->post_url ) ) {
+		$post_url = add_query_arg( 'p', $activity->secondary_item_id, trailingslashit( $blog_url ) );
+	} else {
+		$post_url = $activity->post_url;
+	}
+
+	if ( is_multisite() ) {
+		$blog_link = '<a href="' . esc_url( $blog_url ) . '">' . get_blog_option( $activity->item_id, 'blogname' ) . '</a>';
+
+		if ( ! empty( $bp->activity->track[ $activity->type ]->new_post_type_action_ms ) ) {
+			$action = sprintf( $bp->activity->track[ $activity->type ]->new_post_type_action_ms, $user_link, $post_url, $blog_link );
+		} else {
+			$action = sprintf( _x( '%1$s wrote a new <a href="%2$s">item</a>, on the site %3$s', 'Activity Custom Post Type post action', 'buddypress' ), $user_link, esc_url( $post_url ), $blog_link );
+		}
+	} else {
+		if ( ! empty( $bp->activity->track[ $activity->type ]->new_post_type_action ) ) {
+			$action = sprintf( $bp->activity->track[ $activity->type ]->new_post_type_action, $user_link, $post_url );
+		} else {
+			$action = sprintf( _x( '%1$s wrote a new <a href="%2$s">item</a>', 'Activity Custom Post Type post action', 'buddypress' ), $user_link, esc_url( $post_url ) );
+		}
+	}
+
+	/**
+	 * Filters the formatted custom post type activity post action string.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string               $action   Activity action string value.
+	 * @param BP_Activity_Activity $activity Activity item object.
+	 */
+	return apply_filters( 'bp_activity_custom_post_type_post_action', $action, $activity );
+}
+
+/*
+ * Business functions are where all the magic happens in BuddyPress. They will
+ * handle the actual saving or manipulation of information. Usually they will
+ * hand off to a database class for data access, then return
+ * true or false on success or failure.
+ */
+
+/**
+ * Retrieve an activity or activities.
+ *
+ * The bp_activity_get() function shares all arguments with BP_Activity_Activity::get().
+ * The following is a list of bp_activity_get() parameters that have different
+ * default values from BP_Activity_Activity::get() (value in parentheses is
+ * the default for the bp_activity_get()).
+ *   - 'per_page' (false)
+ *
+ * @since 1.2.0
+ * @since 2.4.0 Introduced the `$fields` parameter.
+ *
+ * @see BP_Activity_Activity::get() For more information on accepted arguments
+ *      and the format of the returned value.
+ * @uses wp_parse_args()
+ * @uses wp_cache_get()
+ * @uses wp_cache_set()
+ * @uses BP_Activity_Activity::get() {@link BP_Activity_Activity}
+ * @uses apply_filters_ref_array() To call the 'bp_activity_get' hook.
+ *
+ * @param array|string $args See BP_Activity_Activity::get() for description.
+ * @return array $activity See BP_Activity_Activity::get() for description.
+ */
+function bp_activity_get( $args = '' ) {
+
+	$r = bp_parse_args( $args, array(
+		'max'               => false,        // Maximum number of results to return
+		'fields'            => 'all',
+		'page'              => 1,            // page 1 without a per_page will result in no pagination.
+		'per_page'          => false,        // results per page
+		'sort'              => 'DESC',       // sort ASC or DESC
+		'display_comments'  => false,        // false for no comments. 'stream' for within stream display, 'threaded' for below each activity item
+
+		'search_terms'      => false,        // Pass search terms as a string
+		'meta_query'        => false,        // Filter by activity meta. See WP_Meta_Query for format
+		'date_query'        => false,        // Filter by date. See first parameter of WP_Date_Query for format
+		'filter_query'      => false,
+		'show_hidden'       => false,        // Show activity items that are hidden site-wide?
+		'exclude'           => false,        // Comma-separated list of activity IDs to exclude.
+		'in'                => false,        // Comma-separated list or array of activity IDs to which you
+		                                     // want to limit the query.
+		'spam'              => 'ham_only',   // 'ham_only' (default), 'spam_only' or 'all'.
+		'update_meta_cache' => true,
+		'count_total'       => false,
+		'scope'             => false,
+
+		/**
+		 * Pass filters as an array -- all filter items can be multiple values comma separated:
+		 * array(
+		 *     'user_id'      => false, // User ID to filter on.
+		 *     'object'       => false, // Object to filter on e.g. groups, profile, status, friends.
+		 *     'action'       => false, // Action to filter on e.g. activity_update, profile_updated.
+		 *     'primary_id'   => false, // Object ID to filter on e.g. a group_id or forum_id or blog_id etc.
+		 *     'secondary_id' => false, // Secondary object ID to filter on e.g. a post_id.
+		 * );
+		 */
+		'filter' => array()
+	) );
+
+	// Attempt to return a cached copy of the first page of sitewide activity.
+	if ( ( 1 === (int) $r['page'] ) && empty( $r['max'] ) && ( 'all' === $r['fields'] ) && empty( $r['search_terms'] ) && empty( $r['meta_query'] ) && empty( $r['date_query'] ) && empty( $r['filter_query'] ) && empty( $r['filter'] ) && empty( $r['scope'] )&& empty( $r['exclude'] ) && empty( $r['in'] ) && ( 'DESC' === $r['sort'] ) && empty( $r['exclude'] ) && ( 'ham_only' === $r['spam'] ) ) {
+
+		$activity = wp_cache_get( 'bp_activity_sitewide_front', 'bp' );
+		if ( false === $activity ) {
+
+			$activity = BP_Activity_Activity::get( array(
+				'page'              => $r['page'],
+				'per_page'          => $r['per_page'],
+				'max'               => $r['max'],
+				'fields'            => $r['fields'],
+				'sort'              => $r['sort'],
+				'search_terms'      => $r['search_terms'],
+				'meta_query'        => $r['meta_query'],
+				'date_query'        => $r['date_query'],
+				'filter_query'      => $r['filter_query'],
+				'filter'            => $r['filter'],
+				'scope'             => $r['scope'],
+				'display_comments'  => $r['display_comments'],
+				'show_hidden'       => $r['show_hidden'],
+				'spam'              => $r['spam'],
+				'update_meta_cache' => $r['update_meta_cache'],
+				'count_total'       => $r['count_total'],
+			) );
+
+			wp_cache_set( 'bp_activity_sitewide_front', $activity, 'bp' );
+		}
+
+	} else {
+		$activity = BP_Activity_Activity::get( array(
+			'page'             => $r['page'],
+			'per_page'         => $r['per_page'],
+			'max'              => $r['max'],
+			'sort'             => $r['sort'],
+			'search_terms'     => $r['search_terms'],
+			'meta_query'       => $r['meta_query'],
+			'date_query'       => $r['date_query'],
+			'filter_query'     => $r['filter_query'],
+			'filter'           => $r['filter'],
+			'scope'            => $r['scope'],
+			'display_comments' => $r['display_comments'],
+			'show_hidden'      => $r['show_hidden'],
+			'exclude'          => $r['exclude'],
+			'in'               => $r['in'],
+			'spam'             => $r['spam'],
+			'count_total'      => $r['count_total'],
+		) );
+	}
+
+	/**
+	 * Filters the requested activity item(s).
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param BP_Activity_Activity $activity Requested activity object.
+	 * @param array                $r        Arguments used for the activity query.
+	 */
+	return apply_filters_ref_array( 'bp_activity_get', array( &$activity, &$r ) );
+}
+
+/**
+ * Fetch specific activity items.
+ *
+ * @since 1.2.0
+ *
+ * @see BP_Activity_Activity::get() For more information on accepted arguments.
+ * @uses wp_parse_args()
+ * @uses apply_filters() To call the 'bp_activity_get_specific' hook.
+ * @uses BP_Activity_Activity::get() {@link BP_Activity_Activity}
+ *
+ * @param array|string $args {
+ *     All arguments and defaults are shared with BP_Activity_Activity::get(),
+ *     except for the following:
+ *     @type string|int|array Single activity ID, comma-separated list of IDs,
+ *                            or array of IDs.
+ * }
+ * @return array $activity See BP_Activity_Activity::get() for description.
+ */
+function bp_activity_get_specific( $args = '' ) {
+
+	$r = bp_parse_args( $args, array(
+		'activity_ids'      => false,      // A single activity_id or array of IDs.
+		'display_comments'  => false,      // True or false to display threaded comments for these specific activity items.
+		'max'               => false,      // Maximum number of results to return.
+		'page'              => 1,          // Page 1 without a per_page will result in no pagination.
+		'per_page'          => false,      // Results per page.
+		'show_hidden'       => true,       // When fetching specific items, show all.
+		'sort'              => 'DESC',     // Sort ASC or DESC
+		'spam'              => 'ham_only', // Retrieve items marked as spam.
+		'update_meta_cache' => true,
+	) );
+
+	$get_args = array(
+		'display_comments'  => $r['display_comments'],
+		'in'                => $r['activity_ids'],
+		'max'               => $r['max'],
+		'page'              => $r['page'],
+		'per_page'          => $r['per_page'],
+		'show_hidden'       => $r['show_hidden'],
+		'sort'              => $r['sort'],
+		'spam'              => $r['spam'],
+		'update_meta_cache' => $r['update_meta_cache'],
+	);
+
+	/**
+	 * Filters the requested specific activity item.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param BP_Activity_Activity $activity Requested activity object.
+	 * @param array                $args     Original passed in arguments.
+	 * @param array                $get_args Constructed arguments used with request.
+	 */
+	return apply_filters( 'bp_activity_get_specific', BP_Activity_Activity::get( $get_args ), $args, $get_args );
+}
+
+/**
+ * Add an activity item.
+ *
+ * @since 1.1.0
+ *
+ * @uses wp_parse_args()
+ * @uses BP_Activity_Activity::save() {@link BP_Activity_Activity}
+ * @uses BP_Activity_Activity::rebuild_activity_comment_tree() {@link BP_Activity_Activity}
+ * @uses wp_cache_delete()
+ * @uses do_action() To call the 'bp_activity_add' hook.
+ *
+ * @param array|string $args {
+ *     An array of arguments.
+ *     @type int|bool $id                Pass an activity ID to update an existing item, or
+ *                                       false to create a new item. Default: false.
+ *     @type string   $action            Optional. The activity action/description, typically
+ *                                       something like "Joe posted an update". Values passed to this param
+ *                                       will be stored in the database and used as a fallback for when the
+ *                                       activity item's format_callback cannot be found (eg, when the
+ *                                       component is disabled). As long as you have registered a
+ *                                       format_callback for your $type, it is unnecessary to include this
+ *                                       argument - BP will generate it automatically.
+ *                                       See {@link bp_activity_set_action()}.
+ *     @type string   $content           Optional. The content of the activity item.
+ *     @type string   $component         The unique name of the component associated with
+ *                                       the activity item - 'groups', 'profile', etc.
+ *     @type string   $type              The specific activity type, used for directory
+ *                                       filtering. 'new_blog_post', 'activity_update', etc.
+ *     @type string   $primary_link      Optional. The URL for this item, as used in
+ *                                       RSS feeds. Defaults to the URL for this activity
+ *                                       item's permalink page.
+ *     @type int|bool $user_id           Optional. The ID of the user associated with the activity
+ *                                       item. May be set to false or 0 if the item is not related
+ *                                       to any user. Default: the ID of the currently logged-in user.
+ *     @type int      $item_id           Optional. The ID of the associated item.
+ *     @type int      $secondary_item_id Optional. The ID of a secondary associated item.
+ *     @type string   $date_recorded     Optional. The GMT time, in Y-m-d h:i:s format, when
+ *                                       the item was recorded. Defaults to the current time.
+ *     @type bool     $hide_sitewide     Should the item be hidden on sitewide streams?
+ *                                       Default: false.
+ *     @type bool     $is_spam           Should the item be marked as spam? Default: false.
+ * }
+ * @return int|bool The ID of the activity on success. False on error.
+ */
+function bp_activity_add( $args = '' ) {
+
+	$r = bp_parse_args( $args, array(
+		'id'                => false,                  // Pass an existing activity ID to update an existing entry.
+		'action'            => '',                     // The activity action - e.g. "Jon Doe posted an update"
+		'content'           => '',                     // Optional: The content of the activity item e.g. "BuddyPress is awesome guys!"
+		'component'         => false,                  // The name/ID of the component e.g. groups, profile, mycomponent.
+		'type'              => false,                  // The activity type e.g. activity_update, profile_updated.
+		'primary_link'      => '',                     // Optional: The primary URL for this item in RSS feeds (defaults to activity permalink).
+		'user_id'           => bp_loggedin_user_id(),  // Optional: The user to record the activity for, can be false if this activity is not for a user.
+		'item_id'           => false,                  // Optional: The ID of the specific item being recorded, e.g. a blog_id.
+		'secondary_item_id' => false,                  // Optional: A second ID used to further filter e.g. a comment_id.
+		'recorded_time'     => bp_core_current_time(), // The GMT time that this activity was recorded.
+		'hide_sitewide'     => false,                  // Should this be hidden on the sitewide activity stream?
+		'is_spam'           => false,                  // Is this activity item to be marked as spam?
+	), 'activity_add' );
+
+	// Make sure we are backwards compatible.
+	if ( empty( $r['component'] ) && !empty( $r['component_name'] ) ) {
+		$r['component'] = $r['component_name'];
+	}
+
+	if ( empty( $r['type'] ) && !empty( $r['component_action'] ) ) {
+		$r['type'] = $r['component_action'];
+	}
+
+	// Setup activity to be added.
+	$activity                    = new BP_Activity_Activity( $r['id'] );
+	$activity->user_id           = $r['user_id'];
+	$activity->component         = $r['component'];
+	$activity->type              = $r['type'];
+	$activity->content           = $r['content'];
+	$activity->primary_link      = $r['primary_link'];
+	$activity->item_id           = $r['item_id'];
+	$activity->secondary_item_id = $r['secondary_item_id'];
+	$activity->date_recorded     = $r['recorded_time'];
+	$activity->hide_sitewide     = $r['hide_sitewide'];
+	$activity->is_spam           = $r['is_spam'];
+	$activity->action            = ! empty( $r['action'] )
+										? $r['action']
+										: bp_activity_generate_action_string( $activity );
+
+	if ( ! $activity->save() ) {
+		return false;
+	}
+
+	// If this is an activity comment, rebuild the tree.
+	if ( 'activity_comment' === $activity->type ) {
+		// Also clear the comment cache for the parent activity ID.
+		wp_cache_delete( $activity->item_id, 'bp_activity_comments' );
+
+		BP_Activity_Activity::rebuild_activity_comment_tree( $activity->item_id );
+	}
+
+	wp_cache_delete( 'bp_activity_sitewide_front', 'bp' );
+
+	/**
+	 * Fires at the end of the execution of adding a new activity item, before returning the new activity item ID.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param array $r Array of parsed arguments for the activity item being added.
+	 */
+	do_action( 'bp_activity_add', $r );
+
+	return $activity->id;
+}
+
+/**
+ * Post an activity update.
+ *
+ * @since 1.2.0
+ *
+ * @uses wp_parse_args()
+ * @uses bp_is_user_inactive()
+ * @uses bp_core_get_userlink()
+ * @uses bp_activity_add()
+ * @uses apply_filters() To call the 'bp_activity_new_update_action' hook.
+ * @uses apply_filters() To call the 'bp_activity_new_update_content' hook.
+ * @uses apply_filters() To call the 'bp_activity_new_update_primary_link' hook.
+ * @uses bp_update_user_meta()
+ * @uses wp_filter_kses()
+ * @uses do_action() To call the 'bp_activity_posted_update' hook.
+ *
+ * @param array|string $args {
+ *     @type string $content The content of the activity update.
+ *     @type int    $user_id Optional. Defaults to the logged-in user.
+ * }
+ * @return int $activity_id The activity id.
+ */
+function bp_activity_post_update( $args = '' ) {
+
+	$r = wp_parse_args( $args, array(
+		'content' => false,
+		'user_id' => bp_loggedin_user_id()
+	) );
+
+	if ( empty( $r['content'] ) || !strlen( trim( $r['content'] ) ) ) {
+		return false;
+	}
+
+	if ( bp_is_user_inactive( $r['user_id'] ) ) {
+		return false;
+	}
+
+	// Record this on the user's profile.
+	$activity_content = $r['content'];
+	$primary_link     = bp_core_get_userlink( $r['user_id'], false, true );
+
+	/**
+	 * Filters the new activity content for current activity item.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $activity_content Activity content posted by user.
+	 */
+	$add_content = apply_filters( 'bp_activity_new_update_content', $activity_content );
+
+	/**
+	 * Filters the activity primary link for current activity item.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $primary_link Link to the profile for the user who posted the activity.
+	 */
+	$add_primary_link = apply_filters( 'bp_activity_new_update_primary_link', $primary_link );
+
+	// Now write the values.
+	$activity_id = bp_activity_add( array(
+		'user_id'      => $r['user_id'],
+		'content'      => $add_content,
+		'primary_link' => $add_primary_link,
+		'component'    => buddypress()->activity->id,
+		'type'         => 'activity_update',
+	) );
+
+	/**
+	 * Filters the latest update content for the activity item.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $r                Content of the activity update.
+	 * @param string $activity_content Content of the activity update.
+	 */
+	$activity_content = apply_filters( 'bp_activity_latest_update_content', $r['content'], $activity_content );
+
+	// Add this update to the "latest update" usermeta so it can be fetched anywhere.
+	bp_update_user_meta( bp_loggedin_user_id(), 'bp_latest_update', array(
+		'id'      => $activity_id,
+		'content' => $activity_content
+	) );
+
+	/**
+	 * Fires at the end of an activity post update, before returning the updated activity item ID.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $content     Content of the activity post update.
+	 * @param int    $user_id     ID of the user posting the activity update.
+	 * @param int    $activity_id ID of the activity item being updated.
+	 */
+	do_action( 'bp_activity_posted_update', $r['content'], $r['user_id'], $activity_id );
+
+	return $activity_id;
+}
+
+/**
+ * Create an activity item for a newly published post type post.
+ *
+ * @since 2.2.0
+ *
+ * @param int     $post_id ID of the new post.
+ * @param WP_Post $post    Post object.
+ * @param int     $user_id ID of the post author.
+ * @return int|bool The ID of the activity on success. False on error.
+ */
+function bp_activity_post_type_publish( $post_id = 0, $post = null, $user_id = 0 ) {
+
+	if ( ! is_a( $post, 'WP_Post' ) ) {
+		return;
+	}
+
+	// Get the post type tracking args.
+	$activity_post_object = bp_activity_get_post_type_tracking_args( $post->post_type );
+
+	if ( 'publish' != $post->post_status || ! empty( $post->post_password ) || empty( $activity_post_object->action_id ) ) {
+		return;
+	}
+
+	if ( empty( $post_id ) ) {
+		$post_id = $post->ID;
+	}
+
+	$blog_id = get_current_blog_id();
+
+	if ( empty( $user_id ) ) {
+		$user_id = (int) $post->post_author;
+	}
+
+	// Bail if an activity item already exists for this post.
+	$existing = bp_activity_get( array(
+		'filter' => array(
+			'action'       => $activity_post_object->action_id,
+			'primary_id'   => $blog_id,
+			'secondary_id' => $post_id,
+		)
+	) );
+
+	if ( ! empty( $existing['activities'] ) ) {
+		return;
+	}
+
+	/**
+	 * Filters whether or not to post the activity.
+	 *
+	 * This is a variable filter, dependent on the post type,
+	 * that lets components or plugins bail early if needed.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param bool $value   Whether or not to continue.
+	 * @param int  $blog_id ID of the current site.
+	 * @param int  $post_id ID of the current post being published.
+	 * @param int  $user_id ID of the current user or post author.
+	 */
+	if ( false === apply_filters( "bp_activity_{$post->post_type}_pre_publish", true, $blog_id, $post_id, $user_id ) ) {
+		return;
+	}
+
+	// Record this in activity streams.
+	$blog_url = get_home_url( $blog_id );
+	$post_url = add_query_arg(
+		'p',
+		$post_id,
+		trailingslashit( $blog_url )
+	);
+
+	// Backward compatibility filters for the 'blogs' component.
+	if ( 'blogs' == $activity_post_object->component_id )  {
+		$activity_content      = apply_filters( 'bp_blogs_activity_new_post_content', $post->post_content, $post, $post_url, $post->post_type );
+		$activity_primary_link = apply_filters( 'bp_blogs_activity_new_post_primary_link', $post_url, $post_id, $post->post_type );
+	} else {
+		$activity_content      = $post->post_content;
+		$activity_primary_link = $post_url;
+	}
+
+	$activity_args = array(
+		'user_id'           => $user_id,
+		'content'           => $activity_content,
+		'primary_link'      => $activity_primary_link,
+		'component'         => $activity_post_object->component_id,
+		'type'              => $activity_post_object->action_id,
+		'item_id'           => $blog_id,
+		'secondary_item_id' => $post_id,
+		'recorded_time'     => $post->post_date_gmt,
+	);
+
+	if ( ! empty( $activity_args['content'] ) ) {
+		// Create the excerpt.
+		$activity_summary = bp_activity_create_summary( $activity_args['content'], $activity_args );
+
+		// Backward compatibility filter for blog posts.
+		if ( 'blogs' == $activity_post_object->component_id )  {
+			$activity_args['content'] = apply_filters( 'bp_blogs_record_activity_content', $activity_summary, $activity_args['content'], $activity_args, $post->post_type );
+		} else {
+			$activity_args['content'] = $activity_summary;
+		}
+	}
+
+	// Set up the action by using the format functions.
+	$action_args = array_merge( $activity_args, array(
+		'post_title' => $post->post_title,
+		'post_url'   => $post_url,
+	) );
+
+	$activity_args['action'] = call_user_func_array( $activity_post_object->format_callback, array( '', (object) $action_args ) );
+
+	// Make sure the action is set.
+	if ( empty( $activity_args['action'] ) ) {
+		return;
+	} else {
+		// Backward compatibility filter for the blogs component.
+		if ( 'blogs' == $activity_post_object->component_id )  {
+			$activity_args['action'] = apply_filters( 'bp_blogs_record_activity_action', $activity_args['action'] );
+		}
+	}
+
+	$activity_id = bp_activity_add( $activity_args );
+
+	/**
+	 * Fires after the publishing of an activity item for a newly published post type post.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param int     $activity_id   ID of the newly published activity item.
+	 * @param WP_Post $post          Post object.
+	 * @param array   $activity_args Array of activity arguments.
+	 */
+	do_action( 'bp_activity_post_type_published', $activity_id, $post, $activity_args );
+
+	return $activity_id;
+}
+
+/**
+ * Update the activity item for a custom post type entry.
+ *
+ * @since 2.2.0
+ *
+ * @param WP_Post $post Post item.
+ * @return bool True on success, false on failure.
+ */
+function bp_activity_post_type_update( $post = null ) {
+
+	if ( ! is_a( $post, 'WP_Post' ) ) {
+		return;
+	}
+
+	// Get the post type tracking args.
+	$activity_post_object = bp_activity_get_post_type_tracking_args( $post->post_type );
+
+	if ( empty( $activity_post_object->action_id ) ) {
+		return;
+	}
+
+	$activity_id = bp_activity_get_activity_id( array(
+		'component'         => $activity_post_object->component_id,
+		'item_id'           => get_current_blog_id(),
+		'secondary_item_id' => $post->ID,
+		'type'              => $activity_post_object->action_id,
+	) );
+
+	// Activity ID doesn't exist, so stop!
+	if ( empty( $activity_id ) ) {
+		return;
+	}
+
+	// Delete the activity if the post was updated with a password.
+	if ( ! empty( $post->post_password ) ) {
+		bp_activity_delete( array( 'id' => $activity_id ) );
+	}
+
+	// Update the activity entry.
+	$activity = new BP_Activity_Activity( $activity_id );
+
+	if ( ! empty( $post->post_content ) ) {
+		$activity_summary = bp_activity_create_summary( $post->post_content, (array) $activity );
+
+		// Backward compatibility filter for the blogs component.
+		if ( 'blogs' == $activity_post_object->component_id ) {
+			$activity->content = apply_filters( 'bp_blogs_record_activity_content', $activity_summary, $post->post_content, (array) $activity, $post->post_type );
+		} else {
+			$activity->content = $activity_summary;
+		}
+	}
+
+	// Save the updated activity.
+	$updated = $activity->save();
+
+	/**
+	 * Fires after the updating of an activity item for a custom post type entry.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param WP_Post              $post     Post object.
+	 * @param BP_Activity_Activity $activity Activity object.
+	 */
+	do_action( 'bp_activity_post_type_updated', $post, $activity );
+
+	return $updated;
+}
+
+/**
+ * Unpublish an activity for the custom post type.
+ *
+ * @since 2.2.0
+ *
+ * @param  int     $post_id ID of the post being unpublished.
+ * @param  WP_Post $post    Post object.
+ * @return bool True on success, false on failure.
+ */
+function bp_activity_post_type_unpublish( $post_id = 0, $post = null ) {
+
+	if ( ! is_a( $post, 'WP_Post' ) ) {
+		return;
+	}
+
+	// Get the post type tracking args.
+	$activity_post_object = bp_activity_get_post_type_tracking_args( $post->post_type );
+
+	if ( empty( $activity_post_object->action_id ) ) {
+		return;
+	}
+
+	if ( empty( $post_id ) ) {
+		$post_id = $post->ID;
+	}
+
+	$delete_activity_args = array(
+		'item_id'           => get_current_blog_id(),
+		'secondary_item_id' => $post_id,
+		'component'         => $activity_post_object->component_id,
+		'type'              => $activity_post_object->action_id,
+		'user_id'           => false,
+	);
+
+	$deleted = bp_activity_delete_by_item_id( $delete_activity_args );
+
+	/**
+	 * Fires after the unpublishing for the custom post type.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array   $delete_activity_args Array of arguments for activity deletion.
+	 * @param WP_Post $post                 Post object.
+	 * @param bool    $activity             Whether or not the activity was successfully deleted.
+	 */
+	do_action( 'bp_activity_post_type_unpublished', $delete_activity_args, $post, $deleted );
+
+	return $deleted;
+}
+
+/**
+ * Add an activity comment.
+ *
+ * @since 1.2.0
+ *
+ * @uses wp_parse_args()
+ * @uses bp_activity_add()
+ * @uses apply_filters() To call the 'bp_activity_comment_action' hook.
+ * @uses apply_filters() To call the 'bp_activity_comment_content' hook.
+ * @uses wp_cache_delete()
+ * @uses do_action() To call the 'bp_activity_comment_posted' hook.
+ *
+ * @param array|string $args {
+ *     @type int    $id          Optional. Pass an ID to update an existing comment.
+ *     @type string $content     The content of the comment.
+ *     @type int    $user_id     Optional. The ID of the user making the comment.
+ *                               Defaults to the ID of the logged-in user.
+ *     @type int    $activity_id The ID of the "root" activity item, ie the oldest
+ *                               ancestor of the comment.
+ *     @type int    $parent_id   Optional. The ID of the parent activity item, ie the item to
+ *                               which the comment is an immediate reply. If not provided,
+ *                               this value defaults to the $activity_id.
+ * }
+ * @return int|bool The ID of the comment on success, otherwise false.
+ */
+function bp_activity_new_comment( $args = '' ) {
+	$bp       = buddypress();
+	$errors   = new WP_Error();
+	$feedback = __( 'There was an error posting your reply. Please try again.', 'buddypress' );
+
+	if ( empty( $bp->activity->errors ) ) {
+		$bp->activity->errors = array();
+	}
+
+	$r = wp_parse_args( $args, array(
+		'id'          => false,
+		'content'     => false,
+		'user_id'     => bp_loggedin_user_id(),
+		'activity_id' => false, // ID of the root activity item.
+		'parent_id'   => false  // ID of a parent comment (optional).
+	) );
+
+	// Bail if missing necessary data.
+	if ( empty( $r['content'] ) || empty( $r['user_id'] ) || empty( $r['activity_id'] ) ) {
+		$errors->add( 'missing_data', $feedback );
+		$bp->activity->errors['new_comment'] = $errors;
+
+		return false;
+	}
+
+	// Maybe set current activity ID as the parent.
+	if ( empty( $r['parent_id'] ) ) {
+		$r['parent_id'] = $r['activity_id'];
+	}
+
+	$activity_id = $r['activity_id'];
+
+	// Get the parent activity.
+	$activity  = new BP_Activity_Activity( $activity_id );
+
+	// Bail if the parent activity does not exist.
+	if ( empty( $activity->date_recorded ) ) {
+		$errors->add( 'missing_activity', __( 'Sorry, the item you are replying to no longer exists.', 'buddypress' ) );
+		$bp->activity->errors['new_comment'] = $errors;
+
+		return false;
+	}
+
+	// Check to see if the parent activity is hidden, and if so, hide this comment publicly.
+	$is_hidden = ( (int) $activity->hide_sitewide ) ? 1 : 0;
+
+	/**
+	 * Filters the content of a new comment.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $r Content for the newly posted comment.
+	 */
+	$comment_content = apply_filters( 'bp_activity_comment_content', $r['content'] );
+
+	// Insert the activity comment.
+	$comment_id = bp_activity_add( array(
+		'id'                => $r['id'],
+		'content'           => $comment_content,
+		'component'         => buddypress()->activity->id,
+		'type'              => 'activity_comment',
+		'user_id'           => $r['user_id'],
+		'item_id'           => $activity_id,
+		'secondary_item_id' => $r['parent_id'],
+		'hide_sitewide'     => $is_hidden
+	) );
+
+	// Comment caches are stored only with the top-level item.
+	wp_cache_delete( $activity_id, 'bp_activity_comments' );
+
+	// Walk the tree to clear caches for all parent items.
+	$clear_id = $r['parent_id'];
+	while ( $clear_id != $activity_id ) {
+		$clear_object = new BP_Activity_Activity( $clear_id );
+		wp_cache_delete( $clear_id, 'bp_activity' );
+		$clear_id = intval( $clear_object->secondary_item_id );
+	}
+	wp_cache_delete( $activity_id, 'bp_activity' );
+
+	/**
+	 * Fires near the end of an activity comment posting, before the returning of the comment ID.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param int                  $comment_id ID of the newly posted activity comment.
+	 * @param array                $r          Array of parsed comment arguments.
+	 * @param BP_Activity_Activity $activity   Activity item being commented on.
+	 */
+	do_action( 'bp_activity_comment_posted', $comment_id, $r, $activity );
+
+	if ( empty( $comment_id ) ) {
+		$errors->add( 'comment_failed', $feedback );
+		$bp->activity->errors['new_comment'] = $errors;
+	}
+
+	return $comment_id;
+}
+
+/**
+ * Fetch the activity_id for an existing activity entry in the DB.
+ *
+ * @since 1.2.0
+ *
+ * @see BP_Activity_Activity::get() For more information on accepted arguments.
+ * @uses wp_parse_args()
+ * @uses apply_filters() To call the 'bp_activity_get_activity_id' hook.
+ * @uses BP_Activity_Activity::save() {@link BP_Activity_Activity}
+ *
+ * @param array|string $args See BP_Activity_Activity::get() for description.
+ * @return int $activity_id The ID of the activity item found.
+ */
+function bp_activity_get_activity_id( $args = '' ) {
+
+	$r = bp_parse_args( $args, array(
+		'user_id'           => false,
+		'component'         => false,
+		'type'              => false,
+		'item_id'           => false,
+		'secondary_item_id' => false,
+		'action'            => false,
+		'content'           => false,
+		'date_recorded'     => false,
+	) );
+
+	/**
+	 * Filters the activity ID being requested.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param BP_Activity_Activity $value ID returned by BP_Activity_Activity get_id() method with provided arguments.
+	 */
+	return apply_filters( 'bp_activity_get_activity_id', BP_Activity_Activity::get_id(
+		$r['user_id'],
+		$r['component'],
+		$r['type'],
+		$r['item_id'],
+		$r['secondary_item_id'],
+		$r['action'],
+		$r['content'],
+		$r['date_recorded']
+	) );
+}
+
+/**
+ * Delete activity item(s).
+ *
+ * If you're looking to hook into one action that provides the ID(s) of
+ * the activity/activities deleted, then use:
+ *
+ * add_action( 'bp_activity_deleted_activities', 'my_function' );
+ *
+ * The action passes one parameter that is a single activity ID or an
+ * array of activity IDs depending on the number deleted.
+ *
+ * If you are deleting an activity comment please use bp_activity_delete_comment();
+ *
+ * @since 1.0.0
+ *
+ * @see BP_Activity_Activity::get() For more information on accepted arguments.
+ * @uses wp_parse_args()
+ * @uses bp_activity_adjust_mention_count()
+ * @uses BP_Activity_Activity::delete() {@link BP_Activity_Activity}
+ * @uses do_action() To call the 'bp_before_activity_delete' hook.
+ * @uses bp_get_user_meta()
+ * @uses bp_delete_user_meta()
+ * @uses do_action() To call the 'bp_activity_delete' hook.
+ * @uses do_action() To call the 'bp_activity_deleted_activities' hook.
+ * @uses wp_cache_delete()
+ *
+ * @param array|string $args To delete specific activity items, use
+ *                           $args = array( 'id' => $ids ); Otherwise, to use
+ *                           filters for item deletion, the argument format is
+ *                           the same as BP_Activity_Activity::get().
+ *                           See that method for a description.
+ * @return bool True on success, false on failure.
+ */
+function bp_activity_delete( $args = '' ) {
+
+	// Pass one or more the of following variables to delete by those variables.
+	$args = bp_parse_args( $args, array(
+		'id'                => false,
+		'action'            => false,
+		'content'           => false,
+		'component'         => false,
+		'type'              => false,
+		'primary_link'      => false,
+		'user_id'           => false,
+		'item_id'           => false,
+		'secondary_item_id' => false,
+		'date_recorded'     => false,
+		'hide_sitewide'     => false
+	) );
+
+	/**
+	 * Fires before an activity item proceeds to be deleted.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $args Array of arguments to be used with the activity deletion.
+	 */
+	do_action( 'bp_before_activity_delete', $args );
+
+	// Adjust the new mention count of any mentioned member.
+	bp_activity_adjust_mention_count( $args['id'], 'delete' );
+
+	$activity_ids_deleted = BP_Activity_Activity::delete( $args );
+	if ( empty( $activity_ids_deleted ) ) {
+		return false;
+	}
+
+	// Check if the user's latest update has been deleted.
+	$user_id = empty( $args['user_id'] )
+		? bp_loggedin_user_id()
+		: $args['user_id'];
+
+	$latest_update = bp_get_user_meta( $user_id, 'bp_latest_update', true );
+	if ( !empty( $latest_update ) ) {
+		if ( in_array( (int) $latest_update['id'], (array) $activity_ids_deleted ) ) {
+			bp_delete_user_meta( $user_id, 'bp_latest_update' );
+		}
+	}
+
+	/**
+	 * Fires after the activity item has been deleted.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param array $args Array of arguments used with the activity deletion.
+	 */
+	do_action( 'bp_activity_delete', $args );
+
+	/**
+	 * Fires after the activity item has been deleted.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $activity_ids_deleted Array of affected activity item IDs.
+	 */
+	do_action( 'bp_activity_deleted_activities', $activity_ids_deleted );
+
+	wp_cache_delete( 'bp_activity_sitewide_front', 'bp' );
+
+	return true;
+}
+
+	/**
+	 * Delete an activity item by activity id.
+	 *
+	 * You should use bp_activity_delete() instead.
+	 *
+	 * @since 1.1.0
+	 * @deprecated 1.2.0
+	 *
+	 * @uses wp_parse_args()
+	 * @uses bp_activity_delete()
+	 *
+	 * @param array|string $args See BP_Activity_Activity::get for a
+	 *                           description of accepted arguments.
+	 * @return bool True on success, false on failure.
+	 */
+	function bp_activity_delete_by_item_id( $args = '' ) {
+
+		$r = bp_parse_args( $args, array(
+			'item_id'           => false,
+			'component'         => false,
+			'type'              => false,
+			'user_id'           => false,
+			'secondary_item_id' => false
+		) );
+
+		return bp_activity_delete( $r );
+	}
+
+	/**
+	 * Delete an activity item by activity id.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @uses bp_activity_delete()
+	 *
+	 * @param int $activity_id ID of the activity item to be deleted.
+	 * @return bool True on success, false on failure.
+	 */
+	function bp_activity_delete_by_activity_id( $activity_id ) {
+		return bp_activity_delete( array( 'id' => $activity_id ) );
+	}
+
+	/**
+	 * Delete an activity item by its content.
+	 *
+	 * You should use bp_activity_delete() instead.
+	 *
+	 * @since 1.1.0
+	 * @deprecated 1.2.0
+	 *
+	 * @uses bp_activity_delete()
+	 *
+	 * @param int    $user_id   The user id.
+	 * @param string $content   The activity id.
+	 * @param string $component The activity component.
+	 * @param string $type      The activity type.
+	 * @return bool True on success, false on failure.
+	 */
+	function bp_activity_delete_by_content( $user_id, $content, $component, $type ) {
+		return bp_activity_delete( array(
+			'user_id'   => $user_id,
+			'content'   => $content,
+			'component' => $component,
+			'type'      => $type
+		) );
+	}
+
+	/**
+	 * Delete a user's activity for a component.
+	 *
+	 * You should use bp_activity_delete() instead.
+	 *
+	 * @since 1.1.0
+	 * @deprecated 1.2.0
+	 *
+	 * @uses bp_activity_delete()
+	 *
+	 * @param int    $user_id   The user id.
+	 * @param string $component The activity component.
+	 * @return bool True on success, false on failure.
+	 */
+	function bp_activity_delete_for_user_by_component( $user_id, $component ) {
+		return bp_activity_delete( array(
+			'user_id'   => $user_id,
+			'component' => $component
+		) );
+	}
+
+/**
+ * Delete an activity comment.
+ *
+ * @since 1.2.0
+ *
+ * @uses apply_filters() To call the 'bp_activity_delete_comment_pre' hook.
+ * @uses bp_activity_delete_children()
+ * @uses bp_activity_delete()
+ * @uses BP_Activity_Activity::rebuild_activity_comment_tree() {@link BP_Activity_Activity}
+ * @uses do_action() To call the 'bp_activity_delete_comment' hook.
+ * @todo Why is an activity id required? We could look this up.
+ * @todo Why do we encourage users to call this function directly? We could just
+ *       as easily examine the activity type in bp_activity_delete() and then
+ *       call this function with the proper arguments if necessary.
+ *
+ * @param int $activity_id The ID of the "root" activity, ie the comment's
+ *                         oldest ancestor.
+ * @param int $comment_id  The ID of the comment to be deleted.
+ * @return bool True on success, false on failure.
+ */
+function bp_activity_delete_comment( $activity_id, $comment_id ) {
+
+	/**
+	 * Filters whether BuddyPress should delete an activity comment or not.
+	 *
+	 * You may want to hook into this filter if you want to override this function and
+	 * handle the deletion of child comments differently. Make sure you return false.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param bool $value       Whether BuddyPress should continue or not.
+	 * @param int  $activity_id ID of the root activity item being deleted.
+	 * @param int  $comment_id  ID of the comment being deleted.
+	 */
+	if ( ! apply_filters( 'bp_activity_delete_comment_pre', true, $activity_id, $comment_id ) ) {
+		return false;
+	}
+
+	// Delete any children of this comment.
+	bp_activity_delete_children( $activity_id, $comment_id );
+
+	// Delete the actual comment.
+	if ( ! bp_activity_delete( array( 'id' => $comment_id, 'type' => 'activity_comment' ) ) ) {
+		return false;
+	}
+
+	// Purge comment cache for the root activity update.
+	wp_cache_delete( $activity_id, 'bp_activity_comments' );
+
+	// Recalculate the comment tree.
+	BP_Activity_Activity::rebuild_activity_comment_tree( $activity_id );
+
+	/**
+	 * Fires at the end of the deletion of an activity comment, before returning success.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param int $activity_id ID of the activity that has had a comment deleted from.
+	 * @param int $comment_id  ID of the comment that was deleted.
+	 */
+	do_action( 'bp_activity_delete_comment', $activity_id, $comment_id );
+
+	return true;
+}
+
+	/**
+	 * Delete an activity comment's children.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses BP_Activity_Activity::get_child_comments() {@link BP_Activity_Activity}
+	 * @uses bp_activity_delete_children()
+	 * @uses bp_activity_delete()
+	 *
+	 * @param int $activity_id The ID of the "root" activity, ie the
+	 *                         comment's oldest ancestor.
+	 * @param int $comment_id  The ID of the comment to be deleted.
+	 */
+	function bp_activity_delete_children( $activity_id, $comment_id ) {
+
+		// Get activity children to delete.
+		$children = BP_Activity_Activity::get_child_comments( $comment_id );
+
+		// Recursively delete all children of this comment.
+		if ( ! empty( $children ) ) {
+			foreach( (array) $children as $child ) {
+				bp_activity_delete_children( $activity_id, $child->id );
+			}
+		}
+
+		// Delete the comment itself.
+		bp_activity_delete( array(
+			'secondary_item_id' => $comment_id,
+			'type'              => 'activity_comment',
+			'item_id'           => $activity_id
+		) );
+	}
+
+/**
+ * Get the permalink for a single activity item.
+ *
+ * When only the $activity_id param is passed, BP has to instantiate a new
+ * BP_Activity_Activity object. To save yourself some processing overhead,
+ * be sure to pass the full $activity_obj parameter as well, if you already
+ * have it available.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_root_domain()
+ * @uses bp_get_activity_root_slug()
+ * @uses apply_filters_ref_array() To call the 'bp_activity_get_permalink' hook.
+ *
+ * @param int         $activity_id  The unique id of the activity object.
+ * @param object|bool $activity_obj Optional. The activity object.
+ * @return string $link Permalink for the activity item.
+ */
+function bp_activity_get_permalink( $activity_id, $activity_obj = false ) {
+	$bp = buddypress();
+
+	if ( empty( $activity_obj ) ) {
+		$activity_obj = new BP_Activity_Activity( $activity_id );
+	}
+
+	if ( isset( $activity_obj->current_comment ) ) {
+		$activity_obj = $activity_obj->current_comment;
+	}
+
+	$use_primary_links = array(
+		'new_blog_post',
+		'new_blog_comment',
+		'new_forum_topic',
+		'new_forum_post',
+	);
+
+	if ( ! empty( $bp->activity->track ) ) {
+		$use_primary_links = array_merge( $use_primary_links, array_keys( $bp->activity->track ) );
+	}
+
+	if ( false !== array_search( $activity_obj->type, $use_primary_links ) ) {
+		$link = $activity_obj->primary_link;
+	} else {
+		if ( 'activity_comment' == $activity_obj->type ) {
+			$link = bp_get_root_domain() . '/' . bp_get_activity_root_slug() . '/p/' . $activity_obj->item_id . '/';
+		} else {
+			$link = bp_get_root_domain() . '/' . bp_get_activity_root_slug() . '/p/' . $activity_obj->id . '/';
+		}
+	}
+
+	/**
+	 * Filters the activity permalink for the specified activity item.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $array Array holding activity permalink and activity item object.
+	 */
+	return apply_filters_ref_array( 'bp_activity_get_permalink', array( $link, &$activity_obj ) );
+}
+
+/**
+ * Hide a user's activity.
+ *
+ * @since 1.2.0
+ *
+ * @uses BP_Activity_Activity::hide_all_for_user() {@link BP_Activity_Activity}
+ *
+ * @param int $user_id The ID of the user whose activity is being hidden.
+ * @return bool True on success, false on failure.
+ */
+function bp_activity_hide_user_activity( $user_id ) {
+	return BP_Activity_Activity::hide_all_for_user( $user_id );
+}
+
+/**
+ * Take content, remove images, and replace them with a single thumbnail image.
+ *
+ * The format of items in the activity stream is such that we do not want to
+ * allow an arbitrary number of arbitrarily large images to be rendered.
+ * However, the activity stream is built to elegantly display a single
+ * thumbnail corresponding to the activity comment. This function looks
+ * through the content, grabs the first image and converts it to a thumbnail,
+ * and removes the rest of the images from the string.
+ *
+ * As of BuddyPress 2.3, this function is no longer in use.
+ *
+ * @since 1.2.0
+ *
+ * @uses esc_attr()
+ * @uses apply_filters() To call the 'bp_activity_thumbnail_content_images' hook.
+ *
+ * @param string      $content The content of the activity item.
+ * @param string|bool $link    Optional. The unescaped URL that the image should link
+ *                             to. If absent, the image will not be a link.
+ * @param array|bool  $args    Optional. The args passed to the activity
+ *                             creation function (eg bp_blogs_record_activity()).
+ * @return string $content The content with images stripped and replaced with a
+ *                         single thumb.
+ */
+function bp_activity_thumbnail_content_images( $content, $link = false, $args = false ) {
+
+	preg_match_all( '/<img[^>]*>/Ui', $content, $matches );
+
+	// Remove <img> tags. Also remove caption shortcodes and caption text if present.
+	$content = preg_replace('|(\[caption(.*?)\])?<img[^>]*>([^\[\[]*\[\/caption\])?|', '', $content );
+
+	if ( !empty( $matches ) && !empty( $matches[0] ) ) {
+
+		// Get the SRC value.
+		preg_match( '/<img.*?(src\=[\'|"]{0,1}.*?[\'|"]{0,1})[\s|>]{1}/i',    $matches[0][0], $src    );
+
+		// Get the width and height.
+		preg_match( '/<img.*?(height\=[\'|"]{0,1}.*?[\'|"]{0,1})[\s|>]{1}/i', $matches[0][0], $height );
+		preg_match( '/<img.*?(width\=[\'|"]{0,1}.*?[\'|"]{0,1})[\s|>]{1}/i',  $matches[0][0], $width  );
+
+		if ( ! empty( $src ) ) {
+			$src = substr( substr( str_replace( 'src=', '', $src[1] ), 0, -1 ), 1 );
+
+			if ( isset( $width[1] ) ) {
+				$width = substr( substr( str_replace( 'width=', '', $width[1] ), 0, -1 ), 1 );
+			}
+
+			if ( isset( $height[1] ) ) {
+				$height = substr( substr( str_replace( 'height=', '', $height[1] ), 0, -1 ), 1 );
+			}
+
+			if ( empty( $width ) || empty( $height ) ) {
+				$width  = 100;
+				$height = 100;
+			}
+
+			$ratio      = (int) $width / (int) $height;
+			$new_height = (int) $height >= 100 ? 100 : $height;
+			$new_width  = $new_height * $ratio;
+			$image      = '<img src="' . esc_url( $src ) . '" width="' . absint( $new_width ) . '" height="' . absint( $new_height ) . '" alt="' . __( 'Thumbnail', 'buddypress' ) . '" class="align-left thumbnail" />';
+
+			if ( !empty( $link ) ) {
+				$image = '<a href="' . esc_url( $link ) . '">' . $image . '</a>';
+			}
+
+			$content = $image . $content;
+		}
+	}
+
+	/**
+	 * Filters the activity content that had a thumbnail replace images.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $content Activity content that had images replaced in.
+	 * @param array  $matches Array of all image tags found in the posted content.
+	 * @param array  $args    Arguments passed into function creating the activity update.
+	 */
+	return apply_filters( 'bp_activity_thumbnail_content_images', $content, $matches, $args );
+}
+
+/**
+ * Create a rich summary of an activity item for the activity stream.
+ *
+ * More than just a simple excerpt, the summary could contain oEmbeds and other types of media.
+ * Currently, it's only used for blog post items, but it will probably be used for all types of
+ * activity in the future.
+ *
+ * @since 2.3.0
+ *
+ * @param string $content  The content of the activity item.
+ * @param array  $activity The data passed to bp_activity_add() or the values
+ *                         from an Activity obj.
+ * @return string $summary
+ */
+function bp_activity_create_summary( $content, $activity ) {
+	$args = array(
+		'width' => isset( $GLOBALS['content_width'] ) ? (int) $GLOBALS['content_width'] : 'medium',
+	);
+
+	// Get the WP_Post object if this activity type is a blog post.
+	if ( $activity['type'] === 'new_blog_post' ) {
+		$content = get_post( $activity['secondary_item_id'] );
+	}
+
+	/**
+	 * Filter the class name of the media extractor when creating an Activity summary.
+	 *
+	 * Use this filter to change the media extractor used to extract media info for the activity item.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $extractor Class name.
+	 * @param string $content   The content of the activity item.
+	 * @param array  $activity  The data passed to bp_activity_add() or the values from an Activity obj.
+	 */
+	$extractor = apply_filters( 'bp_activity_create_summary_extractor_class', 'BP_Media_Extractor', $content, $activity );
+	$extractor = new $extractor;
+
+	/**
+	 * Filter the arguments passed to the media extractor when creating an Activity summary.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param array              $args      Array of bespoke data for the media extractor.
+	 * @param string             $content   The content of the activity item.
+	 * @param array              $activity  The data passed to bp_activity_add() or the values from an Activity obj.
+	 * @param BP_Media_Extractor $extractor The media extractor object.
+	 */
+	$args = apply_filters( 'bp_activity_create_summary_extractor_args', $args, $content, $activity, $extractor );
+
+
+	// Extract media information from the $content.
+	$media = $extractor->extract( $content, BP_Media_Extractor::ALL, $args );
+
+	// If we converted $content to an object earlier, flip it back to a string.
+	if ( is_a( $content, 'WP_Post' ) ) {
+		$content = $content->post_content;
+	}
+
+	$para_count     = substr_count( strtolower( wpautop( $content ) ), '<p>' );
+	$has_audio      = ! empty( $media['has']['audio'] )           && $media['has']['audio'];
+	$has_videos     = ! empty( $media['has']['videos'] )          && $media['has']['videos'];
+	$has_feat_image = ! empty( $media['has']['featured_images'] ) && $media['has']['featured_images'];
+	$has_galleries  = ! empty( $media['has']['galleries'] )       && $media['has']['galleries'];
+	$has_images     = ! empty( $media['has']['images'] )          && $media['has']['images'];
+	$has_embeds     = false;
+
+	// Embeds must be subtracted from the paragraph count.
+	if ( ! empty( $media['has']['embeds'] ) ) {
+		$has_embeds = $media['has']['embeds'] > 0;
+		$para_count -= count( $media['has']['embeds'] );
+	}
+
+	$extracted_media = array();
+	$use_media_type  = '';
+	$image_source    = '';
+
+	// If it's a short article and there's an embed/audio/video, use it.
+	if ( $para_count <= 3 ) {
+		if ( $has_embeds ) {
+			$use_media_type = 'embeds';
+		} elseif ( $has_audio ) {
+			$use_media_type = 'audio';
+		} elseif ( $has_videos ) {
+			$use_media_type = 'videos';
+		}
+	}
+
+	// If not, or in any other situation, try to use an image.
+	if ( ! $use_media_type && $has_images ) {
+		$use_media_type = 'images';
+		$image_source   = 'html';
+
+		// Featured Image > Galleries > inline <img>.
+		if ( $has_feat_image ) {
+			$image_source = 'featured_images';
+
+		} elseif ( $has_galleries ) {
+			$image_source = 'galleries';
+		}
+	}
+
+	// Extract an item from the $media results.
+	if ( $use_media_type ) {
+		if ( $use_media_type === 'images' ) {
+			$extracted_media = wp_list_filter( $media[ $use_media_type ], array( 'source' => $image_source ) );
+			$extracted_media = array_shift( $extracted_media );
+		} else {
+			$extracted_media = array_shift( $media[ $use_media_type ] );
+		}
+
+		/**
+		 * Filter the results of the media extractor when creating an Activity summary.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $extracted_media Extracted media item. See {@link BP_Media_Extractor::extract()} for format.
+		 * @param string $content         Content of the activity item.
+		 * @param array  $activity        The data passed to bp_activity_add() or the values from an Activity obj.
+		 * @param array  $media           All results from the media extraction.
+		 *                                See {@link BP_Media_Extractor::extract()} for format.
+		 * @param string $use_media_type  The kind of media item that was preferentially extracted.
+		 * @param string $image_source    If $use_media_type was "images", the preferential source of the image.
+		 *                                Otherwise empty.
+		 */
+		$extracted_media = apply_filters(
+			'bp_activity_create_summary_extractor_result',
+			$extracted_media,
+			$content,
+			$activity,
+			$media,
+			$use_media_type,
+			$image_source
+		);
+	}
+
+	// Generate a text excerpt for this activity item (and remove any oEmbeds URLs).
+	$summary = strip_shortcodes( html_entity_decode( strip_tags( $content ) ) );
+	$summary = bp_create_excerpt( preg_replace( '#^\s*(https?://[^\s"]+)\s*$#im', '', $summary ) );
+
+	if ( $use_media_type === 'embeds' ) {
+		$summary .= PHP_EOL . PHP_EOL . $extracted_media['url'];
+	} elseif ( $use_media_type === 'images' ) {
+		$summary .= sprintf( ' <img src="%s">', esc_url( $extracted_media['url'] ) );
+	} elseif ( in_array( $use_media_type, array( 'audio', 'videos' ), true ) ) {
+		$summary .= PHP_EOL . PHP_EOL . $extracted_media['original'];  // Full shortcode.
+	}
+
+	/**
+	 * Filters the newly-generated summary for the activity item.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $summary         Activity summary HTML.
+	 * @param string $content         Content of the activity item.
+	 * @param array  $activity        The data passed to bp_activity_add() or the values from an Activity obj.
+	 * @param array  $extracted_media Media item extracted. See {@link BP_Media_Extractor::extract()} for format.
+	 */
+	return apply_filters( 'bp_activity_create_summary', $summary, $content, $activity, $extracted_media );
+}
+
+/**
+ * Fetch whether the current user is allowed to mark items as spam.
+ *
+ * @since 1.6.0
+ *
+ * @return bool True if user is allowed to mark activity items as spam.
+ */
+function bp_activity_user_can_mark_spam() {
+
+	/**
+	 * Filters whether the current user should be able to mark items as spam.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $moderate Whether or not the current user has bp_moderate capability.
+	 */
+	return apply_filters( 'bp_activity_user_can_mark_spam', bp_current_user_can( 'bp_moderate' ) );
+}
+
+/**
+ * Mark an activity item as spam.
+ *
+ * @since 1.6.0
+ *
+ * @param BP_Activity_Activity $activity The activity item to be spammed.
+ * @param string               $source   Optional. Default is "by_a_person" (ie, a person has
+ *                                       manually marked the activity as spam). BP core also
+ *                                       accepts 'by_akismet'.
+ */
+function bp_activity_mark_as_spam( &$activity, $source = 'by_a_person' ) {
+	$bp = buddypress();
+
+	$activity->is_spam = 1;
+
+	// Clear the activity stream first page cache.
+	wp_cache_delete( 'bp_activity_sitewide_front', 'bp' );
+
+	// Clear the activity comment cache for this activity item.
+	wp_cache_delete( $activity->id, 'bp_activity_comments' );
+
+	// If Akismet is active, and this was a manual spam/ham request, stop Akismet checking the activity.
+	if ( 'by_a_person' == $source && !empty( $bp->activity->akismet ) ) {
+		remove_action( 'bp_activity_before_save', array( $bp->activity->akismet, 'check_activity' ), 4, 1 );
+
+		// Build data package for Akismet.
+		$activity_data = BP_Akismet::build_akismet_data_package( $activity );
+
+		// Tell Akismet this is spam.
+		$activity_data = $bp->activity->akismet->send_akismet_request( $activity_data, 'submit', 'spam' );
+
+		// Update meta.
+		add_action( 'bp_activity_after_save', array( $bp->activity->akismet, 'update_activity_spam_meta' ), 1, 1 );
+	}
+
+	/**
+	 * Fires at the end of the process to mark an activity item as spam.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param BP_Activity_Activity $activity Activity item being marked as spam.
+	 * @param string               $source   Source of determination of spam status. For example
+	 *                                       "by_a_person" or "by_akismet".
+	 */
+	do_action( 'bp_activity_mark_as_spam', $activity, $source );
+}
+
+/**
+ * Mark an activity item as ham.
+ *
+ * @since 1.6.0
+ *
+ * @param BP_Activity_Activity $activity The activity item to be hammed. Passed by reference.
+ * @param string               $source   Optional. Default is "by_a_person" (ie, a person has
+ *                                       manually marked the activity as spam). BP core also accepts
+ *                                       'by_akismet'.
+ */
+function bp_activity_mark_as_ham( &$activity, $source = 'by_a_person' ) {
+	$bp = buddypress();
+
+	$activity->is_spam = 0;
+
+	// Clear the activity stream first page cache.
+	wp_cache_delete( 'bp_activity_sitewide_front', 'bp' );
+
+	// Clear the activity comment cache for this activity item.
+	wp_cache_delete( $activity->id, 'bp_activity_comments' );
+
+	// If Akismet is active, and this was a manual spam/ham request, stop Akismet checking the activity.
+	if ( 'by_a_person' == $source && !empty( $bp->activity->akismet ) ) {
+		remove_action( 'bp_activity_before_save', array( $bp->activity->akismet, 'check_activity' ), 4, 1 );
+
+		// Build data package for Akismet.
+		$activity_data = BP_Akismet::build_akismet_data_package( $activity );
+
+		// Tell Akismet this is spam.
+		$activity_data = $bp->activity->akismet->send_akismet_request( $activity_data, 'submit', 'ham' );
+
+		// Update meta.
+		add_action( 'bp_activity_after_save', array( $bp->activity->akismet, 'update_activity_ham_meta' ), 1, 1 );
+	}
+
+	/**
+	 * Fires at the end of the process to mark an activity item as ham.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param BP_Activity_Activity $activity Activity item being marked as ham.
+	 * @param string               $source   Source of determination of ham status. For example
+	 *                                       "by_a_person" or "by_akismet".
+	 */
+	do_action( 'bp_activity_mark_as_ham', $activity, $source );
+}
+
+
+/** Embeds *******************************************************************/
+
+/**
+ * Set up activity oEmbed cache during the activity loop.
+ *
+ * During an activity loop, this function sets up the hooks necessary to grab
+ * each item's embeds from the cache, or put them in the cache if they are
+ * not there yet.
+ *
+ * This does not cover recursive activity comments, as they do not use a real loop.
+ * For that, see {@link bp_activity_comment_embed()}.
+ *
+ * @since 1.5.0
+ *
+ * @see BP_Embed
+ * @see bp_embed_activity_cache()
+ * @see bp_embed_activity_save_cache()
+ *
+ * @uses add_filter() To attach 'bp_get_activity_id' to 'embed_post_id'.
+ * @uses add_filter() To attach 'bp_embed_activity_cache' to 'bp_embed_get_cache'.
+ * @uses add_action() To attach 'bp_embed_activity_save_cache' to 'bp_embed_update_cache'.
+ */
+function bp_activity_embed() {
+	add_filter( 'embed_post_id',         'bp_get_activity_id'                  );
+	add_filter( 'bp_embed_get_cache',    'bp_embed_activity_cache',      10, 3 );
+	add_action( 'bp_embed_update_cache', 'bp_embed_activity_save_cache', 10, 3 );
+}
+add_action( 'activity_loop_start', 'bp_activity_embed' );
+
+/**
+ * Set up activity oEmbed cache while recursing through activity comments.
+ *
+ * While crawling through an activity comment tree
+ * ({@link bp_activity_recurse_comments}), this function sets up the hooks
+ * necessary to grab each comment's embeds from the cache, or put them in
+ * the cache if they are not there yet.
+ *
+ * @since 1.5.0
+ *
+ * @see BP_Embed
+ * @see bp_embed_activity_cache()
+ * @see bp_embed_activity_save_cache()
+ *
+ * @uses add_filter() To attach 'bp_get_activity_comment_id' to 'embed_post_id'.
+ * @uses add_filter() To attach 'bp_embed_activity_cache' to 'bp_embed_get_cache'.
+ * @uses add_action() To attach 'bp_embed_activity_save_cache' to 'bp_embed_update_cache'.
+ */
+function bp_activity_comment_embed() {
+	add_filter( 'embed_post_id',         'bp_get_activity_comment_id'          );
+	add_filter( 'bp_embed_get_cache',    'bp_embed_activity_cache',      10, 3 );
+	add_action( 'bp_embed_update_cache', 'bp_embed_activity_save_cache', 10, 3 );
+}
+add_action( 'bp_before_activity_comment', 'bp_activity_comment_embed' );
+
+/**
+ * When a user clicks on a "Read More" item, make sure embeds are correctly parsed and shown for the expanded content.
+ *
+ * @since 1.5.0
+ *
+ * @see BP_Embed
+ * @uses add_filter() To attach create_function() to 'embed_post_id'.
+ * @uses add_filter() To attach 'bp_embed_activity_cache' to 'bp_embed_get_cache'.
+ * @uses add_action() To attach 'bp_embed_activity_save_cache' to 'bp_embed_update_cache'.
+ *
+ * @param object $activity The activity that is being expanded.
+ */
+function bp_dtheme_embed_read_more( $activity ) {
+	buddypress()->activity->read_more_id = $activity->id;
+
+	add_filter( 'embed_post_id',         create_function( '', 'return buddypress()->activity->read_more_id;' ) );
+	add_filter( 'bp_embed_get_cache',    'bp_embed_activity_cache',      10, 3 );
+	add_action( 'bp_embed_update_cache', 'bp_embed_activity_save_cache', 10, 3 );
+}
+add_action( 'bp_dtheme_get_single_activity_content',       'bp_dtheme_embed_read_more' );
+add_action( 'bp_legacy_theme_get_single_activity_content', 'bp_dtheme_embed_read_more' );
+
+/**
+ * Clean up 'embed_post_id' filter after comment recursion.
+ *
+ * This filter must be removed so that the non-comment filters take over again
+ * once the comments are done being processed.
+ *
+ * @since 1.5.0
+ *
+ * @see bp_activity_comment_embed()
+ * @uses remove_filter() To remove 'bp_get_activity_comment_id' from 'embed_post_id'.
+ */
+function bp_activity_comment_embed_after_recurse() {
+	remove_filter( 'embed_post_id', 'bp_get_activity_comment_id' );
+}
+add_action( 'bp_after_activity_comment', 'bp_activity_comment_embed_after_recurse' );
+
+/**
+ * Fetch an activity item's cached embeds.
+ *
+ * Used during {@link BP_Embed::parse_oembed()} via {@link bp_activity_embed()}.
+ *
+ * @since 1.5.0
+ *
+ * @see BP_Embed::parse_oembed()
+ * @uses bp_activity_get_meta()
+ *
+ * @param string $cache    An empty string passed by BP_Embed::parse_oembed() for
+ *                         functions like this one to filter.
+ * @param int    $id       The ID of the activity item.
+ * @param string $cachekey The cache key generated in BP_Embed::parse_oembed().
+ * @return mixed The cached embeds for this activity item.
+ */
+function bp_embed_activity_cache( $cache, $id, $cachekey ) {
+	return bp_activity_get_meta( $id, $cachekey );
+}
+
+/**
+ * Set an activity item's embed cache.
+ *
+ * Used during {@link BP_Embed::parse_oembed()} via {@link bp_activity_embed()}.
+ *
+ * @since 1.5.0
+ *
+ * @see BP_Embed::parse_oembed()
+ * @uses bp_activity_update_meta()
+ *
+ * @param string $cache    An empty string passed by BP_Embed::parse_oembed() for
+ *                         functions like this one to filter.
+ * @param string $cachekey The cache key generated in BP_Embed::parse_oembed().
+ * @param int    $id       The ID of the activity item.
+ */
+function bp_embed_activity_save_cache( $cache, $cachekey, $id ) {
+	bp_activity_update_meta( $id, $cachekey, $cache );
+}
+
+/**
+ * Should we use Heartbeat to refresh activities?
+ *
+ * @since 2.0.0
+ *
+ * @uses bp_is_activity_heartbeat_active() to check if heartbeat setting is on.
+ * @uses bp_is_activity_directory() to check if the current page is the activity
+ *       directory.
+ * @uses bp_is_group_activity() to check if on a single group, the current page
+ *       is the group activities.
+ *
+ * @return bool True if activity heartbeat is enabled, otherwise false.
+ */
+function bp_activity_do_heartbeat() {
+	$retval = false;
+
+	if ( ! bp_is_activity_heartbeat_active() ) {
+		return $retval;
+	}
+
+	if ( bp_is_activity_directory() || bp_is_group_activity() ) {
+		$retval = true;
+	}
+
+	return $retval;
+}
Index: /tags/2.4.3/src/bp-activity/bp-activity-loader.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-loader.php	(revision 10410)
@@ -0,0 +1,395 @@
+<?php
+/**
+ * BuddyPress Activity Streams Loader.
+ *
+ * An activity stream component, for users, groups, and site tracking.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityCore
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Main Activity Class.
+ *
+ * @since 1.5.0
+ */
+class BP_Activity_Component extends BP_Component {
+
+	/**
+	 * Start the activity component setup process.
+	 *
+	 * @since 1.5.0
+	 */
+	public function __construct() {
+		parent::start(
+			'activity',
+			__( 'Activity Streams', 'buddypress' ),
+			buddypress()->plugin_dir,
+			array(
+				'adminbar_myaccount_order' => 10,
+				'search_query_arg' => 'activity_search',
+			)
+		);
+	}
+
+	/**
+	 * Include component files.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::includes() for a description of arguments.
+	 *
+	 * @param array $includes See BP_Component::includes() for a description.
+	 */
+	public function includes( $includes = array() ) {
+
+		// Files to include.
+		$includes = array(
+			'cssjs',
+			'actions',
+			'screens',
+			'filters',
+			'classes',
+			'template',
+			'functions',
+			'notifications',
+			'cache'
+		);
+
+		// Load Akismet support if Akismet is configured.
+		$akismet_key = bp_get_option( 'wordpress_api_key' );
+
+		/** This filter is documented in bp-activity/bp-activity-actions.php */
+		if ( defined( 'AKISMET_VERSION' ) && class_exists( 'Akismet' ) && ( ! empty( $akismet_key ) || defined( 'WPCOM_API_KEY' ) ) && apply_filters( 'bp_activity_use_akismet', bp_is_akismet_active() ) ) {
+			$includes[] = 'akismet';
+		}
+
+		if ( is_admin() ) {
+			$includes[] = 'admin';
+		}
+
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Set up component global variables.
+	 *
+	 * The BP_ACTIVITY_SLUG constant is deprecated, and only used here for
+	 * backwards compatibility.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_globals() for a description of arguments.
+	 *
+	 * @param array $args See BP_Component::setup_globals() for a description.
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		// Define a slug, if necessary.
+		if ( ! defined( 'BP_ACTIVITY_SLUG' ) ) {
+			define( 'BP_ACTIVITY_SLUG', $this->id );
+		}
+
+		// Global tables for activity component.
+		$global_tables = array(
+			'table_name'      => $bp->table_prefix . 'bp_activity',
+			'table_name_meta' => $bp->table_prefix . 'bp_activity_meta',
+		);
+
+		// Metadata tables for groups component.
+		$meta_tables = array(
+			'activity' => $bp->table_prefix . 'bp_activity_meta',
+		);
+
+		// All globals for activity component.
+		// Note that global_tables is included in this array.
+		$args = array(
+			'slug'                  => BP_ACTIVITY_SLUG,
+			'root_slug'             => isset( $bp->pages->activity->slug ) ? $bp->pages->activity->slug : BP_ACTIVITY_SLUG,
+			'has_directory'         => true,
+			'directory_title'       => _x( 'Site-Wide Activity', 'component directory title', 'buddypress' ),
+			'notification_callback' => 'bp_activity_format_notifications',
+			'search_string'         => __( 'Search Activity...', 'buddypress' ),
+			'global_tables'         => $global_tables,
+			'meta_tables'           => $meta_tables,
+		);
+
+		parent::setup_globals( $args );
+	}
+
+	/**
+	 * Set up component navigation.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_nav() for a description of arguments.
+	 * @uses bp_is_active()
+	 * @uses is_user_logged_in()
+	 * @uses bp_get_friends_slug()
+	 * @uses bp_get_groups_slug()
+	 *
+	 * @param array $main_nav Optional. See BP_Component::setup_nav() for description.
+	 * @param array $sub_nav  Optional. See BP_Component::setup_nav() for description.
+	 */
+	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;
+		}
+
+		// Determine user to use.
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		$slug          = bp_get_activity_slug();
+		$activity_link = trailingslashit( $user_domain . $slug );
+
+		// Add 'Activity' to the main navigation.
+		$main_nav = array(
+			'name'                => _x( 'Activity', 'Profile activity screen nav', 'buddypress' ),
+			'slug'                => $slug,
+			'position'            => 10,
+			'screen_function'     => 'bp_activity_screen_my_activity',
+			'default_subnav_slug' => 'just-me',
+			'item_css_id'         => $this->id
+		);
+
+		// Add the subnav items to the activity nav item if we are using a theme that supports this.
+		$sub_nav[] = array(
+			'name'            => _x( 'Personal', 'Profile activity screen sub nav', 'buddypress' ),
+			'slug'            => 'just-me',
+			'parent_url'      => $activity_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'bp_activity_screen_my_activity',
+			'position'        => 10
+		);
+
+		// Check @mentions.
+		if ( bp_activity_do_mentions() ) {
+			$sub_nav[] = array(
+				'name'            => _x( 'Mentions', 'Profile activity screen sub nav', 'buddypress' ),
+				'slug'            => 'mentions',
+				'parent_url'      => $activity_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'bp_activity_screen_mentions',
+				'position'        => 20,
+				'item_css_id'     => 'activity-mentions'
+			);
+		}
+
+		// Favorite activity items.
+		if ( bp_activity_can_favorite() ) {
+			$sub_nav[] = array(
+				'name'            => _x( 'Favorites', 'Profile activity screen sub nav', 'buddypress' ),
+				'slug'            => 'favorites',
+				'parent_url'      => $activity_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'bp_activity_screen_favorites',
+				'position'        => 30,
+				'item_css_id'     => 'activity-favs'
+			);
+		}
+
+		// Additional menu if friends is active.
+		if ( bp_is_active( 'friends' ) ) {
+			$sub_nav[] = array(
+				'name'            => _x( 'Friends', 'Profile activity screen sub nav', 'buddypress' ),
+				'slug'            => bp_get_friends_slug(),
+				'parent_url'      => $activity_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'bp_activity_screen_friends',
+				'position'        => 40,
+				'item_css_id'     => 'activity-friends'
+			) ;
+		}
+
+		// Additional menu if groups is active.
+		if ( bp_is_active( 'groups' ) ) {
+			$sub_nav[] = array(
+				'name'            => _x( 'Groups', 'Profile activity screen sub nav', 'buddypress' ),
+				'slug'            => bp_get_groups_slug(),
+				'parent_url'      => $activity_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'bp_activity_screen_groups',
+				'position'        => 50,
+				'item_css_id'     => 'activity-groups'
+			);
+		}
+
+		parent::setup_nav( $main_nav, $sub_nav );
+	}
+
+	/**
+	 * Set up the component entries in the WordPress Admin Bar.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_nav() for a description of the $wp_admin_nav
+	 *      parameter array.
+	 * @uses is_user_logged_in()
+	 * @uses trailingslashit()
+	 * @uses bp_get_total_mention_count_for_user()
+	 * @uses bp_loggedin_user_id()
+	 * @uses bp_is_active()
+	 * @uses bp_get_friends_slug()
+	 * @uses bp_get_groups_slug()
+	 *
+	 * @param array $wp_admin_nav See BP_Component::setup_admin_bar() for a
+	 *                            description.
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		// Menus for logged in user.
+		if ( is_user_logged_in() ) {
+
+			// Setup the logged in user variables.
+			$activity_link = trailingslashit( bp_loggedin_user_domain() . bp_get_activity_slug() );
+
+			// Unread message count.
+			if ( bp_activity_do_mentions() ) {
+				$count = bp_get_total_mention_count_for_user( bp_loggedin_user_id() );
+				if ( !empty( $count ) ) {
+					$title = sprintf( _x( 'Mentions <span class="count">%s</span>', 'Toolbar Mention logged in user', 'buddypress' ), bp_core_number_format( $count ) );
+				} else {
+					$title = _x( 'Mentions', 'Toolbar Mention logged in user', 'buddypress' );
+				}
+			}
+
+			// Add the "Activity" sub menu.
+			$wp_admin_nav[] = array(
+				'parent' => buddypress()->my_account_menu_id,
+				'id'     => 'my-account-' . $this->id,
+				'title'  => _x( 'Activity', 'My Account Activity sub nav', 'buddypress' ),
+				'href'   => $activity_link
+			);
+
+			// Personal.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-personal',
+				'title'  => _x( 'Personal', 'My Account Activity sub nav', 'buddypress' ),
+				'href'   => $activity_link
+			);
+
+			// Mentions.
+			if ( bp_activity_do_mentions() ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-mentions',
+					'title'  => $title,
+					'href'   => trailingslashit( $activity_link . 'mentions' )
+				);
+			}
+
+			// Favorite activity items.
+			if ( bp_activity_can_favorite() ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-favorites',
+					'title'  => _x( 'Favorites', 'My Account Activity sub nav', 'buddypress' ),
+					'href'   => trailingslashit( $activity_link . 'favorites' )
+				);
+			}
+
+			// Friends?
+			if ( bp_is_active( 'friends' ) ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-friends',
+					'title'  => _x( 'Friends', 'My Account Activity sub nav', 'buddypress' ),
+					'href'   => trailingslashit( $activity_link . bp_get_friends_slug() )
+				);
+			}
+
+			// Groups?
+			if ( bp_is_active( 'groups' ) ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-groups',
+					'title'  => _x( 'Groups', 'My Account Activity sub nav', 'buddypress' ),
+					'href'   => trailingslashit( $activity_link . bp_get_groups_slug() )
+				);
+			}
+		}
+
+		parent::setup_admin_bar( $wp_admin_nav );
+	}
+
+	/**
+	 * Set up the title for pages and <title>.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses bp_is_activity_component()
+	 * @uses bp_is_my_profile()
+	 * @uses bp_core_fetch_avatar()
+	 */
+	public function setup_title() {
+
+		// Adjust title based on view.
+		if ( bp_is_activity_component() ) {
+			$bp = buddypress();
+
+			if ( bp_is_my_profile() ) {
+				$bp->bp_options_title = _x( 'My Activity', 'Page and <title>', 'buddypress' );
+			} else {
+				$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+					'item_id' => bp_displayed_user_id(),
+					'type'    => 'thumb',
+					'alt'	  => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_displayed_user_fullname() )
+				) );
+				$bp->bp_options_title  = bp_get_displayed_user_fullname();
+			}
+		}
+
+		parent::setup_title();
+	}
+
+	/**
+	 * Set up actions necessary for the component.
+	 *
+	 * @since 1.6.0
+	 */
+	public function setup_actions() {
+
+		// Spam prevention.
+		add_action( 'bp_include', 'bp_activity_setup_akismet' );
+
+		parent::setup_actions();
+	}
+
+	/**
+	 * Setup cache groups.
+	 *
+	 * @since 2.2.0
+	 */
+	public function setup_cache_groups() {
+
+		// Global groups.
+		wp_cache_add_global_groups( array(
+			'bp_activity',
+			'bp_activity_comments',
+			'activity_meta'
+		) );
+
+		parent::setup_cache_groups();
+	}
+}
+
+/**
+ * Bootstrap the Activity component.
+ */
+function bp_setup_activity() {
+	buddypress()->activity = new BP_Activity_Component();
+}
+add_action( 'bp_setup_components', 'bp_setup_activity', 6 );
Index: /tags/2.4.3/src/bp-activity/bp-activity-notifications.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-notifications.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-notifications.php	(revision 10410)
@@ -0,0 +1,555 @@
+<?php
+/**
+ * BuddyPress Activity Notifications.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityNotifications
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/* Emails *********************************************************************/
+
+/**
+ * Send email and BP notifications when a user is mentioned in an update.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_notifications_add_notification()
+ * @uses bp_get_user_meta()
+ * @uses bp_core_get_user_displayname()
+ * @uses bp_activity_get_permalink()
+ * @uses bp_core_get_user_domain()
+ * @uses bp_get_settings_slug()
+ * @uses bp_activity_filter_kses()
+ * @uses bp_core_get_core_userdata()
+ * @uses wp_specialchars_decode()
+ * @uses get_blog_option()
+ * @uses bp_is_active()
+ * @uses bp_is_group()
+ * @uses bp_get_current_group_name()
+ * @uses apply_filters() To call the 'bp_activity_at_message_notification_to' hook.
+ * @uses apply_filters() To call the 'bp_activity_at_message_notification_subject' hook.
+ * @uses apply_filters() To call the 'bp_activity_at_message_notification_message' hook.
+ * @uses wp_mail()
+ * @uses do_action() To call the 'bp_activity_sent_mention_email' hook.
+ *
+ * @param int $activity_id      The ID of the activity update.
+ * @param int $receiver_user_id The ID of the user who is receiving the update.
+ */
+function bp_activity_at_message_notification( $activity_id, $receiver_user_id ) {
+
+	// Don't leave multiple notifications for the same activity item.
+	$notifications = BP_Core_Notification::get_all_for_user( $receiver_user_id, 'all' );
+
+	foreach( $notifications as $notification ) {
+		if ( $activity_id == $notification->item_id ) {
+			return;
+		}
+	}
+
+	$activity = new BP_Activity_Activity( $activity_id );
+
+	$subject = '';
+	$message = '';
+	$content = '';
+
+	// Now email the user with the contents of the message (if they have enabled email notifications).
+	if ( 'no' != bp_get_user_meta( $receiver_user_id, 'notification_activity_new_mention', true ) ) {
+		$poster_name = bp_core_get_user_displayname( $activity->user_id );
+
+		$message_link  = bp_activity_get_permalink( $activity_id );
+		$settings_slug = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+		$settings_link = bp_core_get_user_domain( $receiver_user_id ) . $settings_slug . '/notifications/';
+
+		$poster_name = stripslashes( $poster_name );
+		$content = bp_activity_filter_kses( strip_tags( stripslashes( $activity->content ) ) );
+
+		// Set up and send the message.
+		$ud       = bp_core_get_core_userdata( $receiver_user_id );
+		$to       = $ud->user_email;
+		$subject  = bp_get_email_subject( array( 'text' => sprintf( __( '%s mentioned you in an update', 'buddypress' ), $poster_name ) ) );
+
+		if ( bp_is_active( 'groups' ) && bp_is_group() ) {
+			$message = sprintf( __(
+'%1$s mentioned you in the group "%2$s":
+
+"%3$s"
+
+To view and respond to the message, log in and visit: %4$s
+
+---------------------
+', 'buddypress' ), $poster_name, bp_get_current_group_name(), $content, $message_link );
+		} else {
+			$message = sprintf( __(
+'%1$s mentioned you in an update:
+
+"%2$s"
+
+To view and respond to the message, log in and visit: %3$s
+
+---------------------
+', 'buddypress' ), $poster_name, $content, $message_link );
+		}
+
+		// Only show the disable notifications line if the settings component is enabled.
+		if ( bp_is_active( 'settings' ) ) {
+			$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+		}
+
+		/**
+		 * Filters the user email that the @mention notification will be sent to.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $to User email the notification is being sent to.
+		 */
+		$to 	 = apply_filters( 'bp_activity_at_message_notification_to', $to );
+
+		/**
+		 * Filters the @mention notification subject that will be sent to user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $subject     Email notification subject text.
+		 * @param string $poster_name Name of the person who made the @mention.
+		 */
+		$subject = apply_filters( 'bp_activity_at_message_notification_subject', $subject, $poster_name );
+
+		/**
+		 * Filters the @mention notification message that will be sent to user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $message       Email notification message text.
+		 * @param string $poster_name   Name of the person who made the @mention.
+		 * @param string $content       Content of the @mention.
+		 * @param string $message_link  URL permalink for the activity message.
+		 * @param string $settings_link URL permalink for the user's notification settings area.
+		 */
+		$message = apply_filters( 'bp_activity_at_message_notification_message', $message, $poster_name, $content, $message_link, $settings_link );
+
+		wp_mail( $to, $subject, $message );
+	}
+
+	/**
+	 * Fires after the sending of an @mention email notification.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param BP_Activity_Activity $activity         Activity Item object.
+	 * @param string               $subject          Email notification subject text.
+	 * @param string               $message          Email notification message text.
+	 * @param string               $content          Content of the @mention.
+	 * @param int                  $receiver_user_id The ID of the user who is receiving the update.
+	 */
+	do_action( 'bp_activity_sent_mention_email', $activity, $subject, $message, $content, $receiver_user_id );
+}
+
+/**
+ * Send email and BP notifications when an activity item receives a comment.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_user_meta()
+ * @uses bp_core_get_user_displayname()
+ * @uses bp_activity_get_permalink()
+ * @uses bp_core_get_user_domain()
+ * @uses bp_get_settings_slug()
+ * @uses bp_activity_filter_kses()
+ * @uses bp_core_get_core_userdata()
+ * @uses wp_specialchars_decode()
+ * @uses get_blog_option()
+ * @uses bp_get_root_blog_id()
+ * @uses apply_filters() To call the 'bp_activity_new_comment_notification_to' hook.
+ * @uses apply_filters() To call the 'bp_activity_new_comment_notification_subject' hook.
+ * @uses apply_filters() To call the 'bp_activity_new_comment_notification_message' hook.
+ * @uses wp_mail()
+ * @uses do_action() To call the 'bp_activity_sent_reply_to_update_email' hook.
+ * @uses apply_filters() To call the 'bp_activity_new_comment_notification_comment_author_to' hook.
+ * @uses apply_filters() To call the 'bp_activity_new_comment_notification_comment_author_subject' hook.
+ * @uses apply_filters() To call the 'bp_activity_new_comment_notification_comment_author_message' hook.
+ * @uses do_action() To call the 'bp_activity_sent_reply_to_reply_email' hook.
+ *
+ * @param int   $comment_id   The comment id.
+ * @param int   $commenter_id The ID of the user who posted the comment.
+ * @param array $params       {@link bp_activity_new_comment()}.
+ * @return bool
+ */
+function bp_activity_new_comment_notification( $comment_id = 0, $commenter_id = 0, $params = array() ) {
+
+	// Set some default parameters.
+	$activity_id = 0;
+	$parent_id   = 0;
+
+	extract( $params );
+
+	$original_activity = new BP_Activity_Activity( $activity_id );
+
+	if ( $original_activity->user_id != $commenter_id && 'no' != bp_get_user_meta( $original_activity->user_id, 'notification_activity_new_reply', true ) ) {
+		$poster_name   = bp_core_get_user_displayname( $commenter_id );
+		$thread_link   = bp_activity_get_permalink( $activity_id );
+		$settings_slug = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+		$settings_link = bp_core_get_user_domain( $original_activity->user_id ) . $settings_slug . '/notifications/';
+
+		$poster_name = stripslashes( $poster_name );
+		$content = bp_activity_filter_kses( stripslashes($content) );
+
+		// Set up and send the message.
+		$ud      = bp_core_get_core_userdata( $original_activity->user_id );
+		$to      = $ud->user_email;
+		$subject = bp_get_email_subject( array( 'text' => sprintf( __( '%s replied to one of your updates', 'buddypress' ), $poster_name ) ) );
+		$message = sprintf( __(
+'%1$s replied to one of your updates:
+
+"%2$s"
+
+To view your original update and all comments, log in and visit: %3$s
+
+---------------------
+', 'buddypress' ), $poster_name, $content, $thread_link );
+
+		// Only show the disable notifications line if the settings component is enabled.
+		if ( bp_is_active( 'settings' ) ) {
+			$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+		}
+
+		/**
+		 * Filters the user email that the new comment notification will be sent to.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $to User email the notification is being sent to.
+		 */
+		$to = apply_filters( 'bp_activity_new_comment_notification_to', $to );
+
+		/**
+		 * Filters the new comment notification subject that will be sent to user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $subject     Email notification subject text.
+		 * @param string $poster_name Name of the person who made the comment.
+		 */
+		$subject = apply_filters( 'bp_activity_new_comment_notification_subject', $subject, $poster_name );
+
+		/**
+		 * Filters the new comment notification message that will be sent to user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $message       Email notification message text.
+		 * @param string $poster_name   Name of the person who made the comment.
+		 * @param string $content       Content of the comment.
+		 * @param string $thread_link   URL permalink for the activity thread.
+		 * @param string $settings_link URL permalink for the user's notification settings area.
+		 */
+		$message = apply_filters( 'bp_activity_new_comment_notification_message', $message, $poster_name, $content, $thread_link, $settings_link );
+
+		wp_mail( $to, $subject, $message );
+
+		/**
+		 * Fires after the sending of a reply to an update email notification.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int    $user_id      ID of the original activity item author.
+		 * @param string $subject      Email notification subject text.
+		 * @param string $message      Email notification message text.
+		 * @param int    $comment_id   ID for the newly received comment.
+		 * @param int    $commenter_id ID of the user who made the comment.
+		 * @param array  $params       Arguments used with the original activity comment.
+		 */
+		do_action( 'bp_activity_sent_reply_to_update_email', $original_activity->user_id, $subject, $message, $comment_id, $commenter_id, $params );
+	}
+
+	/*
+	 * If this is a reply to another comment, send an email notification to the
+	 * author of the immediate parent comment.
+	 */
+	if ( empty( $parent_id ) || ( $activity_id == $parent_id ) ) {
+		return false;
+	}
+
+	$parent_comment = new BP_Activity_Activity( $parent_id );
+
+	if ( $parent_comment->user_id != $commenter_id && $original_activity->user_id != $parent_comment->user_id && 'no' != bp_get_user_meta( $parent_comment->user_id, 'notification_activity_new_reply', true ) ) {
+		$poster_name   = bp_core_get_user_displayname( $commenter_id );
+		$thread_link   = bp_activity_get_permalink( $activity_id );
+		$settings_slug = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+		$settings_link = bp_core_get_user_domain( $parent_comment->user_id ) . $settings_slug . '/notifications/';
+
+		// Set up and send the message.
+		$ud       = bp_core_get_core_userdata( $parent_comment->user_id );
+		$to       = $ud->user_email;
+		$subject = bp_get_email_subject( array( 'text' => sprintf( __( '%s replied to one of your comments', 'buddypress' ), $poster_name ) ) );
+
+		$poster_name = stripslashes( $poster_name );
+		$content = bp_activity_filter_kses( stripslashes( $content ) );
+
+$message = sprintf( __(
+'%1$s replied to one of your comments:
+
+"%2$s"
+
+To view the original activity, your comment and all replies, log in and visit: %3$s
+
+---------------------
+', 'buddypress' ), $poster_name, $content, $thread_link );
+
+		// Only show the disable notifications line if the settings component is enabled.
+		if ( bp_is_active( 'settings' ) ) {
+			$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+		}
+
+		/**
+		 * Filters the user email that the new comment reply notification will be sent to.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $to User email the notification is being sent to.
+		 */
+		$to = apply_filters( 'bp_activity_new_comment_notification_comment_author_to', $to );
+
+		/**
+		 * Filters the new comment reply notification subject that will be sent to user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $subject     Email notification subject text.
+		 * @param string $poster_name Name of the person who made the comment reply.
+		 */
+		$subject = apply_filters( 'bp_activity_new_comment_notification_comment_author_subject', $subject, $poster_name );
+
+		/**
+		 * Filters the new comment reply notification message that will be sent to user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $message       Email notification message text.
+		 * @param string $poster_name   Name of the person who made the comment reply.
+		 * @param string $content       Content of the comment reply.
+		 * @param string $settings_link URL permalink for the user's notification settings area.
+		 * @param string $thread_link   URL permalink for the activity thread.
+		 */
+		$message = apply_filters( 'bp_activity_new_comment_notification_comment_author_message', $message, $poster_name, $content, $settings_link, $thread_link );
+
+		wp_mail( $to, $subject, $message );
+
+		/**
+		 * Fires after the sending of a reply to a reply email notification.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int    $user_id      ID of the parent activity item author.
+		 * @param string $subject      Email notification subject text.
+		 * @param string $message      Email notification message text.
+		 * @param int    $comment_id   ID for the newly received comment.
+		 * @param int    $commenter_id ID of the user who made the comment.
+		 * @param array  $params       Arguments used with the original activity comment.
+		 */
+		do_action( 'bp_activity_sent_reply_to_reply_email', $parent_comment->user_id, $subject, $message, $comment_id, $commenter_id, $params );
+	}
+}
+
+/**
+ * Helper method to map action arguments to function parameters.
+ *
+ * @since 1.9.0
+ *
+ * @param int   $comment_id ID of the comment being notified about.
+ * @param array $params     Parameters to use with notification.
+ */
+function bp_activity_new_comment_notification_helper( $comment_id, $params ) {
+	bp_activity_new_comment_notification( $comment_id, $params['user_id'], $params );
+}
+add_action( 'bp_activity_comment_posted', 'bp_activity_new_comment_notification_helper', 10, 2 );
+
+/** Notifications *************************************************************/
+
+/**
+ * Format notifications related to activity.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_loggedin_user_domain()
+ * @uses bp_get_activity_slug()
+ * @uses bp_core_get_user_displayname()
+ * @uses apply_filters() To call the 'bp_activity_multiple_at_mentions_notification' hook.
+ * @uses apply_filters() To call the 'bp_activity_single_at_mentions_notification' hook.
+ * @uses do_action() To call 'activity_format_notifications' hook.
+ *
+ * @param string $action            The type of activity item. Just 'new_at_mention' for now.
+ * @param int    $item_id           The activity ID.
+ * @param int    $secondary_item_id In the case of at-mentions, this is the mentioner's ID.
+ * @param int    $total_items       The total number of notifications to format.
+ * @param string $format            'string' to get a BuddyBar-compatible notification, 'array' otherwise.
+ * @return string $return Formatted @mention notification.
+ */
+function bp_activity_format_notifications( $action, $item_id, $secondary_item_id, $total_items, $format = 'string' ) {
+
+	switch ( $action ) {
+		case 'new_at_mention':
+			$activity_id      = $item_id;
+			$poster_user_id   = $secondary_item_id;
+			$at_mention_link  = bp_loggedin_user_domain() . bp_get_activity_slug() . '/mentions/';
+			$at_mention_title = sprintf( __( '@%s Mentions', 'buddypress' ), bp_get_loggedin_user_username() );
+			$amount = 'single';
+
+			if ( (int) $total_items > 1 ) {
+				$text = sprintf( __( 'You have %1$d new mentions', 'buddypress' ), (int) $total_items );
+				$amount = 'multiple';
+			} else {
+				$user_fullname = bp_core_get_user_displayname( $poster_user_id );
+				$text =  sprintf( __( '%1$s mentioned you', 'buddypress' ), $user_fullname );
+			}
+		break;
+	}
+
+	if ( 'string' == $format ) {
+
+		/**
+		 * Filters the @mention notification for the string format.
+		 *
+		 * This is a variable filter that is dependent on how many items
+		 * need notified about. The two possible hooks are bp_activity_single_at_mentions_notification
+		 * or bp_activity_multiple_at_mentions_notification.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $string          HTML anchor tag for the mention.
+		 * @param string $at_mention_link The permalink for the mention.
+		 * @param int    $total_items     How many items being notified about.
+		 * @param int    $activity_id     ID of the activity item being formatted.
+		 * @param int    $poster_user_id  ID of the user posting the mention.
+		 */
+		$return = apply_filters( 'bp_activity_' . $amount . '_at_mentions_notification', '<a href="' . esc_url( $at_mention_link ) . '" title="' . esc_attr( $at_mention_title ) . '">' . esc_html( $text ) . '</a>', $at_mention_link, (int) $total_items, $activity_id, $poster_user_id );
+	} else {
+
+		/**
+		 * Filters the @mention notification for any non-string format.
+		 *
+		 * This is a variable filter that is dependent on how many items need notified about.
+		 * The two possible hooks are bp_activity_single_at_mentions_notification
+		 * or bp_activity_multiple_at_mentions_notification.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param array  $array           Array holding the content and permalink for the mention notification.
+		 * @param string $at_mention_link The permalink for the mention.
+		 * @param int    $total_items     How many items being notified about.
+		 * @param int    $activity_id     ID of the activity item being formatted.
+		 * @param int    $poster_user_id  ID of the user posting the mention.
+		 */
+		$return = apply_filters( 'bp_activity_' . $amount . '_at_mentions_notification', array(
+			'text' => $text,
+			'link' => $at_mention_link
+		), $at_mention_link, (int) $total_items, $activity_id, $poster_user_id );
+	}
+
+	/**
+	 * Fires right before returning the formatted activity notifications.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $action            The type of activity item.
+	 * @param int    $item_id           The activity ID.
+	 * @param int    $secondary_item_id @mention mentioner ID.
+	 * @param int    $total_items       Total amount of items to format.
+	 */
+	do_action( 'activity_format_notifications', $action, $item_id, $secondary_item_id, $total_items );
+
+	return $return;
+}
+
+/**
+ * Notify a member when their nicename is mentioned in an activity stream item.
+ *
+ * Hooked to the 'bp_activity_sent_mention_email' action, we piggy back off the
+ * existing email code for now, since it does the heavy lifting for us. In the
+ * future when we separate emails from Notifications, this will need its own
+ * 'bp_activity_at_name_send_emails' equivalent helper function.
+ *
+ * @since 1.9.0
+ *
+ * @param object $activity           Activity object.
+ * @param string $subject (not used) Notification subject.
+ * @param string $message (not used) Notification message.
+ * @param string $content (not used) Notification content.
+ * @param int    $receiver_user_id   ID of user receiving notification.
+ */
+function bp_activity_at_mention_add_notification( $activity, $subject, $message, $content, $receiver_user_id ) {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_add_notification( array(
+			'user_id'           => $receiver_user_id,
+			'item_id'           => $activity->id,
+			'secondary_item_id' => $activity->user_id,
+			'component_name'    => buddypress()->activity->id,
+			'component_action'  => 'new_at_mention',
+			'date_notified'     => bp_core_current_time(),
+			'is_new'            => 1,
+		) );
+	}
+}
+add_action( 'bp_activity_sent_mention_email', 'bp_activity_at_mention_add_notification', 10, 5 );
+
+/**
+ * Mark at-mention notifications as read when users visit their Mentions page.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_notifications_mark_all_notifications_by_type()
+ */
+function bp_activity_remove_screen_notifications() {
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return;
+	}
+
+	// Only mark read if you're looking at your own mentions.
+	if ( ! bp_is_my_profile() ) {
+		return;
+	}
+
+	bp_notifications_mark_notifications_by_type( bp_loggedin_user_id(), buddypress()->activity->id, 'new_at_mention' );
+}
+add_action( 'bp_activity_screen_mentions', 'bp_activity_remove_screen_notifications' );
+
+/**
+ * Mark at-mention notification as read when user visits the activity with the mention.
+ *
+ * @since 2.0.0
+ *
+ * @param BP_Activity_Activity $activity Activity object.
+ */
+function bp_activity_remove_screen_notifications_single_activity_permalink( $activity ) {
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return;
+	}
+
+	if ( ! is_user_logged_in() ) {
+		return;
+	}
+
+	// Mark as read any notifications for the current user related to this activity item.
+	bp_notifications_mark_notifications_by_item_id( bp_loggedin_user_id(), $activity->id, buddypress()->activity->id, 'new_at_mention' );
+}
+add_action( 'bp_activity_screen_single_activity_permalink', 'bp_activity_remove_screen_notifications_single_activity_permalink' );
+
+/**
+ * Delete at-mention notifications when the corresponding activity item is deleted.
+ *
+ * @since 2.0.0
+ *
+ * @param array $activity_ids_deleted IDs of deleted activity items.
+ */
+function bp_activity_at_mention_delete_notification( $activity_ids_deleted = array() ) {
+	// Let's delete all without checking if content contains any mentions
+	// to avoid a query to get the activity.
+	if ( bp_is_active( 'notifications' ) && ! empty( $activity_ids_deleted ) ) {
+		foreach ( $activity_ids_deleted as $activity_id ) {
+			bp_notifications_delete_all_notifications_by_type( $activity_id, buddypress()->activity->id );
+		}
+	}
+}
+add_action( 'bp_activity_deleted_activities', 'bp_activity_at_mention_delete_notification', 10 );
Index: /tags/2.4.3/src/bp-activity/bp-activity-screens.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-screens.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-screens.php	(revision 10410)
@@ -0,0 +1,587 @@
+<?php
+/**
+ * BuddyPress Activity Screens.
+ *
+ * The functions in this file detect, with each page load, whether an Activity
+ * component page is being requested. If so, it parses any necessary data from
+ * the URL, and tells BuddyPress to load the appropriate template.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityScreens
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Load the Activity directory.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_displayed_user_id()
+ * @uses bp_is_activity_component()
+ * @uses bp_current_action()
+ * @uses bp_update_is_directory()
+ * @uses do_action() To call the 'bp_activity_screen_index' hook.
+ * @uses bp_core_load_template()
+ * @uses apply_filters() To call the 'bp_activity_screen_index' hook.
+ */
+function bp_activity_screen_index() {
+	if ( bp_is_activity_directory() ) {
+		bp_update_is_directory( true, 'activity' );
+
+		/**
+		 * Fires right before the loading of the Activity directory screen template file.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_activity_screen_index' );
+
+		/**
+		 * Filters the template to load for the Activity directory screen.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $template Path to the activity template to load.
+		 */
+		bp_core_load_template( apply_filters( 'bp_activity_screen_index', 'activity/index' ) );
+	}
+}
+add_action( 'bp_screens', 'bp_activity_screen_index' );
+
+/**
+ * Load the 'My Activity' page.
+ *
+ * @since 1.0.0
+ *
+ * @uses do_action() To call the 'bp_activity_screen_my_activity' hook.
+ * @uses bp_core_load_template()
+ * @uses apply_filters() To call the 'bp_activity_template_my_activity' hook.
+ */
+function bp_activity_screen_my_activity() {
+
+	/**
+	 * Fires right before the loading of the "My Activity" screen template file.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'bp_activity_screen_my_activity' );
+
+	/**
+	 * Filters the template to load for the "My Activity" screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the activity template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_activity_template_my_activity', 'members/single/home' ) );
+}
+
+/**
+ * Load the 'My Friends' activity page.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_is_active()
+ * @uses bp_update_is_item_admin()
+ * @uses bp_current_user_can()
+ * @uses do_action() To call the 'bp_activity_screen_friends' hook.
+ * @uses bp_core_load_template()
+ * @uses apply_filters() To call the 'bp_activity_template_friends_activity' hook.
+ */
+function bp_activity_screen_friends() {
+	if ( !bp_is_active( 'friends' ) )
+		return false;
+
+	bp_update_is_item_admin( bp_current_user_can( 'bp_moderate' ), 'activity' );
+
+	/**
+	 * Fires right before the loading of the "My Friends" screen template file.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_activity_screen_friends' );
+
+	/**
+	 * Filters the template to load for the "My Friends" screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the activity template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_activity_template_friends_activity', 'members/single/home' ) );
+}
+
+/**
+ * Load the 'My Groups' activity page.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_is_active()
+ * @uses bp_update_is_item_admin()
+ * @uses bp_current_user_can()
+ * @uses do_action() To call the 'bp_activity_screen_groups' hook.
+ * @uses bp_core_load_template()
+ * @uses apply_filters() To call the 'bp_activity_template_groups_activity' hook.
+ */
+function bp_activity_screen_groups() {
+	if ( !bp_is_active( 'groups' ) )
+		return false;
+
+	bp_update_is_item_admin( bp_current_user_can( 'bp_moderate' ), 'activity' );
+
+	/**
+	 * Fires right before the loading of the "My Groups" screen template file.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_activity_screen_groups' );
+
+	/**
+	 * Filters the template to load for the "My Groups" screen.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $template Path to the activity template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_activity_template_groups_activity', 'members/single/home' ) );
+}
+
+/**
+ * Load the 'Favorites' activity page.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_update_is_item_admin()
+ * @uses bp_current_user_can()
+ * @uses do_action() To call the 'bp_activity_screen_favorites' hook.
+ * @uses bp_core_load_template()
+ * @uses apply_filters() To call the 'bp_activity_template_favorite_activity' hook.
+ */
+function bp_activity_screen_favorites() {
+	bp_update_is_item_admin( bp_current_user_can( 'bp_moderate' ), 'activity' );
+
+	/**
+	 * Fires right before the loading of the "Favorites" screen template file.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_activity_screen_favorites' );
+
+	/**
+	 * Filters the template to load for the "Favorites" screen.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $template Path to the activity template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_activity_template_favorite_activity', 'members/single/home' ) );
+}
+
+/**
+ * Load the 'Mentions' activity page.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_update_is_item_admin()
+ * @uses bp_current_user_can()
+ * @uses do_action() To call the 'bp_activity_screen_mentions' hook.
+ * @uses bp_core_load_template()
+ * @uses apply_filters() To call the 'bp_activity_template_mention_activity' hook.
+ */
+function bp_activity_screen_mentions() {
+	bp_update_is_item_admin( bp_current_user_can( 'bp_moderate' ), 'activity' );
+
+	/**
+	 * Fires right before the loading of the "Mentions" screen template file.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_activity_screen_mentions' );
+
+	/**
+	 * Filters the template to load for the "Mentions" screen.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $template Path to the activity template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_activity_template_mention_activity', 'members/single/home' ) );
+}
+
+/**
+ * Reset the logged-in user's new mentions data when he visits his mentions screen.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_is_my_profile()
+ * @uses bp_activity_clear_new_mentions()
+ * @uses bp_loggedin_user_id()
+ */
+function bp_activity_reset_my_new_mentions() {
+	if ( bp_is_my_profile() )
+		bp_activity_clear_new_mentions( bp_loggedin_user_id() );
+}
+add_action( 'bp_activity_screen_mentions', 'bp_activity_reset_my_new_mentions' );
+
+/**
+ * Load the page for a single activity item.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_is_activity_component()
+ * @uses bp_activity_get_specific()
+ * @uses bp_current_action()
+ * @uses bp_action_variables()
+ * @uses bp_do_404()
+ * @uses bp_is_active()
+ * @uses groups_get_group()
+ * @uses groups_is_user_member()
+ * @uses apply_filters_ref_array() To call the 'bp_activity_permalink_access' hook.
+ * @uses do_action() To call the 'bp_activity_screen_single_activity_permalink' hook.
+ * @uses bp_core_add_message()
+ * @uses is_user_logged_in()
+ * @uses bp_core_redirect()
+ * @uses site_url()
+ * @uses esc_url()
+ * @uses bp_get_root_domain()
+ * @uses bp_get_activity_root_slug()
+ * @uses bp_core_load_template()
+ * @uses apply_filters() To call the 'bp_activity_template_profile_activity_permalink' hook.
+ */
+function bp_activity_screen_single_activity_permalink() {
+	$bp = buddypress();
+
+	// No displayed user or not viewing activity component.
+	if ( !bp_is_activity_component() )
+		return false;
+
+	if ( ! bp_current_action() || !is_numeric( bp_current_action() ) )
+		return false;
+
+	// Get the activity details.
+	$activity = bp_activity_get_specific( array( 'activity_ids' => bp_current_action(), 'show_hidden' => true, 'spam' => 'ham_only', ) );
+
+	// 404 if activity does not exist
+	if ( empty( $activity['activities'][0] ) || bp_action_variables() ) {
+		bp_do_404();
+		return;
+
+	} else {
+		$activity = $activity['activities'][0];
+	}
+
+	// Default access is true.
+	$has_access = true;
+
+	// If activity is from a group, do an extra cap check.
+	if ( isset( $bp->groups->id ) && $activity->component == $bp->groups->id ) {
+
+		// Activity is from a group, but groups is currently disabled.
+		if ( !bp_is_active( 'groups') ) {
+			bp_do_404();
+			return;
+		}
+
+		// Check to see if the group is not public, if so, check the
+		// user has access to see this activity.
+		if ( $group = groups_get_group( array( 'group_id' => $activity->item_id ) ) ) {
+
+			// Group is not public.
+			if ( 'public' != $group->status ) {
+
+				// User is not a member of group.
+				if ( !groups_is_user_member( bp_loggedin_user_id(), $group->id ) ) {
+					$has_access = false;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Filters the access permission for a single activity view.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $access Array holding the current $has_access value and current activity item instance.
+	 */
+	$has_access = apply_filters_ref_array( 'bp_activity_permalink_access', array( $has_access, &$activity ) );
+
+	/**
+	 * Fires before the loading of a single activity template file.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param BP_Activity_Activity $activity   Object representing the current activity item being displayed.
+	 * @param bool                 $has_access Whether or not the current user has access to view activity.
+	 */
+	do_action( 'bp_activity_screen_single_activity_permalink', $activity, $has_access );
+
+	// Access is specifically disallowed.
+	if ( false === $has_access ) {
+
+		// User feedback.
+		bp_core_add_message( __( 'You do not have access to this activity.', 'buddypress' ), 'error' );
+
+		// Redirect based on logged in status.
+		if ( is_user_logged_in() ) {
+			$url = bp_loggedin_user_domain();
+
+		} else {
+			$url = sprintf(
+				site_url( 'wp-login.php?redirect_to=%s' ),
+				urlencode( esc_url_raw( bp_activity_get_permalink( (int) bp_current_action() ) ) )
+			);
+		}
+
+		bp_core_redirect( $url );
+	}
+
+	/**
+	 * Filters the template to load for a single activity screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the activity template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_activity_template_profile_activity_permalink', 'members/single/activity/permalink' ) );
+}
+add_action( 'bp_screens', 'bp_activity_screen_single_activity_permalink' );
+
+/**
+ * Add activity notifications settings to the notifications settings page.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_user_meta()
+ * @uses bp_core_get_username()
+ * @uses do_action() To call the 'bp_activity_screen_notification_settings' hook.
+ */
+function bp_activity_screen_notification_settings() {
+
+	if ( bp_activity_do_mentions() ) {
+		if ( ! $mention = bp_get_user_meta( bp_displayed_user_id(), 'notification_activity_new_mention', true ) ) {
+			$mention = 'yes';
+		}
+	}
+
+	if ( ! $reply = bp_get_user_meta( bp_displayed_user_id(), 'notification_activity_new_reply', true ) ) {
+		$reply = 'yes';
+	}
+
+	?>
+
+	<table class="notification-settings" id="activity-notification-settings">
+		<thead>
+			<tr>
+				<th class="icon">&nbsp;</th>
+				<th class="title"><?php _e( 'Activity', 'buddypress' ) ?></th>
+				<th class="yes"><?php _e( 'Yes', 'buddypress' ) ?></th>
+				<th class="no"><?php _e( 'No', 'buddypress' )?></th>
+			</tr>
+		</thead>
+
+		<tbody>
+			<?php if ( bp_activity_do_mentions() ) : ?>
+				<tr id="activity-notification-settings-mentions">
+					<td>&nbsp;</td>
+					<td><?php printf( __( 'A member mentions you in an update using "@%s"', 'buddypress' ), bp_core_get_username( bp_displayed_user_id() ) ) ?></td>
+					<td class="yes"><input type="radio" name="notifications[notification_activity_new_mention]" id="notification-activity-new-mention-yes" value="yes" <?php checked( $mention, 'yes', true ) ?>/><label for="notification-activity-new-mention-yes" class="bp-screen-reader-text"><?php _e( 'Yes, send email', 'buddypress' ); ?></label></td>
+					<td class="no"><input type="radio" name="notifications[notification_activity_new_mention]" id="notification-activity-new-mention-no" value="no" <?php checked( $mention, 'no', true ) ?>/><label for="notification-activity-new-mention-no" class="bp-screen-reader-text"><?php _e( 'No, do not send email', 'buddypress' ); ?></label></td>
+				</tr>
+			<?php endif; ?>
+
+			<tr id="activity-notification-settings-replies">
+				<td>&nbsp;</td>
+				<td><?php _e( "A member replies to an update or comment you've posted", 'buddypress' ) ?></td>
+				<td class="yes"><input type="radio" name="notifications[notification_activity_new_reply]" id="notification-activity-new-reply-yes" value="yes" <?php checked( $reply, 'yes', true ) ?>/><label for="notification-activity-new-reply-yes" class="bp-screen-reader-text"><?php _e( 'Yes, send email', 'buddypress' ); ?></label></td>
+				<td class="no"><input type="radio" name="notifications[notification_activity_new_reply]" id="notification-activity-new-reply-no" value="no" <?php checked( $reply, 'no', true ) ?>/><label for="notification-activity-new-reply-no" class="bp-screen-reader-text"><?php _e( 'No, do not send email', 'buddypress' ); ?></label></td>
+			</tr>
+
+			<?php
+
+			/**
+			 * Fires inside the closing </tbody> tag for activity screen notification settings.
+			 *
+			 * @since 1.2.0
+			 */
+			do_action( 'bp_activity_screen_notification_settings' ) ?>
+		</tbody>
+	</table>
+
+<?php
+}
+add_action( 'bp_notification_settings', 'bp_activity_screen_notification_settings', 1 );
+
+/** Theme Compatibility *******************************************************/
+
+/**
+ * The main theme compat class for BuddyPress Activity.
+ *
+ * This class sets up the necessary theme compatibility actions to safely output
+ * activity template parts to the_title and the_content areas of a theme.
+ *
+ * @since 1.7.0
+ */
+class BP_Activity_Theme_Compat {
+
+	/**
+	 * Set up the activity component theme compatibility.
+	 *
+	 * @since 1.7.0
+	 */
+	public function __construct() {
+		add_action( 'bp_setup_theme_compat', array( $this, 'is_activity' ) );
+	}
+
+	/**
+	 * Set up the theme compatibility hooks, if we're looking at an activity page.
+	 *
+	 * @since 1.7.0
+	 */
+	public function is_activity() {
+
+		// Bail if not looking at a group.
+		if ( ! bp_is_activity_component() )
+			return;
+
+		// Activity Directory.
+		if ( ! bp_displayed_user_id() && ! bp_current_action() ) {
+			bp_update_is_directory( true, 'activity' );
+
+			/** This action is documented in bp-activity/bp-activity-screens.php */
+			do_action( 'bp_activity_screen_index' );
+
+			add_filter( 'bp_get_buddypress_template',                array( $this, 'directory_template_hierarchy' ) );
+			add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'directory_dummy_post' ) );
+			add_filter( 'bp_replace_the_content',                    array( $this, 'directory_content'    ) );
+
+		// Single activity.
+		} elseif ( bp_is_single_activity() ) {
+			add_filter( 'bp_get_buddypress_template',                array( $this, 'single_template_hierarchy' ) );
+			add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'single_dummy_post' ) );
+			add_filter( 'bp_replace_the_content',                    array( $this, 'single_dummy_content'    ) );
+		}
+	}
+
+	/** Directory *************************************************************/
+
+	/**
+	 * Add template hierarchy to theme compat for the activity directory page.
+	 *
+	 * This is to mirror how WordPress has {@link https://codex.wordpress.org/Template_Hierarchy template hierarchy}.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $templates The templates from bp_get_theme_compat_templates().
+	 * @return array $templates Array of custom templates to look for.
+	 */
+	public function directory_template_hierarchy( $templates ) {
+
+		/**
+		 * Filters the template hierarchy for the activity directory page.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param array $index-directory Array holding template names to be merged into template list.
+		 */
+		$new_templates = apply_filters( 'bp_template_hierarchy_activity_directory', array(
+			'activity/index-directory.php'
+		) );
+
+		// Merge new templates with existing stack
+		// @see bp_get_theme_compat_templates().
+		$templates = array_merge( (array) $new_templates, $templates );
+
+		return $templates;
+	}
+
+	/**
+	 * Update the global $post with directory data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function directory_dummy_post() {
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => bp_get_directory_title( 'activity' ),
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with the groups index template part.
+	 *
+	 * @since 1.7.0
+	 */
+	public function directory_content() {
+		return bp_buffer_template_part( 'activity/index', null, false );
+	}
+
+	/** Single ****************************************************************/
+
+	/**
+	 * Add custom template hierarchy to theme compat for activity permalink pages.
+	 *
+	 * This is to mirror how WordPress has {@link https://codex.wordpress.org/Template_Hierarchy template hierarchy}.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $templates The templates from bp_get_theme_compat_templates().
+	 * @return array $templates Array of custom templates to look for.
+	 */
+	public function single_template_hierarchy( $templates ) {
+
+		/**
+		 * Filters the template hierarchy for the activity permalink pages.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param array $index Array holding template names to be merged into template list.
+		 */
+		$new_templates = apply_filters( 'bp_template_hierarchy_activity_single_item', array(
+			'activity/single/index.php'
+		) );
+
+		// Merge new templates with existing stack
+		// @see bp_get_theme_compat_templates().
+		$templates = array_merge( (array) $new_templates, $templates );
+
+		return $templates;
+	}
+
+	/**
+	 * Update the global $post with the displayed user's data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function single_dummy_post() {
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => __( 'Activity', 'buddypress' ),
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with the members' activity permalink template part.
+	 *
+	 * @since 1.7.0
+	 */
+	public function single_dummy_content() {
+		return bp_buffer_template_part( 'activity/single/home', null, false );
+	}
+}
+new BP_Activity_Theme_Compat();
Index: /tags/2.4.3/src/bp-activity/bp-activity-template.php
===================================================================
--- /tags/2.4.3/src/bp-activity/bp-activity-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/bp-activity-template.php	(revision 10410)
@@ -0,0 +1,4524 @@
+<?php
+/**
+ * BuddyPress Activity Template Functions.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityTemplate
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the activity component slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activity_slug()
+ */
+function bp_activity_slug() {
+	echo bp_get_activity_slug();
+}
+	/**
+	 * Return the activity component slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses apply_filters() To call the 'bp_get_activity_slug' hook.
+	 *
+	 * @return string The activity component slug.
+	 */
+	function bp_get_activity_slug() {
+
+		/**
+		 * Filters the activity component slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $slug Activity component slug.
+		 */
+		return apply_filters( 'bp_get_activity_slug', buddypress()->activity->slug );
+	}
+
+/**
+ * Output the activity component root slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activity_root_slug()
+ */
+function bp_activity_root_slug() {
+	echo bp_get_activity_root_slug();
+}
+	/**
+	 * Return the activity component root slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses apply_filters() To call the 'bp_get_activity_root_slug' hook.
+	 *
+	 * @return string The activity component root slug.
+	 */
+	function bp_get_activity_root_slug() {
+
+		/**
+		 * Filters the activity component root slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $root_slug Activity component root slug.
+		 */
+		return apply_filters( 'bp_get_activity_root_slug', buddypress()->activity->root_slug );
+	}
+
+/**
+ * Output activity directory permalink.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activity_directory_permalink()
+ */
+function bp_activity_directory_permalink() {
+	echo esc_url( bp_get_activity_directory_permalink() );
+}
+	/**
+	 * Return activity directory permalink.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses trailingslashit()
+	 * @uses bp_get_root_domain()
+	 * @uses bp_get_activity_root_slug()
+	 * @uses apply_filters() To call the 'bp_get_activity_directory_permalink' hook.
+	 *
+	 * @return string Activity directory permalink.
+	 */
+	function bp_get_activity_directory_permalink() {
+
+		/**
+		 * Filters the activity directory permalink.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $url Permalink url for the activity directory.
+		 */
+		return apply_filters( 'bp_get_activity_directory_permalink', trailingslashit( bp_get_root_domain() . '/' . bp_get_activity_root_slug() ) );
+	}
+
+/**
+ * The main activity template loop class.
+ *
+ * This is responsible for loading a group of activity items and displaying them.
+ *
+ * @since 1.0.0
+ */
+class BP_Activity_Template {
+
+	/**
+	 * The loop iterator.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $current_activity = -1;
+
+	/**
+	 * The activity count.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $activity_count;
+
+	/**
+	 * The total activity count.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $total_activity_count;
+
+	/**
+	 * Array of activities located by the query.
+	 *
+	 * @since 1.5.0
+	 * @var array
+	 */
+	public $activities;
+
+	/**
+	 * The activity object currently being iterated on.
+	 *
+	 * @since 1.5.0
+	 * @var object
+	 */
+	public $activity;
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @since 1.5.0
+	 * @var bool
+	 */
+	public $in_the_loop;
+
+	/**
+	 * URL parameter key for activity pagination. Default: 'acpage'.
+	 *
+	 * @since 2.1.0
+	 * @var string
+	 */
+	public $pag_arg;
+
+	/**
+	 * The page number being requested.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $pag_page;
+
+	/**
+	 * The number of items being requested per page.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $pag_num;
+
+	/**
+	 * An HTML string containing pagination links.
+	 *
+	 * @since 1.5.0
+	 * @var string
+	 */
+	public $pag_links;
+
+	/**
+	 * The displayed user's full name.
+	 *
+	 * @since 1.5.0
+	 * @var string
+	 */
+	public $full_name;
+
+	/**
+	 * Constructor method.
+	 *
+	 * The arguments passed to this class constructor are of the same
+	 * format as {@link BP_Activity_Activity::get()}.
+	 *
+	 * @see BP_Activity_Activity::get() for a description of the argument
+	 *      structure, as well as default values.
+	 *
+	 * @param array $args {
+	 *     Array of arguments. Supports all arguments from
+	 *     BP_Activity_Activity::get(), as well as 'page_arg' and
+	 *     'include'. Default values for 'per_page' and 'display_comments'
+	 *     differ from the originating function, and are described below.
+	 *     @type string      $page_arg         The string used as a query parameter in
+	 *                                         pagination links. Default: 'acpage'.
+	 *     @type array|bool  $include          Pass an array of activity IDs to
+	 *                                         retrieve only those items, or false to noop the 'include'
+	 *                                         parameter. 'include' differs from 'in' in that 'in' forms
+	 *                                         an IN clause that works in conjunction with other filters
+	 *                                         passed to the function, while 'include' is interpreted as
+	 *                                         an exact list of items to retrieve, which skips all other
+	 *                                         filter-related parameters. Default: false.
+	 *     @type int|bool    $per_page         Default: 20.
+	 *     @type string|bool $display_comments Default: 'threaded'.
+	 * }
+	 */
+	public function __construct( $args ) {
+		$bp = buddypress();
+
+		// Backward compatibility with old method of passing arguments.
+		if ( !is_array( $args ) || func_num_args() > 1 ) {
+			_deprecated_argument( __METHOD__, '1.6', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+			$old_args_keys = array(
+				0 => 'page',
+				1 => 'per_page',
+				2 => 'max',
+				3 => 'include',
+				4 => 'sort',
+				5 => 'filter',
+				6 => 'search_terms',
+				7 => 'display_comments',
+				8 => 'show_hidden',
+				9 => 'exclude',
+				10 => 'in',
+				11 => 'spam',
+				12 => 'page_arg'
+			);
+
+			$func_args = func_get_args();
+			$args = bp_core_parse_args_array( $old_args_keys, $func_args );
+		}
+
+		$defaults = array(
+			'page'              => 1,
+			'per_page'          => 20,
+			'page_arg'          => 'acpage',
+			'max'               => false,
+			'fields'            => 'all',
+			'count_total'       => false,
+			'sort'              => false,
+			'include'           => false,
+			'exclude'           => false,
+			'in'                => false,
+			'filter'            => false,
+			'scope'             => false,
+			'search_terms'      => false,
+			'meta_query'        => false,
+			'date_query'        => false,
+			'filter_query'      => false,
+			'display_comments'  => 'threaded',
+			'show_hidden'       => false,
+			'spam'              => 'ham_only',
+			'update_meta_cache' => true,
+		);
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r );
+
+		$this->pag_arg  = sanitize_key( $r['page_arg'] );
+		$this->pag_page = bp_sanitize_pagination_arg( $this->pag_arg, $r['page']     );
+		$this->pag_num  = bp_sanitize_pagination_arg( 'num',          $r['per_page'] );
+
+		// Check if blog/forum replies are disabled.
+		$this->disable_blogforum_replies = (bool) bp_core_get_root_option( 'bp-disable-blogforum-comments' );
+
+		// Get an array of the logged in user's favorite activities.
+		$this->my_favs = maybe_unserialize( bp_get_user_meta( bp_loggedin_user_id(), 'bp_favorite_activities', true ) );
+
+		// Fetch specific activity items based on ID's.
+		if ( !empty( $include ) ) {
+			$this->activities = bp_activity_get_specific( array(
+				'activity_ids'      => explode( ',', $include ),
+				'max'               => $max,
+				'count_total'       => $count_total,
+				'page'              => $this->pag_page,
+				'per_page'          => $this->pag_num,
+				'sort'              => $sort,
+				'display_comments'  => $display_comments,
+				'show_hidden'       => $show_hidden,
+				'spam'              => $spam,
+				'update_meta_cache' => $update_meta_cache,
+			) );
+
+		// Fetch all activity items.
+		} else {
+			$this->activities = bp_activity_get( array(
+				'display_comments'  => $display_comments,
+				'max'               => $max,
+				'count_total'       => $count_total,
+				'per_page'          => $this->pag_num,
+				'page'              => $this->pag_page,
+				'sort'              => $sort,
+				'search_terms'      => $search_terms,
+				'meta_query'        => $meta_query,
+				'date_query'        => $date_query,
+				'filter_query'      => $filter_query,
+				'filter'            => $filter,
+				'scope'             => $scope,
+				'show_hidden'       => $show_hidden,
+				'exclude'           => $exclude,
+				'in'                => $in,
+				'spam'              => $spam,
+				'update_meta_cache' => $update_meta_cache,
+			) );
+		}
+
+		// The total_activity_count property will be set only if a
+		// 'count_total' query has taken place.
+		if ( ! is_null( $this->activities['total'] ) ) {
+			if ( ! $max || $max >= (int) $this->activities['total'] ) {
+				$this->total_activity_count = (int) $this->activities['total'];
+			} else {
+				$this->total_activity_count = (int) $max;
+			}
+		}
+
+		$this->has_more_items = $this->activities['has_more_items'];
+
+		$this->activities = $this->activities['activities'];
+
+		if ( $max ) {
+			if ( $max >= count($this->activities) ) {
+				$this->activity_count = count( $this->activities );
+			} else {
+				$this->activity_count = (int) $max;
+			}
+		} else {
+			$this->activity_count = count( $this->activities );
+		}
+
+		$this->full_name = bp_get_displayed_user_fullname();
+
+		// Fetch parent content for activity comments so we do not have to query in the loop.
+		foreach ( (array) $this->activities as $activity ) {
+			if ( 'activity_comment' != $activity->type ) {
+				continue;
+			}
+
+			$parent_ids[] = $activity->item_id;
+		}
+
+		if ( !empty( $parent_ids ) ) {
+			$activity_parents = bp_activity_get_specific( array( 'activity_ids' => $parent_ids ) );
+		}
+
+		if ( !empty( $activity_parents['activities'] ) ) {
+			foreach( $activity_parents['activities'] as $parent ) {
+				$this->activity_parents[$parent->id] = $parent;
+			}
+
+			unset( $activity_parents );
+		}
+
+		if ( (int) $this->total_activity_count && (int) $this->pag_num ) {
+			$this->pag_links = paginate_links( array(
+				'base'      => add_query_arg( $this->pag_arg, '%#%' ),
+				'format'    => '',
+				'total'     => ceil( (int) $this->total_activity_count / (int) $this->pag_num ),
+				'current'   => (int) $this->pag_page,
+				'prev_text' => _x( '&larr;', 'Activity pagination previous text', 'buddypress' ),
+				'next_text' => _x( '&rarr;', 'Activity pagination next text', 'buddypress' ),
+				'mid_size'  => 1,
+				'add_args'  => array(),
+			) );
+		}
+	}
+
+	/**
+	 * Whether there are activity items available in the loop.
+	 *
+	 * @see bp_has_activities()
+	 *
+	 * @return bool True if there are items in the loop, otherwise false.
+	 */
+	function has_activities() {
+		if ( $this->activity_count ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Set up the next activity item and iterate index.
+	 *
+	 * @return object The next activity item to iterate over.
+	 */
+	public function next_activity() {
+		$this->current_activity++;
+		$this->activity = $this->activities[ $this->current_activity ];
+
+		return $this->activity;
+	}
+
+	/**
+	 * Rewind the posts and reset post index.
+	 */
+	public function rewind_activities() {
+		$this->current_activity = -1;
+		if ( $this->activity_count > 0 ) {
+			$this->activity = $this->activities[0];
+		}
+	}
+
+	/**
+	 * Whether there are activity items left in the loop to iterate over.
+	 *
+	 * This method is used by {@link bp_activities()} as part of the while loop
+	 * that controls iteration inside the activities loop, eg:
+	 *     while ( bp_activities() ) { ...
+	 *
+	 * @see bp_activities()
+	 *
+	 * @return bool True if there are more activity items to show,
+	 *              otherwise false.
+	 */
+	public function user_activities() {
+		if ( ( $this->current_activity + 1 ) < $this->activity_count ) {
+			return true;
+		} elseif ( ( $this->current_activity + 1 ) == $this->activity_count ) {
+
+			/**
+			 * Fires right before the rewinding of activity posts.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'activity_loop_end' );
+
+			// Do some cleaning up after the loop.
+			$this->rewind_activities();
+		}
+
+		$this->in_the_loop = false;
+
+		return false;
+	}
+
+	/**
+	 * Set up the current activity item inside the loop.
+	 *
+	 * Used by {@link bp_the_activity()} to set up the current activity item
+	 * data while looping, so that template tags used during that iteration
+	 * make reference to the current activity item.
+	 *
+	 * @see bp_the_activity()
+	 */
+	public function the_activity() {
+
+		$this->in_the_loop = true;
+		$this->activity    = $this->next_activity();
+
+		if ( is_array( $this->activity ) ) {
+			$this->activity = (object) $this->activity;
+		}
+
+		// Loop has just started.
+		if ( $this->current_activity == 0 ) {
+
+			/**
+			 * Fires if the current activity item is the first in the activity loop.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action('activity_loop_start');
+		}
+	}
+}
+
+/**
+ * Initialize the activity loop.
+ *
+ * Based on the $args passed, bp_has_activities() populates the
+ * $activities_template global, enabling the use of BuddyPress templates and
+ * template functions to display a list of activity items.
+ *
+ * @since 1.0.0
+ * @since 2.4.0 Introduced the `$fields` parameter.
+ *
+ * @global object $activities_template {@link BP_Activity_Template}
+ * @uses groups_is_user_member()
+ * @uses bp_current_action()
+ * @uses bp_is_current_action()
+ * @uses bp_get_activity_slug()
+ * @uses bp_action_variable()
+ * @uses wp_parse_args()
+ * @uses bp_is_active()
+ * @uses friends_get_friend_user_ids()
+ * @uses groups_get_user_groups()
+ * @uses bp_activity_get_user_favorites()
+ * @uses apply_filters() To call the 'bp_has_activities' hook.
+ *
+ * @param array|string $args {
+ *     Arguments for limiting the contents of the activity loop. Most arguments
+ *     are in the same format as {@link BP_Activity_Activity::get()}. However,
+ *     because the format of the arguments accepted here differs in a number of
+ *     ways, and because bp_has_activities() determines some default arguments in
+ *     a dynamic fashion, we list all accepted arguments here as well.
+ *
+ *     Arguments can be passed as an associative array, or as a URL querystring
+ *     (eg, 'user_id=4&display_comments=threaded').
+ *
+ *     @type int               $page             Which page of results to fetch. Using page=1 without per_page will result
+ *                                               in no pagination. Default: 1.
+ *     @type int|bool          $per_page         Number of results per page. Default: 20.
+ *     @type string            $page_arg         String used as a query parameter in pagination links. Default: 'acpage'.
+ *     @type int|bool          $max              Maximum number of results to return. Default: false (unlimited).
+ *     @type string            $fields           Activity fields to retrieve. 'all' to fetch entire activity objects,
+ *                                               'ids' to get only the activity IDs. Default 'all'.
+ *     @type string|bool       $count_total      If true, an additional DB query is run to count the total activity items
+ *                                               for the query. Default: false.
+ *     @type string            $sort             'ASC' or 'DESC'. Default: 'DESC'.
+ *     @type array|bool        $exclude          Array of activity IDs to exclude. Default: false.
+ *     @type array|bool        $in               Array of IDs to limit query by (IN). 'in' is intended to be used in
+ *                                               conjunction with other filter parameters. Default: false.
+ *     @type array|bool        $include          Array of exact activity IDs to query. Providing an 'include' array will
+ *                                               override all other filters passed in the argument array. When viewing the
+ *                                               permalink page for a single activity item, this value defaults to the ID of
+ *                                               that item. Otherwise the default is false.
+ *     @type array             $meta_query       Limit by activitymeta by passing an array of meta_query conditions. See
+ *                                               {@link WP_Meta_Query::queries} for a description of the syntax.
+ *     @type array             $date_query       Limit by date by passing an array of date_query conditions. See first
+ *                                               parameter of {@link WP_Date_Query::__construct()} for syntax.
+ *     @type array             $filter_query     Advanced activity filtering.  See {@link BP_Activity_Query::__construct()}.
+ *     @type string            $search_terms     Limit results by a search term. Default: false.
+ *     @type string            $scope            Use a BuddyPress pre-built filter.
+ *                                                 - 'just-me' retrieves items belonging only to a user; this is equivalent
+ *                                                   to passing a 'user_id' argument.
+ *                                                 - 'friends' retrieves items belonging to the friends of a user.
+ *                                                 - 'groups' retrieves items belonging to groups to which a user belongs to.
+ *                                                 - 'favorites' retrieves a user's favorited activity items.
+ *                                                 - 'mentions' retrieves items where a user has received an @-mention.
+ *                                               The default value of 'scope' is set to one of the above if that value
+ *                                               appears in the appropriate place in the URL; eg, 'scope' will be 'groups'
+ *                                               when visiting http://example.com/members/joe/activity/groups/. Otherwise
+ *                                               defaults to false.
+ *     @type int|array|bool    $user_id          The ID(s) of user(s) whose activity should be fetched. Pass a single ID or
+ *                                               an array of IDs. When viewing a user profile page (but not that user's
+ *                                               activity subpages, ie My Friends, My Groups, etc), 'user_id' defaults to
+ *                                               the ID of the displayed user. Otherwise the default is false.
+ *     @type string|array|bool $object           Filters by the `component` column in the database, which is generally the
+ *                                               component ID in the case of BuddyPress components, or the plugin slug in
+ *                                               the case of plugins. For example, 'groups' will limit results to those that
+ *                                               are associated with the BP Groups component. Accepts a single component
+ *                                               string, or an array of multiple components. Defaults to 'groups' when
+ *                                               viewing the page of a single group, the My Groups activity filter, or the
+ *                                               Activity > Groups filter of a user profile. Otherwise defaults to false.
+ *     @type string|array|bool $action           Filters by the `type` column in the database, which is a string
+ *                                               categorizing the activity item (eg, 'new_blog_post', 'created_group').
+ *                                               Accepts a comma-delimited string or an array of types. Default: false.
+ *     @type int|array|bool    $primary_id       Filters by the `item_id` column in the database. The meaning of
+ *                                               'primary_id' differs between components/types; for example, in the case of
+ *                                               'created_group', 'primary_id' is the ID of the group. Accepts a single ID,
+ *                                               or an array of multiple IDs. When viewing a single group, defaults to the
+ *                                               current group ID. When viewing a user's Groups stream page, defaults to the
+ *                                               IDs of the user's groups. Otherwise defaults to false.
+ *     @type int|array|bool    $secondary_id     Filters by the `secondary_item_id` column in the database. The meaning of
+ *                                               'secondary_id' differs between components/types. Accepts a single ID, or an
+ *                                               array of multiple IDs. Defaults to false.
+ *     @type int               $offset           Return only activity items with an ID greater than or equal to this one.
+ *                                               Note that providing an offset will disable pagination. Default: false.
+ *     @type string|bool       $display_comments How to handle activity comments. Possible values:
+ *                                                 - 'threaded' - comments appear in a threaded tree, under their parent
+ *                                                   items.
+ *                                                 - 'stream' - the activity stream is presented in a flat manner, with
+ *                                                   comments sorted in chronological order alongside other activity items.
+ *                                                 - false - don't fetch activity comments at all.
+ *                                               Default: 'threaded'.
+ *     @type bool              $show_hidden      Whether to show items marked hide_sitewide. Defaults to false, except in
+ *                                               the following cases:
+ *                                                 - User is viewing his own activity stream.
+ *                                                 - User is viewing the activity stream of a non-public group of which he
+ *                                                   is a member.
+ *     @type string|bool       $spam             Spam status. 'ham_only', 'spam_only', or false to show all activity
+ *                                               regardless of spam status. Default: 'ham_only'.
+ *     @type bool              $populate_extras  Whether to pre-fetch the activity metadata for the queried items.
+ *                                               Default: true.
+ * }
+ * @return bool Returns true when activities are found, otherwise false.
+ */
+function bp_has_activities( $args = '' ) {
+	global $activities_template;
+
+	// Get BuddyPress.
+	$bp = buddypress();
+
+	/*
+	 * Smart Defaults.
+	 */
+
+	// User filtering.
+	$user_id = bp_displayed_user_id()
+		? bp_displayed_user_id()
+		: false;
+
+	// Group filtering.
+	if ( bp_is_group() ) {
+		$object      = $bp->groups->id;
+		$primary_id  = bp_get_current_group_id();
+		$show_hidden = (bool) ( groups_is_user_member( bp_loggedin_user_id(), $primary_id ) || bp_current_user_can( 'bp_moderate' ) );
+	} else {
+		$object      = false;
+		$primary_id  = false;
+		$show_hidden = false;
+	}
+
+	// The default scope should recognize custom slugs.
+	$scope = array_key_exists( bp_current_action(), (array) $bp->loaded_components )
+		? $bp->loaded_components[ bp_current_action() ]
+		: bp_current_action();
+
+	// Support for permalinks on single item pages: /groups/my-group/activity/124/.
+	$include = bp_is_current_action( bp_get_activity_slug() )
+		? bp_action_variable( 0 )
+		: false;
+
+	$search_terms_default = false;
+	$search_query_arg = bp_core_get_component_search_query_arg( 'activity' );
+	if ( ! empty( $_REQUEST[ $search_query_arg ] ) ) {
+		$search_terms_default = stripslashes( $_REQUEST[ $search_query_arg ] );
+	}
+
+	/*
+	 * Parse Args.
+	 */
+
+	// Note: any params used for filtering can be a single value, or multiple
+	// values comma separated.
+	$r = bp_parse_args( $args, array(
+		'display_comments'  => 'threaded',   // False for none, stream/threaded - show comments in the stream or threaded under items.
+		'include'           => $include,     // Pass an activity_id or string of IDs comma-separated.
+		'exclude'           => false,        // Pass an activity_id or string of IDs comma-separated.
+		'in'                => false,        // Comma-separated list or array of activity IDs among which to search.
+		'sort'              => 'DESC',       // Sort DESC or ASC.
+		'page'              => 1,            // Which page to load.
+		'per_page'          => 20,           // Number of items per page.
+		'page_arg'          => 'acpage',     // See https://buddypress.trac.wordpress.org/ticket/3679.
+		'max'               => false,        // Max number to return.
+		'fields'            => 'all',
+		'count_total'       => false,
+		'show_hidden'       => $show_hidden, // Show activity items that are hidden site-wide?
+		'spam'              => 'ham_only',   // Hide spammed items.
+
+		// Scope - pre-built activity filters for a user (friends/groups/favorites/mentions).
+		'scope'             => $scope,
+
+		// Filtering
+		'user_id'           => $user_id,     // user_id to filter on.
+		'object'            => $object,      // Object to filter on e.g. groups, profile, status, friends.
+		'action'            => false,        // Action to filter on e.g. activity_update, new_forum_post, profile_updated.
+		'primary_id'        => $primary_id,  // Object ID to filter on e.g. a group_id or forum_id or blog_id etc.
+		'secondary_id'      => false,        // Secondary object ID to filter on e.g. a post_id.
+		'offset'            => false,        // Return only items >= this ID.
+		'since'             => false,        // Return only items recorded since this Y-m-d H:i:s date.
+
+		'meta_query'        => false,        // Filter on activity meta. See WP_Meta_Query for format.
+		'date_query'        => false,        // Filter by date. See first parameter of WP_Date_Query for format.
+		'filter_query'      => false,        // Advanced filtering.  See BP_Activity_Query for format.
+
+		// Searching.
+		'search_terms'      => $search_terms_default,
+		'update_meta_cache' => true,
+	), 'has_activities' );
+
+	/*
+	 * Smart Overrides.
+	 */
+
+	// Translate various values for 'display_comments'
+	// This allows disabling comments via ?display_comments=0
+	// or =none or =false. Final true is a strict type check. See #5029.
+	if ( in_array( $r['display_comments'], array( 0, '0', 'none', 'false' ), true ) ) {
+		$r['display_comments'] = false;
+	}
+
+	// Ignore pagination if an offset is passed.
+	if ( ! empty( $r['offset'] ) ) {
+		$r['page'] = 0;
+	}
+
+	// Search terms.
+	if ( ! empty( $_REQUEST['s'] ) && empty( $r['search_terms'] ) ) {
+		$r['search_terms'] = $_REQUEST['s'];
+	}
+
+	// Do not exceed the maximum per page.
+	if ( ! empty( $r['max'] ) && ( (int) $r['per_page'] > (int) $r['max'] ) ) {
+		$r['per_page'] = $r['max'];
+	}
+
+	/**
+	 * Filters whether BuddyPress should enable afilter support.
+	 *
+	 * Support for basic filters in earlier BP versions is disabled by default.
+	 * To enable, put add_filter( 'bp_activity_enable_afilter_support', '__return_true' );
+	 * into bp-custom.php or your theme's functions.php.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value True if BuddyPress should enable afilter support.
+	 */
+	if ( isset( $_GET['afilter'] ) && apply_filters( 'bp_activity_enable_afilter_support', false ) ) {
+		$r['filter'] = array(
+			'object' => $_GET['afilter']
+		);
+	} elseif ( ! empty( $r['user_id'] ) || ! empty( $r['object'] ) || ! empty( $r['action'] ) || ! empty( $r['primary_id'] ) || ! empty( $r['secondary_id'] ) || ! empty( $r['offset'] ) || ! empty( $r['since'] ) ) {
+		$r['filter'] = array(
+			'user_id'      => $r['user_id'],
+			'object'       => $r['object'],
+			'action'       => $r['action'],
+			'primary_id'   => $r['primary_id'],
+			'secondary_id' => $r['secondary_id'],
+			'offset'       => $r['offset'],
+			'since'        => $r['since']
+		);
+	} else {
+		$r['filter'] = false;
+	}
+
+	// If specific activity items have been requested, override the $hide_spam
+	// argument. This prevents backpat errors with AJAX.
+	if ( ! empty( $r['include'] ) && ( 'ham_only' === $r['spam'] ) ) {
+		$r['spam'] = 'all';
+	}
+
+	/*
+	 * Query
+	 */
+
+	$activities_template = new BP_Activity_Template( $r );
+
+	/**
+	 * Filters whether or not there are activity items to display.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool   $value               Whether or not there are activity items to display.
+	 * @param string $activities_template Current activities template being used.
+	 * @param array  $r                   Array of arguments passed into the BP_Activity_Template class.
+	 */
+	return apply_filters( 'bp_has_activities', $activities_template->has_activities(), $activities_template, $r );
+}
+
+/**
+ * Determine if there are still activities left in the loop.
+ *
+ * @since 1.0.0
+ *
+ * @global object $activities_template {@link BP_Activity_Template}
+ * @uses BP_Activity_Template::user_activities() {@link BP_Activity_Template::user_activities()}
+ *
+ * @return bool Returns true when activities are found.
+ */
+function bp_activities() {
+	global $activities_template;
+	return $activities_template->user_activities();
+}
+
+/**
+ * Get the current activity object in the loop.
+ *
+ * @since 1.0.0
+ *
+ * @global object $activities_template {@link BP_Activity_Template}
+ * @uses BP_Activity_Template::the_activity() {@link BP_Activity_Template::the_activity()}
+ *
+ * @return object The current activity within the loop.
+ */
+function bp_the_activity() {
+	global $activities_template;
+	return $activities_template->the_activity();
+}
+
+/**
+ * Output the URL for the Load More link.
+ *
+ * @since 2.1.0
+ */
+function bp_activity_load_more_link() {
+	echo esc_url( bp_get_activity_load_more_link() );
+}
+	/**
+	 * Get the URL for the Load More link.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return string $link
+	 */
+	function bp_get_activity_load_more_link() {
+		global $activities_template;
+
+		$url  = bp_get_requested_url();
+		$link = add_query_arg( $activities_template->pag_arg, $activities_template->pag_page + 1, $url );
+
+		/**
+		 * Filters the Load More link URL.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $link                The "Load More" link URL with appropriate query args.
+		 * @param string $url                 The original URL.
+		 * @param object $activities_template The activity template loop global.
+		 */
+		return apply_filters( 'bp_get_activity_load_more_link', $link, $url, $activities_template );
+	}
+
+/**
+ * Output the activity pagination count.
+ *
+ * @since 1.0.0
+ *
+ * @global object $activities_template {@link BP_Activity_Template}
+ * @uses BP_Activity_Template::the_activity() {@link BP_Activity_Template::the_activity()}
+ */
+function bp_activity_pagination_count() {
+	echo bp_get_activity_pagination_count();
+}
+
+	/**
+	 * Return the activity pagination count.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses bp_core_number_format()
+	 *
+	 * @return string The pagination text.
+	 */
+	function bp_get_activity_pagination_count() {
+		global $activities_template;
+
+		$start_num = intval( ( $activities_template->pag_page - 1 ) * $activities_template->pag_num ) + 1;
+		$from_num  = bp_core_number_format( $start_num );
+		$to_num    = bp_core_number_format( ( $start_num + ( $activities_template->pag_num - 1 ) > $activities_template->total_activity_count ) ? $activities_template->total_activity_count : $start_num + ( $activities_template->pag_num - 1 ) );
+		$total     = bp_core_number_format( $activities_template->total_activity_count );
+
+		if ( 1 == $activities_template->total_activity_count ) {
+			$message = __( 'Viewing 1 item', 'buddypress' );
+		} else {
+			$message = sprintf( _n( 'Viewing %1$s - %2$s of %3$s item', 'Viewing %1$s - %2$s of %3$s items', $activities_template->total_activity_count, 'buddypress' ), $from_num, $to_num, $total );
+		}
+
+		return $message;
+	}
+
+/**
+ * Output the activity pagination links.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_get_activity_pagination_links()
+ */
+function bp_activity_pagination_links() {
+	echo bp_get_activity_pagination_links();
+}
+
+	/**
+	 * Return the activity pagination links.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_pagination_links' hook.
+	 *
+	 * @return string The pagination links.
+	 */
+	function bp_get_activity_pagination_links() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity pagination link output.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $pag_links Output for the activity pagination links.
+		 */
+		return apply_filters( 'bp_get_activity_pagination_links', $activities_template->pag_links );
+	}
+
+/**
+ * Return true when there are more activity items to be shown than currently appear.
+ *
+ * @since 1.5.0
+ *
+ * @global object $activities_template {@link BP_Activity_Template}
+ * @uses apply_filters() To call the 'bp_activity_has_more_items' hook.
+ *
+ * @return bool $has_more_items True if more items, false if not.
+ */
+function bp_activity_has_more_items() {
+	global $activities_template;
+
+	if ( ! empty( $activities_template->has_more_items )  ) {
+		$has_more_items = true;
+	} else {
+		$remaining_pages = 0;
+
+		if ( ! empty( $activities_template->pag_page ) ) {
+			$remaining_pages = floor( ( $activities_template->total_activity_count - 1 ) / ( $activities_template->pag_num * $activities_template->pag_page ) );
+		}
+
+		$has_more_items = (int) $remaining_pages > 0;
+	}
+
+	/**
+	 * Filters whether there are more activity items to display.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $has_more_items Whether or not there are more activity items to display.
+	 */
+	return apply_filters( 'bp_activity_has_more_items', $has_more_items );
+}
+
+/**
+ * Output the activity count.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_count()
+ */
+function bp_activity_count() {
+	echo bp_get_activity_count();
+}
+
+	/**
+	 * Return the activity count.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_count' hook.
+	 *
+	 * @return int The activity count.
+	 */
+	function bp_get_activity_count() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity count for the activity template.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $activity_count The count for total activity.
+		 */
+		return apply_filters( 'bp_get_activity_count', (int) $activities_template->activity_count );
+	}
+
+/**
+ * Output the number of activities per page.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_per_page()
+ */
+function bp_activity_per_page() {
+	echo bp_get_activity_per_page();
+}
+
+	/**
+	 * Return the number of activities per page.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_per_page' hook.
+	 *
+	 * @return int The activities per page.
+	 */
+	function bp_get_activity_per_page() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity posts per page value.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $pag_num How many post should be displayed for pagination.
+		 */
+		return apply_filters( 'bp_get_activity_per_page', (int) $activities_template->pag_num );
+	}
+
+/**
+ * Output the activities title.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_get_activities_title()
+ * @todo Deprecate.
+ */
+function bp_activities_title() {
+	echo bp_get_activities_title();
+}
+
+	/**
+	 * Return the activities title.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global string $bp_activity_title
+	 * @uses apply_filters() To call the 'bp_get_activities_title' hook.
+	 * @todo Deprecate.
+	 *
+	 * @return string The activities title.
+	 */
+	function bp_get_activities_title() {
+		global $bp_activity_title;
+
+		/**
+		 * Filters the activities title for the activity template.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $bp_activity_title The title to be displayed.
+		 */
+		return apply_filters( 'bp_get_activities_title', $bp_activity_title );
+	}
+
+/**
+ * {@internal Missing Description}
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_get_activities_no_activity()
+ * @todo Deprecate.
+ */
+function bp_activities_no_activity() {
+	echo bp_get_activities_no_activity();
+}
+
+	/**
+	 * {@internal Missing Description}
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global string $bp_activity_no_activity
+	 * @uses apply_filters() To call the 'bp_get_activities_no_activity' hook.
+	 * @todo Deprecate.
+	 *
+	 * @return string
+	 */
+	function bp_get_activities_no_activity() {
+		global $bp_activity_no_activity;
+
+		/**
+		 * Filters the text used when there is no activity to display.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $bp_activity_no_activity Text to display for no activity.
+		 */
+		return apply_filters( 'bp_get_activities_no_activity', $bp_activity_no_activity );
+	}
+
+/**
+ * Output the activity ID.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_id()
+ */
+function bp_activity_id() {
+	echo bp_get_activity_id();
+}
+
+	/**
+	 * Return the activity ID.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_id' hook.
+	 *
+	 * @return int The activity ID.
+	 */
+	function bp_get_activity_id() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity ID being displayed.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $id The activity ID.
+		 */
+		return apply_filters( 'bp_get_activity_id', $activities_template->activity->id );
+	}
+
+/**
+ * Output the activity item ID.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_item_id()
+ */
+function bp_activity_item_id() {
+	echo bp_get_activity_item_id();
+}
+
+	/**
+	 * Return the activity item ID.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_item_id' hook.
+	 *
+	 * @return int The activity item ID.
+	 */
+	function bp_get_activity_item_id() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity item ID being displayed.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $item_id The activity item ID.
+		 */
+		return apply_filters( 'bp_get_activity_item_id', $activities_template->activity->item_id );
+	}
+
+/**
+ * Output the activity secondary item ID.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_secondary_item_id()
+ */
+function bp_activity_secondary_item_id() {
+	echo bp_get_activity_secondary_item_id();
+}
+
+	/**
+	 * Return the activity secondary item ID.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_secondary_item_id' hook.
+	 *
+	 * @return int The activity secondary item ID.
+	 */
+	function bp_get_activity_secondary_item_id() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity secondary item ID being displayed.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $secondary_item_id The activity secondary item ID.
+		 */
+		return apply_filters( 'bp_get_activity_secondary_item_id', $activities_template->activity->secondary_item_id );
+	}
+
+/**
+ * Output the date the activity was recorded.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_date_recorded()
+ */
+function bp_activity_date_recorded() {
+	echo bp_get_activity_date_recorded();
+}
+
+	/**
+	 * Return the date the activity was recorded.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_date_recorded' hook.
+	 *
+	 * @return string The date the activity was recorded.
+	 */
+	function bp_get_activity_date_recorded() {
+		global $activities_template;
+
+		/**
+		 * Filters the date the activity was recorded.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $date_recorded The activity's date.
+		 */
+		return apply_filters( 'bp_get_activity_date_recorded', $activities_template->activity->date_recorded );
+	}
+
+/**
+ * Output the display name of the member who posted the activity.
+ *
+ * @since 2.1.0
+ *
+ * @uses bp_get_activity_member_display_name()
+ */
+function bp_activity_member_display_name() {
+	echo bp_get_activity_member_display_name();
+}
+
+	/**
+	 * Return the display name of the member who posted the activity.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_member_display_name' hook.
+	 *
+	 * @return string The date the activity was recorded.
+	 */
+	function bp_get_activity_member_display_name() {
+		global $activities_template;
+
+		$retval = isset( $activities_template->activity->display_name )
+			? $activities_template->activity->display_name
+			: '';
+
+		/**
+		 * Filters the display name of the member who posted the activity.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param int $retval Display name for the member who posted.
+		 */
+		return apply_filters( 'bp_get_activity_member_display_name', $retval );
+	}
+
+/**
+ * Output the activity object name.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_object_name()
+ */
+function bp_activity_object_name() {
+	echo bp_get_activity_object_name();
+}
+
+	/**
+	 * Return the activity object name.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_object_name' hook.
+	 *
+	 * @return string The activity object name.
+	 */
+	function bp_get_activity_object_name() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity object name.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $activity_component The activity object name.
+		 */
+		return apply_filters( 'bp_get_activity_object_name', $activities_template->activity->component );
+	}
+
+/**
+ * Output the activity type.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_type()
+ */
+function bp_activity_type() {
+	echo bp_get_activity_type();
+}
+
+	/**
+	 * Return the activity type.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_type' hook.
+	 *
+	 * @return string The activity type.
+	 */
+	function bp_get_activity_type() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity type.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $activity_type The activity type.
+		 */
+		return apply_filters( 'bp_get_activity_type', $activities_template->activity->type );
+	}
+
+	/**
+	 * Output the activity action name.
+	 *
+	 * Just a wrapper for bp_activity_type().
+	 *
+	 * @since 1.2.0
+	 * @deprecated 1.5.0
+	 *
+	 * @todo Properly deprecate in favor of bp_activity_type() and
+	 *       remove redundant echo
+	 *
+	 * @uses bp_activity_type()
+	 */
+	function bp_activity_action_name() { echo bp_activity_type(); }
+
+	/**
+	 * Return the activity type.
+	 *
+	 * Just a wrapper for bp_get_activity_type().
+	 *
+	 * @since 1.2.0
+	 * @deprecated 1.5.0
+	 *
+	 * @todo Properly deprecate in favor of bp_get_activity_type().
+	 *
+	 * @uses bp_get_activity_type()
+	 *
+	 * @return string The activity type.
+	 */
+	function bp_get_activity_action_name() { return bp_get_activity_type(); }
+
+/**
+ * Output the activity user ID.
+ *
+ * @since 1.1.0
+ *
+ * @uses bp_get_activity_user_id()
+ */
+function bp_activity_user_id() {
+	echo bp_get_activity_user_id();
+}
+
+	/**
+	 * Return the activity user ID.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_user_id' hook.
+	 *
+	 * @return int The activity user ID.
+	 */
+	function bp_get_activity_user_id() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity user ID.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int $user_id The activity user ID.
+		 */
+		return apply_filters( 'bp_get_activity_user_id', $activities_template->activity->user_id );
+	}
+
+/**
+ * Output the activity user link.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_user_link()
+ */
+function bp_activity_user_link() {
+	echo bp_get_activity_user_link();
+}
+
+	/**
+	 * Return the activity user link.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses bp_core_get_user_domain()
+	 * @uses apply_filters() To call the 'bp_get_activity_user_link' hook.
+	 *
+	 * @return string $link The activity user link.
+	 */
+	function bp_get_activity_user_link() {
+		global $activities_template;
+
+		if ( empty( $activities_template->activity->user_id ) || empty( $activities_template->activity->user_nicename ) || empty( $activities_template->activity->user_login ) ) {
+			$link = $activities_template->activity->primary_link;
+		} else {
+			$link = bp_core_get_user_domain( $activities_template->activity->user_id, $activities_template->activity->user_nicename, $activities_template->activity->user_login );
+		}
+
+		/**
+		 * Filters the activity user link.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $link The activity user link.
+		 */
+		return apply_filters( 'bp_get_activity_user_link', $link );
+	}
+
+/**
+ * Output the avatar of the user that performed the action.
+ *
+ * @since 1.1.0
+ *
+ * @see bp_get_activity_avatar() for description of arguments.
+ * @uses bp_get_activity_avatar()
+ *
+ * @param array|string $args See {@link bp_get_activity_avatar()} for description.
+ */
+function bp_activity_avatar( $args = '' ) {
+	echo bp_get_activity_avatar( $args );
+}
+	/**
+	 * Return the avatar of the user that performed the action.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @see bp_core_fetch_avatar() For a description of the arguments.
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses bp_is_single_activity()
+	 * @uses wp_parse_args()
+	 * @uses apply_filters() To call the 'bp_get_activity_avatar_object_' . $current_activity_item->component hook.
+	 * @uses apply_filters() To call the 'bp_get_activity_avatar_item_id' hook.
+	 * @uses bp_core_fetch_avatar()
+	 * @uses apply_filters() To call the 'bp_get_activity_avatar' hook.
+	 *
+	 * @param array|string $args  {
+	 *     Arguments are listed here with an explanation of their defaults.
+	 *     For more information about the arguments, see
+	 *     {@link bp_core_fetch_avatar()}.
+	 *     @type string      $alt     Default: 'Profile picture of [user name]' if
+	 *                                activity user name is available, otherwise 'Profile picture'.
+	 *     @type string      $class   Default: 'avatar'.
+	 *     @type string|bool $email   Default: Email of the activity's
+	 *                                associated user, if available. Otherwise false.
+	 *     @type string      $type    Default: 'full' when viewing a single activity
+	 *                                permalink page, otherwise 'thumb'.
+	 *     @type int|bool    $user_id Default: ID of the activity's user.
+	 * }
+	 * @return string User avatar string.
+	 */
+	function bp_get_activity_avatar( $args = '' ) {
+		global $activities_template;
+
+		$bp = buddypress();
+
+		// On activity permalink pages, default to the full-size avatar.
+		$type_default = bp_is_single_activity() ? 'full' : 'thumb';
+
+		// Within the activity comment loop, the current activity should be set
+		// to current_comment. Otherwise, just use activity.
+		$current_activity_item = isset( $activities_template->activity->current_comment ) ? $activities_template->activity->current_comment : $activities_template->activity;
+
+		// Activity user display name.
+		$dn_default  = isset( $current_activity_item->display_name ) ? $current_activity_item->display_name : '';
+
+		// Prepend some descriptive text to alt.
+		$alt_default = !empty( $dn_default ) ? sprintf( __( 'Profile picture of %s', 'buddypress' ), $dn_default ) : __( 'Profile picture', 'buddypress' );
+
+		$defaults = array(
+			'alt'     => $alt_default,
+			'class'   => 'avatar',
+			'email'   => false,
+			'type'    => $type_default,
+			'user_id' => false
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		if ( !isset( $height ) && !isset( $width ) ) {
+
+			// Backpat.
+			if ( isset( $bp->avatar->full->height ) || isset( $bp->avatar->thumb->height ) ) {
+				$height = ( 'full' == $type ) ? $bp->avatar->full->height : $bp->avatar->thumb->height;
+			} else {
+				$height = 20;
+			}
+
+			// Backpat.
+			if ( isset( $bp->avatar->full->width ) || isset( $bp->avatar->thumb->width ) ) {
+				$width = ( 'full' == $type ) ? $bp->avatar->full->width : $bp->avatar->thumb->width;
+			} else {
+				$width = 20;
+			}
+		}
+
+		/**
+		 * Filters the activity avatar object based on current activity item component.
+		 *
+		 * This is a variable filter dependent on the component used.
+		 * Possible hooks are bp_get_activity_avatar_object_blog,
+		 * bp_get_activity_avatar_object_group, and bp_get_activity_avatar_object_user.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $component Component being displayed.
+		 */
+		$object  = apply_filters( 'bp_get_activity_avatar_object_' . $current_activity_item->component, 'user' );
+		$item_id = !empty( $user_id ) ? $user_id : $current_activity_item->user_id;
+
+		/**
+		 * Filters the activity avatar item ID.
+		 *
+		 * @since 1.2.10
+		 *
+		 * @param int $item_id Item ID for the activity avatar.
+		 */
+		$item_id = apply_filters( 'bp_get_activity_avatar_item_id', $item_id );
+
+		// If this is a user object pass the users' email address for Gravatar so we don't have to prefetch it.
+		if ( 'user' == $object && empty( $user_id ) && empty( $email ) && isset( $current_activity_item->user_email ) ) {
+			$email = $current_activity_item->user_email;
+		}
+
+		/**
+		 * Filters the value returned by bp_core_fetch_avatar.
+		 *
+		 * @since 1.1.3
+		 *
+		 * @param array $value Array of arguments calculated for use with bp_core_fetch_avatar.
+		 */
+		return apply_filters( 'bp_get_activity_avatar', bp_core_fetch_avatar( array(
+			'item_id' => $item_id,
+			'object'  => $object,
+			'type'    => $type,
+			'alt'     => $alt,
+			'class'   => $class,
+			'width'   => $width,
+			'height'  => $height,
+			'email'   => $email
+		) ) );
+	}
+
+/**
+ * Output the avatar of the object that action was performed on.
+ *
+ * @since 1.2.0
+ *
+ * @see bp_get_activity_secondary_avatar() for description of arguments.
+ * @uses bp_get_activity_secondary_avatar()
+ *
+ * @param array|string $args See {@link bp_get_activity_secondary_avatar} for description.
+ */
+function bp_activity_secondary_avatar( $args = '' ) {
+	echo bp_get_activity_secondary_avatar( $args );
+}
+
+	/**
+	 * Return the avatar of the object that action was performed on.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @see bp_core_fetch_avatar() for description of arguments.
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses wp_parse_args()
+	 * @uses get_blog_option()
+	 * @uses apply_filters() To call the 'bp_get_activity_secondary_avatar_object_' . $activities_template->activity->component hook.
+	 * @uses apply_filters() To call the 'bp_get_activity_secondary_avatar_item_id' hook.
+	 * @uses bp_core_fetch_avatar()
+	 * @uses apply_filters() To call the 'bp_get_activity_secondary_avatar' hook.
+	 *
+	 * @param array|string $args  {
+	 *     For a complete description of arguments, see {@link bp_core_fetch_avatar()}.
+	 *     @type string      $alt     Default value varies based on current activity
+	 *                                item component.
+	 *     @type string      $type    Default: 'full' when viewing a single activity
+	 *                                permalink page, otherwise 'thumb'.
+	 *     @type string      $class   Default: 'avatar'.
+	 *     @type string|bool $email   Default: email of the activity's user.
+	 *     @type int|bool    $user_id Default: ID of the activity's user.
+	 * }
+	 * @return string The secondary avatar.
+	 */
+	function bp_get_activity_secondary_avatar( $args = '' ) {
+		global $activities_template;
+
+		$r = wp_parse_args( $args, array(
+			'alt'        => '',
+			'type'       => 'thumb',
+			'width'      => 20,
+			'height'     => 20,
+			'class'      => 'avatar',
+			'link_class' => '',
+			'linked'     => true,
+			'email'      => false
+		) );
+		extract( $r, EXTR_SKIP );
+
+		// Set item_id and object (default to user).
+		switch ( $activities_template->activity->component ) {
+			case 'groups' :
+				if ( bp_disable_group_avatar_uploads() ) {
+					return false;
+				}
+
+				$object  = 'group';
+				$item_id = $activities_template->activity->item_id;
+				$link    = '';
+				$name    = '';
+
+				// Only if groups is active.
+				if ( bp_is_active( 'groups' ) ) {
+					$group = groups_get_group( array(
+						'group_id'          => $item_id,
+						'populate_extras'   => false,
+						'update_meta_cache' => false,
+					) );
+					$link  = bp_get_group_permalink( $group );
+					$name  = $group->name;
+				}
+
+				if ( empty( $alt ) ) {
+					$alt = __( 'Group logo', 'buddypress' );
+
+					if ( ! empty( $name ) ) {
+						$alt = sprintf( __( 'Group logo of %s', 'buddypress' ), $name );
+					}
+				}
+
+				break;
+			case 'blogs' :
+				$object  = 'blog';
+				$item_id = $activities_template->activity->item_id;
+				$link    = home_url();
+
+				if ( empty( $alt ) ) {
+					$alt = sprintf( __( 'Profile picture of the author of the site %s', 'buddypress' ), get_blog_option( $item_id, 'blogname' ) );
+				}
+
+				break;
+			case 'friends' :
+				$object  = 'user';
+				$item_id = $activities_template->activity->secondary_item_id;
+				$link    = bp_core_get_userlink( $item_id, false, true );
+
+				if ( empty( $alt ) ) {
+					$alt = sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_core_get_user_displayname( $activities_template->activity->secondary_item_id ) );
+				}
+
+				break;
+			default :
+				$object  = 'user';
+				$item_id = $activities_template->activity->user_id;
+				$email   = $activities_template->activity->user_email;
+				$link    = bp_core_get_userlink( $item_id, false, true );
+
+				if ( empty( $alt ) ) {
+					$alt = sprintf( __( 'Profile picture of %s', 'buddypress' ), $activities_template->activity->display_name );
+				}
+
+				break;
+		}
+
+		/**
+		 * Filters the activity secondary avatar object based on current activity item component.
+		 *
+		 * This is a variable filter dependent on the component used. Possible hooks are
+		 * bp_get_activity_secondary_avatar_object_blog, bp_get_activity_secondary_avatar_object_group,
+		 * and bp_get_activity_secondary_avatar_object_user.
+		 *
+		 * @since 1.2.10
+		 *
+		 * @param string $object Component being displayed.
+		 */
+		$object  = apply_filters( 'bp_get_activity_secondary_avatar_object_' . $activities_template->activity->component, $object );
+
+		/**
+		 * Filters the activity secondary avatar item ID.
+		 *
+		 * @since 1.2.10
+		 *
+		 * @param int $item_id ID for the secondary avatar item.
+		 */
+		$item_id = apply_filters( 'bp_get_activity_secondary_avatar_item_id', $item_id );
+
+		// If we have no item_id or object, there is no avatar to display.
+		if ( empty( $item_id ) || empty( $object ) ) {
+			return false;
+		}
+
+		// Get the avatar.
+		$avatar = bp_core_fetch_avatar( array(
+			'item_id' => $item_id,
+			'object'  => $object,
+			'type'    => $type,
+			'alt'     => $alt,
+			'class'   => $class,
+			'width'   => $width,
+			'height'  => $height,
+			'email'   => $email
+		) );
+
+		if ( !empty( $linked ) ) {
+
+			/**
+			 * Filters the secondary avatar link for current activity.
+			 *
+			 * @since 1.7.0
+			 *
+			 * @param string $link      Link to wrap the avatar image in.
+			 * @param string $component Activity component being acted on.
+			 */
+			$link = apply_filters( 'bp_get_activity_secondary_avatar_link', $link, $activities_template->activity->component );
+
+			/**
+			 * Filters the determined avatar for the secondary activity item.
+			 *
+			 * @since 1.2.10
+			 *
+			 * @param string $avatar Formatted HTML <img> element, or raw avatar URL.
+			 */
+			$avatar = apply_filters( 'bp_get_activity_secondary_avatar', $avatar );
+
+			return sprintf( '<a href="%s" class="%s">%s</a>',
+				$link,
+				$link_class,
+				$avatar
+			);
+		}
+
+		/** This filter is documented in bp-activity/bp-activity-template.php */
+		return apply_filters( 'bp_get_activity_secondary_avatar', $avatar );
+	}
+
+/**
+ * Output the activity action.
+ *
+ * @since 1.2.0
+ *
+ * @param array $args See bp_get_activity_action().
+ * @uses bp_get_activity_action()
+ */
+function bp_activity_action( $args = array() ) {
+	echo bp_get_activity_action( $args );
+}
+
+	/**
+	 * Return the activity action.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters_ref_array() To call the 'bp_get_activity_action_pre_meta' hook.
+	 * @uses bp_insert_activity_meta()
+	 * @uses apply_filters_ref_array() To call the 'bp_get_activity_action' hook.
+	 *
+	 * @param array $args {
+	 *     @type bool $no_timestamp Whether to exclude the timestamp.
+	 * }
+	 *
+	 * @return string The activity action.
+	 */
+	function bp_get_activity_action( $args = array() ) {
+		global $activities_template;
+
+		$r = wp_parse_args( $args, array(
+			'no_timestamp' => false,
+		) );
+
+		/**
+		 * Filters the activity action before the action is inserted as meta.
+		 *
+		 * @since 1.2.10
+		 *
+		 * @param array $value Array containing the current action, the current activity, and the $args array passed into the function.
+		 */
+		$action = apply_filters_ref_array( 'bp_get_activity_action_pre_meta', array(
+			$activities_template->activity->action,
+			&$activities_template->activity,
+			$r
+		) );
+
+		// Prepend the activity action meta (link, time since, etc...).
+		if ( ! empty( $action ) && empty( $r['no_timestamp'] ) ) {
+			$action = bp_insert_activity_meta( $action );
+		}
+
+		/**
+		 * Filters the activity action after the action has been inserted as meta.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param array $value Array containing the current action, the current activity, and the $args array passed into the function.
+		 */
+		return apply_filters_ref_array( 'bp_get_activity_action', array(
+			$action,
+			&$activities_template->activity,
+			$r
+		) );
+	}
+
+/**
+ * Output the activity content body.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_content_body()
+ */
+function bp_activity_content_body() {
+	echo bp_get_activity_content_body();
+}
+
+	/**
+	 * Return the activity content body.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses bp_insert_activity_meta()
+	 * @uses apply_filters_ref_array() To call the 'bp_get_activity_content_body' hook.
+	 *
+	 * @return string The activity content body.
+	 */
+	function bp_get_activity_content_body() {
+		global $activities_template;
+
+		// Backwards compatibility if action is not being used.
+		if ( empty( $activities_template->activity->action ) && ! empty( $activities_template->activity->content ) ) {
+			$activities_template->activity->content = bp_insert_activity_meta( $activities_template->activity->content );
+		}
+
+		/**
+		 * Filters the activity content body.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param array $value Array containing the current activity content body and the current activity.
+		 */
+		return apply_filters_ref_array( 'bp_get_activity_content_body', array( $activities_template->activity->content, &$activities_template->activity ) );
+	}
+
+/**
+ * Does the activity have content?
+ *
+ * @since 1.2.0
+ *
+ * @global object $activities_template {@link BP_Activity_Template}
+ *
+ * @return bool True if activity has content, false otherwise.
+ */
+function bp_activity_has_content() {
+	global $activities_template;
+
+	if ( ! empty( $activities_template->activity->content ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Output the activity content.
+ *
+ * @since 1.0.0
+ * @deprecated 1.5.0
+ *
+ * @todo properly deprecate this function.
+ *
+ * @uses bp_get_activity_content()
+ */
+function bp_activity_content() {
+	echo bp_get_activity_content();
+}
+
+	/**
+	 * Return the activity content.
+	 *
+	 * @since 1.0.0
+	 * @deprecated 1.5.0
+	 *
+	 * @todo properly deprecate this function.
+	 *
+	 * @uses bp_get_activity_action()
+	 * @uses bp_get_activity_content_body()
+	 * @uses apply_filters() To call the 'bp_get_activity_content' hook.
+	 *
+	 * @return string The activity content.
+	 */
+	function bp_get_activity_content() {
+
+		/**
+		 * If you want to filter activity update content, please use
+		 * the filter 'bp_get_activity_content_body'.
+		 *
+		 * This function is mainly for backwards compatibility.
+		 */
+		$content = bp_get_activity_action() . ' ' . bp_get_activity_content_body();
+		return apply_filters( 'bp_get_activity_content', $content );
+	}
+
+/**
+ * Attach metadata about an activity item to the activity content.
+ *
+ * This metadata includes the time since the item was posted (which will appear
+ * as a link to the item's permalink).
+ *
+ * @since 1.2.0
+ *
+ * @global object $activities_template {@link BP_Activity_Template}
+ * @uses bp_core_time_since()
+ * @uses apply_filters_ref_array() To call the 'bp_activity_time_since' hook.
+ * @uses bp_is_single_activity()
+ * @uses bp_activity_get_permalink()
+ * @uses esc_attr__()
+ * @uses apply_filters_ref_array() To call the 'bp_activity_permalink' hook.
+ * @uses apply_filters() To call the 'bp_insert_activity_meta' hook.
+ *
+ * @param string $content The activity content.
+ * @return string The activity content with the metadata string attached.
+ */
+function bp_insert_activity_meta( $content = '' ) {
+	global $activities_template;
+
+	// Strip any legacy time since placeholders from BP 1.0-1.1.
+	$new_content = str_replace( '<span class="time-since">%s</span>', '', $content );
+
+	// Get the time since this activity was recorded.
+	$date_recorded  = bp_core_time_since( $activities_template->activity->date_recorded );
+
+	/**
+	 * Filters the activity item time since markup.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $value Array containing the time since markup and the current activity component.
+	 */
+	$time_since = apply_filters_ref_array( 'bp_activity_time_since', array(
+		'<span class="time-since">' . $date_recorded . '</span>',
+		&$activities_template->activity
+	) );
+
+	// Insert the permalink.
+	if ( ! bp_is_single_activity() ) {
+
+		// Setup variables for activity meta.
+		$activity_permalink = bp_activity_get_permalink( $activities_template->activity->id, $activities_template->activity );
+		$activity_meta      = sprintf( '%1$s <a href="%2$s" class="view activity-time-since" title="%3$s">%4$s</a>',
+			$new_content,
+			$activity_permalink,
+			esc_attr__( 'View Discussion', 'buddypress' ),
+			$time_since
+		);
+
+		/**
+		 * Filters the activity permalink to be added to the activity content.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param array $value Array containing the html markup for the activity permalink, after being parsed by
+		 *                     sprintf and current activity component.
+		 */
+		$new_content = apply_filters_ref_array( 'bp_activity_permalink', array(
+			$activity_meta,
+			&$activities_template->activity
+		) );
+	} else {
+		$new_content .= str_pad( $time_since, strlen( $time_since ) + 2, ' ', STR_PAD_BOTH );
+	}
+
+	/**
+	 * Filters the activity content after activity metadata has been attached.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $content Activity content with the activity metadata added.
+	 */
+	return apply_filters( 'bp_insert_activity_meta', $new_content, $content );
+}
+
+/**
+ * Determine if the current user can delete an activity item.
+ *
+ * @since 1.2.0
+ *
+ * @global object $activities_template {@link BP_Activity_Template}
+ * @uses apply_filters() To call the 'bp_activity_user_can_delete' hook.
+ *
+ * @param object|bool $activity Optional. Falls back on the current item in the loop.
+ * @return bool True if can delete, false otherwise.
+ */
+function bp_activity_user_can_delete( $activity = false ) {
+	global $activities_template;
+
+	// Try to use current activity if none was passed.
+	if ( empty( $activity ) && ! empty( $activities_template->activity ) ) {
+		$activity = $activities_template->activity;
+	}
+
+	// If current_comment is set, we'll use that in place of the main activity.
+	if ( isset( $activity->current_comment ) ) {
+		$activity = $activity->current_comment;
+	}
+
+	// Assume the user cannot delete the activity item.
+	$can_delete = false;
+
+	// Only logged in users can delete activity.
+	if ( is_user_logged_in() ) {
+
+		// Community moderators can always delete activity (at least for now).
+		if ( bp_current_user_can( 'bp_moderate' ) ) {
+			$can_delete = true;
+		}
+
+		// Users are allowed to delete their own activity. This is actually
+		// quite powerful, because doing so also deletes all comments to that
+		// activity item. We should revisit this eventually.
+		if ( isset( $activity->user_id ) && ( (int) $activity->user_id === bp_loggedin_user_id() ) ) {
+			$can_delete = true;
+		}
+
+		// Viewing a single item, and this user is an admin of that item.
+		if ( bp_is_single_item() && bp_is_item_admin() ) {
+			$can_delete = true;
+		}
+	}
+
+	/**
+	 * Filters whether the current user can delete an activity item.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool   $can_delete Whether the user can delete the item.
+	 * @param object $activity   Current activity item object.
+	 */
+	return (bool) apply_filters( 'bp_activity_user_can_delete', $can_delete, $activity );
+}
+
+/**
+ * Output the activity parent content.
+ *
+ * @since 1.2.0
+ *
+ * @see bp_get_activity_parent_content() for a description of arguments.
+ * @uses bp_get_activity_parent_content()
+ *
+ * @param array|string $args See {@link bp_get_activity_parent_content} for description.
+ */
+function bp_activity_parent_content( $args = '' ) {
+	echo bp_get_activity_parent_content($args);
+}
+
+	/**
+	 * Return the activity content.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_parent_content' hook.
+	 *
+	 * @param string $args Unused. Left over from an earlier implementation.
+	 * @return mixed False on failure, otherwise the activity parent content.
+	 */
+	function bp_get_activity_parent_content( $args = '' ) {
+		global $activities_template;
+
+		// Bail if no activity on no item ID.
+		if ( empty( $activities_template->activity ) || empty( $activities_template->activity->item_id ) ) {
+			return false;
+		}
+
+		// Get the ID of the parent activity content.
+		$parent_id = (int) $activities_template->activity->item_id;
+
+		// Bail if no parent content.
+		if ( empty( $activities_template->activity_parents[ $parent_id ] ) ) {
+			return false;
+		}
+
+		// Bail if no action.
+		if ( empty( $activities_template->activity_parents[ $parent_id ]->action ) ) {
+			return false;
+		}
+
+		// Content always includes action.
+		$content = $activities_template->activity_parents[ $parent_id ]->action;
+
+		// Maybe append activity content, if it exists.
+		if ( ! empty( $activities_template->activity_parents[ $parent_id ]->content ) ) {
+			$content .= ' ' . $activities_template->activity_parents[ $parent_id ]->content;
+		}
+
+		// Remove the time since content for backwards compatibility.
+		$content = str_replace( '<span class="time-since">%s</span>', '', $content );
+
+		// Remove images.
+		$content = preg_replace( '/<img[^>]*>/Ui', '', $content );
+
+		/**
+		 * Filters the activity parent content.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $content Content set to be displayed as parent content.
+		 */
+		return apply_filters( 'bp_get_activity_parent_content', $content );
+	}
+
+/**
+ * Output the parent activity's user ID.
+ *
+ * @since 1.7.0
+ */
+function bp_activity_parent_user_id() {
+	echo bp_get_activity_parent_user_id();
+}
+
+	/**
+	 * Return the parent activity's user ID.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @global BP_Activity_Template $activities_template
+	 *
+	 * @return bool|int False if parent activity can't be found, otherwise
+	 *                  the parent activity's user ID.
+	 */
+	function bp_get_activity_parent_user_id() {
+		global $activities_template;
+
+		// Bail if no activity on no item ID.
+		if ( empty( $activities_template->activity ) || empty( $activities_template->activity->item_id ) ) {
+			return false;
+		}
+
+		// Get the ID of the parent activity content.
+		$parent_id = (int) $activities_template->activity->item_id;
+
+		// Bail if no parent item.
+		if ( empty( $activities_template->activity_parents[ $parent_id ] ) ) {
+			return false;
+		}
+
+		// Bail if no parent user ID.
+		if ( empty( $activities_template->activity_parents[ $parent_id ]->user_id ) ) {
+			return false;
+		}
+
+		$retval = $activities_template->activity_parents[ $parent_id ]->user_id;
+
+		/**
+		 * Filters the activity parent item's user ID.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param int $retval ID for the activity parent's user.
+		 */
+		return (int) apply_filters( 'bp_get_activity_parent_user_id', $retval );
+	}
+
+/**
+ * Output whether or not the current activity is in a current user's favorites.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_is_favorite()
+ */
+function bp_activity_is_favorite() {
+	echo bp_get_activity_is_favorite();
+}
+
+	/**
+	 * Return whether the current activity is in a current user's favorites.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_is_favorite' hook.
+	 *
+	 * @return bool True if user favorite, false otherwise.
+	 */
+	function bp_get_activity_is_favorite() {
+		global $activities_template;
+
+		/**
+		 * Filters whether the current activity item is in the current user's favorites.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param bool $value Whether or not the current activity item is in the current user's favorites.
+		 */
+ 		return (bool) apply_filters( 'bp_get_activity_is_favorite', in_array( $activities_template->activity->id, (array) $activities_template->my_favs ) );
+	}
+
+/**
+ * Output the comment markup for an activity item.
+ *
+ * @since 1.2.0
+ *
+ * @todo deprecate $args param
+ *
+ * @param array|string $args See {@link bp_activity_get_comments} for description.
+ */
+function bp_activity_comments( $args = '' ) {
+	echo bp_activity_get_comments( $args );
+}
+
+	/**
+	 * Get the comment markup for an activity item.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @todo deprecate $args param
+	 * @todo Given that checks for children already happen in bp_activity_recurse_comments(),
+	 *       this function can probably be streamlined or removed.
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses bp_activity_recurse_comments()
+	 *
+	 * @param string $args Unused. Left over from an earlier implementation.
+	 * @return bool
+	 */
+	function bp_activity_get_comments( $args = '' ) {
+		global $activities_template;
+
+		if ( empty( $activities_template->activity->children ) ) {
+			return false;
+		}
+
+		bp_activity_recurse_comments( $activities_template->activity );
+	}
+
+		/**
+		 * Loops through a level of activity comments and loads the template for each.
+		 *
+		 * Note: The recursion itself used to happen entirely in this function. Now it is
+		 * split between here and the comment.php template.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @global object $activities_template {@link BP_Activity_Template}
+		 * @uses locate_template()
+		 *
+		 * @param object $comment The activity object currently being recursed.
+		 * @return bool|string
+		 */
+		function bp_activity_recurse_comments( $comment ) {
+			global $activities_template;
+
+			if ( empty( $comment ) ) {
+				return false;
+			}
+
+			if ( empty( $comment->children ) ) {
+				return false;
+			}
+
+			/**
+			 * Filters the opening tag for the template that lists activity comments.
+			 *
+			 * @since 1.6.0
+			 *
+			 * @param string $value Opening tag for the HTML markup to use.
+			 */
+			echo apply_filters( 'bp_activity_recurse_comments_start_ul', '<ul>' );
+			foreach ( (array) $comment->children as $comment_child ) {
+
+				// Put the comment into the global so it's available to filters.
+				$activities_template->activity->current_comment = $comment_child;
+
+				$template = bp_locate_template( 'activity/comment.php', false, false );
+
+				// Backward compatibility. In older versions of BP, the markup was
+				// generated in the PHP instead of a template. This ensures that
+				// older themes (which are not children of bp-default and won't
+				// have the new template) will still work.
+				if ( !$template ) {
+					$template = buddypress()->plugin_dir . '/bp-themes/bp-default/activity/comment.php';
+				}
+
+				load_template( $template, false );
+
+				unset( $activities_template->activity->current_comment );
+			}
+
+			/**
+			 * Filters the closing tag for the template that list activity comments.
+			 *
+			 * @since  1.6.0
+			 *
+			 * @param string $value Closing tag for the HTML markup to use.
+			 */
+			echo apply_filters( 'bp_activity_recurse_comments_end_ul', '</ul>' );
+		}
+
+/**
+ * Utility function that returns the comment currently being recursed.
+ *
+ * @since 1.5.0
+ *
+ * @global object $activities_template {@link BP_Activity_Template}
+ * @uses apply_filters() To call the 'bp_activity_current_comment' hook.
+ *
+ * @return object|bool $current_comment The activity comment currently being
+ *                                      displayed. False on failure.
+ */
+function bp_activity_current_comment() {
+	global $activities_template;
+
+	$current_comment = !empty( $activities_template->activity->current_comment )
+		? $activities_template->activity->current_comment
+		: false;
+
+	/**
+	 * Filters the current comment being recursed.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param object|bool $current_comment The activity comment currently being displayed. False on failure.
+	 */
+	return apply_filters( 'bp_activity_current_comment', $current_comment );
+}
+
+
+/**
+ * Output the ID of the activity comment currently being displayed.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activity_comment_id()
+ */
+function bp_activity_comment_id() {
+	echo bp_get_activity_comment_id();
+}
+
+	/**
+	 * Return the ID of the activity comment currently being displayed.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_activity_comment_id' hook.
+	 *
+	 * @return int|bool $comment_id The ID of the activity comment currently
+	 *                              being displayed, false if none is found.
+	 */
+	function bp_get_activity_comment_id() {
+		global $activities_template;
+
+		$comment_id = isset( $activities_template->activity->current_comment->id ) ? $activities_template->activity->current_comment->id : false;
+
+		/**
+		 * Filters the ID of the activity comment currently being displayed.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int|bool $comment_id ID for the comment currently being displayed.
+		 */
+		return apply_filters( 'bp_activity_comment_id', $comment_id );
+	}
+
+/**
+ * Output the ID of the author of the activity comment currently being displayed.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activity_comment_user_id()
+ */
+function bp_activity_comment_user_id() {
+	echo bp_get_activity_comment_user_id();
+}
+
+	/**
+	 * Return the ID of the author of the activity comment currently being displayed.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_activity_comment_user_id' hook.
+	 *
+	 * @return int|bool $user_id The user_id of the author of the displayed
+	 *                           activity comment. False on failure.
+	 */
+	function bp_get_activity_comment_user_id() {
+		global $activities_template;
+
+		$user_id = isset( $activities_template->activity->current_comment->user_id ) ? $activities_template->activity->current_comment->user_id : false;
+
+		/**
+		 * Filters the ID of the author of the activity comment currently being displayed.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int|bool $user_id ID for the author of the comment currently being displayed.
+		 */
+		return apply_filters( 'bp_activity_comment_user_id', $user_id );
+	}
+
+/**
+ * Output the author link for the activity comment currently being displayed.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activity_comment_user_link()
+ */
+function bp_activity_comment_user_link() {
+	echo bp_get_activity_comment_user_link();
+}
+
+	/**
+	 * Return the author link for the activity comment currently being displayed.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses bp_core_get_user_domain()
+	 * @uses bp_get_activity_comment_user_id()
+	 * @uses apply_filters() To call the 'bp_activity_comment_user_link' hook.
+	 *
+	 * @return string $user_link The URL of the activity comment author's profile.
+	 */
+	function bp_get_activity_comment_user_link() {
+		$user_link = bp_core_get_user_domain( bp_get_activity_comment_user_id() );
+
+		/**
+		 * Filters the author link for the activity comment currently being displayed.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $user_link Link for the author of the activity comment currently being displayed.
+		 */
+		return apply_filters( 'bp_activity_comment_user_link', $user_link );
+	}
+
+/**
+ * Output the author name for the activity comment currently being displayed.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activity_comment_name()
+ */
+function bp_activity_comment_name() {
+	echo bp_get_activity_comment_name();
+}
+
+	/**
+	 * Return the author name for the activity comment currently being displayed.
+	 *
+	 * The use of the 'bp_acomment_name' filter is deprecated. Please use
+	 * 'bp_activity_comment_name'.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_acomment_name' hook.
+	 * @uses apply_filters() To call the 'bp_activity_comment_name' hook.
+	 *
+	 * @return string $name The full name of the activity comment author.
+	 */
+	function bp_get_activity_comment_name() {
+		global $activities_template;
+
+		if ( isset( $activities_template->activity->current_comment->user_fullname ) ) {
+
+			$name = apply_filters( 'bp_acomment_name', $activities_template->activity->current_comment->user_fullname, $activities_template->activity->current_comment );  // Backward compatibility.
+		} else {
+			$name = $activities_template->activity->current_comment->display_name;
+		}
+
+		/**
+		 * Filters the name of the author for the activity comment.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $name Name to be displayed with the activity comment.
+		 */
+		return apply_filters( 'bp_activity_comment_name', $name );
+	}
+
+/**
+ * Output the formatted date_recorded of the activity comment currently being displayed.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activity_comment_date_recorded()
+ */
+function bp_activity_comment_date_recorded() {
+	echo bp_get_activity_comment_date_recorded();
+}
+
+	/**
+	 * Return the formatted date_recorded for the activity comment currently being displayed.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses bp_core_time_since()
+	 * @uses apply_filters() To call the 'bp_activity_comment_date_recorded' hook.
+	 *
+	 * @return string|bool $date_recorded Time since the activity was recorded,
+	 *                                    in the form "%s ago". False on failure.
+	 */
+	function bp_get_activity_comment_date_recorded() {
+
+		/**
+		 * Filters the recorded date of the activity comment currently being displayed.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string|bool Date for the activity comment currently being displayed.
+		 */
+		return apply_filters( 'bp_activity_comment_date_recorded', bp_core_time_since( bp_get_activity_comment_date_recorded_raw() ) );
+	}
+
+/**
+ * Output the date_recorded of the activity comment currently being displayed.
+ *
+ * @since 2.3.0
+ *
+ * @uses bp_get_activity_comment_date_recorded()
+ */
+function bp_activity_comment_date_recorded_raw() {
+	echo bp_get_activity_comment_date_recorded_raw();
+}
+
+	/**
+	 * Return the date_recorded for the activity comment currently being displayed.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses bp_core_time_since()
+	 * @uses apply_filters() To call the 'bp_activity_comment_date_recorded' hook.
+	 *
+	 * @return string|bool $date_recorded Time since the activity was recorded,
+	 *                                    in the form "%s ago". False on failure.
+	 */
+	function bp_get_activity_comment_date_recorded_raw() {
+		global $activities_template;
+
+		/**
+		 * Filters the raw recorded date of the activity comment currently being displayed.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param string|bool Raw date for the activity comment currently being displayed.
+		 */
+		return apply_filters( 'bp_activity_comment_date_recorded', $activities_template->activity->current_comment->date_recorded );
+	}
+
+/**
+ * Output the 'delete' URL for the activity comment currently being displayed.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activity_comment_delete_link()
+ */
+function bp_activity_comment_delete_link() {
+	echo bp_get_activity_comment_delete_link();
+}
+
+	/**
+	 * Gets the 'delete' URL for the activity comment currently being displayed.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses wp_nonce_url()
+	 * @uses bp_get_root_domain()
+	 * @uses bp_get_activity_slug()
+	 * @uses bp_get_activity_comment_id()
+	 * @uses apply_filters() To call the 'bp_activity_comment_delete_link' hook.
+	 *
+	 * @return string $link The nonced URL for deleting the current
+	 *                      activity comment.
+	 */
+	function bp_get_activity_comment_delete_link() {
+		$link = wp_nonce_url( bp_get_root_domain() . '/' . bp_get_activity_slug() . '/delete/' . bp_get_activity_comment_id() . '?cid=' . bp_get_activity_comment_id(), 'bp_activity_delete_link' );
+
+		/**
+		 * Filters the link used for deleting the activity comment currently being displayed.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $link Link to use for deleting the currently displayed activity comment.
+		 */
+		return apply_filters( 'bp_activity_comment_delete_link', $link );
+	}
+
+/**
+ * Output the content of the activity comment currently being displayed.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activity_comment_content()
+ */
+function bp_activity_comment_content() {
+	echo bp_get_activity_comment_content();
+}
+
+	/**
+	 * Return the content of the activity comment currently being displayed.
+	 *
+	 * The content is run through two filters. 'bp_get_activity_content'
+	 * will apply all filters applied to activity items in general. Use
+	 * 'bp_activity_comment_content' to modify the content of activity
+	 * comments only.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_content' hook.
+	 * @uses apply_filters() To call the 'bp_activity_comment_content' hook.
+	 *
+	 * @return string $content The content of the current activity comment.
+	 */
+	function bp_get_activity_comment_content() {
+		global $activities_template;
+
+		/** This filter is documented in bp-activity/bp-activity-template.php */
+		$content = apply_filters( 'bp_get_activity_content', $activities_template->activity->current_comment->content );
+
+		/**
+		 * Filters the content of the current activity comment.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $content The content of the current activity comment.
+		 */
+		return apply_filters( 'bp_activity_comment_content', $content );
+	}
+
+/**
+ * Output the activity comment count.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_activity_get_comment_count()
+ */
+function bp_activity_comment_count() {
+	echo bp_activity_get_comment_count();
+}
+
+	/**
+	 * Return the comment count of an activity item.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses bp_activity_recurse_comment_count()
+	 * @uses apply_filters() To call the 'bp_activity_get_comment_count' hook.
+	 *
+	 * @param array|null $deprecated Deprecated.
+	 * @return int $count The activity comment count.
+	 */
+	function bp_activity_get_comment_count( $deprecated = null ) {
+		global $activities_template;
+
+		// Deprecated notice about $args.
+		if ( ! empty( $deprecated ) ) {
+			_deprecated_argument( __FUNCTION__, '1.2', sprintf( __( '%1$s no longer accepts arguments. See the inline documentation at %2$s for more details.', 'buddypress' ), __FUNCTION__, __FILE__ ) );
+		}
+
+		// Get the count using the purpose-built recursive function.
+		$count = ! empty( $activities_template->activity->children )
+			? bp_activity_recurse_comment_count( $activities_template->activity )
+			: 0;
+
+		/**
+		 * Filters the activity comment count.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $count The activity comment count.
+		 */
+		return apply_filters( 'bp_activity_get_comment_count', (int) $count );
+	}
+
+		/**
+		 * Return the total number of comments to the current comment.
+		 *
+		 * This function recursively adds the total number of comments each
+		 * activity child has, and returns them.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @uses bp_activity_recurse_comment_count()
+		 * @uses apply_filters() To call the 'bp_activity_recurse_comment_count' hook.
+		 *
+		 * @param object $comment Activity comment object.
+		 * @param int    $count The current iteration count.
+		 * @return int $count The activity comment count.
+		 */
+		function bp_activity_recurse_comment_count( $comment, $count = 0 ) {
+
+			// Copy the count.
+			$new_count = $count;
+
+			// Loop through children and recursively count comments.
+			if ( ! empty( $comment->children ) ) {
+				foreach ( (array) $comment->children as $comment ) {
+					$new_count++;
+					$new_count = bp_activity_recurse_comment_count( $comment, $new_count );
+				}
+			}
+
+			/**
+			 * Filters the total number of comments for the current comment.
+			 *
+			 * @since 2.1.0
+			 *
+			 * @param int    $new_count New total count for the current comment.
+			 * @param object $comment   Activity comment object.
+			 * @param int    $count     Current iteration count for the current comment.
+			 */
+			return apply_filters( 'bp_activity_recurse_comment_count', $new_count, $comment, $count );
+		}
+
+/**
+ * Output the depth of the current activity comment.
+ *
+ * @since 2.0.0
+ */
+function bp_activity_comment_depth() {
+	echo bp_activity_get_comment_depth();
+}
+	/**
+	 * Return the current activity comment depth.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @return int $depth Depth for the current activity comment.
+	 */
+	function bp_activity_get_comment_depth() {
+		global $activities_template;
+
+		/**
+		 * Filters the comment depth of the current activity comment.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param int $depth Depth for the current activity comment.
+		 */
+		return apply_filters( 'bp_activity_get_comment_depth', $activities_template->activity->current_comment->depth );
+	}
+
+/**
+ * Output the activity comment link.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_comment_link()
+ */
+function bp_activity_comment_link() {
+	echo bp_get_activity_comment_link();
+}
+
+	/**
+	 * Return the activity comment link.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_comment_link' hook.
+	 *
+	 * @return string The activity comment link.
+	 */
+	function bp_get_activity_comment_link() {
+		global $activities_template;
+
+		/**
+		 * Filters the comment link for the current activity comment.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Constructed URL parameters with activity IDs.
+		 */
+		return apply_filters( 'bp_get_activity_comment_link', '?ac=' . $activities_template->activity->id . '/#ac-form-' . $activities_template->activity->id );
+	}
+
+/**
+ * Output the activity comment form no JavaScript display CSS.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_comment_form_nojs_display()
+ */
+function bp_activity_comment_form_nojs_display() {
+	echo bp_get_activity_comment_form_nojs_display();
+}
+
+	/**
+	 * Return the activity comment form no JavaScript display CSS.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 *
+	 * @return string|bool The activity comment form no JavaScript
+	 *                     display CSS. False on failure.
+	 */
+	function bp_get_activity_comment_form_nojs_display() {
+		global $activities_template;
+
+		if ( isset( $_GET['ac'] ) && ( $_GET['ac'] === ( $activities_template->activity->id . '/' ) ) ) {
+			return 'style="display: block"';
+		}
+
+		return false;
+	}
+
+/**
+ * Output the activity comment form action.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_comment_form_action()
+ */
+function bp_activity_comment_form_action() {
+	echo bp_get_activity_comment_form_action();
+}
+
+	/**
+	 * Return the activity comment form action.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses home_url()
+	 * @uses bp_get_activity_root_slug()
+	 * @uses apply_filters() To call the 'bp_get_activity_comment_form_action' hook.
+	 *
+	 * @return string The activity comment form action.
+	 */
+	function bp_get_activity_comment_form_action() {
+
+		/**
+		 * Filters the activity comment form action URL.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value URL to use in the comment form's action attribute.
+		 */
+		return apply_filters( 'bp_get_activity_comment_form_action', home_url( bp_get_activity_root_slug() . '/reply/' ) );
+	}
+
+/**
+ * Output the activity permalink ID.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_permalink_id()
+ */
+function bp_activity_permalink_id() {
+	echo bp_get_activity_permalink_id();
+}
+
+	/**
+	 * Return the activity permalink ID.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses apply_filters() To call the 'bp_get_activity_permalink_id' hook.
+	 *
+	 * @return string The activity permalink ID.
+	 */
+	function bp_get_activity_permalink_id() {
+
+		/**
+		 * Filters the activity action permalink ID.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Current action for the activity item.
+		 */
+		return apply_filters( 'bp_get_activity_permalink_id', bp_current_action() );
+	}
+
+/**
+ * Output the activity thread permalink.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_permalink_id()
+ */
+function bp_activity_thread_permalink() {
+	echo bp_get_activity_thread_permalink();
+}
+
+	/**
+	 * Return the activity thread permalink.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses bp_activity_get_permalink()
+	 * @uses apply_filters() To call the 'bp_get_activity_thread_permalink' hook.
+	 *
+	 * @return string $link The activity thread permalink.
+	 */
+	function bp_get_activity_thread_permalink() {
+		global $activities_template;
+
+		$link = bp_activity_get_permalink( $activities_template->activity->id, $activities_template->activity );
+
+		/**
+		 * Filters the activity thread permalink.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $link The activity thread permalink.
+		 */
+		return apply_filters( 'bp_get_activity_thread_permalink', $link );
+	}
+
+/**
+ * Output the activity comment permalink.
+ *
+ * @since 1.8.0
+ *
+ * @uses bp_get_activity_permalink_id()
+ */
+function bp_activity_comment_permalink() {
+	echo bp_get_activity_comment_permalink();
+}
+	/**
+	 * Return the activity comment permalink.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @uses bp_activity_get_permalink()
+	 * @uses apply_filters() To call the 'bp_get_activity_comment_permalink' hook.
+	 *
+	 * @return string $link The activity comment permalink.
+	 */
+	function bp_get_activity_comment_permalink() {
+		global $activities_template;
+
+		// Check that comment exists.
+		$comment_id = isset( $activities_template->activity->current_comment->id )
+			? $activities_template->activity->current_comment->id
+			: 0;
+
+		// Setup the comment link.
+		$comment_link = ! empty( $comment_id )
+			? '#acomment-' .$comment_id
+			: false;
+
+		// Append comment ID to end of activity permalink.
+		$link = bp_activity_get_permalink( $activities_template->activity->id, $activities_template->activity ) . $comment_link;
+
+		/**
+		 * Filters the activity comment permalink.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param string $link       Activity comment permalink.
+		 * @param int    $comment_id ID for the current activity comment.
+		 */
+		return apply_filters( 'bp_get_activity_comment_permalink', $link, $comment_id );
+	}
+
+/**
+ * Output the activity favorite link.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_favorite_link()
+ */
+function bp_activity_favorite_link() {
+	echo bp_get_activity_favorite_link();
+}
+
+	/**
+	 * Return the activity favorite link.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses wp_nonce_url()
+	 * @uses home_url()
+	 * @uses bp_get_activity_root_slug()
+	 * @uses apply_filters() To call the 'bp_get_activity_favorite_link' hook.
+	 *
+	 * @return string The activity favorite link.
+	 */
+	function bp_get_activity_favorite_link() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity favorite link.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Constructed link for favoriting the activity comment.
+		 */
+		return apply_filters( 'bp_get_activity_favorite_link', wp_nonce_url( home_url( bp_get_activity_root_slug() . '/favorite/' . $activities_template->activity->id . '/' ), 'mark_favorite' ) );
+	}
+
+/**
+ * Output the activity unfavorite link.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_unfavorite_link()
+ */
+function bp_activity_unfavorite_link() {
+	echo bp_get_activity_unfavorite_link();
+}
+
+	/**
+	 * Return the activity unfavorite link.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses wp_nonce_url()
+	 * @uses home_url()
+	 * @uses bp_get_activity_root_slug()
+	 * @uses apply_filters() To call the 'bp_get_activity_unfavorite_link' hook.
+	 *
+	 * @return string The activity unfavorite link.
+	 */
+	function bp_get_activity_unfavorite_link() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity unfavorite link.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Constructed link for unfavoriting the activity comment.
+		 */
+		return apply_filters( 'bp_get_activity_unfavorite_link', wp_nonce_url( home_url( bp_get_activity_root_slug() . '/unfavorite/' . $activities_template->activity->id . '/' ), 'unmark_favorite' ) );
+	}
+
+/**
+ * Output the activity CSS class.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_get_activity_css_class()
+ */
+function bp_activity_css_class() {
+	echo bp_get_activity_css_class();
+}
+
+	/**
+	 * Return the current activity item's CSS class.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_activity_mini_activity_types' hook.
+	 * @uses bp_activity_get_comment_count()
+	 * @uses bp_activity_can_comment()
+	 * @uses apply_filters() To call the 'bp_get_activity_css_class' hook.
+	 *
+	 * @return string The activity item's CSS class.
+	 */
+	function bp_get_activity_css_class() {
+		global $activities_template;
+
+		/**
+		 * Filters the available mini activity actions available as CSS classes.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param array $value Array of classes used to determine classes applied to HTML element.
+		 */
+		$mini_activity_actions = apply_filters( 'bp_activity_mini_activity_types', array(
+			'friendship_accepted',
+			'friendship_created',
+			'new_blog',
+			'joined_group',
+			'created_group',
+			'new_member'
+		) );
+
+		$class = ' activity-item';
+
+		if ( in_array( $activities_template->activity->type, (array) $mini_activity_actions ) || empty( $activities_template->activity->content ) ) {
+			$class .= ' mini';
+		}
+
+		if ( bp_activity_get_comment_count() && bp_activity_can_comment() ) {
+			$class .= ' has-comments';
+		}
+
+		/**
+		 * Filters the determined classes to add to the HTML element.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Classes to be added to the HTML element.
+		 */
+		return apply_filters( 'bp_get_activity_css_class', $activities_template->activity->component . ' ' . $activities_template->activity->type . $class );
+	}
+
+/**
+ * Output the activity delete link.
+ *
+ * @since 1.1.0
+ *
+ * @uses bp_get_activity_delete_link()
+ */
+function bp_activity_delete_link() {
+	echo bp_get_activity_delete_link();
+}
+
+	/**
+	 * Return the activity delete link.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses bp_get_root_domain()
+	 * @uses bp_get_activity_root_slug()
+	 * @uses bp_is_activity_component()
+	 * @uses bp_current_action()
+	 * @uses wp_get_referer()
+	 * @uses wp_nonce_url()
+	 * @uses apply_filters() To call the 'bp_get_activity_delete_link' hook.
+	 *
+	 * @return string $link Activity delete link. Contains $redirect_to arg
+	 *                      if on single activity page.
+	 */
+	function bp_get_activity_delete_link() {
+
+		$url   = bp_get_activity_delete_url();
+		$class = 'delete-activity';
+
+		// Determine if we're on a single activity page, and customize accordingly.
+		if ( bp_is_activity_component() && is_numeric( bp_current_action() ) ) {
+			$class = 'delete-activity-single';
+		}
+
+		$link = '<a href="' . esc_url( $url ) . '" class="button item-button bp-secondary-action ' . $class . ' confirm" rel="nofollow">' . __( 'Delete', 'buddypress' ) . '</a>';
+
+		/**
+		 * Filters the activity delete link.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $link Activity delete HTML link.
+		 */
+		return apply_filters( 'bp_get_activity_delete_link', $link );
+	}
+
+/**
+ * Output the URL to delete a single activity stream item.
+ *
+ * @since 2.1.0
+ *
+ * @uses bp_get_activity_delete_link()
+ */
+function bp_activity_delete_url() {
+	echo esc_url( bp_get_activity_delete_url() );
+}
+	/**
+	 * Return the URL to delete a single activity item.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses bp_get_root_domain()
+	 * @uses bp_get_activity_root_slug()
+	 * @uses bp_is_activity_component()
+	 * @uses bp_current_action()
+	 * @uses add_query_arg()
+	 * @uses wp_get_referer()
+	 * @uses wp_nonce_url()
+	 * @uses apply_filters() To call the 'bp_get_activity_delete_link' hook.
+	 *
+	 * @return string $link Activity delete link. Contains $redirect_to arg
+	 *                      if on single activity page.
+	 */
+	function bp_get_activity_delete_url() {
+		global $activities_template;
+
+		$url = trailingslashit( bp_get_root_domain() . '/' . bp_get_activity_root_slug() . '/delete/' . $activities_template->activity->id );
+
+		// Determine if we're on a single activity page, and customize accordingly.
+		if ( bp_is_activity_component() && is_numeric( bp_current_action() ) ) {
+			$url = add_query_arg( array( 'redirect_to' => wp_get_referer() ), $url );
+		}
+
+		$url = wp_nonce_url( $url, 'bp_activity_delete_link' );
+
+		/**
+		 * Filters the activity delete URL.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $url Activity delete URL.
+		 */
+		return apply_filters( 'bp_get_activity_delete_url', $url );
+	}
+
+/**
+ * Output the activity latest update link.
+ *
+ * @since 1.2.0
+ *
+ * @see bp_get_activity_latest_update() for description of parameters.
+ * @uses bp_get_activity_latest_update()
+ *
+ * @param int $user_id See {@link bp_get_activity_latest_update()} for description.
+ */
+function bp_activity_latest_update( $user_id = 0 ) {
+	echo bp_get_activity_latest_update( $user_id );
+}
+
+	/**
+	 * Return the activity latest update link.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses bp_is_user_inactive()
+	 * @uses bp_core_is_user_deleted()
+	 * @uses bp_get_user_meta()
+	 * @uses apply_filters() To call the 'bp_get_activity_latest_update_excerpt' hook.
+	 * @uses bp_create_excerpt()
+	 * @uses bp_get_root_domain()
+	 * @uses bp_get_activity_root_slug()
+	 * @uses apply_filters() To call the 'bp_get_activity_latest_update' hook.
+	 *
+	 * @param int $user_id If empty, will fall back on displayed user.
+	 * @return string|bool $latest_update The activity latest update link.
+	 *                                    False on failure.
+	 */
+	function bp_get_activity_latest_update( $user_id = 0 ) {
+
+		if ( empty( $user_id ) ) {
+			$user_id = bp_displayed_user_id();
+		}
+
+		if ( bp_is_user_inactive( $user_id ) ) {
+			return false;
+		}
+
+		if ( !$update = bp_get_user_meta( $user_id, 'bp_latest_update', true ) ) {
+			return false;
+		}
+
+		/**
+		 * Filters the latest update excerpt.
+		 *
+		 * @since 1.2.10
+		 *
+		 * @param string $value The excerpt for the latest update.
+		 */
+		$latest_update = apply_filters( 'bp_get_activity_latest_update_excerpt', trim( strip_tags( bp_create_excerpt( $update['content'], 358 ) ) ) );
+
+		$latest_update = sprintf(
+			'%s <a href="%s">%s</a>',
+			$latest_update,
+			esc_url_raw( bp_activity_get_permalink( $update['id'] ) ),
+			esc_attr__( 'View', 'buddypress' )
+		);
+
+		/**
+		 * Filters the latest update excerpt with view link appended to the end.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $latest_update The latest update with "view" link appended to it.
+		 */
+		return apply_filters( 'bp_get_activity_latest_update', $latest_update );
+	}
+
+/**
+ * Output the activity filter links.
+ *
+ * @since 1.1.0
+ *
+ * @see bp_get_activity_filter_links() for description of parameters.
+ * @uses bp_get_activity_filter_links()
+ *
+ * @param array|bool $args See {@link bp_get_activity_filter_links()} for description.
+ */
+function bp_activity_filter_links( $args = false ) {
+	echo bp_get_activity_filter_links( $args );
+}
+
+	/**
+	 * Return the activity filter links.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @uses wp_parse_args()
+	 * @uses BP_Activity_Activity::get_recorded_components() {@link BP_Activity_Activity}
+	 * @uses esc_attr()
+	 * @uses add_query_arg()
+	 * @uses remove_query_arg()
+	 * @uses apply_filters() To call the 'bp_get_activity_filter_link_href' hook.
+	 * @uses apply_filters() To call the 'bp_get_activity_filter_links' hook.
+	 *
+	 * @param array|bool $args {
+	 *     @type string $style The type of markup to use for the links.
+	 *                         'list', 'paragraph', or 'span'. Default: 'list'.
+	 * }
+	 * @return string|bool $component_links The activity filter links.
+	 *         False on failure.
+	 */
+	function bp_get_activity_filter_links( $args = false ) {
+
+		$r = wp_parse_args( $args, array(
+			'style' => 'list'
+		) );
+
+		// Define local variable.
+		$component_links = array();
+
+		// Fetch the names of components that have activity recorded in the DB.
+		$components = BP_Activity_Activity::get_recorded_components();
+
+		if ( empty( $components ) ) {
+			return false;
+		}
+
+		foreach ( (array) $components as $component ) {
+
+			// Skip the activity comment filter.
+			if ( 'activity' == $component ) {
+				continue;
+			}
+
+			if ( isset( $_GET['afilter'] ) && $component == $_GET['afilter'] ) {
+				$selected = ' class="selected"';
+			} else {
+				$selected = '';
+			}
+
+			$component = esc_attr( $component );
+
+			switch ( $r['style'] ) {
+				case 'list':
+					$tag = 'li';
+					$before = '<li id="afilter-' . $component . '"' . $selected . '>';
+					$after = '</li>';
+				break;
+				case 'paragraph':
+					$tag = 'p';
+					$before = '<p id="afilter-' . $component . '"' . $selected . '>';
+					$after = '</p>';
+				break;
+				case 'span':
+					$tag = 'span';
+					$before = '<span id="afilter-' . $component . '"' . $selected . '>';
+					$after = '</span>';
+				break;
+			}
+
+			$link = add_query_arg( 'afilter', $component );
+			$link = remove_query_arg( 'acpage' , $link );
+
+			/**
+			 * Filters the activity filter link URL for the current activity component.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param string $link      The URL for the current component.
+			 * @param string $component The current component getting links constructed for.
+			 */
+			$link = apply_filters( 'bp_get_activity_filter_link_href', $link, $component );
+
+			$component_links[] = $before . '<a href="' . esc_url( $link ) . '">' . ucwords( $component ) . '</a>' . $after;
+		}
+
+		$link = remove_query_arg( 'afilter' , $link );
+
+		if ( isset( $_GET['afilter'] ) ) {
+			$component_links[] = '<' . $tag . ' id="afilter-clear"><a href="' . esc_url( $link ) . '">' . __( 'Clear Filter', 'buddypress' ) . '</a></' . $tag . '>';
+		}
+
+		/**
+		 * Filters all of the constructed filter links.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value All of the links to be displayed to the user.
+		 */
+ 		return apply_filters( 'bp_get_activity_filter_links', implode( "\n", $component_links ) );
+	}
+
+/**
+ * Determine if a comment can be made on an activity item.
+ *
+ * @since 1.2.0
+ *
+ * @global object $activities_template {@link BP_Activity_Template}
+ * @uses bp_get_activity_action_name()
+ * @uses apply_filters() To call the 'bp_activity_can_comment' hook.
+ *
+ * @return bool $can_comment True if item can receive comments.
+ */
+function bp_activity_can_comment() {
+	global $activities_template;
+	$bp = buddypress();
+
+	// Assume activity can be commented on.
+	$can_comment = true;
+
+	// Determine ability to comment based on activity action name.
+	$activity_action = bp_get_activity_action_name();
+
+	$turn_off = 0;
+	if ( ! empty( $activities_template->disable_blogforum_replies ) ) {
+		$turn_off = 1;
+	}
+
+	$maybe_turn_off = array_fill_keys( array(
+		'new_blog_post',
+		'new_blog_comment',
+		'new_forum_topic',
+		'new_forum_post',
+	), $turn_off );
+
+	$maybe_turn_off['activity_comment'] = 1;
+
+	// Fetch all the tracked post types once.
+	if ( empty( $bp->activity->track ) ) {
+		$bp->activity->track = bp_activity_get_post_types_tracking_args();
+	}
+
+	foreach ( $bp->activity->track as $action => $tracking_args ) {
+		if ( empty( $tracking_args->activity_comment ) ) {
+			$maybe_turn_off[ $action ] = $turn_off;
+		}
+	}
+
+	$can_comment = empty( $maybe_turn_off[ $activity_action ] );
+
+	/**
+	 * Filters whether a comment can be made on an activity item.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool   $can_comment     Status on if activity can be commented on.
+	 * @param string $activity_action Current activity action being checked on.
+	 */
+	return apply_filters( 'bp_activity_can_comment', $can_comment, $activity_action );
+}
+
+/**
+ * Determine whether a comment can be made on an activity reply item.
+ *
+ * @since 1.5.0
+ *
+ * @param  bool|object $comment     Activity comment.
+ * @return bool        $can_comment True if comment can receive comments,
+ *                                  otherwise false.
+ */
+function bp_activity_can_comment_reply( $comment = false ) {
+
+	// Assume activity can be commented on.
+	$can_comment = true;
+
+	// Check that comment exists.
+	if ( empty( $comment ) ) {
+		$comment = bp_activity_current_comment();
+	}
+
+	if ( ! empty( $comment ) ) {
+
+		// Fall back on current comment in activity loop.
+		$comment_depth = isset( $comment->depth )
+			? intval( $comment->depth )
+			: bp_activity_get_comment_depth();
+
+		// Threading is turned on, so check the depth.
+		if ( get_option( 'thread_comments' ) ) {
+			$can_comment = (bool) ( $comment_depth < get_option( 'thread_comments_depth' ) );
+
+		// No threading for comment replies if no threading for comments.
+		} else {
+			$can_comment = false;
+		}
+	}
+
+	/**
+	 * Filters whether a comment can be made on an activity reply item.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool   $can_comment Status on if activity reply can be commented on.
+	 * @param string $comment     Current comment being checked on.
+	 */
+	return (bool) apply_filters( 'bp_activity_can_comment_reply', $can_comment, $comment );
+}
+
+/**
+ * Determine whether favorites are allowed.
+ *
+ * Defaults to true, but can be modified by plugins.
+ *
+ * @since 1.5.0
+ *
+ * @uses apply_filters() To call the 'bp_activity_can_favorite' hook.
+ *
+ * @return bool True if comment can receive comments.
+ */
+function bp_activity_can_favorite() {
+
+	/**
+	 * Filters whether or not users can favorite activity items.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $value Whether or not favoriting is enabled.
+	 */
+	return apply_filters( 'bp_activity_can_favorite', true );
+}
+
+/**
+ * Output the total favorite count for a specified user.
+ *
+ * @since 1.2.0
+ *
+ * @see bp_get_total_favorite_count_for_user() for description of parameters.
+ * @uses bp_get_total_favorite_count_for_user()
+ *
+ * @param int $user_id See {@link bp_get_total_favorite_count_for_user()}.
+ */
+function bp_total_favorite_count_for_user( $user_id = 0 ) {
+	echo bp_get_total_favorite_count_for_user( $user_id );
+}
+
+	/**
+	 * Return the total favorite count for a specified user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses bp_activity_total_favorites_for_user()
+	 * @uses apply_filters() To call the 'bp_get_total_favorite_count_for_user' hook.
+	 *
+	 * @param int $user_id ID of user being queried. Default: displayed user ID.
+	 * @return int The total favorite count for the specified user.
+	 */
+	function bp_get_total_favorite_count_for_user( $user_id = 0 ) {
+		$retval = false;
+
+		if ( bp_activity_can_favorite() ) {
+			// Default to displayed user if none is passed.
+			$user_id = empty( $user_id )
+				? bp_displayed_user_id()
+				: $user_id;
+
+			// Get user meta if user ID exists.
+			if ( ! empty( $user_id ) ) {
+				$retval = bp_activity_total_favorites_for_user( $user_id );
+			}
+		}
+
+		/**
+		 * Filters the total favorite count for a user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int|bool $retval Total favorite count for a user. False on no favorites.
+		 */
+		return apply_filters( 'bp_get_total_favorite_count_for_user', $retval );
+	}
+
+
+/**
+ * Output the total mention count for a specified user.
+ *
+ * @since 1.2.0
+ *
+ * @see bp_get_total_mention_count_for_user() for description of parameters.
+ * @uses bp_get_total_favorite_count_for_user()
+ *
+ * @param int $user_id See {@link bp_get_total_mention_count_for_user()}.
+ */
+function bp_total_mention_count_for_user( $user_id = 0 ) {
+	echo bp_get_total_mention_count_for_user( $user_id );
+}
+
+	/**
+	 * Return the total mention count for a specified user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses bp_get_user_meta()
+	 * @uses apply_filters() To call the 'bp_get_total_mention_count_for_user' hook.
+	 *
+	 * @param int $user_id ID of user being queried. Default: displayed user ID.
+	 * @return int The total mention count for the specified user.
+	 */
+	function bp_get_total_mention_count_for_user( $user_id = 0 ) {
+
+		// Default to displayed user if none is passed.
+		$user_id = empty( $user_id )
+			? bp_displayed_user_id()
+			: $user_id;
+
+		// Get user meta if user ID exists.
+		$retval = ! empty( $user_id )
+			? bp_get_user_meta( $user_id, 'bp_new_mention_count', true )
+			: false;
+
+		/**
+		 * Filters the total mention count for a user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int|bool $retval Total mention count for a user. False on no mentions.
+		 */
+		return apply_filters( 'bp_get_total_mention_count_for_user', $retval );
+	}
+
+/**
+ * Output the public message link for displayed user.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_send_public_message_link()
+ */
+function bp_send_public_message_link() {
+	echo esc_url( bp_get_send_public_message_link() );
+}
+
+	/**
+	 * Return the public message link for the displayed user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses is_user_logged_in()
+	 * @uses bp_is_my_profile()
+	 * @uses bp_is_user()
+	 * @uses wp_nonce_url()
+	 * @uses bp_get_activity_directory_permalink()
+	 * @uses apply_filters() To call the 'bp_get_send_public_message_link' hook.
+	 *
+	 * @return string The public message link for the displayed user.
+	 */
+	function bp_get_send_public_message_link() {
+
+		// No link if not logged in, not looking at someone else's profile.
+		if ( ! is_user_logged_in() || ! bp_is_user() || bp_is_my_profile() ) {
+			$retval = '';
+		} else {
+			$args   = array( 'r' => bp_get_displayed_user_mentionname() );
+			$url    = add_query_arg( $args, bp_get_activity_directory_permalink() );
+			$retval = wp_nonce_url( $url );
+		}
+
+		/**
+		 * Filters the public message link for the displayed user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $retval The URL for the public message link.
+		 */
+		return apply_filters( 'bp_get_send_public_message_link', $retval );
+	}
+
+/**
+ * Recurse through all activity comments and return the activity comment IDs.
+ *
+ * @since 2.0.0
+ *
+ * @param array $activity Array of activities generated from {@link bp_activity_get()}.
+ * @param array $activity_ids Used for recursion purposes in this function.
+ * @return array
+ */
+function bp_activity_recurse_comments_activity_ids( $activity = array(), $activity_ids = array() ) {
+	if ( is_array( $activity ) && ! empty( $activity['activities'] ) ) {
+		$activity = $activity['activities'][0];
+	}
+
+	if ( ! empty( $activity->children ) ) {
+		foreach ($activity->children as $child ) {
+			$activity_ids[] = $child->id;
+
+			if( ! empty( $child->children ) ) {
+				$activity_ids = bp_activity_recurse_comments_activity_ids( $child, $activity_ids );
+			}
+		}
+	}
+
+	return $activity_ids;
+}
+
+/**
+ * Output the mentioned user display name.
+ *
+ * @since 1.2.0
+ *
+ * @see bp_get_mentioned_user_display_name() for description of parameters.
+ * @uses bp_get_mentioned_user_display_name()
+ *
+ * @param int|string|bool $user_id_or_username See {@link bp_get_mentioned_user_display_name()}.
+ */
+function bp_mentioned_user_display_name( $user_id_or_username = false ) {
+	echo bp_get_mentioned_user_display_name( $user_id_or_username );
+}
+
+	/**
+	 * Returns the mentioned user display name.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses bp_core_get_user_displayname()
+	 * @uses apply_filters() To call the 'bp_get_mentioned_user_display_name' hook.
+	 *
+	 * @param int|string|bool $user_id_or_username User ID or username.
+	 * @return string The mentioned user's display name.
+	 */
+	function bp_get_mentioned_user_display_name( $user_id_or_username = false ) {
+
+		// Get user display name.
+		$name = bp_core_get_user_displayname( $user_id_or_username );
+
+		// If user somehow has no name, return this really lame string.
+		if ( empty( $name ) ) {
+			$name = __( 'a user', 'buddypress' );
+		}
+
+		/**
+		 * Filters the mentioned user display name.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string     $name                Display name for the mentioned user.
+		 * @param int|string $user_id_or_username User ID or username use for query.
+		 */
+		return apply_filters( 'bp_get_mentioned_user_display_name', $name, $user_id_or_username );
+	}
+
+/**
+ * Output button for sending a public message (an @-mention).
+ *
+ * @since 1.2.0
+ *
+ * @see bp_get_send_public_message_button() for description of parameters.
+ * @uses bp_get_send_public_message_button()
+ *
+ * @param array|string $args See {@link bp_get_send_public_message_button()}.
+ */
+function bp_send_public_message_button( $args = '' ) {
+	echo bp_get_send_public_message_button( $args );
+}
+
+	/**
+	 * Return button for sending a public message (an @-mention).
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses bp_get_send_public_message_link()
+	 * @uses wp_parse_args()
+	 * @uses bp_get_button()
+	 * @uses apply_filters() To call the 'bp_get_send_public_message_button' hook.
+	 *
+	 * @param array|string $args {
+	 *     All arguments are optional. See {@link BP_Button} for complete
+	 *     descriptions.
+	 *     @type string $id                Default: 'public_message'.
+	 *     @type string $component         Default: 'activity'.
+	 *     @type bool   $must_be_logged_in Default: true.
+	 *     @type bool   $block_self        Default: true.
+	 *     @type string $wrapper_id        Default: 'post-mention'.
+	 *     @type string $link_href         Default: the public message link for
+	 *                                     the current member in the loop.
+	 *     @type string $link_title        Default: 'Send a public message on your
+	 *                                     activity stream.'.
+	 *     @type string $link_text         Default: 'Public Message'.
+	 *     @type string $link_class        Default: 'activity-button mention'.
+	 * }
+	 * @return string The button for sending a public message.
+	 */
+	function bp_get_send_public_message_button( $args = '' ) {
+
+		$r = bp_parse_args( $args, array(
+			'id'                => 'public_message',
+			'component'         => 'activity',
+			'must_be_logged_in' => true,
+			'block_self'        => true,
+			'wrapper_id'        => 'post-mention',
+			'link_href'         => bp_get_send_public_message_link(),
+			'link_title'        => __( 'Send a public message on your activity stream.', 'buddypress' ),
+			'link_text'         => __( 'Public Message', 'buddypress' ),
+			'link_class'        => 'activity-button mention'
+		) );
+
+		/**
+		 * Filters the public message button HTML.
+		 *
+		 * @since 1.2.10
+		 *
+		 * @param array $r Array of arguments for the public message button HTML.
+		 */
+		return bp_get_button( apply_filters( 'bp_get_send_public_message_button', $r ) );
+	}
+
+/**
+ * Output the activity post form action.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_activity_post_form_action()
+ */
+function bp_activity_post_form_action() {
+	echo bp_get_activity_post_form_action();
+}
+
+	/**
+	 * Return the activity post form action.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses home_url()
+	 * @uses bp_get_activity_root_slug()
+	 * @uses apply_filters() To call the 'bp_get_activity_post_form_action' hook.
+	 *
+	 * @return string The activity post form action.
+	 */
+	function bp_get_activity_post_form_action() {
+
+		/**
+		 * Filters the action url used for the activity post form.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value URL to be used for the activity post form.
+		 */
+		return apply_filters( 'bp_get_activity_post_form_action', home_url( bp_get_activity_root_slug() . '/post/' ) );
+	}
+
+/**
+ * Echo a list of linked avatars of users who have commented on the current activity item.
+ *
+ * Use this function to easily output activity comment authors' avatars.
+ *
+ * Avatars are wrapped in <li> elements, but you've got to provide your own
+ * <ul> or <ol> wrapper markup.
+ *
+ * @since 1.7.0
+ *
+ * @see bp_core_fetch_avatar() for a description of arguments.
+ *
+ * @param array $args See {@link bp_core_fetch_avatar()}.
+ */
+function bp_activity_comments_user_avatars( $args = array() ) {
+
+	$r = bp_parse_args( $args, array(
+		'height' => false,
+		'html'   => true,
+		'type'   => 'thumb',
+		'width'  => false,
+	) );
+
+	// Get the user IDs of everyone who has left a comment to the current activity item.
+	$user_ids = bp_activity_get_comments_user_ids();
+	$output   = array();
+	$retval   = '';
+
+	if ( ! empty( $user_ids ) ) {
+		foreach ( (array) $user_ids as $user_id ) {
+
+			// Skip an empty user ID.
+			if ( empty( $user_id ) ) {
+				continue;
+			}
+
+			// Get profile link for this user.
+			$profile_link = bp_core_get_user_domain( $user_id );
+
+			// Get avatar for this user.
+			$image_html   = bp_core_fetch_avatar( array(
+				'item_id' => $user_id,
+				'height'  => $r['height'],
+				'html'    => $r['html'],
+				'type'    => $r['type'],
+				'width'   => $r['width']
+			) );
+
+			// If user has link & avatar, add them to the output array.
+			if ( ! empty( $profile_link ) && ! empty( $image_html ) ) {
+				$output[] = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $profile_link ), $image_html );
+			}
+		}
+
+		// If output array is not empty, wrap everything in some list items.
+		if ( ! empty( $output ) ) {
+			$retval = '<li>' . implode( '</li><li>', $output ) . '</li>';
+		}
+	}
+
+	/**
+	 * Filters the list of linked avatars for users who have commented on the current activity item.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $retval HTML markup for the list of avatars.
+	 * @param array  $r      Array of arguments used for each avatar.
+	 * @param array  $output Array of each avatar found, before imploded into single string.
+	 */
+	echo apply_filters( 'bp_activity_comments_user_avatars', $retval, $r, $output );
+}
+
+/**
+ * Return the IDs of every user who's left a comment on the current activity item.
+ *
+ * @since 1.7.0
+ *
+ * @return bool|array An array of IDs, or false if none are found.
+ */
+function bp_activity_get_comments_user_ids() {
+	global $activities_template;
+
+	$user_ids = ! empty( $activities_template->activity->children )
+		? (array) bp_activity_recurse_comments_user_ids( $activities_template->activity->children )
+		: array();
+
+	/**
+	 * Filters the list of user IDs for the current activity item.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $value Array of unique user IDs for the current activity item.
+	 */
+	return apply_filters( 'bp_activity_get_comments_user_ids', array_unique( $user_ids ) );
+}
+
+	/**
+	 * Recurse through all activity comments and collect the IDs of the users who wrote them.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $comments Array of {@link BP_Activity_Activity} items.
+	 * @return array Array of user IDs.
+	 */
+	function bp_activity_recurse_comments_user_ids( array $comments = array() ) {
+
+		// Default user ID's array.
+		$user_ids = array();
+
+		// Loop through comments and try to get user ID's.
+		if ( ! empty( $comments ) ) {
+			foreach ( $comments as $comment ) {
+
+				// If a user is a spammer, their activity items will have been
+				// automatically marked as spam. Skip these.
+				if ( ! empty( $comment->is_spam ) ) {
+					continue;
+				}
+
+				// Add user ID to array.
+				$user_ids[] = $comment->user_id;
+
+				// Check for commentception.
+				if ( ! empty( $comment->children ) ) {
+					$user_ids = array_merge( $user_ids, bp_activity_recurse_comments_user_ids( $comment->children ) );
+				}
+			}
+		}
+
+		/**
+		 * Filters the list of user IDs for the current activity comment item.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param array $user_ids Array of user IDs for the current activity comment item.
+		 * @param array $comments Array of comments being checked for user IDs.
+		 */
+		return apply_filters( 'bp_activity_recurse_comments_user_ids', $user_ids, $comments );
+	}
+
+/**
+ * Output the mentionname for the displayed user.
+ *
+ * @since 1.9.0
+ */
+function bp_displayed_user_mentionname() {
+	echo bp_get_displayed_user_mentionname();
+}
+	/**
+	 * Get the mentionname for the displayed user.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return string Mentionname for the displayed user, if available.
+	 */
+	function bp_get_displayed_user_mentionname() {
+
+		/**
+		 * Filters the mentionname for the displayed user.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $value The mentionanme for the displayed user.
+		 */
+		return apply_filters( 'bp_get_displayed_user_mentionname', bp_activity_get_user_mentionname( bp_displayed_user_id() ) );
+	}
+
+/**
+ * Echo a list of all registered activity types for use in dropdowns or checkbox lists.
+ *
+ * @since 1.7.0
+ *
+ * @param string       $output Optional. Either 'select' or 'checkbox'. Default: 'select'.
+ * @param array|string $args {
+ *     Optional extra arguments.
+ *     @type string       $checkbox_name When returning checkboxes, sets the 'name'
+ *                                       attribute.
+ *     @type array|string $selected      A list of types that should be checked/
+ *                                       selected.
+ * }
+ */
+function bp_activity_types_list( $output = 'select', $args = '' ) {
+
+	$args = bp_parse_args( $args, array(
+		'checkbox_name' => 'bp_activity_types',
+		'selected'      => array(),
+	) );
+
+	$activities = bp_activity_get_types();
+	natsort( $activities );
+
+	// Loop through the activity types and output markup.
+	foreach ( $activities as $type => $description ) {
+
+		// See if we need to preselect the current type.
+		$checked  = checked(  true, in_array( $type, (array) $args['selected'] ), false );
+		$selected = selected( true, in_array( $type, (array) $args['selected'] ), false );
+
+		// Switch output based on the element.
+		switch ( $output ) {
+			case 'select' :
+				printf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $type ), $selected, esc_html( $description ) );
+				break;
+			case 'checkbox' :
+				printf( '<label style="" for="%1$s[]">%2$s<input type="checkbox" id="%1$s[]" name="%1$s[]" value="%3$s" %4$s/></label>', esc_attr( $args['checkbox_name'] ), esc_html( $description ), esc_attr( $args['checkbox_name'] ), esc_attr( $args['checkbox_name'] ), esc_attr( $type ), $checked );
+				break;
+		}
+
+		/**
+		 * Fires at the end of the listing of activity types.
+		 *
+		 * This is a variable action hook. The actual hook to use will depend on the output type specified.
+		 * Two default hooks are bp_activity_types_list_select and bp_activity_types_list_checkbox.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param array  $args        Array of arguments passed into function.
+		 * @param string $type        Activity type being rendered in the output.
+		 * @param string $description Description of the activity type being rendered.
+		 */
+		do_action( 'bp_activity_types_list_' . $output, $args, $type, $description );
+	}
+
+	// Backpat with BP-Default for dropdown boxes only.
+	if ( 'select' === $output ) {
+		do_action( 'bp_activity_filter_options' );
+	}
+}
+
+
+/* RSS Feed Template Tags ****************************************************/
+
+/**
+ * Output the sitewide activity feed link.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_get_sitewide_activity_feed_link()
+ */
+function bp_sitewide_activity_feed_link() {
+	echo bp_get_sitewide_activity_feed_link();
+}
+
+	/**
+	 * Returns the sitewide activity feed link.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @uses home_url()
+	 * @uses bp_get_activity_root_slug()
+	 * @uses apply_filters() To call the 'bp_get_sitewide_activity_feed_link' hook.
+	 *
+	 * @return string The sitewide activity feed link.
+	 */
+	function bp_get_sitewide_activity_feed_link() {
+
+		/**
+		 * Filters the sidewide activity feed link.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value The feed link for sitewide activity.
+		 */
+		return apply_filters( 'bp_get_sitewide_activity_feed_link', bp_get_root_domain() . '/' . bp_get_activity_root_slug() . '/feed/' );
+	}
+
+/**
+ * Output the member activity feed link.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_member_activity_feed_link()
+ */
+function bp_member_activity_feed_link() {
+	echo bp_get_member_activity_feed_link();
+}
+
+/**
+ * Output the member activity feed link.
+ *
+ * @since 1.0.0
+ * @deprecated 1.2.0
+ *
+ * @todo properly deprecate in favor of bp_member_activity_feed_link().
+ *
+ * @uses bp_get_member_activity_feed_link()
+ */
+function bp_activities_member_rss_link() { echo bp_get_member_activity_feed_link(); }
+
+	/**
+	 * Return the member activity feed link.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @uses bp_is_profile_component()
+	 * @uses bp_is_current_action()
+	 * @uses bp_displayed_user_domain()
+	 * @uses bp_get_activity_slug()
+	 * @uses bp_is_active()
+	 * @uses bp_get_friends_slug()
+	 * @uses bp_get_groups_slug()
+	 * @uses apply_filters() To call the 'bp_get_activities_member_rss_link' hook.
+	 *
+	 * @return string $link The member activity feed link.
+	 */
+	function bp_get_member_activity_feed_link() {
+
+		// Single member activity feed link.
+		if ( bp_is_profile_component() || bp_is_current_action( 'just-me' ) ) {
+			$link = bp_displayed_user_domain() . bp_get_activity_slug() . '/feed/';
+
+		// Friend feed link.
+		} elseif ( bp_is_active( 'friends' ) && bp_is_current_action( bp_get_friends_slug() ) ) {
+			$link = bp_displayed_user_domain() . bp_get_activity_slug() . '/' . bp_get_friends_slug() . '/feed/';
+
+		// Group feed link.
+		} elseif ( bp_is_active( 'groups'  ) && bp_is_current_action( bp_get_groups_slug()  ) ) {
+			$link = bp_displayed_user_domain() . bp_get_activity_slug() . '/' . bp_get_groups_slug() . '/feed/';
+
+		// Favorites activity feed link.
+		} elseif ( 'favorites' === bp_current_action() ) {
+			$link = bp_displayed_user_domain() . bp_get_activity_slug() . '/favorites/feed/';
+
+		// Mentions activity feed link.
+		} elseif ( ( 'mentions' === bp_current_action() ) && bp_activity_do_mentions() ) {
+			$link = bp_displayed_user_domain() . bp_get_activity_slug() . '/mentions/feed/';
+
+		// No feed link.
+		} else {
+			$link = '';
+		}
+
+		/**
+		 * Filters the member activity feed link.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $link URL for the member activity feed.
+		 */
+		return apply_filters( 'bp_get_activities_member_rss_link', $link );
+	}
+
+	/**
+	 * Return the member activity feed link.
+	 *
+	 * @since 1.0.0
+	 * @deprecated 1.2.0
+	 *
+	 * @todo properly deprecate in favor of bp_get_member_activity_feed_link().
+	 *
+	 * @uses bp_get_member_activity_feed_link()
+	 *
+	 * @return string The member activity feed link.
+	 */
+	function bp_get_activities_member_rss_link() { return bp_get_member_activity_feed_link(); }
+
+
+/** Template tags for RSS feed output ****************************************/
+
+/**
+ * Outputs the activity feed item guid.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_activity_feed_item_guid()
+ */
+function bp_activity_feed_item_guid() {
+	echo bp_get_activity_feed_item_guid();
+}
+
+	/**
+	 * Returns the activity feed item guid.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_feed_item_guid' hook.
+	 *
+	 * @return string The activity feed item guid.
+	 */
+	function bp_get_activity_feed_item_guid() {
+		global $activities_template;
+
+		/**
+		 * Filters the activity feed item guid.
+		 *
+		 * @since 1.1.3
+		 *
+		 * @param string $value Calculated md5 value for the activity feed item.
+		 */
+		return apply_filters( 'bp_get_activity_feed_item_guid', md5( $activities_template->activity->date_recorded . '-' . $activities_template->activity->content ) );
+	}
+
+/**
+ * Output the activity feed item title.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_get_activity_feed_item_title()
+ */
+function bp_activity_feed_item_title() {
+	echo bp_get_activity_feed_item_title();
+}
+
+	/**
+	 * Return the activity feed item title.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses ent2ncr()
+	 * @uses convert_chars()
+	 * @uses bp_create_excerpt()
+	 * @uses apply_filters() To call the 'bp_get_activity_feed_item_title' hook.
+	 *
+	 * @return string $title The activity feed item title.
+	 */
+	function bp_get_activity_feed_item_title() {
+		global $activities_template;
+
+		if ( !empty( $activities_template->activity->action ) ) {
+			$content = $activities_template->activity->action;
+		} else {
+			$content = $activities_template->activity->content;
+		}
+
+		$content = explode( '<span', $content );
+		$title   = strip_tags( ent2ncr( trim( convert_chars( $content[0] ) ) ) );
+
+		if ( ':' === substr( $title, -1 ) ) {
+			$title = substr( $title, 0, -1 );
+		}
+
+		if ( 'activity_update' === $activities_template->activity->type ) {
+			$title .= ': ' . strip_tags( ent2ncr( trim( convert_chars( bp_create_excerpt( $activities_template->activity->content, 70, array( 'ending' => " [&#133;]" ) ) ) ) ) );
+		}
+
+		/**
+		 * Filters the activity feed item title.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $title The title for the activity feed item.
+		 */
+		return apply_filters( 'bp_get_activity_feed_item_title', $title );
+	}
+
+/**
+ * Output the activity feed item link.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_get_activity_feed_item_link()
+ */
+function bp_activity_feed_item_link() {
+	echo bp_get_activity_feed_item_link();
+}
+
+	/**
+	 * Return the activity feed item link.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_feed_item_link' hook.
+	 *
+	 * @return string The activity feed item link.
+	 */
+	function bp_get_activity_feed_item_link() {
+		global $activities_template;
+
+		$retval = ! empty( $activities_template->activity->primary_link )
+			? $activities_template->activity->primary_link
+			: '';
+
+		/**
+		 * Filters the activity feed item link.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $retval The URL for the activity feed item.
+		 */
+		return apply_filters( 'bp_get_activity_feed_item_link', $retval );
+	}
+
+/**
+ * Output the activity feed item date.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_get_activity_feed_item_date()
+ */
+function bp_activity_feed_item_date() {
+	echo bp_get_activity_feed_item_date();
+}
+
+	/**
+	 * Return the activity feed item date.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses apply_filters() To call the 'bp_get_activity_feed_item_date' hook.
+	 *
+	 * @return string The activity feed item date.
+	 */
+	function bp_get_activity_feed_item_date() {
+		global $activities_template;
+
+		$retval = ! empty( $activities_template->activity->date_recorded )
+			? $activities_template->activity->date_recorded
+			: '';
+
+		/**
+		 * Filters the activity feed item date.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $retval The date for the activity feed item.
+		 */
+		return apply_filters( 'bp_get_activity_feed_item_date', $retval );
+	}
+
+/**
+ * Output the activity feed item description.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_get_activity_feed_item_description()
+ */
+function bp_activity_feed_item_description() {
+	echo bp_get_activity_feed_item_description();
+}
+
+	/**
+	 * Return the activity feed item description.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global object $activities_template {@link BP_Activity_Template}
+	 * @uses ent2ncr()
+	 * @uses convert_chars()
+	 * @uses apply_filters() To call the 'bp_get_activity_feed_item_description' hook.
+	 *
+	 * @return string The activity feed item description.
+	 */
+	function bp_get_activity_feed_item_description() {
+		global $activities_template;
+
+		// Get the content, if exists.
+		$content = ! empty( $activities_template->activity->content )
+			? $activities_template->activity->content
+			: '';
+
+		// Perform a few string conversions on the content, if it's not empty.
+		if ( ! empty( $content ) ) {
+			$content = ent2ncr( convert_chars( str_replace( '%s', '', $content ) ) );
+		}
+
+		/**
+		 * Filters the activity feed item description.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $content The description for the activity feed item.
+		 */
+		return apply_filters( 'bp_get_activity_feed_item_description', $content );
+	}
+
+/**
+ * Template tag so we can hook activity feed to <head>.
+ *
+ * @since 1.5.0
+ *
+ * @uses bloginfo()
+ * @uses bp_sitewide_activity_feed_link()
+ */
+function bp_activity_sitewide_feed() {
+?>
+
+	<link rel="alternate" type="application/rss+xml" title="<?php bloginfo( 'name' ) ?> | <?php _e( 'Site Wide Activity RSS Feed', 'buddypress' ) ?>" href="<?php bp_sitewide_activity_feed_link() ?>" />
+
+<?php
+}
+add_action( 'bp_head', 'bp_activity_sitewide_feed' );
+
+/**
+ * Display available filters depending on the scope.
+ *
+ * @since 2.1.0
+ *
+ * @param string $context The current context. 'activity', 'member',
+ *                        'member_groups', 'group'.
+ * @uses bp_get_activity_show_filters()
+ */
+function bp_activity_show_filters( $context = '' ) {
+	echo bp_get_activity_show_filters( $context );
+}
+	/**
+	 * Get available filters depending on the scope.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param string $context The current context. 'activity', 'member',
+	 *                        'member_groups', 'group'.
+	 *
+	 * @return string HTML for <option> values.
+	 */
+	function bp_get_activity_show_filters( $context = '' ) {
+		// Set default context based on current page.
+		if ( empty( $context ) ) {
+
+			// On member pages, default to 'member', unless this
+			// is a user's Groups activity.
+			if ( bp_is_user() ) {
+				if ( bp_is_active( 'groups' ) && bp_is_current_action( bp_get_groups_slug() ) ) {
+					$context = 'member_groups';
+				} else {
+					$context = 'member';
+				}
+
+			// On individual group pages, default to 'group'.
+			} elseif ( bp_is_active( 'groups' ) && bp_is_group() ) {
+				$context = 'group';
+
+			// 'activity' everywhere else.
+			} else {
+				$context = 'activity';
+			}
+		}
+
+		$filters = array();
+
+		// Walk through the registered actions, and prepare an the
+		// select box options.
+		foreach ( bp_activity_get_actions() as $actions ) {
+			foreach ( $actions as $action ) {
+				if ( ! in_array( $context, (array) $action['context'] ) ) {
+					continue;
+				}
+
+				// Friends activity collapses two filters into one.
+				if ( in_array( $action['key'], array( 'friendship_accepted', 'friendship_created' ) ) ) {
+					$action['key'] = 'friendship_accepted,friendship_created';
+				}
+
+				$filters[ $action['key'] ] = $action['label'];
+			}
+		}
+
+		/**
+		 * Filters the options available in the activity filter dropdown.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param array  $filters Array of filter options for the given context, in the following format: $option_value => $option_name.
+		 * @param string $context Context for the filter. 'activity', 'member', 'member_groups', 'group'.
+		 */
+		$filters = apply_filters( 'bp_get_activity_show_filters_options', $filters, $context );
+
+		// Build the options output.
+		$output = '';
+
+		if ( ! empty( $filters ) ) {
+			foreach ( $filters as $value => $filter ) {
+				$output .= '<option value="' . esc_attr( $value ) . '">' . esc_html( $filter ) . '</option>' . "\n";
+			}
+		}
+
+		/**
+		 * Filters the HTML markup result for the activity filter dropdown.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $output  HTML output for the activity filter dropdown.
+		 * @param array  $filters Array of filter options for the given context, in the following format: $option_value => $option_name.
+		 * @param string $context Context for the filter. 'activity', 'member', 'member_groups', 'group'.
+		 */
+		return apply_filters( 'bp_get_activity_show_filters', $output, $filters, $context );
+	}
Index: /tags/2.4.3/src/bp-activity/classes/class-bp-activity-activity.php
===================================================================
--- /tags/2.4.3/src/bp-activity/classes/class-bp-activity-activity.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/classes/class-bp-activity-activity.php	(revision 10410)
@@ -0,0 +1,1793 @@
+<?php
+/**
+ * BuddyPress Activity Classes
+ *
+ * @package BuddyPress
+ * @subpackage Activity
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Database interaction class for the BuddyPress activity component.
+ *
+ * Instance methods are available for creating/editing an activity,
+ * static methods for querying activities.
+ *
+ * @since 1.0.0
+ */
+class BP_Activity_Activity {
+
+	/** Properties ************************************************************/
+
+	/**
+	 * ID of the activity item.
+	 *
+	 * @var int
+	 */
+	var $id;
+
+	/**
+	 * ID of the associated item.
+	 *
+	 * @var int
+	 */
+	var $item_id;
+
+	/**
+	 * ID of the associated secondary item.
+	 *
+	 * @var int
+	 */
+	var $secondary_item_id;
+
+	/**
+	 * ID of user associated with the activity item.
+	 *
+	 * @var int
+	 */
+	var $user_id;
+
+	/**
+	 * The primary URL for the activity in RSS feeds.
+	 *
+	 * @var string
+	 */
+	var $primary_link;
+
+	/**
+	 * BuddyPress component the activity item relates to.
+	 *
+	 * @var string
+	 */
+	var $component;
+
+	/**
+	 * Activity type, eg 'new_blog_post'.
+	 *
+	 * @var string
+	 */
+	var $type;
+
+	/**
+	 * Description of the activity, eg 'Alex updated his profile.'.
+	 *
+	 * @var string
+	 */
+	var $action;
+
+	/**
+	 * The content of the activity item.
+	 *
+	 * @var string
+	 */
+	var $content;
+
+	/**
+	 * The date the activity item was recorded, in 'Y-m-d h:i:s' format.
+	 *
+	 * @var string
+	 */
+	var $date_recorded;
+
+	/**
+	 * Whether the item should be hidden in sitewide streams.
+	 *
+	 * @var int
+	 */
+	var $hide_sitewide = false;
+
+	/**
+	 * Node boundary start for activity or activity comment.
+	 *
+	 * @var int
+	 */
+	var $mptt_left;
+
+	/**
+	 * Node boundary end for activity or activity comment.
+	 *
+	 * @var int
+	 */
+	var $mptt_right;
+
+	/**
+	 * Whether this item is marked as spam.
+	 *
+	 * @var int
+	 */
+	var $is_spam;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @param int|bool $id Optional. The ID of a specific activity item.
+	 */
+	public function __construct( $id = false ) {
+		if ( !empty( $id ) ) {
+			$this->id = $id;
+			$this->populate();
+		}
+	}
+
+	/**
+	 * Populate the object with data about the specific activity item.
+	 */
+	public function populate() {
+		global $wpdb;
+
+		$row = wp_cache_get( $this->id, 'bp_activity' );
+
+		if ( false === $row ) {
+			$bp  = buddypress();
+			$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->activity->table_name} WHERE id = %d", $this->id ) );
+
+			wp_cache_set( $this->id, $row, 'bp_activity' );
+		}
+
+		if ( ! empty( $row ) ) {
+			$this->id                = (int) $row->id;
+			$this->item_id           = (int) $row->item_id;
+			$this->secondary_item_id = (int) $row->secondary_item_id;
+			$this->user_id           = (int) $row->user_id;
+			$this->primary_link      = $row->primary_link;
+			$this->component         = $row->component;
+			$this->type              = $row->type;
+			$this->action            = $row->action;
+			$this->content           = $row->content;
+			$this->date_recorded     = $row->date_recorded;
+			$this->hide_sitewide     = $row->hide_sitewide;
+			$this->mptt_left         = (int) $row->mptt_left;
+			$this->mptt_right        = (int) $row->mptt_right;
+			$this->is_spam           = $row->is_spam;
+		}
+
+		// Generate dynamic 'action' when possible.
+		$action = bp_activity_generate_action_string( $this );
+		if ( false !== $action ) {
+			$this->action = $action;
+
+		// If no callback is available, use the literal string from
+		// the database row.
+		} elseif ( ! empty( $row->action ) ) {
+			$this->action = $row->action;
+
+		// Provide a fallback to avoid PHP notices.
+		} else {
+			$this->action = '';
+		}
+	}
+
+	/**
+	 * Save the activity item to the database.
+	 *
+	 * @return bool True on success.
+	 */
+	public function save() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$this->id                = apply_filters_ref_array( 'bp_activity_id_before_save',                array( $this->id,                &$this ) );
+		$this->item_id           = apply_filters_ref_array( 'bp_activity_item_id_before_save',           array( $this->item_id,           &$this ) );
+		$this->secondary_item_id = apply_filters_ref_array( 'bp_activity_secondary_item_id_before_save', array( $this->secondary_item_id, &$this ) );
+		$this->user_id           = apply_filters_ref_array( 'bp_activity_user_id_before_save',           array( $this->user_id,           &$this ) );
+		$this->primary_link      = apply_filters_ref_array( 'bp_activity_primary_link_before_save',      array( $this->primary_link,      &$this ) );
+		$this->component         = apply_filters_ref_array( 'bp_activity_component_before_save',         array( $this->component,         &$this ) );
+		$this->type              = apply_filters_ref_array( 'bp_activity_type_before_save',              array( $this->type,              &$this ) );
+		$this->action            = apply_filters_ref_array( 'bp_activity_action_before_save',            array( $this->action,            &$this ) );
+		$this->content           = apply_filters_ref_array( 'bp_activity_content_before_save',           array( $this->content,           &$this ) );
+		$this->date_recorded     = apply_filters_ref_array( 'bp_activity_date_recorded_before_save',     array( $this->date_recorded,     &$this ) );
+		$this->hide_sitewide     = apply_filters_ref_array( 'bp_activity_hide_sitewide_before_save',     array( $this->hide_sitewide,     &$this ) );
+		$this->mptt_left         = apply_filters_ref_array( 'bp_activity_mptt_left_before_save',         array( $this->mptt_left,         &$this ) );
+		$this->mptt_right        = apply_filters_ref_array( 'bp_activity_mptt_right_before_save',        array( $this->mptt_right,        &$this ) );
+		$this->is_spam           = apply_filters_ref_array( 'bp_activity_is_spam_before_save',           array( $this->is_spam,           &$this ) );
+
+		/**
+		 * Fires before the current activity item gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Activity_Activity $this Current instance of the activity item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_activity_before_save', array( &$this ) );
+
+		if ( empty( $this->component ) || empty( $this->type ) ) {
+			return false;
+		}
+
+		if ( empty( $this->primary_link ) ) {
+			$this->primary_link = bp_loggedin_user_domain();
+		}
+
+		// If we have an existing ID, update the activity item, otherwise insert it.
+		if ( ! empty( $this->id ) ) {
+			$q = $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET user_id = %d, component = %s, type = %s, action = %s, content = %s, primary_link = %s, date_recorded = %s, item_id = %d, secondary_item_id = %d, hide_sitewide = %d, is_spam = %d WHERE id = %d", $this->user_id, $this->component, $this->type, $this->action, $this->content, $this->primary_link, $this->date_recorded, $this->item_id, $this->secondary_item_id, $this->hide_sitewide, $this->is_spam, $this->id );
+		} else {
+			$q = $wpdb->prepare( "INSERT INTO {$bp->activity->table_name} ( user_id, component, type, action, content, primary_link, date_recorded, item_id, secondary_item_id, hide_sitewide, is_spam ) VALUES ( %d, %s, %s, %s, %s, %s, %s, %d, %d, %d, %d )", $this->user_id, $this->component, $this->type, $this->action, $this->content, $this->primary_link, $this->date_recorded, $this->item_id, $this->secondary_item_id, $this->hide_sitewide, $this->is_spam );
+		}
+
+		if ( false === $wpdb->query( $q ) ) {
+			return false;
+		}
+
+		// If this is a new activity item, set the $id property.
+		if ( empty( $this->id ) ) {
+			$this->id = $wpdb->insert_id;
+
+		// If an existing activity item, prevent any changes to the content generating new @mention notifications.
+		} else {
+			add_filter( 'bp_activity_at_name_do_notifications', '__return_false' );
+		}
+
+		/**
+		 * Fires after an activity item has been saved to the database.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Activity_Activity $this Current instance of activity item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_activity_after_save', array( &$this ) );
+
+		return true;
+	}
+
+	/** Static Methods ***************************************************/
+
+	/**
+	 * Get activity items, as specified by parameters.
+	 *
+	 * @since 1.0.0
+	 * @since 2.4.0 Introduced the `$fields` parameter.
+	 *
+	 * @see BP_Activity_Activity::get_filter_sql() for a description of the
+	 *      'filter' parameter.
+	 * @see WP_Meta_Query::queries for a description of the 'meta_query'
+	 *      parameter format.
+	 *
+	 * @param array $args {
+	 *     An array of arguments. All items are optional.
+	 *     @type int          $page              Which page of results to fetch. Using page=1 without per_page will result
+	 *                                           in no pagination. Default: 1.
+	 *     @type int|bool     $per_page          Number of results per page. Default: 25.
+	 *     @type int|bool     $max               Maximum number of results to return. Default: false (unlimited).
+	 *     @type string       $fields            Activity fields to return. Pass 'ids' to get only the activity IDs.
+	 *                                           'all' returns full activity objects.
+	 *     @type string       $sort              ASC or DESC. Default: 'DESC'.
+	 *     @type array        $exclude           Array of activity IDs to exclude. Default: false.
+	 *     @type array        $in                Array of ids to limit query by (IN). Default: false.
+	 *     @type array        $meta_query        Array of meta_query conditions. See WP_Meta_Query::queries.
+	 *     @type array        $date_query        Array of date_query conditions. See first parameter of
+	 *                                           WP_Date_Query::__construct().
+	 *     @type array        $filter_query      Array of advanced query conditions. See BP_Activity_Query::__construct().
+	 *     @type string|array $scope             Pre-determined set of activity arguments.
+	 *     @type array        $filter            See BP_Activity_Activity::get_filter_sql().
+	 *     @type string       $search_terms      Limit results by a search term. Default: false.
+	 *     @type bool         $display_comments  Whether to include activity comments. Default: false.
+	 *     @type bool         $show_hidden       Whether to show items marked hide_sitewide. Default: false.
+	 *     @type string       $spam              Spam status. Default: 'ham_only'.
+	 *     @type bool         $update_meta_cache Whether to pre-fetch metadata for queried activity items. Default: true.
+	 *     @type string|bool  $count_total       If true, an additional DB query is run to count the total activity items
+	 *                                           for the query. Default: false.
+	 * }
+	 * @return array The array returned has two keys:
+	 *               - 'total' is the count of located activities
+	 *               - 'activities' is an array of the located activities
+	 */
+	public static function get( $args = array() ) {
+		global $wpdb;
+
+		// Backward compatibility with old method of passing arguments.
+		if ( !is_array( $args ) || func_num_args() > 1 ) {
+			_deprecated_argument( __METHOD__, '1.6', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+			$old_args_keys = array(
+				0 => 'max',
+				1 => 'page',
+				2 => 'per_page',
+				3 => 'sort',
+				4 => 'search_terms',
+				5 => 'filter',
+				6 => 'display_comments',
+				7 => 'show_hidden',
+				8 => 'exclude',
+				9 => 'in',
+				10 => 'spam'
+			);
+
+			$func_args = func_get_args();
+			$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
+		}
+
+		$bp = buddypress();
+		$r  = wp_parse_args( $args, array(
+			'page'              => 1,          // The current page.
+			'per_page'          => 25,         // Activity items per page.
+			'max'               => false,      // Max number of items to return.
+			'fields'            => 'all',      // Fields to include.
+			'sort'              => 'DESC',     // ASC or DESC.
+			'exclude'           => false,      // Array of ids to exclude.
+			'in'                => false,      // Array of ids to limit query by (IN).
+			'meta_query'        => false,      // Filter by activitymeta.
+			'date_query'        => false,      // Filter by date.
+			'filter_query'      => false,      // Advanced filtering - see BP_Activity_Query.
+			'filter'            => false,      // See self::get_filter_sql().
+			'scope'             => false,      // Preset activity arguments.
+			'search_terms'      => false,      // Terms to search by.
+			'display_comments'  => false,      // Whether to include activity comments.
+			'show_hidden'       => false,      // Show items marked hide_sitewide.
+			'spam'              => 'ham_only', // Spam status.
+			'update_meta_cache' => true,       // Whether or not to update meta cache.
+			'count_total'       => false,      // Whether or not to use count_total.
+		) );
+
+		// Select conditions.
+		$select_sql = "SELECT DISTINCT a.id";
+
+		$from_sql   = " FROM {$bp->activity->table_name} a";
+
+		$join_sql   = '';
+
+		// Where conditions.
+		$where_conditions = array();
+
+		// Excluded types.
+		$excluded_types = array();
+
+		// Scope takes precedence.
+		if ( ! empty( $r['scope'] ) ) {
+			$scope_query = self::get_scope_query_sql( $r['scope'], $r );
+
+			// Add our SQL conditions if matches were found.
+			if ( ! empty( $scope_query['sql'] ) ) {
+				$where_conditions['scope_query_sql'] = $scope_query['sql'];
+			}
+
+			// Override some arguments if needed.
+			if ( ! empty( $scope_query['override'] ) ) {
+				$r = self::array_replace_recursive( $r, $scope_query['override'] );
+			}
+
+		// Advanced filtering.
+		} elseif ( ! empty( $r['filter_query'] ) ) {
+			$filter_query = new BP_Activity_Query( $r['filter_query'] );
+			$sql          = $filter_query->get_sql();
+			if ( ! empty( $sql ) ) {
+				$where_conditions['filter_query_sql'] = $sql;
+			}
+		}
+
+		// Regular filtering.
+		if ( $r['filter'] && $filter_sql = BP_Activity_Activity::get_filter_sql( $r['filter'] ) ) {
+			$where_conditions['filter_sql'] = $filter_sql;
+		}
+
+		// Spam.
+		if ( 'ham_only' == $r['spam'] ) {
+			$where_conditions['spam_sql'] = 'a.is_spam = 0';
+		} elseif ( 'spam_only' == $r['spam'] ) {
+			$where_conditions['spam_sql'] = 'a.is_spam = 1';
+		}
+
+		// Searching.
+		if ( $r['search_terms'] ) {
+			$search_terms_like = '%' . bp_esc_like( $r['search_terms'] ) . '%';
+			$where_conditions['search_sql'] = $wpdb->prepare( 'a.content LIKE %s', $search_terms_like );
+		}
+
+		// Sorting.
+		$sort = $r['sort'];
+		if ( $sort != 'ASC' && $sort != 'DESC' ) {
+			$sort = 'DESC';
+		}
+
+		// Hide Hidden Items?
+		if ( ! $r['show_hidden'] ) {
+			$where_conditions['hidden_sql'] = "a.hide_sitewide = 0";
+		}
+
+		// Exclude specified items.
+		if ( ! empty( $r['exclude'] ) ) {
+			$exclude = implode( ',', wp_parse_id_list( $r['exclude'] ) );
+			$where_conditions['exclude'] = "a.id NOT IN ({$exclude})";
+		}
+
+		// The specific ids to which you want to limit the query.
+		if ( ! empty( $r['in'] ) ) {
+			$in = implode( ',', wp_parse_id_list( $r['in'] ) );
+			$where_conditions['in'] = "a.id IN ({$in})";
+		}
+
+		// Process meta_query into SQL.
+		$meta_query_sql = self::get_meta_query_sql( $r['meta_query'] );
+
+		if ( ! empty( $meta_query_sql['join'] ) ) {
+			$join_sql .= $meta_query_sql['join'];
+		}
+
+		if ( ! empty( $meta_query_sql['where'] ) ) {
+			$where_conditions[] = $meta_query_sql['where'];
+		}
+
+		// Process date_query into SQL.
+		$date_query_sql = self::get_date_query_sql( $r['date_query'] );
+
+		if ( ! empty( $date_query_sql ) ) {
+			$where_conditions['date'] = $date_query_sql;
+		}
+
+		// Alter the query based on whether we want to show activity item
+		// comments in the stream like normal comments or threaded below
+		// the activity.
+		if ( false === $r['display_comments'] || 'threaded' === $r['display_comments'] ) {
+			$excluded_types[] = 'activity_comment';
+		}
+
+		// Exclude 'last_activity' items unless the 'action' filter has
+		// been explicitly set.
+		if ( empty( $r['filter']['object'] ) ) {
+			$excluded_types[] = 'last_activity';
+		}
+
+		// Build the excluded type sql part.
+		if ( ! empty( $excluded_types ) ) {
+			$not_in = "'" . implode( "', '", esc_sql( $excluded_types ) ) . "'";
+			$where_conditions['excluded_types'] = "a.type NOT IN ({$not_in})";
+		}
+
+		/**
+		 * Filters the MySQL WHERE conditions for the Activity items get method.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param array  $where_conditions Current conditions for MySQL WHERE statement.
+		 * @param array  $r                Parsed arguments passed into method.
+		 * @param string $select_sql       Current SELECT MySQL statement at point of execution.
+		 * @param string $from_sql         Current FROM MySQL statement at point of execution.
+		 * @param string $join_sql         Current INNER JOIN MySQL statement at point of execution.
+		 */
+		$where_conditions = apply_filters( 'bp_activity_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql );
+
+		// Join the where conditions together.
+		$where_sql = 'WHERE ' . join( ' AND ', $where_conditions );
+
+		/**
+		 * Filters the preferred order of indexes for activity item.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param array Array of indexes in preferred order.
+		 */
+		$indexes = apply_filters( 'bp_activity_preferred_index_order', array( 'user_id', 'item_id', 'secondary_item_id', 'date_recorded', 'component', 'type', 'hide_sitewide', 'is_spam' ) );
+
+		foreach( $indexes as $key => $index ) {
+			if ( false !== strpos( $where_sql, $index ) ) {
+				$the_index = $index;
+				break; // Take the first one we find.
+			}
+		}
+
+		if ( !empty( $the_index ) ) {
+			$index_hint_sql = "USE INDEX ({$the_index})";
+		} else {
+			$index_hint_sql = '';
+		}
+
+		// Sanitize page and per_page parameters.
+		$page     = absint( $r['page']     );
+		$per_page = absint( $r['per_page'] );
+
+		$retval = array(
+			'activities'     => null,
+			'total'          => null,
+			'has_more_items' => null,
+		);
+
+		/**
+		 * Filters if BuddyPress should use legacy query structure over current structure for version 2.0+.
+		 *
+		 * It is not recommended to use the legacy structure, but allowed to if needed.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param bool                 $value Whether to use legacy structure or not.
+		 * @param BP_Activity_Activity $value Current method being called.
+		 * @param array                $r     Parsed arguments passed into method.
+		 */
+		if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, $r ) ) {
+
+			// Legacy queries joined against the user table.
+			$select_sql = "SELECT DISTINCT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name";
+			$from_sql   = " FROM {$bp->activity->table_name} a LEFT JOIN {$wpdb->users} u ON a.user_id = u.ID";
+
+			if ( ! empty( $page ) && ! empty( $per_page ) ) {
+				$pag_sql    = $wpdb->prepare( "LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page );
+
+				/** This filter is documented in bp-activity/bp-activity-classes.php */
+				$activities = $wpdb->get_results( apply_filters( 'bp_activity_get_user_join_filter', "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort} {$pag_sql}", $select_sql, $from_sql, $where_sql, $sort, $pag_sql ) );
+			} else {
+				$pag_sql    = '';
+
+				/**
+				 * Filters the legacy MySQL query statement so plugins can alter before results are fetched.
+				 *
+				 * @since 1.5.0
+				 *
+				 * @param string $value      Concatenated MySQL statement pieces to be query results with for legacy query.
+				 * @param string $select_sql Final SELECT MySQL statement portion for legacy query.
+				 * @param string $from_sql   Final FROM MySQL statement portion for legacy query.
+				 * @param string $where_sql  Final WHERE MySQL statement portion for legacy query.
+				 * @param string $sort       Final sort direction for legacy query.
+				 */
+				$activities = $wpdb->get_results( apply_filters( 'bp_activity_get_user_join_filter', "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort}", $select_sql, $from_sql, $where_sql, $sort, $pag_sql ) );
+			}
+
+		} else {
+			// Query first for activity IDs.
+			$activity_ids_sql = "{$select_sql} {$from_sql} {$join_sql} {$where_sql} ORDER BY a.date_recorded {$sort}";
+
+			if ( ! empty( $per_page ) && ! empty( $page ) ) {
+				// We query for $per_page + 1 items in order to
+				// populate the has_more_items flag.
+				$activity_ids_sql .= $wpdb->prepare( " LIMIT %d, %d", absint( ( $page - 1 ) * $per_page ), $per_page + 1 );
+			}
+
+			/**
+			 * Filters the paged activities MySQL statement.
+			 *
+			 * @since 2.0.0
+			 *
+			 * @param string $activity_ids_sql MySQL statement used to query for Activity IDs.
+			 * @param array  $r                Array of arguments passed into method.
+			 */
+			$activity_ids_sql = apply_filters( 'bp_activity_paged_activities_sql', $activity_ids_sql, $r );
+
+			$activity_ids = $wpdb->get_col( $activity_ids_sql );
+
+			$retval['has_more_items'] = ! empty( $per_page ) && count( $activity_ids ) > $per_page;
+
+			// If we've fetched more than the $per_page value, we
+			// can discard the extra now.
+			if ( ! empty( $per_page ) && count( $activity_ids ) === $per_page + 1 ) {
+				array_pop( $activity_ids );
+			}
+
+			if ( 'ids' === $r['fields'] ) {
+				$activities = array_map( 'intval', $activity_ids );
+			} else {
+				$activities = self::get_activity_data( $activity_ids );
+			}
+		}
+
+		if ( 'ids' !== $r['fields'] ) {
+			// Get the fullnames of users so we don't have to query in the loop.
+			$activities = self::append_user_fullnames( $activities );
+
+			// Get activity meta.
+			$activity_ids = array();
+			foreach ( (array) $activities as $activity ) {
+				$activity_ids[] = $activity->id;
+			}
+
+			if ( ! empty( $activity_ids ) && $r['update_meta_cache'] ) {
+				bp_activity_update_meta_cache( $activity_ids );
+			}
+
+			if ( $activities && $r['display_comments'] ) {
+				$activities = BP_Activity_Activity::append_comments( $activities, $r['spam'] );
+			}
+
+			// Pre-fetch data associated with activity users and other objects.
+			BP_Activity_Activity::prefetch_object_data( $activities );
+
+			// Generate action strings.
+			$activities = BP_Activity_Activity::generate_action_strings( $activities );
+		}
+
+		$retval['activities'] = $activities;
+
+		// If $max is set, only return up to the max results.
+		if ( ! empty( $r['count_total'] ) ) {
+
+			/**
+			 * Filters the total activities MySQL statement.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param string $value     MySQL statement used to query for total activities.
+			 * @param string $where_sql MySQL WHERE statement portion.
+			 * @param string $sort      Sort direction for query.
+			 */
+			$total_activities_sql = apply_filters( 'bp_activity_total_activities_sql', "SELECT count(DISTINCT a.id) FROM {$bp->activity->table_name} a {$join_sql} {$where_sql}", $where_sql, $sort );
+			$total_activities     = $wpdb->get_var( $total_activities_sql );
+
+			if ( !empty( $r['max'] ) ) {
+				if ( (int) $total_activities > (int) $r['max'] ) {
+					$total_activities = $r['max'];
+				}
+			}
+
+			$retval['total'] = $total_activities;
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * Convert activity IDs to activity objects, as expected in template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $activity_ids Array of activity IDs.
+	 * @return array
+	 */
+	protected static function get_activity_data( $activity_ids = array() ) {
+		global $wpdb;
+
+		// Bail if no activity ID's passed.
+		if ( empty( $activity_ids ) ) {
+			return array();
+		}
+
+		// Get BuddyPress.
+		$bp = buddypress();
+
+		$activities   = array();
+		$uncached_ids = bp_get_non_cached_ids( $activity_ids, 'bp_activity' );
+
+		// Prime caches as necessary.
+		if ( ! empty( $uncached_ids ) ) {
+			// Format the activity ID's for use in the query below.
+			$uncached_ids_sql = implode( ',', wp_parse_id_list( $uncached_ids ) );
+
+			// Fetch data from activity table, preserving order.
+			$queried_adata = $wpdb->get_results( "SELECT * FROM {$bp->activity->table_name} WHERE id IN ({$uncached_ids_sql})");
+
+			// Put that data into the placeholders created earlier,
+			// and add it to the cache.
+			foreach ( (array) $queried_adata as $adata ) {
+				wp_cache_set( $adata->id, $adata, 'bp_activity' );
+			}
+		}
+
+		// Now fetch data from the cache.
+		foreach ( $activity_ids as $activity_id ) {
+			$activities[] = wp_cache_get( $activity_id, 'bp_activity' );
+		}
+
+		// Then fetch user data.
+		$user_query = new BP_User_Query( array(
+			'user_ids'        => wp_list_pluck( $activities, 'user_id' ),
+			'populate_extras' => false,
+		) );
+
+		// Associated located user data with activity items.
+		foreach ( $activities as $a_index => $a_item ) {
+			$a_user_id = intval( $a_item->user_id );
+			$a_user    = isset( $user_query->results[ $a_user_id ] ) ? $user_query->results[ $a_user_id ] : '';
+
+			if ( !empty( $a_user ) ) {
+				$activities[ $a_index ]->user_email    = $a_user->user_email;
+				$activities[ $a_index ]->user_nicename = $a_user->user_nicename;
+				$activities[ $a_index ]->user_login    = $a_user->user_login;
+				$activities[ $a_index ]->display_name  = $a_user->display_name;
+			}
+		}
+
+		return $activities;
+	}
+
+	/**
+	 * Append xProfile fullnames to an activity array.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $activities Activities array.
+	 * @return array
+	 */
+	protected static function append_user_fullnames( $activities ) {
+
+		if ( bp_is_active( 'xprofile' ) && ! empty( $activities ) ) {
+			$activity_user_ids = wp_list_pluck( $activities, 'user_id' );
+
+			if ( ! empty( $activity_user_ids ) ) {
+				$fullnames = bp_core_get_user_displaynames( $activity_user_ids );
+				if ( ! empty( $fullnames ) ) {
+					foreach ( (array) $activities as $i => $activity ) {
+						if ( ! empty( $fullnames[ $activity->user_id ] ) ) {
+							$activities[ $i ]->user_fullname = $fullnames[ $activity->user_id ];
+						}
+					}
+				}
+			}
+		}
+
+		return $activities;
+	}
+
+	/**
+	 * Pre-fetch data for objects associated with activity items.
+	 *
+	 * Activity items are associated with users, and often with other
+	 * BuddyPress data objects. Here, we pre-fetch data about these
+	 * associated objects, so that inline lookups - done primarily when
+	 * building action strings - do not result in excess database queries.
+	 *
+	 * The only object data required for activity component activity types
+	 * (activity_update and activity_comment) is related to users, and that
+	 * info is fetched separately in BP_Activity_Activity::get_activity_data().
+	 * So this method contains nothing but a filter that allows other
+	 * components, such as bp-friends and bp-groups, to hook in and prime
+	 * their own caches at the beginning of an activity loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $activities Array of activities.
+	 * @return array $activities Array of activities.
+	 */
+	protected static function prefetch_object_data( $activities ) {
+
+		/**
+		 * Filters inside prefetch_object_data method to aid in pre-fetching object data associated with activity item.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $activities Array of activities.
+		 */
+		return apply_filters( 'bp_activity_prefetch_object_data', $activities );
+	}
+
+	/**
+	 * Generate action strings for the activities located in BP_Activity_Activity::get().
+	 *
+	 * If no string can be dynamically generated for a given item
+	 * (typically because the activity type has not been properly
+	 * registered), the static 'action' value pulled from the database will
+	 * be left in place.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $activities Array of activities.
+	 * @return array
+	 */
+	protected static function generate_action_strings( $activities ) {
+		foreach ( $activities as $key => $activity ) {
+			$generated_action = bp_activity_generate_action_string( $activity );
+			if ( false !== $generated_action ) {
+				$activity->action = $generated_action;
+			}
+
+			$activities[ $key ] = $activity;
+		}
+
+		return $activities;
+	}
+
+	/**
+	 * Get the SQL for the 'meta_query' param in BP_Activity_Activity::get().
+	 *
+	 * We use WP_Meta_Query to do the heavy lifting of parsing the
+	 * meta_query array and creating the necessary SQL clauses. However,
+	 * since BP_Activity_Activity::get() builds its SQL differently than
+	 * WP_Query, we have to alter the return value (stripping the leading
+	 * AND keyword from the 'where' clause).
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param array $meta_query An array of meta_query filters. See the
+	 *                          documentation for WP_Meta_Query for details.
+	 * @return array $sql_array 'join' and 'where' clauses.
+	 */
+	public static function get_meta_query_sql( $meta_query = array() ) {
+		global $wpdb;
+
+		$sql_array = array(
+			'join'  => '',
+			'where' => '',
+		);
+
+		if ( ! empty( $meta_query ) ) {
+			$activity_meta_query = new WP_Meta_Query( $meta_query );
+
+			// WP_Meta_Query expects the table name at
+			// $wpdb->activitymeta.
+			$wpdb->activitymeta = buddypress()->activity->table_name_meta;
+
+			$meta_sql = $activity_meta_query->get_sql( 'activity', 'a', 'id' );
+
+			// Strip the leading AND - BP handles it in get().
+			$sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] );
+			$sql_array['join']  = $meta_sql['join'];
+		}
+
+		return $sql_array;
+	}
+
+	/**
+	 * Get the SQL for the 'date_query' param in BP_Activity_Activity::get().
+	 *
+	 * We use BP_Date_Query, which extends WP_Date_Query, to do the heavy lifting
+	 * of parsing the date_query array and creating the necessary SQL clauses.
+	 * However, since BP_Activity_Activity::get() builds its SQL differently than
+	 * WP_Query, we have to alter the return value (stripping the leading AND
+	 * keyword from the query).
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param array $date_query An array of date_query parameters. See the
+	 *                          documentation for the first parameter of WP_Date_Query.
+	 * @return string
+	 */
+	public static function get_date_query_sql( $date_query = array() ) {
+		$sql = '';
+
+		// Date query.
+		if ( ! empty( $date_query ) && is_array( $date_query ) ) {
+			$date_query = new BP_Date_Query( $date_query, 'date_recorded' );
+			$sql = preg_replace( '/^\sAND/', '', $date_query->get_sql() );
+		}
+
+		return $sql;
+	}
+
+	/**
+	 * Get the SQL for the 'scope' param in BP_Activity_Activity::get().
+	 *
+	 * A scope is a predetermined set of activity arguments.  This method is used
+	 * to grab these activity arguments and override any existing args if needed.
+	 *
+	 * Can handle multiple scopes.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param  mixed $scope  The activity scope. Accepts string or array of scopes.
+	 * @param  array $r      Current activity arguments. Same as those of BP_Activity_Activity::get(),
+	 *                       but merged with defaults.
+	 * @return array 'sql' WHERE SQL string and 'override' activity args.
+	 */
+	public static function get_scope_query_sql( $scope = false, $r = array() ) {
+
+		// Define arrays for future use.
+		$query_args = array();
+		$override   = array();
+		$retval     = array();
+
+		// Check for array of scopes.
+		if ( is_array( $scope ) ) {
+			$scopes = $scope;
+
+		// Explode a comma separated string of scopes.
+		} elseif ( is_string( $scope ) ) {
+			$scopes = explode( ',', $scope );
+		}
+
+		// Bail if no scope passed.
+		if ( empty( $scopes ) ) {
+			return false;
+		}
+
+		// Helper to easily grab the 'user_id'.
+		if ( ! empty( $r['filter']['user_id'] ) ) {
+			$r['user_id'] = $r['filter']['user_id'];
+		}
+
+		// Parse each scope; yes! we handle multiples!
+		foreach ( $scopes as $scope ) {
+			$scope_args = array();
+
+			/**
+			 * Plugins can hook here to set their activity arguments for custom scopes.
+			 *
+			 * This is a dynamic filter based on the activity scope. eg:
+			 *   - 'bp_activity_set_groups_scope_args'
+			 *   - 'bp_activity_set_friends_scope_args'
+			 *
+			 * To see how this filter is used, plugin devs should check out:
+			 *   - bp_groups_filter_activity_scope() - used for 'groups' scope
+			 *   - bp_friends_filter_activity_scope() - used for 'friends' scope
+			 *
+			 * @since 2.2.0
+			 *
+			 *  @param array {
+			 *     Activity query clauses.
+			 *     @type array {
+			 *         Activity arguments for your custom scope.
+			 *         See {@link BP_Activity_Query::_construct()} for more details.
+			 *     }
+			 *     @type array $override Optional. Override existing activity arguments passed by $r.
+			 * }
+			 * @param array $r Current activity arguments passed in BP_Activity_Activity::get().
+			 */
+			$scope_args = apply_filters( "bp_activity_set_{$scope}_scope_args", array(), $r );
+
+			if ( ! empty( $scope_args ) ) {
+				// Merge override properties from other scopes
+				// this might be a problem...
+				if ( ! empty( $scope_args['override'] ) ) {
+					$override = array_merge( $override, $scope_args['override'] );
+					unset( $scope_args['override'] );
+				}
+
+				// Save scope args.
+				if ( ! empty( $scope_args ) ) {
+					$query_args[] = $scope_args;
+				}
+			}
+		}
+
+		if ( ! empty( $query_args ) ) {
+			// Set relation to OR.
+			$query_args['relation'] = 'OR';
+
+			$query = new BP_Activity_Query( $query_args );
+			$sql   = $query->get_sql();
+			if ( ! empty( $sql ) ) {
+				$retval['sql'] = $sql;
+			}
+		}
+
+		if ( ! empty( $override ) ) {
+			$retval['override'] = $override;
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * In BuddyPress 1.2.x, this was used to retrieve specific activity stream items (for example, on an activity's permalink page).
+	 *
+	 * As of 1.5.x, use BP_Activity_Activity::get() with an 'in' parameter instead.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @deprecated 1.5
+	 * @deprecated Use BP_Activity_Activity::get() with an 'in' parameter instead.
+	 *
+	 * @param mixed    $activity_ids     Array or comma-separated string of activity IDs to retrieve.
+	 * @param int|bool $max              Maximum number of results to return. (Optional; default is no maximum).
+	 * @param int      $page             The set of results that the user is viewing. Used in pagination. (Optional; default is 1).
+	 * @param int      $per_page         Specifies how many results per page. Used in pagination. (Optional; default is 25).
+	 * @param string   $sort             MySQL column sort; ASC or DESC. (Optional; default is DESC).
+	 * @param bool     $display_comments Retrieve an activity item's associated comments or not. (Optional; default is false).
+	 * @return array
+	 */
+	public static function get_specific( $activity_ids, $max = false, $page = 1, $per_page = 25, $sort = 'DESC', $display_comments = false ) {
+		_deprecated_function( __FUNCTION__, '1.5', 'Use BP_Activity_Activity::get() with the "in" parameter instead.' );
+		return BP_Activity_Activity::get( $max, $page, $per_page, $sort, false, false, $display_comments, false, false, $activity_ids );
+	}
+
+	/**
+	 * Get the first activity ID that matches a set of criteria.
+	 *
+	 * @todo Should parameters be optional?
+	 *
+	 * @param int    $user_id           User ID to filter by.
+	 * @param string $component         Component to filter by.
+	 * @param string $type              Activity type to filter by.
+	 * @param int    $item_id           Associated item to filter by.
+	 * @param int    $secondary_item_id Secondary associated item to filter by.
+	 * @param string $action            Action to filter by.
+	 * @param string $content           Content to filter by.
+	 * @param string $date_recorded     Date to filter by.
+	 * @return int|bool Activity ID on success, false if none is found.
+	 */
+	public static function get_id( $user_id, $component, $type, $item_id, $secondary_item_id, $action, $content, $date_recorded ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$where_args = false;
+
+		if ( ! empty( $user_id ) ) {
+			$where_args[] = $wpdb->prepare( "user_id = %d", $user_id );
+		}
+
+		if ( ! empty( $component ) ) {
+			$where_args[] = $wpdb->prepare( "component = %s", $component );
+		}
+
+		if ( ! empty( $type ) ) {
+			$where_args[] = $wpdb->prepare( "type = %s", $type );
+		}
+
+		if ( ! empty( $item_id ) ) {
+			$where_args[] = $wpdb->prepare( "item_id = %d", $item_id );
+		}
+
+		if ( ! empty( $secondary_item_id ) ) {
+			$where_args[] = $wpdb->prepare( "secondary_item_id = %d", $secondary_item_id );
+		}
+
+		if ( ! empty( $action ) ) {
+			$where_args[] = $wpdb->prepare( "action = %s", $action );
+		}
+
+		if ( ! empty( $content ) ) {
+			$where_args[] = $wpdb->prepare( "content = %s", $content );
+		}
+
+		if ( ! empty( $date_recorded ) ) {
+			$where_args[] = $wpdb->prepare( "date_recorded = %s", $date_recorded );
+		}
+
+		if ( ! empty( $where_args ) ) {
+			$where_sql = 'WHERE ' . join( ' AND ', $where_args );
+			return $wpdb->get_var( "SELECT id FROM {$bp->activity->table_name} {$where_sql}" );
+		}
+
+		return false;
+	}
+
+	/**
+	 * Delete activity items from the database.
+	 *
+	 * To delete a specific activity item, pass an 'id' parameter.
+	 * Otherwise use the filters.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $args {
+	 *     @int    $id                Optional. The ID of a specific item to delete.
+	 *     @string $action            Optional. The action to filter by.
+	 *     @string $content           Optional. The content to filter by.
+	 *     @string $component         Optional. The component name to filter by.
+	 *     @string $type              Optional. The activity type to filter by.
+	 *     @string $primary_link      Optional. The primary URL to filter by.
+	 *     @int    $user_id           Optional. The user ID to filter by.
+	 *     @int    $item_id           Optional. The associated item ID to filter by.
+	 *     @int    $secondary_item_id Optional. The secondary associated item ID to filter by.
+	 *     @string $date_recorded     Optional. The date to filter by.
+	 *     @int    $hide_sitewide     Optional. Default: false.
+	 * }
+	 * @return array|bool An array of deleted activity IDs on success, false on failure.
+	 */
+	public static function delete( $args = array() ) {
+		global $wpdb;
+
+		$bp = buddypress();
+		$r  = wp_parse_args( $args, array(
+			'id'                => false,
+			'action'            => false,
+			'content'           => false,
+			'component'         => false,
+			'type'              => false,
+			'primary_link'      => false,
+			'user_id'           => false,
+			'item_id'           => false,
+			'secondary_item_id' => false,
+			'date_recorded'     => false,
+			'hide_sitewide'     => false
+		) );
+
+		// Setup empty array from where query arguments.
+		$where_args = array();
+
+		// ID.
+		if ( ! empty( $r['id'] ) ) {
+			$where_args[] = $wpdb->prepare( "id = %d", $r['id'] );
+		}
+
+		// User ID.
+		if ( ! empty( $r['user_id'] ) ) {
+			$where_args[] = $wpdb->prepare( "user_id = %d", $r['user_id'] );
+		}
+
+		// Action.
+		if ( ! empty( $r['action'] ) ) {
+			$where_args[] = $wpdb->prepare( "action = %s", $r['action'] );
+		}
+
+		// Content.
+		if ( ! empty( $r['content'] ) ) {
+			$where_args[] = $wpdb->prepare( "content = %s", $r['content'] );
+		}
+
+		// Component.
+		if ( ! empty( $r['component'] ) ) {
+			$where_args[] = $wpdb->prepare( "component = %s", $r['component'] );
+		}
+
+		// Type.
+		if ( ! empty( $r['type'] ) ) {
+			$where_args[] = $wpdb->prepare( "type = %s", $r['type'] );
+		}
+
+		// Primary Link.
+		if ( ! empty( $r['primary_link'] ) ) {
+			$where_args[] = $wpdb->prepare( "primary_link = %s", $r['primary_link'] );
+		}
+
+		// Item ID.
+		if ( ! empty( $r['item_id'] ) ) {
+			$where_args[] = $wpdb->prepare( "item_id = %d", $r['item_id'] );
+		}
+
+		// Secondary item ID.
+		if ( ! empty( $r['secondary_item_id'] ) ) {
+			$where_args[] = $wpdb->prepare( "secondary_item_id = %d", $r['secondary_item_id'] );
+		}
+
+		// Date Recorded.
+		if ( ! empty( $r['date_recorded'] ) ) {
+			$where_args[] = $wpdb->prepare( "date_recorded = %s", $r['date_recorded'] );
+		}
+
+		// Hidden sitewide.
+		if ( ! empty( $r['hide_sitewide'] ) ) {
+			$where_args[] = $wpdb->prepare( "hide_sitewide = %d", $r['hide_sitewide'] );
+		}
+
+		// Bail if no where arguments.
+		if ( empty( $where_args ) ) {
+			return false;
+		}
+
+		// Join the where arguments for querying.
+		$where_sql = 'WHERE ' . join( ' AND ', $where_args );
+
+		// Fetch all activities being deleted so we can perform more actions.
+		$activities = $wpdb->get_results( "SELECT * FROM {$bp->activity->table_name} {$where_sql}" );
+
+		/**
+		 * Action to allow intercepting activity items to be deleted.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array $activities Array of activities.
+		 * @param array $r          Array of parsed arguments.
+		 */
+		do_action_ref_array( 'bp_activity_before_delete', array( $activities, $r ) );
+
+		// Attempt to delete activities from the database.
+		$deleted = $wpdb->query( "DELETE FROM {$bp->activity->table_name} {$where_sql}" );
+
+		// Bail if nothing was deleted.
+		if ( empty( $deleted ) ) {
+			return false;
+		}
+
+		/**
+		 * Action to allow intercepting activity items just deleted.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array $activities Array of activities.
+		 * @param array $r          Array of parsed arguments.
+		 */
+		do_action_ref_array( 'bp_activity_after_delete', array( $activities, $r ) );
+
+		// Pluck the activity IDs out of the $activities array.
+		$activity_ids = wp_parse_id_list( wp_list_pluck( $activities, 'id' ) );
+
+		// Handle accompanying activity comments and meta deletion.
+		if ( ! empty( $activity_ids ) ) {
+
+			// Delete all activity meta entries for activity items.
+			BP_Activity_Activity::delete_activity_meta_entries( $activity_ids );
+
+			// Setup empty array for comments.
+			$comment_ids = array();
+
+			// Loop through activity ids and attempt to delete comments.
+			foreach ( $activity_ids as $activity_id ) {
+
+				// Attempt to delete comments.
+				$comments = BP_Activity_Activity::delete( array(
+					'type'    => 'activity_comment',
+					'item_id' => $activity_id
+				) );
+
+				// Merge IDs together.
+				if ( ! empty( $comments ) ) {
+					$comment_ids = array_merge( $comment_ids, $comments );
+				}
+			}
+
+			// Merge activity IDs with any deleted comment IDs.
+			if ( ! empty( $comment_ids ) ) {
+				$activity_ids = array_unique( array_merge( $activity_ids, $comment_ids ) );
+			}
+		}
+
+		return $activity_ids;
+	}
+
+	/**
+	 * Delete the comments associated with a set of activity items.
+	 *
+	 * This method is no longer used by BuddyPress, and it is recommended not to
+	 * use it going forward, and use BP_Activity_Activity::delete() instead.
+	 *
+	 * @since 1.2.0
+	 * @deprecated 2.3.0
+	 *
+	 * @param array $activity_ids Activity IDs whose comments should be deleted.
+	 * @param bool  $delete_meta  Should we delete the activity meta items for these comments.
+	 *
+	 * @return bool True on success.
+	 */
+	public static function delete_activity_item_comments( $activity_ids = array(), $delete_meta = true ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$delete_meta  = (bool) $delete_meta;
+		$activity_ids = implode( ',', wp_parse_id_list( $activity_ids ) );
+
+		if ( $delete_meta ) {
+			// Fetch the activity comment IDs for our deleted activity items.
+			$activity_comment_ids = $wpdb->get_col( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" );
+
+			if ( ! empty( $activity_comment_ids ) ) {
+				self::delete_activity_meta_entries( $activity_comment_ids );
+			}
+		}
+
+		return $wpdb->query( "DELETE FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND item_id IN ({$activity_ids})" );
+	}
+
+	/**
+	 * Delete the meta entries associated with a set of activity items.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $activity_ids Activity IDs whose meta should be deleted.
+	 * @return bool True on success.
+	 */
+	public static function delete_activity_meta_entries( $activity_ids = array() ) {
+		$activity_ids = wp_parse_id_list( $activity_ids );
+
+		foreach ( $activity_ids as $activity_id ) {
+			bp_activity_delete_meta( $activity_id );
+		}
+
+		return true;
+	}
+
+	/**
+	 * Append activity comments to their associated activity items.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param array  $activities Activities to fetch comments for.
+	 * @param string $spam       Optional. 'ham_only' (default), 'spam_only' or 'all'.
+	 * @return array The updated activities with nested comments.
+	 */
+	public static function append_comments( $activities, $spam = 'ham_only' ) {
+		$activity_comments = array();
+
+		// Now fetch the activity comments and parse them into the correct position in the activities array.
+		foreach ( (array) $activities as $activity ) {
+			$top_level_parent_id = 'activity_comment' == $activity->type ? $activity->item_id : 0;
+			$activity_comments[$activity->id] = BP_Activity_Activity::get_activity_comments( $activity->id, $activity->mptt_left, $activity->mptt_right, $spam, $top_level_parent_id );
+		}
+
+		// Merge the comments with the activity items.
+		foreach ( (array) $activities as $key => $activity ) {
+			if ( isset( $activity_comments[$activity->id] ) ) {
+				$activities[$key]->children = $activity_comments[$activity->id];
+			}
+		}
+
+		return $activities;
+	}
+
+	/**
+	 * Get activity comments that are associated with a specific activity ID.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param int    $activity_id         Activity ID to fetch comments for.
+	 * @param int    $left                Left-most node boundary.
+	 * @param int    $right               Right-most node boundary.
+	 * @param string $spam                Optional. 'ham_only' (default), 'spam_only' or 'all'.
+	 * @param int    $top_level_parent_id Optional. The id of the root-level parent activity item.
+	 * @return array The updated activities with nested comments.
+	 */
+	public static function get_activity_comments( $activity_id, $left, $right, $spam = 'ham_only', $top_level_parent_id = 0 ) {
+		global $wpdb;
+
+		if ( empty( $top_level_parent_id ) ) {
+			$top_level_parent_id = $activity_id;
+		}
+
+		$comments = wp_cache_get( $activity_id, 'bp_activity_comments' );
+
+		// We store the string 'none' to cache the fact that the
+		// activity item has no comments.
+		if ( 'none' === $comments ) {
+			$comments = false;
+
+		// A true cache miss.
+		} elseif ( empty( $comments ) ) {
+
+			$bp = buddypress();
+
+			// Select the user's fullname with the query.
+			if ( bp_is_active( 'xprofile' ) ) {
+				$fullname_select = ", pd.value as user_fullname";
+				$fullname_from = ", {$bp->profile->table_name_data} pd ";
+				$fullname_where = "AND pd.user_id = a.user_id AND pd.field_id = 1";
+
+			// Prevent debug errors.
+			} else {
+				$fullname_select = $fullname_from = $fullname_where = '';
+			}
+
+			// Don't retrieve activity comments marked as spam.
+			if ( 'ham_only' == $spam ) {
+				$spam_sql = 'AND a.is_spam = 0';
+			} elseif ( 'spam_only' == $spam ) {
+				$spam_sql = 'AND a.is_spam = 1';
+			} else {
+				$spam_sql = '';
+			}
+
+			// Legacy query - not recommended.
+			$func_args = func_get_args();
+
+			/**
+			 * Filters if BuddyPress should use the legacy activity query.
+			 *
+			 * @since 2.0.0
+			 *
+			 * @param bool                 $value     Whether or not to use the legacy query.
+			 * @param BP_Activity_Activity $value     Magic method referring to currently called method.
+			 * @param array                $func_args Array of the method's argument list.
+			 */
+			if ( apply_filters( 'bp_use_legacy_activity_query', false, __METHOD__, $func_args ) ) {
+
+				/**
+				 * Filters the MySQL prepared statement for the legacy activity query.
+				 *
+				 * @since 1.5.0
+				 *
+				 * @param string $value       Prepared statement for the activity query.
+				 * @param int    $activity_id Activity ID to fetch comments for.
+				 * @param int    $left        Left-most node boundary.
+				 * @param int    $right       Right-most node boundary.
+				 * @param string $spam_sql    SQL Statement portion to differentiate between ham or spam.
+				 */
+				$sql = apply_filters( 'bp_activity_comments_user_join_filter', $wpdb->prepare( "SELECT a.*, u.user_email, u.user_nicename, u.user_login, u.display_name{$fullname_select} FROM {$bp->activity->table_name} a, {$wpdb->users} u{$fullname_from} WHERE u.ID = a.user_id {$fullname_where} AND a.type = 'activity_comment' {$spam_sql} AND a.item_id = %d AND a.mptt_left > %d AND a.mptt_left < %d ORDER BY a.date_recorded ASC", $top_level_parent_id, $left, $right ), $activity_id, $left, $right, $spam_sql );
+
+				$descendants = $wpdb->get_results( $sql );
+
+			// We use the mptt BETWEEN clause to limit returned
+			// descendants to the correct part of the tree.
+			} else {
+				$sql = $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} a WHERE a.type = 'activity_comment' {$spam_sql} AND a.item_id = %d and a.mptt_left > %d AND a.mptt_left < %d ORDER BY a.date_recorded ASC", $top_level_parent_id, $left, $right );
+
+				$descendant_ids = $wpdb->get_col( $sql );
+				$descendants    = self::get_activity_data( $descendant_ids );
+				$descendants    = self::append_user_fullnames( $descendants );
+			}
+
+			$ref = array();
+
+			// Loop descendants and build an assoc array.
+			foreach ( (array) $descendants as $d ) {
+				$d->children = array();
+
+				// If we have a reference on the parent.
+				if ( isset( $ref[ $d->secondary_item_id ] ) ) {
+					$ref[ $d->secondary_item_id ]->children[ $d->id ] = $d;
+					$ref[ $d->id ] =& $ref[ $d->secondary_item_id ]->children[ $d->id ];
+
+				// If we don't have a reference on the parent, put in the root level.
+				} else {
+					$comments[ $d->id ] = $d;
+					$ref[ $d->id ] =& $comments[ $d->id ];
+				}
+			}
+
+			// Calculate depth for each item.
+			foreach ( $ref as &$r ) {
+				$depth = 1;
+				$parent_id = $r->secondary_item_id;
+
+				while ( $parent_id !== $r->item_id ) {
+					$depth++;
+
+					// When display_comments=stream, the parent comment may not be part of the
+					// returned results, so we manually fetch it.
+					if ( empty( $ref[ $parent_id ] ) ) {
+						$direct_parent = new BP_Activity_Activity( $parent_id );
+						if ( isset( $direct_parent->secondary_item_id ) ) {
+							// If the direct parent is not an activity update, that means we've reached
+							// the parent activity item (eg. new_blog_post).
+							if ( 'activity_update' !== $direct_parent->type ) {
+								$parent_id = $r->item_id;
+
+							} else {
+								$parent_id = $direct_parent->secondary_item_id;
+							}
+
+						} else {
+							// Something went wrong.  Short-circuit the depth calculation.
+							$parent_id = $r->item_id;
+						}
+					} else {
+						$parent_id = $ref[ $parent_id ]->secondary_item_id;
+					}
+				}
+				$r->depth = $depth;
+			}
+
+			// If we cache a value of false, it'll count as a cache
+			// miss the next time the activity comments are fetched.
+			// Storing the string 'none' is a hack workaround to
+			// avoid unnecessary queries.
+			if ( false === $comments ) {
+				$cache_value = 'none';
+			} else {
+				$cache_value = $comments;
+			}
+
+			wp_cache_set( $activity_id, $cache_value, 'bp_activity_comments' );
+		}
+
+		return $comments;
+	}
+
+	/**
+	 * Rebuild nested comment tree under an activity or activity comment.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param  int $parent_id ID of an activity or activity comment.
+	 * @param  int $left      Node boundary start for activity or activity comment.
+	 * @return int Right      Node boundary of activity or activity comment.
+	 */
+	public static function rebuild_activity_comment_tree( $parent_id, $left = 1 ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		// The right value of this node is the left value + 1.
+		$right = intval( $left + 1 );
+
+		// Get all descendants of this node.
+		$comments    = BP_Activity_Activity::get_child_comments( $parent_id );
+		$descendants = wp_list_pluck( $comments, 'id' );
+
+		// Loop the descendants and recalculate the left and right values.
+		foreach ( (array) $descendants as $descendant_id ) {
+			$right = BP_Activity_Activity::rebuild_activity_comment_tree( $descendant_id, $right );
+		}
+
+		// We've got the left value, and now that we've processed the children
+		// of this node we also know the right value.
+		if ( 1 === $left ) {
+			$wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE id = %d", $left, $right, $parent_id ) );
+		} else {
+			$wpdb->query( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET mptt_left = %d, mptt_right = %d WHERE type = 'activity_comment' AND id = %d", $left, $right, $parent_id ) );
+		}
+
+		// Return the right value of this node + 1.
+		return intval( $right + 1 );
+	}
+
+	/**
+	 * Get child comments of an activity or activity comment.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param int $parent_id ID of an activity or activity comment.
+	 * @return object Numerically indexed array of child comments.
+	 */
+	public static function get_child_comments( $parent_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE type = 'activity_comment' AND secondary_item_id = %d", $parent_id ) );
+	}
+
+	/**
+	 * Get a list of components that have recorded activity associated with them.
+	 *
+	 * @param bool $skip_last_activity If true, components will not be
+	 *                                 included if the only activity type associated with them is
+	 *                                 'last_activity'. (Since 2.0.0, 'last_activity' is stored in
+	 *                                 the activity table, but these items are not full-fledged
+	 *                                 activity items.) Default: true.
+	 * @return array List of component names.
+	 */
+	public static function get_recorded_components( $skip_last_activity = true ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( true === $skip_last_activity ) {
+			$components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} WHERE action != '' AND action != 'last_activity' ORDER BY component ASC" );
+		} else {
+			$components = $wpdb->get_col( "SELECT DISTINCT component FROM {$bp->activity->table_name} ORDER BY component ASC" );
+		}
+
+		return $components;
+	}
+
+	/**
+	 * Get sitewide activity items for use in an RSS feed.
+	 *
+	 * @param int $limit Optional. Number of items to fetch. Default: 35.
+	 * @return array $activity_feed List of activity items, with RSS data added.
+	 */
+	public static function get_sitewide_items_for_feed( $limit = 35 ) {
+		$activities    = bp_activity_get_sitewide( array( 'max' => $limit ) );
+		$activity_feed = array();
+
+		for ( $i = 0, $count = count( $activities ); $i < $count; ++$i ) {
+			$title                            = explode( '<span', $activities[$i]['content'] );
+			$activity_feed[$i]['title']       = trim( strip_tags( $title[0] ) );
+			$activity_feed[$i]['link']        = $activities[$i]['primary_link'];
+			$activity_feed[$i]['description'] = @sprintf( $activities[$i]['content'], '' );
+			$activity_feed[$i]['pubdate']     = $activities[$i]['date_recorded'];
+		}
+
+		return $activity_feed;
+	}
+
+	/**
+	 * Create SQL IN clause for filter queries.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Activity_Activity::get_filter_sql()
+	 *
+	 * @param string     $field The database field.
+	 * @param array|bool $items The values for the IN clause, or false when none are found.
+	 * @return string|bool
+	 */
+	public static function get_in_operator_sql( $field, $items ) {
+		global $wpdb;
+
+		// Split items at the comma.
+		if ( ! is_array( $items ) ) {
+			$items = explode( ',', $items );
+		}
+
+		// Array of prepared integers or quoted strings.
+		$items_prepared = array();
+
+		// Clean up and format each item.
+		foreach ( $items as $item ) {
+			// Clean up the string.
+			$item = trim( $item );
+			// Pass everything through prepare for security and to safely quote strings.
+			$items_prepared[] = ( is_numeric( $item ) ) ? $wpdb->prepare( '%d', $item ) : $wpdb->prepare( '%s', $item );
+		}
+
+		// Build IN operator sql syntax.
+		if ( count( $items_prepared ) )
+			return sprintf( '%s IN ( %s )', trim( $field ), implode( ',', $items_prepared ) );
+		else
+			return false;
+	}
+
+	/**
+	 * Create filter SQL clauses.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $filter_array {
+	 *     Fields and values to filter by.
+	 *
+	 *     @type array|string|int $user_id User ID(s).
+	 *     @type array|string     $object       Corresponds to the 'component'
+	 *                                          column in the database.
+	 *     @type array|string     $action       Corresponds to the 'type' column
+	 *                                          in the database.
+	 *     @type array|string|int $primary_id   Corresponds to the 'item_id'
+	 *                                          column in the database.
+	 *     @type array|string|int $secondary_id Corresponds to the
+	 *                                          'secondary_item_id' column in the database.
+	 *     @type int              $offset       Return only those items with an ID greater
+	 *                                          than the offset value.
+	 *     @type string           $since        Return only those items that have a
+	 *                                          date_recorded value greater than a
+	 *                                          given MySQL-formatted date.
+	 * }
+	 * @return string The filter clause, for use in a SQL query.
+	 */
+	public static function get_filter_sql( $filter_array ) {
+
+		$filter_sql = array();
+
+		if ( !empty( $filter_array['user_id'] ) ) {
+			$user_sql = BP_Activity_Activity::get_in_operator_sql( 'a.user_id', $filter_array['user_id'] );
+			if ( !empty( $user_sql ) )
+				$filter_sql[] = $user_sql;
+		}
+
+		if ( !empty( $filter_array['object'] ) ) {
+			$object_sql = BP_Activity_Activity::get_in_operator_sql( 'a.component', $filter_array['object'] );
+			if ( !empty( $object_sql ) )
+				$filter_sql[] = $object_sql;
+		}
+
+		if ( !empty( $filter_array['action'] ) ) {
+			$action_sql = BP_Activity_Activity::get_in_operator_sql( 'a.type', $filter_array['action'] );
+			if ( ! empty( $action_sql ) )
+				$filter_sql[] = $action_sql;
+		}
+
+		if ( !empty( $filter_array['primary_id'] ) ) {
+			$pid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.item_id', $filter_array['primary_id'] );
+			if ( !empty( $pid_sql ) )
+				$filter_sql[] = $pid_sql;
+		}
+
+		if ( !empty( $filter_array['secondary_id'] ) ) {
+			$sid_sql = BP_Activity_Activity::get_in_operator_sql( 'a.secondary_item_id', $filter_array['secondary_id'] );
+			if ( !empty( $sid_sql ) )
+				$filter_sql[] = $sid_sql;
+		}
+
+		if ( ! empty( $filter_array['offset'] ) ) {
+			$sid_sql = absint( $filter_array['offset'] );
+			$filter_sql[] = "a.id >= {$sid_sql}";
+		}
+
+		if ( ! empty( $filter_array['since'] ) ) {
+			// Validate that this is a proper Y-m-d H:i:s date.
+			// Trick: parse to UNIX date then translate back.
+			$translated_date = date( 'Y-m-d H:i:s', strtotime( $filter_array['since'] ) );
+			if ( $translated_date === $filter_array['since'] ) {
+				$filter_sql[] = "a.date_recorded > '{$translated_date}'";
+			}
+		}
+
+		if ( empty( $filter_sql ) )
+			return false;
+
+		return join( ' AND ', $filter_sql );
+	}
+
+	/**
+	 * Get the date/time of last recorded activity.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @return string ISO timestamp.
+	 */
+	public static function get_last_updated() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( "SELECT date_recorded FROM {$bp->activity->table_name} ORDER BY date_recorded DESC LIMIT 1" );
+	}
+
+	/**
+	 * Get favorite count for a given user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param int $user_id The ID of the user whose favorites you're counting.
+	 * @return int $value A count of the user's favorites.
+	 */
+	public static function total_favorite_count( $user_id ) {
+
+		// Get activities from user meta.
+		$favorite_activity_entries = bp_get_user_meta( $user_id, 'bp_favorite_activities', true );
+		if ( ! empty( $favorite_activity_entries ) ) {
+			return count( maybe_unserialize( $favorite_activity_entries ) );
+		}
+
+		// No favorites.
+		return 0;
+	}
+
+	/**
+	 * Check whether an activity item exists with a given string content.
+	 *
+	 * @param string $content The content to filter by.
+	 * @return int|bool The ID of the first matching item if found, otherwise false.
+	 */
+	public static function check_exists_by_content( $content ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->activity->table_name} WHERE content = %s", $content ) );
+	}
+
+	/**
+	 * Hide all activity for a given user.
+	 *
+	 * @param int $user_id The ID of the user whose activity you want to mark hidden.
+	 * @return mixed
+	 */
+	public static function hide_all_for_user( $user_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "UPDATE {$bp->activity->table_name} SET hide_sitewide = 1 WHERE user_id = %d", $user_id ) );
+	}
+
+	/**
+	 * PHP-agnostic version of {@link array_replace_recursive()}.
+	 *
+	 * The array_replace_recursive() function is a PHP 5.3 function.  BuddyPress (and
+	 * WordPress) currently supports down to PHP 5.2, so this method is a workaround
+	 * for PHP 5.2.
+	 *
+	 * Note: array_replace_recursive() supports infinite arguments, but for our use-
+	 * case, we only need to support two arguments.
+	 *
+	 * Subject to removal once WordPress makes PHP 5.3.0 the minimum requirement.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @see http://php.net/manual/en/function.array-replace-recursive.php#109390
+	 *
+	 * @param  array $base         Array with keys needing to be replaced.
+	 * @param  array $replacements Array with the replaced keys.
+	 * @return array
+	 */
+	protected static function array_replace_recursive( $base = array(), $replacements = array() ) {
+		if ( function_exists( 'array_replace_recursive' ) ) {
+			return array_replace_recursive( $base, $replacements );
+		}
+
+		// PHP 5.2-compatible version
+		// http://php.net/manual/en/function.array-replace-recursive.php#109390.
+		foreach ( array_slice( func_get_args(), 1 ) as $replacements ) {
+			$bref_stack = array( &$base );
+			$head_stack = array( $replacements );
+
+			do {
+				end( $bref_stack );
+
+				$bref = &$bref_stack[ key( $bref_stack ) ];
+				$head = array_pop( $head_stack );
+
+				unset( $bref_stack[ key($bref_stack) ] );
+
+				foreach ( array_keys( $head ) as $key ) {
+					if ( isset( $key, $bref ) && is_array( $bref[$key] ) && is_array( $head[$key] ) ) {
+						$bref_stack[] = &$bref[ $key ];
+						$head_stack[] = $head[ $key ];
+					} else {
+						$bref[ $key ] = $head[ $key ];
+					}
+				}
+			} while( count( $head_stack ) );
+		}
+
+		return $base;
+	}
+}
Index: /tags/2.4.3/src/bp-activity/classes/class-bp-activity-feed.php
===================================================================
--- /tags/2.4.3/src/bp-activity/classes/class-bp-activity-feed.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/classes/class-bp-activity-feed.php	(revision 10410)
@@ -0,0 +1,447 @@
+<?php
+/**
+ * BuddyPress Activity Classes.
+ *
+ * @package BuddyPress
+ * @subpackage ActivityFeeds
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Create a RSS feed using the activity component.
+ *
+ * You should only construct a new feed when you've validated that you're on
+ * the appropriate screen.
+ *
+ * @since 1.8.0
+ *
+ * See {@link bp_activity_action_sitewide_feed()} as an example.
+ *
+ * @param array $args {
+ *   @type string $id               Required. Internal id for the feed; should be alphanumeric only.
+ *   @type string $title            Optional. RSS feed title.
+ *   @type string $link             Optional. Relevant link for the RSS feed.
+ *   @type string $description      Optional. RSS feed description.
+ *   @type string $ttl              Optional. Time-to-live. (see inline doc in constructor)
+ *   @type string $update_period    Optional. Part of the syndication module.
+ *                                            (see inline doc in constructor for more info)
+ *   @type string $update_frequency Optional. Part of the syndication module.
+ *                                            (see inline doc in constructor for more info)
+ *   @type string $max              Optional. Number of feed items to display.
+ *   @type array  $activity_args    Optional. Arguments passed to {@link bp_has_activities()}
+ * }
+ */
+class BP_Activity_Feed {
+
+	/**
+	 * Holds our custom class properties.
+	 *
+	 * These variables are stored in a protected array that is magically
+	 * updated using PHP 5.2+ methods.
+	 *
+	 * @see BP_Feed::__construct() This is where $data is added.
+	 * @var array
+	 */
+	protected $data;
+
+	/**
+	 * Magic method for checking the existence of a certain data variable.
+	 *
+	 * @param string $key Property to check.
+	 *
+	 * @return bool Whether or not data variable exists.
+	 */
+	public function __isset( $key ) { return isset( $this->data[$key] ); }
+
+	/**
+	 * Magic method for getting a certain data variable.
+	 *
+	 * @param string $key Property to get.
+	 *
+	 * @return mixed Data in variable if available or null.
+	 */
+	public function __get( $key ) { return isset( $this->data[$key] ) ? $this->data[$key] : null; }
+
+	/**
+	 * Magic method for setting a certain data variable.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string $key   The property to set.
+	 * @param mixed  $value The value to set.
+	 */
+	public function __set( $key, $value ) { $this->data[$key] = $value; }
+
+	/**
+	 * Constructor.
+	 *
+	 * @param array $args Optional.
+	 */
+	public function __construct( $args = array() ) {
+
+		/**
+		 * Filters if BuddyPress should consider feeds enabled. If disabled, it will return early.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param bool true Default true aka feeds are enabled.
+		 */
+		if ( false === (bool) apply_filters( 'bp_activity_enable_feeds', true ) ) {
+			global $wp_query;
+
+			// Set feed flag to false.
+			$wp_query->is_feed = false;
+
+			return false;
+		}
+
+		// Setup data.
+		$this->data = wp_parse_args( $args, array(
+			// Internal identifier for the RSS feed - should be alphanumeric only.
+			'id'               => '',
+
+			// RSS title - should be plain-text.
+			'title'            => '',
+
+			// Relevant link for the RSS feed.
+			'link'             => '',
+
+			// RSS description - should be plain-text.
+			'description'      => '',
+
+			// Time-to-live - number of minutes to cache the data before an aggregator
+			// requests it again.  This is only acknowledged if the RSS client supports it
+			//
+			// See: http://www.rssboard.org/rss-profile#element-channel-ttl.
+			// See: http://www.kbcafe.com/rss/rssfeedstate.html#ttl.
+			'ttl'              => '30',
+
+			// Syndication module - similar to ttl, but not really supported by RSS
+			// clients
+			//
+			// See: http://web.resource.org/rss/1.0/modules/syndication/#description.
+			// See: http://www.kbcafe.com/rss/rssfeedstate.html#syndicationmodule.
+			'update_period'    => 'hourly',
+			'update_frequency' => 2,
+
+			// Number of items to display.
+			'max'              => 50,
+
+			// Activity arguments passed to bp_has_activities().
+			'activity_args'    => array()
+		) );
+
+		/**
+		 * Fires before the feed is setup so plugins can modify.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param BP_Activity_Feed $this Current instance of activity feed. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_activity_feed_prefetch', array( &$this ) );
+
+		// Setup class properties.
+		$this->setup_properties();
+
+		// Check if id is valid.
+		if ( empty( $this->id ) ) {
+			_doing_it_wrong( 'BP_Activity_Feed', __( "RSS feed 'id' must be defined", 'buddypress' ), 'BP 1.8' );
+			return false;
+		}
+
+		/**
+		 * Fires after the feed is setup so plugins can modify.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param BP_Activity_Feed $this Current instance of activity feed. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_activity_feed_postfetch', array( &$this ) );
+
+		// Setup feed hooks.
+		$this->setup_hooks();
+
+		// Output the feed.
+		$this->output();
+
+		// Kill the rest of the output.
+		die();
+	}
+
+	/** SETUP ****************************************************************/
+
+	/**
+	 * Setup and validate the class properties.
+	 */
+	protected function setup_properties() {
+		$this->id               = sanitize_title( $this->id );
+		$this->title            = strip_tags( $this->title );
+		$this->link             = esc_url_raw( $this->link );
+		$this->description      = strip_tags( $this->description );
+		$this->ttl              = (int) $this->ttl;
+		$this->update_period    = strip_tags( $this->update_period );
+		$this->update_frequency = (int) $this->update_frequency;
+
+		$this->activity_args    = wp_parse_args( $this->activity_args, array(
+			'max'              => $this->max,
+			'per_page'         => $this->max,
+			'display_comments' => 'stream'
+		) );
+
+	}
+
+	/**
+	 * Setup some hooks that are used in the feed.
+	 *
+	 * Currently, these hooks are used to maintain backwards compatibility with
+	 * the RSS feeds previous to BP 1.8.
+	 */
+	protected function setup_hooks() {
+		add_action( 'bp_activity_feed_rss_attributes',   array( $this, 'backpat_rss_attributes' ) );
+		add_action( 'bp_activity_feed_channel_elements', array( $this, 'backpat_channel_elements' ) );
+		add_action( 'bp_activity_feed_item_elements',    array( $this, 'backpat_item_elements' ) );
+	}
+
+	/** BACKPAT HOOKS ********************************************************/
+
+	/**
+	 * Fire a hook to ensure backward compatibility for RSS attributes.
+	 */
+	public function backpat_rss_attributes() {
+
+		/**
+		 * Fires inside backpat_rss_attributes method for backwards compatibility related to RSS attributes.
+		 *
+		 * This hook was originally separated out for individual components but has since been abstracted into the BP_Activity_Feed class.
+		 *
+		 * @since 1.0.0
+		 */
+		do_action( 'bp_activity_' . $this->id . '_feed' );
+	}
+
+	/**
+	 * Fire a hook to ensure backward compatibility for channel elements.
+	 */
+	public function backpat_channel_elements() {
+
+		/**
+		 * Fires inside backpat_channel_elements method for backwards compatibility related to RSS channel elements.
+		 *
+		 * This hook was originally separated out for individual components but has since been abstracted into the BP_Activity_Feed class.
+		 *
+		 * @since 1.0.0
+		 */
+		do_action( 'bp_activity_' . $this->id . '_feed_head' );
+	}
+
+	/**
+	 * Fire a hook to ensure backward compatibility for item elements.
+	 */
+	public function backpat_item_elements() {
+		switch ( $this->id ) {
+
+			// Sitewide and friends feeds use the 'personal' hook.
+			case 'sitewide' :
+			case 'friends' :
+				$id = 'personal';
+
+				break;
+
+			default :
+				$id = $this->id;
+
+				break;
+		}
+
+		/**
+		 * Fires inside backpat_item_elements method for backwards compatibility related to RSS item elements.
+		 *
+		 * This hook was originally separated out for individual components but has since been abstracted into the BP_Activity_Feed class.
+		 *
+		 * @since 1.0.0
+		 */
+		do_action( 'bp_activity_' . $id . '_feed_item' );
+	}
+
+	/** HELPERS **************************************************************/
+
+	/**
+	 * Output the feed's item content.
+	 */
+	protected function feed_content() {
+		bp_activity_content_body();
+
+		switch ( $this->id ) {
+
+			// Also output parent activity item if we're on a specific feed.
+			case 'favorites' :
+			case 'friends' :
+			case 'mentions' :
+			case 'personal' :
+
+				if ( 'activity_comment' == bp_get_activity_action_name() ) :
+			?>
+				<strong><?php _e( 'In reply to', 'buddypress' ) ?></strong> -
+				<?php bp_activity_parent_content() ?>
+			<?php
+				endif;
+
+				break;
+		}
+	}
+
+	/**
+	 * Sets various HTTP headers related to Content-Type and browser caching.
+	 *
+	 * Most of this class method is derived from {@link WP::send_headers()}.
+	 *
+	 * @since 1.9.0
+	 */
+	protected function http_headers() {
+		// Set up some additional headers if not on a directory page
+		// this is done b/c BP uses pseudo-pages.
+		if ( ! bp_is_directory() ) {
+			global $wp_query;
+
+			$wp_query->is_404 = false;
+			status_header( 200 );
+		}
+
+		// Set content-type.
+		@header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true );
+		send_nosniff_header();
+
+		// Cache-related variables.
+		$last_modified      = mysql2date( 'D, d M Y H:i:s O', bp_activity_get_last_updated(), false );
+		$modified_timestamp = strtotime( $last_modified );
+		$etag               = md5( $last_modified );
+
+		// Set cache-related headers.
+		@header( 'Last-Modified: ' . $last_modified );
+		@header( 'Pragma: no-cache' );
+		@header( 'ETag: ' . '"' . $etag . '"' );
+
+		// First commit of BuddyPress! (Easter egg).
+		@header( 'Expires: Tue, 25 Mar 2008 17:13:55 GMT');
+
+		// Get ETag from supported user agents.
+		if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) {
+			$client_etag = wp_unslash( $_SERVER['HTTP_IF_NONE_MATCH'] );
+
+			// Remove quotes from ETag.
+			$client_etag = trim( $client_etag, '"' );
+
+			// Strip suffixes from ETag if they exist (eg. "-gzip").
+			$etag_suffix_pos = strpos( $client_etag, '-' );
+			if ( ! empty( $etag_suffix_pos ) ) {
+				$client_etag = substr( $client_etag, 0, $etag_suffix_pos );
+			}
+
+		// No ETag found.
+		} else {
+			$client_etag = false;
+		}
+
+		// Get client last modified timestamp from supported user agents.
+		$client_last_modified      = empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ? '' : trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
+		$client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;
+
+		// Set 304 status if feed hasn't been updated since last fetch.
+		if ( ( $client_last_modified && $client_etag ) ?
+				 ( ( $client_modified_timestamp >= $modified_timestamp ) && ( $client_etag == $etag ) ) :
+				 ( ( $client_modified_timestamp >= $modified_timestamp ) || ( $client_etag == $etag ) ) ) {
+			$status = 304;
+		} else {
+			$status = false;
+		}
+
+		// If feed hasn't changed as reported by the user agent, set 304 status header.
+		if ( ! empty( $status ) ) {
+			status_header( $status );
+
+			// Cached response, so stop now!
+			if ( $status == 304 ) {
+				exit();
+			}
+		}
+	}
+
+	/** OUTPUT ***************************************************************/
+
+	/**
+	 * Output the RSS feed.
+	 */
+	protected function output() {
+		$this->http_headers();
+		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:atom="http://www.w3.org/2005/Atom"
+	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
+	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
+	<?php
+
+	/**
+	 * Fires at the end of the opening RSS tag for feed output so plugins can add extra attributes.
+	 *
+	 * @since 1.8.0
+	 */
+	do_action( 'bp_activity_feed_rss_attributes' ); ?>
+>
+
+<channel>
+	<title><?php echo $this->title; ?></title>
+	<link><?php echo $this->link; ?></link>
+	<atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
+	<description><?php echo $this->description ?></description>
+	<lastBuildDate><?php echo mysql2date( 'D, d M Y H:i:s O', bp_activity_get_last_updated(), false ); ?></lastBuildDate>
+	<generator>https://buddypress.org/?v=<?php bp_version(); ?></generator>
+	<language><?php bloginfo_rss( 'language' ); ?></language>
+	<ttl><?php echo $this->ttl; ?></ttl>
+	<sy:updatePeriod><?php echo $this->update_period; ?></sy:updatePeriod>
+ 	<sy:updateFrequency><?php echo $this->update_frequency; ?></sy:updateFrequency>
+	<?php
+
+	/**
+	 * Fires at the end of channel elements list in RSS feed so plugins can add extra channel elements.
+	 *
+	 * @since 1.8.0
+	 */
+	do_action( 'bp_activity_feed_channel_elements' ); ?>
+
+	<?php if ( bp_has_activities( $this->activity_args ) ) : ?>
+		<?php while ( bp_activities() ) : bp_the_activity(); ?>
+			<item>
+				<guid isPermaLink="false"><?php bp_activity_feed_item_guid(); ?></guid>
+				<title><?php echo stripslashes( bp_get_activity_feed_item_title() ); ?></title>
+				<link><?php bp_activity_thread_permalink() ?></link>
+				<pubDate><?php echo mysql2date( 'D, d M Y H:i:s O', bp_get_activity_feed_item_date(), false ); ?></pubDate>
+
+				<?php if ( bp_get_activity_feed_item_description() ) : ?>
+					<content:encoded><![CDATA[<?php $this->feed_content(); ?>]]></content:encoded>
+				<?php endif; ?>
+
+				<?php if ( bp_activity_can_comment() ) : ?>
+					<slash:comments><?php bp_activity_comment_count(); ?></slash:comments>
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires at the end of the individual RSS Item list in RSS feed so plugins can add extra item elements.
+				 *
+				 * @since 1.8.0
+				 */
+				do_action( 'bp_activity_feed_item_elements' ); ?>
+			</item>
+		<?php endwhile; ?>
+
+	<?php endif; ?>
+</channel>
+</rss><?php
+	}
+}
Index: /tags/2.4.3/src/bp-activity/classes/class-bp-activity-query.php
===================================================================
--- /tags/2.4.3/src/bp-activity/classes/class-bp-activity-query.php	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/classes/class-bp-activity-query.php	(revision 10410)
@@ -0,0 +1,246 @@
+<?php
+/**
+ * BuddyPress Activity Classes
+ *
+ * @package BuddyPress
+ * @subpackage ActivityQuery
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Class for generating the WHERE SQL clause for advanced activity fetching.
+ *
+ * This is notably used in {@link BP_Activity_Activity::get()} with the
+ * 'filter_query' parameter.
+ *
+ * @since 2.2.0
+ */
+class BP_Activity_Query extends BP_Recursive_Query {
+	/**
+	 * Array of activity queries.
+	 *
+	 * See {@see BP_Activity_Query::__construct()} for information on query arguments.
+	 *
+	 * @since 2.2.0
+	 * @var array
+	 */
+	public $queries = array();
+
+	/**
+	 * Table alias.
+	 *
+	 * @since 2.2.0
+	 * @var string
+	 */
+	public $table_alias = '';
+
+	/**
+	 * Supported DB columns.
+	 *
+	 * See the 'wp_bp_activity' DB table schema.
+	 *
+	 * @since 2.2.0
+	 * @var array
+	 */
+	public $db_columns = array(
+		'id', 'user_id', 'component', 'type', 'action', 'content',
+		'item_id', 'secondary_item_id', 'hide_sitewide', 'is_spam',
+	);
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $query {
+	 *     Array of query clauses.
+	 *     @type array {
+	 *         @type string $column   Required. The column to query against. Basically, any DB column in the main
+	 *                                'wp_bp_activity' table.
+	 *         @type string $value    Required. Value to filter by.
+	 *         @type string $compare  Optional. The comparison operator. Default '='.
+	 *                                Accepts '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'LIKE',
+	 *                                'NOT LIKE', BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', 'RLIKE'.
+	 *         @type string $relation Optional. The boolean relationship between the activity queries.
+	 *                                Accepts 'OR', 'AND'. Default 'AND'.
+	 *         @type array {
+	 *             Optional. Another fully-formed activity query. See parameters above.
+	 *         }
+	 *     }
+	 * }
+	 */
+	public function __construct( $query = array() ) {
+		if ( ! is_array( $query ) ) {
+			return;
+		}
+
+		$this->queries = $this->sanitize_query( $query );
+	}
+
+	/**
+	 * Generates WHERE SQL clause to be appended to a main query.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string $alias An existing table alias that is compatible with the current query clause.
+	 *                      Default: 'a'. BP_Activity_Activity::get() uses 'a', so we default to that.
+	 * @return string SQL fragment to append to the main WHERE clause.
+	 */
+	public function get_sql( $alias = 'a' ) {
+		if ( ! empty( $alias ) ) {
+			$this->table_alias = sanitize_title( $alias );
+		}
+
+		$sql = $this->get_sql_clauses();
+
+		// We only need the 'where' clause.
+		//
+		// Also trim trailing "AND" clause from parent BP_Recursive_Query class
+		// since it's not necessary for our needs.
+		return preg_replace( '/^\sAND/', '', $sql['where'] );
+	}
+
+	/**
+	 * Generate WHERE clauses for a first-order clause.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param  array $clause       Array of arguments belonging to the clause.
+	 * @param  array $parent_query Parent query to which the clause belongs.
+	 * @return array {
+	 *     @type array $where Array of subclauses for the WHERE statement.
+	 *     @type array $join  Empty array. Not used.
+	 * }
+	 */
+	protected function get_sql_for_clause( $clause, $parent_query ) {
+		global $wpdb;
+
+		$sql_chunks = array(
+			'where' => array(),
+			'join' => array(),
+		);
+
+		$column = isset( $clause['column'] ) ? $this->validate_column( $clause['column'] ) : '';
+		$value  = isset( $clause['value'] )  ? $clause['value'] : '';
+		if ( empty( $column ) || ! isset( $clause['value'] ) ) {
+			return $sql_chunks;
+		}
+
+		if ( isset( $clause['compare'] ) ) {
+			$clause['compare'] = strtoupper( $clause['compare'] );
+		} else {
+			$clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
+		}
+
+		// Default 'compare' to '=' if no valid operator is found.
+		if ( ! in_array( $clause['compare'], array(
+			'=', '!=', '>', '>=', '<', '<=',
+			'LIKE', 'NOT LIKE',
+			'IN', 'NOT IN',
+			'BETWEEN', 'NOT BETWEEN',
+			'REGEXP', 'NOT REGEXP', 'RLIKE'
+		) ) ) {
+			$clause['compare'] = '=';
+		}
+
+		$compare = $clause['compare'];
+
+		$alias = ! empty( $this->table_alias ) ? "{$this->table_alias}." : '';
+
+		// Next, Build the WHERE clause.
+		$where = '';
+
+		// Value.
+		if ( isset( $clause['value'] ) ) {
+			if ( in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
+				if ( ! is_array( $value ) ) {
+					$value = preg_split( '/[,\s]+/', $value );
+				}
+			}
+
+			// Tinyint.
+			if ( ! empty( $column ) && true === in_array( $column, array( 'hide_sitewide', 'is_spam' ) ) ) {
+				$sql_chunks['where'][] = $wpdb->prepare( "{$alias}{$column} = %d", $value );
+
+			} else {
+				switch ( $compare ) {
+					// IN uses different syntax.
+					case 'IN' :
+					case 'NOT IN' :
+						$in_sql = BP_Activity_Activity::get_in_operator_sql( "{$alias}{$column}", $value );
+
+						// 'NOT IN' operator is as easy as a string replace!
+						if ( 'NOT IN' === $compare ) {
+							$in_sql = str_replace( 'IN', 'NOT IN', $in_sql );
+						}
+
+						$sql_chunks['where'][] = $in_sql;
+						break;
+
+					case 'BETWEEN' :
+					case 'NOT BETWEEN' :
+						$value = array_slice( $value, 0, 2 );
+						$where = $wpdb->prepare( '%s AND %s', $value );
+						break;
+
+					case 'LIKE' :
+					case 'NOT LIKE' :
+						$value = '%' . bp_esc_like( $value ) . '%';
+						$where = $wpdb->prepare( '%s', $value );
+						break;
+
+					default :
+						$where = $wpdb->prepare( '%s', $value );
+						break;
+
+				}
+			}
+
+			if ( $where ) {
+				$sql_chunks['where'][] = "{$alias}{$column} {$compare} {$where}";
+			}
+		}
+
+		/*
+		 * Multiple WHERE clauses should be joined in parentheses.
+		 */
+		if ( 1 < count( $sql_chunks['where'] ) ) {
+			$sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
+		}
+
+		return $sql_chunks;
+	}
+
+	/**
+	 * Determine whether a clause is first-order.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $query Clause to check.
+	 * @return bool
+	 */
+	protected function is_first_order_clause( $query ) {
+		return isset( $query['column'] ) || isset( $query['value'] );
+	}
+
+	/**
+	 * Validates a column name parameter.
+	 *
+	 * Column names are checked against a whitelist of known tables.
+	 * See {@link BP_Activity_Query::db_tables}.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string $column The user-supplied column name.
+	 * @return string A validated column name value.
+	 */
+	public function validate_column( $column = '' ) {
+		if ( in_array( $column, $this->db_columns ) ) {
+			return $column;
+		} else {
+			return '';
+		}
+	}
+}
Index: /tags/2.4.3/src/bp-activity/css/mentions-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-activity/css/mentions-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/css/mentions-rtl.css	(revision 10410)
@@ -0,0 +1,93 @@
+.atwho-view {
+	background: rgba(204, 204, 204, 0.8);
+	border-radius: 2px;
+	border: 1px solid rgb(204, 204, 204);
+	box-shadow: 0 0 5px rgba(204, 204, 204, 0.25), 0 0 1px #FFF;
+	color: #D84800;
+	display: none;
+	font-family: sans-serif;
+	margin-top: 18px;
+	position: absolute;
+	top: 0;
+	z-index: 1000; /* >999 for wp-admin */
+}
+/* @noflip */
+.atwho-view {
+	left: 0; 
+}
+.atwho-view ul {
+	background: #FFF;
+	list-style: none;
+	margin: auto;
+	padding: 0;
+}
+.atwho-view ul li {
+	border-bottom: 1px solid #EFEFEF;
+	box-sizing: content-box;
+	cursor: pointer;
+	display: block;
+	font-size: 14px;
+	height: 20px;
+	line-height: 20px;
+	margin: 0;
+	overflow: hidden;
+	padding: 5px 10px;
+}
+.atwho-view img {
+	border-radius: 2px;
+	float: left;
+	height: 20px;
+	margin-top:0;
+	width: 20px;
+}
+.atwho-view strong {
+	background: #EFEFEF;
+	font: bold;
+}
+.atwho-view .username strong {
+	color: #D54E21;
+}
+.atwho-view small {
+	color: #AAA;
+	float: left;
+	font-size: smaller;
+	font-weight: normal;
+	margin: 0 40px 0 10px;
+}
+.atwho-view .cur {
+	background: rgba(239, 239, 239, 0.5);
+}
+
+@media (max-width: 900px) {
+	.atwho-view img {
+		float: right;
+		margin: 0 0 0 10px;
+	}
+}
+@media (max-width: 400px) {
+	.atwho-view ul li {
+		font-size: 16px;
+		line-height: 23px;
+		padding: 13px;
+	}
+	.atwho-view ul li img {
+		height: 30px;
+		margin-top: -5px;
+		width: 30px;
+	}
+	.atwho-view {
+		border-radius: 0;
+		height: 100%;
+		right: 0 !important;
+		width: 100%;
+	}
+	.atwho-view ul li .username {
+		display: inline-block;
+		margin: -10px 0 0 0;
+		padding: 10px 0;
+	}
+	.atwho-view ul li small {
+		display: inline-block;
+		margin-right: 20px;
+	}
+}
Index: /tags/2.4.3/src/bp-activity/css/mentions.css
===================================================================
--- /tags/2.4.3/src/bp-activity/css/mentions.css	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/css/mentions.css	(revision 10410)
@@ -0,0 +1,93 @@
+.atwho-view {
+	background: rgba(204, 204, 204, 0.8);
+	border-radius: 2px;
+	border: 1px solid rgb(204, 204, 204);
+	box-shadow: 0 0 5px rgba(204, 204, 204, 0.25), 0 0 1px #FFF;
+	color: #D84800;
+	display: none;
+	font-family: sans-serif;
+	margin-top: 18px;
+	position: absolute;
+	top: 0;
+	z-index: 1000; /* >999 for wp-admin */
+}
+/* @noflip */
+.atwho-view {
+	left: 0; 
+}
+.atwho-view ul {
+	background: #FFF;
+	list-style: none;
+	margin: auto;
+	padding: 0;
+}
+.atwho-view ul li {
+	border-bottom: 1px solid #EFEFEF;
+	box-sizing: content-box;
+	cursor: pointer;
+	display: block;
+	font-size: 14px;
+	height: 20px;
+	line-height: 20px;
+	margin: 0;
+	overflow: hidden;
+	padding: 5px 10px;
+}
+.atwho-view img {
+	border-radius: 2px;
+	float: right;
+	height: 20px;
+	margin-top:0;
+	width: 20px;
+}
+.atwho-view strong {
+	background: #EFEFEF;
+	font: bold;
+}
+.atwho-view .username strong {
+	color: #D54E21;
+}
+.atwho-view small {
+	color: #AAA;
+	float: right;
+	font-size: smaller;
+	font-weight: normal;
+	margin: 0 10px 0 40px;
+}
+.atwho-view .cur {
+	background: rgba(239, 239, 239, 0.5);
+}
+
+@media (max-width: 900px) {
+	.atwho-view img {
+		float: left;
+		margin: 0 10px 0 0;
+	}
+}
+@media (max-width: 400px) {
+	.atwho-view ul li {
+		font-size: 16px;
+		line-height: 23px;
+		padding: 13px;
+	}
+	.atwho-view ul li img {
+		height: 30px;
+		margin-top: -5px;
+		width: 30px;
+	}
+	.atwho-view {
+		border-radius: 0;
+		height: 100%;
+		left: 0 !important;
+		width: 100%;
+	}
+	.atwho-view ul li .username {
+		display: inline-block;
+		margin: -10px 0 0 0;
+		padding: 10px 0;
+	}
+	.atwho-view ul li small {
+		display: inline-block;
+		margin-left: 20px;
+	}
+}
Index: /tags/2.4.3/src/bp-activity/js/mentions.js
===================================================================
--- /tags/2.4.3/src/bp-activity/js/mentions.js	(revision 10410)
+++ /tags/2.4.3/src/bp-activity/js/mentions.js	(revision 10410)
@@ -0,0 +1,255 @@
+/* global bp */
+
+window.bp = window.bp || {};
+
+( function( bp, $, undefined ) {
+	var mentionsQueryCache = [],
+		mentionsItem;
+
+	bp.mentions       = bp.mentions || {};
+	bp.mentions.users = window.bp.mentions.users || [];
+
+	if ( typeof window.BP_Suggestions === 'object' ) {
+		bp.mentions.users = window.BP_Suggestions.friends || bp.mentions.users;
+	}
+
+	/**
+	 * Adds BuddyPress @mentions to form inputs.
+	 *
+	 * @param {array|object} options If array, becomes the suggestions' data source. If object, passed as config to $.atwho().
+	 * @since 2.1.0
+	 */
+	$.fn.bp_mentions = function( options ) {
+		if ( $.isArray( options ) ) {
+			options = { data: options };
+		}
+
+		/**
+		 * Default options for at.js; see https://github.com/ichord/At.js/.
+		 */
+		var suggestionsDefaults = {
+			delay:               200,
+			hide_without_suffix: true,
+			insert_tpl:          '</>${atwho-data-value}</>', // For contentEditable, the fake tags make jQuery insert a textNode.
+			limit:               10,
+			start_with_space:    false,
+			suffix:              '',
+
+			callbacks: {
+				/**
+				 * Custom filter to only match the start of spaced words.
+				 * Based on the core/default one.
+				 *
+				 * @param {string} query
+				 * @param {array} data
+				 * @param {string} search_key
+				 * @return {array}
+				 * @since 2.1.0
+				 */
+				filter: function( query, data, search_key ) {
+					var item, _i, _len, _results = [],
+					regxp = new RegExp( '^' + query + '| ' + query, 'ig' ); // start of string, or preceded by a space.
+
+					for ( _i = 0, _len = data.length; _i < _len; _i++ ) {
+						item = data[ _i ];
+						if ( item[ search_key ].toLowerCase().match( regxp ) ) {
+							_results.push( item );
+						}
+					}
+
+					return _results;
+				},
+
+				/**
+				 * Removes some spaces around highlighted string and tweaks regex to allow spaces
+				 * (to match display_name). Based on the core default.
+				 *
+				 * @param {unknown} li
+				 * @param {string} query
+				 * @return {string}
+				 * @since 2.1.0
+				 */
+				highlighter: function( li, query ) {
+					if ( ! query ) {
+						return li;
+					}
+
+					var regexp = new RegExp( '>(\\s*|[\\w\\s]*)(' + this.at.replace( '+', '\\+') + '?' + query.replace( '+', '\\+' ) + ')([\\w ]*)\\s*<', 'ig' );
+					return li.replace( regexp, function( str, $1, $2, $3 ) {
+						return '>' + $1 + '<strong>' + $2 + '</strong>' + $3 + '<';
+					});
+				},
+
+				/**
+				 * Reposition the suggestion list dynamically.
+				 *
+				 * @param {unknown} offset
+				 * @since 2.1.0
+				 */
+				before_reposition: function( offset ) {
+					// get the iframe, if any, already applied with atwho
+					var caret,
+							line,
+							iframeOffset,
+							move,
+							$view = $( '#atwho-ground-' + this.id + ' .atwho-view' ),
+							$body = $( 'body' ),
+							atwhoDataValue = this.$inputor.data( 'atwho' );
+
+					if ( 'undefined' !== atwhoDataValue && 'undefined' !== atwhoDataValue.iframe && null !== atwhoDataValue.iframe ) {
+						caret = this.$inputor.caret( 'offset', { iframe: atwhoDataValue.iframe } );
+						// Caret.js no longer calculates iframe caret position from the window (it's now just within the iframe).
+						// We need to get the iframe offset from the window and merge that into our object.
+						iframeOffset = $( atwhoDataValue.iframe ).offset();
+						if ( 'undefined' !== iframeOffset ) {
+							caret.left += iframeOffset.left;
+							caret.top  += iframeOffset.top;
+						}
+					} else {
+						caret = this.$inputor.caret( 'offset' );
+					}
+
+					// If the caret is past horizontal half, then flip it, yo
+					if ( caret.left > ( $body.width() / 2 ) ) {
+						$view.addClass( 'right' );
+						move = caret.left - offset.left - this.view.$el.width();
+					} else {
+						$view.removeClass( 'right' );
+						move = caret.left - offset.left + 1;
+					}
+
+					// If we're on a small screen, scroll to caret
+					if ( $body.width() <= 400 ) {
+						$( document ).scrollTop( caret.top - 6 );
+					}
+
+					// New position is under the caret (never above) and positioned to follow
+					// Dynamic sizing based on the input area (remove 'px' from end)
+					line = parseInt( this.$inputor.css( 'line-height' ).substr( 0, this.$inputor.css( 'line-height' ).length - 2 ), 10 );
+					if ( !line || line < 5 ) { // sanity check, and catch no line-height
+						line = 19;
+					}
+
+					offset.top   = caret.top + line;
+					offset.left += move;
+				},
+
+				/**
+				 * Override default behaviour which inserts junk tags in the WordPress Visual editor.
+				 *
+				 * @param {unknown} $inputor Element which we're inserting content into.
+				 * @param {string) content The content that will be inserted.
+				 * @param {string) suffix Applied to the end of the content string.
+				 * @return {string}
+				 * @since 2.1.0
+				 */
+				inserting_wrapper: function( $inputor, content, suffix ) {
+					return '' + content + suffix;
+				}
+			}
+		},
+
+		/**
+		 * Default options for our @mentions; see https://github.com/ichord/At.js/.
+		 */
+		mentionsDefaults = {
+			callbacks: {
+				/**
+				 * If there are no matches for the query in this.data, then query BuddyPress.
+				 *
+				 * @param {string} query Partial @mention to search for.
+				 * @param {function} render_view Render page callback function.
+				 * @since 2.1.0
+				 */
+				remote_filter: function( query, render_view ) {
+					var self = $( this ),
+						params = {};
+
+					mentionsItem = mentionsQueryCache[ query ];
+					if ( typeof mentionsItem === 'object' ) {
+						render_view( mentionsItem );
+						return;
+					}
+
+					if ( self.xhr ) {
+						self.xhr.abort();
+					}
+
+					params = { 'action': 'bp_get_suggestions', 'term': query, 'type': 'members' };
+
+					if ( $.isNumeric( this.$inputor.data( 'suggestions-group-id' ) ) ) {
+						params['group-id'] = parseInt( this.$inputor.data( 'suggestions-group-id' ), 10 );
+					}
+
+					self.xhr = $.getJSON( ajaxurl, params )
+						/**
+						 * Success callback for the @suggestions lookup.
+						 *
+						 * @param {object} response Details of users matching the query.
+						 * @since 2.1.0
+						 */
+						.done(function( response ) {
+							if ( ! response.success ) {
+								return;
+							}
+
+							var data = $.map( response.data,
+								/**
+								 * Create a composite index to determine ordering of results;
+								 * nicename matches will appear on top.
+								 *
+								 * @param {array} suggestion A suggestion's original data.
+								 * @return {array} A suggestion's new data.
+								 * @since 2.1.0
+								 */
+								function( suggestion ) {
+									suggestion.search = suggestion.search || suggestion.ID + ' ' + suggestion.name;
+									return suggestion;
+								}
+							);
+
+							mentionsQueryCache[ query ] = data;
+							render_view( data );
+						});
+				}
+			},
+
+			data: $.map( options.data,
+				/**
+				 * Create a composite index to search against of nicename + display name.
+				 * This will also determine ordering of results, so nicename matches will appear on top.
+				 *
+				 * @param {array} suggestion A suggestion's original data.
+				 * @return {array} A suggestion's new data.
+				 * @since 2.1.0
+				 */
+				function( suggestion ) {
+					suggestion.search = suggestion.search || suggestion.ID + ' ' + suggestion.name;
+					return suggestion;
+				}
+			),
+
+			at:         '@',
+			search_key: 'search',
+			tpl:        '<li data-value="@${ID}"><img src="${image}" /><span class="username">@${ID}</span><small>${name}</small></li>'
+		},
+
+		opts = $.extend( true, {}, suggestionsDefaults, mentionsDefaults, options );
+		return $.fn.atwho.call( this, opts );
+	};
+
+	$( document ).ready(function() {
+		// Activity/reply, post comments, dashboard post 'text' editor.
+		$( '.bp-suggestions, #comments form textarea, .wp-editor-area' ).bp_mentions( bp.mentions.users );
+	});
+
+	bp.mentions.tinyMCEinit = function() {
+		if ( typeof window.tinyMCE === 'undefined' || window.tinyMCE.activeEditor === null || typeof window.tinyMCE.activeEditor === 'undefined' ) {
+			return;
+		} else {
+			$( window.tinyMCE.activeEditor.contentDocument.activeElement )
+				.atwho( 'setIframe', $( '#content_ifr' )[0] )
+				.bp_mentions( bp.mentions.users );
+		}
+	};
+})( bp, jQuery );
Index: /tags/2.4.3/src/bp-blogs/bp-blogs-actions.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/bp-blogs-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/bp-blogs-actions.php	(revision 10410)
@@ -0,0 +1,33 @@
+<?php
+/**
+ * BuddyPress Blogs Actions.
+ *
+ * @package BuddyPress
+ * @subpackage BlogsActions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Redirect to a random blog in the multisite network.
+ *
+ * @since 1.0.0
+ */
+function bp_blogs_redirect_to_random_blog() {
+
+	// Bail if not looking for a random blog
+	if ( ! bp_is_blogs_component() || ! isset( $_GET['random-blog'] ) )
+		return;
+
+	// Multisite is active so find a random blog
+	if ( is_multisite() ) {
+		$blog = bp_blogs_get_random_blogs( 1, 1 );
+		bp_core_redirect( get_home_url( $blog['blogs'][0]->blog_id ) );
+
+	// No multisite and still called, always redirect to root
+	} else {
+		bp_core_redirect( bp_core_get_root_domain() );
+	}
+}
+add_action( 'bp_actions', 'bp_blogs_redirect_to_random_blog' );
Index: /tags/2.4.3/src/bp-blogs/bp-blogs-activity.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/bp-blogs-activity.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/bp-blogs-activity.php	(revision 10410)
@@ -0,0 +1,1117 @@
+<?php
+/**
+ * BuddyPress Blogs Activity.
+ *
+ * @package BuddyPress
+ * @subpackage BlogsActivity
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register activity actions for the blogs component.
+ *
+ * @since 1.0.0
+ *
+ * @return bool|null Returns false if activity component is not active.
+ */
+function bp_blogs_register_activity_actions() {
+	$bp = buddypress();
+
+	// Bail if activity is not active
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	if ( is_multisite() ) {
+		bp_activity_set_action(
+			$bp->blogs->id,
+			'new_blog',
+			__( 'New site created', 'buddypress' ),
+			'bp_blogs_format_activity_action_new_blog',
+			__( 'New Sites', 'buddypress' ),
+			array( 'activity', 'member' ),
+			0
+		);
+	}
+
+	// Only add the comment type if the 'post' post type is trackable
+	if ( post_type_supports( 'post', 'buddypress-activity' ) ) {
+		bp_activity_set_action(
+			$bp->blogs->id,
+			'new_blog_comment',
+			__( 'New post comment posted', 'buddypress' ),
+			'bp_blogs_format_activity_action_new_blog_comment',
+			__( 'Comments', 'buddypress' ),
+			array( 'activity', 'member' ),
+			10
+		);
+	}
+
+	/**
+	 * Fires after the registry of the default blog component activity actions.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_blogs_register_activity_actions' );
+}
+add_action( 'bp_register_activity_actions', 'bp_blogs_register_activity_actions' );
+
+/**
+ * Format 'new_blog' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity data object.
+ *
+ * @return string
+ */
+function bp_blogs_format_activity_action_new_blog( $action, $activity ) {
+	$blog_url  = bp_blogs_get_blogmeta( $activity->item_id, 'url' );
+	$blog_name = bp_blogs_get_blogmeta( $activity->item_id, 'name' );
+
+	$action = sprintf( __( '%s created the site %s', 'buddypress' ), bp_core_get_userlink( $activity->user_id ), '<a href="' . esc_url( $blog_url ) . '">' . esc_html( $blog_name ) . '</a>' );
+
+	// Legacy filter - requires the BP_Blogs_Blog object
+	if ( has_filter( 'bp_blogs_activity_created_blog_action' ) ) {
+		$user_blog = BP_Blogs_Blog::get_user_blog( $activity->user_id, $activity->item_id );
+		if ( $user_blog ) {
+			$recorded_blog = new BP_Blogs_Blog( $user_blog );
+		}
+
+		if ( isset( $recorded_blog ) ) {
+			$action = apply_filters( 'bp_blogs_activity_created_blog_action', $action, $recorded_blog, $blog_name, bp_blogs_get_blogmeta( $activity->item_id, 'description' ) );
+		}
+	}
+
+	/**
+	 * Filters the new blog activity action for the new blog.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $action   Constructed activity action.
+	 * @param object $activity Activity data object.
+	 */
+	return apply_filters( 'bp_blogs_format_activity_action_new_blog', $action, $activity );
+}
+
+/**
+ * Format 'new_blog_post' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity data object.
+ *
+ * @return string Constructed activity action.
+ */
+function bp_blogs_format_activity_action_new_blog_post( $action, $activity ) {
+	$blog_url  = bp_blogs_get_blogmeta( $activity->item_id, 'url' );
+	$blog_name = bp_blogs_get_blogmeta( $activity->item_id, 'name' );
+
+	if ( empty( $blog_url ) || empty( $blog_name ) ) {
+		$blog_url  = get_home_url( $activity->item_id );
+		$blog_name = get_blog_option( $activity->item_id, 'blogname' );
+
+		bp_blogs_update_blogmeta( $activity->item_id, 'url', $blog_url );
+		bp_blogs_update_blogmeta( $activity->item_id, 'name', $blog_name );
+	}
+
+	/**
+	 * When the post is published we are faking an activity object
+	 * to which we add 2 properties :
+	 * - the post url
+	 * - the post title
+	 * This is done to build the 'post link' part of the activity
+	 * action string.
+	 * NB: in this case the activity has not yet been created.
+	 */
+	if ( isset( $activity->post_url ) ) {
+		$post_url = $activity->post_url;
+
+	/**
+	 * The post_url property is not set, we need to build the url
+	 * thanks to the post id which is also saved as the secondary
+	 * item id property of the activity object.
+	 */
+	} else {
+		$post_url = add_query_arg( 'p', $activity->secondary_item_id, trailingslashit( $blog_url ) );
+	}
+
+	// Should be the case when the post has just been published
+	if ( isset( $activity->post_title ) ) {
+		$post_title = $activity->post_title;
+
+	// If activity already exists try to get the post title from activity meta
+	} else if ( ! empty( $activity->id ) ) {
+		$post_title = bp_activity_get_meta( $activity->id, 'post_title' );
+	}
+
+	/**
+	 * In case the post was published without a title
+	 * or the activity meta was not found
+	 */
+	if ( empty( $post_title ) ) {
+		// Defaults to no title
+		$post_title = esc_html__( '(no title)', 'buddypress' );
+
+		switch_to_blog( $activity->item_id );
+
+		$post = get_post( $activity->secondary_item_id );
+		if ( is_a( $post, 'WP_Post' ) ) {
+			// Does the post have a title ?
+			if ( ! empty( $post->post_title ) ) {
+				$post_title = $post->post_title;
+			}
+
+			// Make sure the activity exists before saving the post title in activity meta
+			if ( ! empty( $activity->id ) ) {
+				bp_activity_update_meta( $activity->id, 'post_title', $post_title );
+			}
+		}
+
+		restore_current_blog();
+	}
+
+	// Build the 'post link' part of the activity action string
+	$post_link  = '<a href="' . esc_url( $post_url ) . '">' . $post_title . '</a>';
+
+	$user_link = bp_core_get_userlink( $activity->user_id );
+
+	// Build the complete activity action string
+	if ( is_multisite() ) {
+		$action  = sprintf( __( '%1$s wrote a new post, %2$s, on the site %3$s', 'buddypress' ), $user_link, $post_link, '<a href="' . esc_url( $blog_url ) . '">' . esc_html( $blog_name ) . '</a>' );
+	} else {
+		$action  = sprintf( __( '%1$s wrote a new post, %2$s', 'buddypress' ), $user_link, $post_link );
+	}
+
+	// Legacy filter - requires the post object
+	if ( has_filter( 'bp_blogs_activity_new_post_action' ) ) {
+		switch_to_blog( $activity->item_id );
+		$post = get_post( $activity->secondary_item_id );
+		restore_current_blog();
+
+		if ( ! empty( $post ) && ! is_wp_error( $post ) ) {
+			$action = apply_filters( 'bp_blogs_activity_new_post_action', $action, $post, $post_url );
+		}
+	}
+
+	/**
+	 * Filters the new blog post action for the new blog.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $action   Constructed activity action.
+	 * @param object $activity Activity data object.
+	 */
+	return apply_filters( 'bp_blogs_format_activity_action_new_blog_post', $action, $activity );
+}
+
+/**
+ * Format 'new_blog_comment' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity data object.
+ *
+ * @return string Constructed activity action.
+ */
+function bp_blogs_format_activity_action_new_blog_comment( $action, $activity ) {
+	$blog_url  = bp_blogs_get_blogmeta( $activity->item_id, 'url' );
+	$blog_name = bp_blogs_get_blogmeta( $activity->item_id, 'name' );
+
+	if ( empty( $blog_url ) || empty( $blog_name ) ) {
+		$blog_url  = get_home_url( $activity->item_id );
+		$blog_name = get_blog_option( $activity->item_id, 'blogname' );
+
+		bp_blogs_update_blogmeta( $activity->item_id, 'url', $blog_url );
+		bp_blogs_update_blogmeta( $activity->item_id, 'name', $blog_name );
+	}
+
+	$post_url   = bp_activity_get_meta( $activity->id, 'post_url' );
+	$post_title = bp_activity_get_meta( $activity->id, 'post_title' );
+
+	// Should only be empty at the time of post creation
+	if ( empty( $post_url ) || empty( $post_title ) ) {
+		switch_to_blog( $activity->item_id );
+
+		$comment = get_comment( $activity->secondary_item_id );
+
+		if ( ! empty( $comment->comment_post_ID ) ) {
+			$post_url = add_query_arg( 'p', $comment->comment_post_ID, trailingslashit( $blog_url ) );
+			bp_activity_update_meta( $activity->id, 'post_url', $post_url );
+
+			$post = get_post( $comment->comment_post_ID );
+
+			if ( is_a( $post, 'WP_Post' ) ) {
+				$post_title = $post->post_title;
+				bp_activity_update_meta( $activity->id, 'post_title', $post_title );
+			}
+		}
+
+		restore_current_blog();
+	}
+
+	$post_link = '<a href="' . esc_url( $post_url ) . '">' . $post_title . '</a>';
+	$user_link = bp_core_get_userlink( $activity->user_id );
+
+	if ( is_multisite() ) {
+		$action  = sprintf( __( '%1$s commented on the post, %2$s, on the site %3$s', 'buddypress' ), $user_link, $post_link, '<a href="' . esc_url( $blog_url ) . '">' . esc_html( $blog_name ) . '</a>' );
+	} else {
+		$action  = sprintf( __( '%1$s commented on the post, %2$s', 'buddypress' ), $user_link, $post_link );
+	}
+
+	// Legacy filter - requires the comment object
+	if ( has_filter( 'bp_blogs_activity_new_comment_action' ) ) {
+		switch_to_blog( $activity->item_id );
+		$comment = get_comment( $activity->secondary_item_id );
+		restore_current_blog();
+
+		if ( ! empty( $comment ) && ! is_wp_error( $comment ) ) {
+			$action = apply_filters( 'bp_blogs_activity_new_comment_action', $action, $comment, $post_url . '#' . $activity->secondary_item_id );
+		}
+	}
+
+	/**
+	 * Filters the new blog comment action for the new blog.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $action   Constructed activity action.
+	 * @param object $activity Activity data object.
+	 */
+	return apply_filters( 'bp_blogs_format_activity_action_new_blog_comment', $action, $activity );
+}
+
+/**
+ * Fetch data related to blogs at the beginning of an activity loop.
+ *
+ * This reduces database overhead during the activity loop.
+ *
+ * @since 2.0.0
+ *
+ * @param array $activities Array of activity items.
+ *
+ * @return array
+ */
+function bp_blogs_prefetch_activity_object_data( $activities ) {
+	if ( empty( $activities ) ) {
+		return $activities;
+	}
+
+	$blog_ids = array();
+
+	foreach ( $activities as $activity ) {
+		if ( buddypress()->blogs->id !== $activity->component ) {
+			continue;
+		}
+
+		$blog_ids[] = $activity->item_id;
+	}
+
+	if ( ! empty( $blog_ids ) ) {
+		bp_blogs_update_meta_cache( $blog_ids );
+	}
+
+	return $activities;
+}
+add_filter( 'bp_activity_prefetch_object_data', 'bp_blogs_prefetch_activity_object_data' );
+
+/**
+ * Record blog-related activity to the activity stream.
+ *
+ * @since 1.0.0
+ *
+ * @see bp_activity_add() for description of parameters.
+ *
+ * @param array|string $args {
+ *     See {@link bp_activity_add()} for complete description of arguments.
+ *     The arguments listed here have different default values from
+ *     bp_activity_add().
+ *     @type string $component Default: 'blogs'.
+ * }
+ * @return int|bool On success, returns the activity ID. False on failure.
+ */
+function bp_blogs_record_activity( $args = '' ) {
+
+	// Bail if activity is not active
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	$bp = buddypress();
+
+	$defaults = array(
+		'user_id'           => bp_loggedin_user_id(),
+		'action'            => '',
+		'content'           => '',
+		'primary_link'      => '',
+		'component'         => $bp->blogs->id,
+		'type'              => false,
+		'item_id'           => false,
+		'secondary_item_id' => false,
+		'recorded_time'     => bp_core_current_time(),
+		'hide_sitewide'     => false
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+
+	if ( ! empty( $r['action'] ) ) {
+
+		/**
+		 * Filters the action associated with activity for activity stream.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Action for the activity stream.
+		 */
+		$r['action'] = apply_filters( 'bp_blogs_record_activity_action', $r['action'] );
+	}
+
+	if ( ! empty( $r['content'] ) ) {
+
+		/**
+		 * Filters the content associated with activity for activity stream.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Generated summary from content for the activity stream.
+		 * @param string $value Content for the activity stream.
+		 * @param array  $r     Array of arguments used for the activity stream item.
+		 */
+		$r['content'] = apply_filters( 'bp_blogs_record_activity_content', bp_activity_create_summary( $r['content'], $r ), $r['content'], $r );
+	}
+
+	// Check for an existing entry and update if one exists.
+	$id = bp_activity_get_activity_id( array(
+		'user_id'           => $r['user_id'],
+		'component'         => $r['component'],
+		'type'              => $r['type'],
+		'item_id'           => $r['item_id'],
+		'secondary_item_id' => $r['secondary_item_id'],
+	) );
+
+	return bp_activity_add( array( 'id' => $id, 'user_id' => $r['user_id'], 'action' => $r['action'], 'content' => $r['content'], 'primary_link' => $r['primary_link'], 'component' => $r['component'], 'type' => $r['type'], 'item_id' => $r['item_id'], 'secondary_item_id' => $r['secondary_item_id'], 'recorded_time' => $r['recorded_time'], 'hide_sitewide' => $r['hide_sitewide'] ) );
+}
+
+/**
+ * Delete a blog-related activity stream item.
+ *
+ * @since 1.0.0
+ *
+ * @see bp_activity_delete() for description of parameters.
+ *
+ * @param array|string $args {
+ *     See {@link bp_activity_delete()} for complete description of arguments.
+ *     The arguments listed here have different default values from
+ *     bp_activity_add().
+ *     @type string $component Default: 'blogs'.
+ * }
+ * @return bool True on success, false on failure.
+ */
+function bp_blogs_delete_activity( $args = '' ) {
+
+	// Bail if activity is not active
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	$r = bp_parse_args( $args, array(
+		'item_id'           => false,
+		'component'         => buddypress()->blogs->id,
+		'type'              => false,
+		'user_id'           => false,
+		'secondary_item_id' => false
+	) );
+
+	bp_activity_delete_by_item_id( $r );
+}
+
+/**
+ * Check if a blog post's activity item should be closed from commenting.
+ *
+ * This mirrors the {@link comments_open()} and {@link _close_comments_for_old_post()}
+ * functions, but for use with the BuddyPress activity stream to be as
+ * lightweight as possible.
+ *
+ * By lightweight, we actually mirror a few of the blog's commenting settings
+ * to blogmeta and checks the values in blogmeta instead.  This is to prevent
+ * multiple {@link switch_to_blog()} calls in the activity stream.
+ *
+ * @since 2.0.0
+ *
+ * @param object $activity The BP_Activity_Activity object
+ *
+ * @return bool
+ */
+function bp_blogs_comments_open( $activity ) {
+	$open = true;
+
+	$blog_id = $activity->item_id;
+
+	// see if we've mirrored the close comments option before
+	$days_old = bp_blogs_get_blogmeta( $blog_id, 'close_comments_days_old' );
+
+	// we've never cached these items before, so do it now
+	if ( '' === $days_old ) {
+		switch_to_blog( $blog_id );
+
+		// use comments_open()
+		remove_filter( 'comments_open', 'bp_comments_open', 10, 2 );
+		$open = comments_open( $activity->secondary_item_id );
+		add_filter( 'comments_open', 'bp_comments_open', 10, 2 );
+
+		// might as well mirror values to blogmeta since we're here!
+		$thread_depth = get_option( 'thread_comments' );
+		if ( ! empty( $thread_depth ) ) {
+			$thread_depth = get_option( 'thread_comments_depth' );
+		} else {
+			// perhaps filter this?
+			$thread_depth = 1;
+		}
+
+		bp_blogs_update_blogmeta( $blog_id, 'close_comments_for_old_posts', get_option( 'close_comments_for_old_posts' ) );
+		bp_blogs_update_blogmeta( $blog_id, 'close_comments_days_old',      get_option( 'close_comments_days_old' ) );
+		bp_blogs_update_blogmeta( $blog_id, 'thread_comments_depth',        $thread_depth );
+
+		restore_current_blog();
+
+	// check blogmeta and manually check activity item
+	// basically a copy of _close_comments_for_old_post()
+	} else {
+
+		// comments are closed
+		if ( 'closed' == bp_activity_get_meta( $activity->id, 'post_comment_status' ) ) {
+			return false;
+		}
+
+		if ( ! bp_blogs_get_blogmeta( $blog_id, 'close_comments_for_old_posts' ) ) {
+			return $open;
+		}
+
+		$days_old = (int) $days_old;
+		if ( ! $days_old ) {
+			return $open;
+		}
+
+		/* commenting out for now - needs some more thought...
+		   should we add the post type to activity meta?
+
+		$post = get_post($post_id);
+
+		// This filter is documented in wp-includes/comment.php
+		$post_types = apply_filters( 'close_comments_for_post_types', array( 'post' ) );
+		if ( ! in_array( $post->post_type, $post_types ) )
+			return $open;
+		*/
+
+		if ( time() - strtotime( $activity->date_recorded ) > ( $days_old * DAY_IN_SECONDS ) ) {
+			return false;
+		}
+
+		return $open;
+	}
+
+	return $open;
+}
+
+/** POST COMMENT SYNCHRONIZATION ****************************************/
+
+/**
+ * Syncs activity comments and posts them back as blog comments.
+ *
+ * Note: This is only a one-way sync - activity comments -> blog comment.
+ *
+ * For blog post -> activity comment, see {@link bp_blogs_record_comment()}.
+ *
+ * @since 2.0.0
+ *
+ * @param int    $comment_id      The activity ID for the posted activity comment.
+ * @param array  $params          Parameters for the activity comment.
+ * @param object $parent_activity Parameters of the parent activity item (in this case, the blog post).
+ */
+function bp_blogs_sync_add_from_activity_comment( $comment_id, $params, $parent_activity ) {
+	// if parent activity isn't a blog post, stop now!
+	if ( $parent_activity->type != 'new_blog_post' ) {
+		return;
+	}
+
+	// if activity comments are disabled for blog posts, stop now!
+	if ( bp_disable_blogforum_comments() ) {
+		return;
+	}
+
+	// get userdata
+	if ( $params['user_id'] == bp_loggedin_user_id() ) {
+		$user = buddypress()->loggedin_user->userdata;
+	} else {
+		$user = bp_core_get_core_userdata( $params['user_id'] );
+	}
+
+	// see if a parent WP comment ID exists
+	if ( ! empty( $params['parent_id'] ) ) {
+		$comment_parent = bp_activity_get_meta( $params['parent_id'], 'bp_blogs_post_comment_id' );
+	} else {
+		$comment_parent = 0;
+	}
+
+	// comment args
+	$args = array(
+		'comment_post_ID'      => $parent_activity->secondary_item_id,
+		'comment_author'       => bp_core_get_user_displayname( $params['user_id'] ),
+		'comment_author_email' => $user->user_email,
+		'comment_author_url'   => bp_core_get_user_domain( $params['user_id'], $user->user_nicename, $user->user_login ),
+		'comment_content'      => $params['content'],
+		'comment_type'         => '', // could be interesting to add 'buddypress' here...
+		'comment_parent'       => (int) $comment_parent,
+		'user_id'              => $params['user_id'],
+
+		// commenting these out for now
+		//'comment_author_IP'    => '127.0.0.1',
+		//'comment_agent'        => '',
+
+		'comment_approved'     => 1
+	);
+
+	// prevent separate activity entry being made
+	remove_action( 'comment_post', 'bp_blogs_record_comment', 10, 2 );
+
+	// handle multisite
+	switch_to_blog( $parent_activity->item_id );
+
+	// handle timestamps for the WP comment after we've switched to the blog
+	$args['comment_date']     = current_time( 'mysql' );
+	$args['comment_date_gmt'] = current_time( 'mysql', 1 );
+
+	// post the comment
+	$post_comment_id = wp_insert_comment( $args );
+
+	// add meta to comment
+	add_comment_meta( $post_comment_id, 'bp_activity_comment_id', $comment_id );
+
+	// add meta to activity comment
+	bp_activity_update_meta( $comment_id, 'bp_blogs_post_comment_id', $post_comment_id );
+
+	// resave activity comment with WP comment permalink
+	//
+	// in bp_blogs_activity_comment_permalink(), we change activity comment
+	// permalinks to use the post comment link
+	//
+	// @todo since this is done after AJAX posting, the activity comment permalink
+	//       doesn't change on the frontend until the next page refresh.
+	$resave_activity = new BP_Activity_Activity( $comment_id );
+	$resave_activity->primary_link = get_comment_link( $post_comment_id );
+
+	/**
+	 * Now that the activity id exists and the post comment was created, we don't need to update
+	 * the content of the comment as there are no chances it has evolved.
+	 */
+	remove_action( 'bp_activity_before_save', 'bp_blogs_sync_activity_edit_to_post_comment', 20 );
+
+	$resave_activity->save();
+
+	// add the edit activity comment hook back
+	add_action( 'bp_activity_before_save', 'bp_blogs_sync_activity_edit_to_post_comment', 20 );
+
+	// multisite again!
+	restore_current_blog();
+
+	// add the comment hook back
+	add_action( 'comment_post', 'bp_blogs_record_comment', 10, 2 );
+
+	/**
+	 * Fires after activity comments have been synced and posted as blog comments.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param int    $comment_id      The activity ID for the posted activity comment.
+	 * @param array  $args            Array of args used for the comment syncing.
+	 * @param object $parent_activity Parameters of the blog post parent activity item.
+	 * @param object $user            User data object for the blog comment.
+	 */
+	do_action( 'bp_blogs_sync_add_from_activity_comment', $comment_id, $args, $parent_activity, $user );
+}
+add_action( 'bp_activity_comment_posted', 'bp_blogs_sync_add_from_activity_comment', 10, 3 );
+
+/**
+ * Deletes the blog comment when the associated activity comment is deleted.
+ *
+ * Note: This is hooked on the 'bp_activity_delete_comment_pre' filter instead
+ * of the 'bp_activity_delete_comment' action because we need to fetch the
+ * activity comment children before they are deleted.
+ *
+ * @since 2.0.0
+ *
+ * @param bool $retval
+ * @param int  $parent_activity_id The parent activity ID for the activity comment.
+ * @param int  $activity_id        The activity ID for the pending deleted activity comment.
+ *
+ * @return bool
+ */
+function bp_blogs_sync_delete_from_activity_comment( $retval, $parent_activity_id, $activity_id ) {
+	// check if parent activity is a blog post
+	$parent_activity = new BP_Activity_Activity( $parent_activity_id );
+	if ( 'new_blog_post' != $parent_activity->type ) {
+		return $retval;
+	}
+
+	// fetch the activity comments for the activity item
+	$activity = bp_activity_get( array(
+		'in'               => $activity_id,
+		'display_comments' => 'stream',
+	) );
+
+	// get all activity comment IDs for the pending deleted item
+	$activity_ids   = bp_activity_recurse_comments_activity_ids( $activity );
+	$activity_ids[] = $activity_id;
+
+	// handle multisite
+	// switch to the blog where the comment was made
+	switch_to_blog( $parent_activity->item_id );
+
+	// remove associated blog comments
+	bp_blogs_remove_associated_blog_comments( $activity_ids, current_user_can( 'moderate_comments' ) );
+
+	// multisite again!
+	restore_current_blog();
+
+	// rebuild activity comment tree
+	// emulate bp_activity_delete_comment()
+	BP_Activity_Activity::rebuild_activity_comment_tree( $parent_activity_id );
+
+	// we're overriding the default bp_activity_delete_comment() functionality
+	// so we need to return false
+	return false;
+}
+add_filter( 'bp_activity_delete_comment_pre', 'bp_blogs_sync_delete_from_activity_comment', 10, 3 );
+
+/**
+ * Updates the blog comment when the associated activity comment is edited.
+ *
+ * @since 2.0.0
+ *
+ * @param BP_Activity_Activity $activity The activity object.
+ */
+function bp_blogs_sync_activity_edit_to_post_comment( BP_Activity_Activity $activity ) {
+	// not an activity comment? stop now!
+	if ( 'activity_comment' !== $activity->type ) {
+		return;
+	}
+
+	// this is a new entry, so stop!
+	// we only want edits!
+	if ( empty( $activity->id ) ) {
+		return;
+	}
+
+	// prevent recursion
+	remove_action( 'bp_activity_before_save', 'bp_blogs_sync_activity_edit_to_post_comment', 20 );
+
+	// Try to see if a corresponding blog comment exists
+	$post_comment_id = bp_activity_get_meta( $activity->id, 'bp_blogs_post_comment_id' );
+
+	if ( empty( $post_comment_id ) ) {
+		return;
+	}
+
+	// fetch parent activity item
+	$parent_activity = new BP_Activity_Activity( $activity->item_id );
+
+	// sanity check
+	if ( 'new_blog_post' !== $parent_activity->type ) {
+		return;
+	}
+
+	// handle multisite
+	switch_to_blog( $parent_activity->item_id );
+
+	// update the blog post comment
+	wp_update_comment( array(
+		'comment_ID'      => $post_comment_id,
+		'comment_content' => $activity->content
+	) );
+
+	restore_current_blog();
+}
+add_action( 'bp_activity_before_save', 'bp_blogs_sync_activity_edit_to_post_comment', 20 );
+
+/**
+ * When a post is trashed, remove each comment's associated activity meta.
+ *
+ * When a post is trashed and later untrashed, we currently don't reinstate
+ * activity items for these comments since their activity entries are already
+ * deleted when initially trashed.
+ *
+ * Since these activity entries are deleted, we need to remove the deleted
+ * activity comment IDs from each comment's meta when a post is trashed.
+ *
+ * @since 2.0.0
+ *
+ * @param int   $post_id  The post ID.
+ * @param array $comments Array of comment statuses. The key is comment ID, the
+ *                        value is the $comment->comment_approved value.
+ */
+function bp_blogs_remove_activity_meta_for_trashed_comments( $post_id = 0, $comments = array() ) {
+	if ( ! empty( $comments ) ) {
+		foreach ( array_keys( $comments ) as $comment_id ) {
+			delete_comment_meta( $comment_id, 'bp_activity_comment_id' );
+		}
+	}
+}
+add_action( 'trashed_post_comments', 'bp_blogs_remove_activity_meta_for_trashed_comments', 10, 2 );
+
+/**
+ * Filter 'new_blog_comment' bp_has_activities() loop to include new- and old-style blog activity comment items.
+ *
+ * In BuddyPress 2.0, the schema for storing activity items related to blog
+ * posts changed. Instead creating new top-level 'new_blog_comment' activity
+ * items, blog comments are recorded in the activity stream as comments on the
+ * 'new_blog_post' activity items corresponding to the parent post. This filter
+ * ensures that the 'new_blog_comment' filter in bp_has_activities() (which
+ * powers the 'Comments' filter in the activity directory dropdown) includes
+ * both old-style and new-style activity comments.
+ *
+ * This implementation involves filtering the activity queries directly, and
+ * should be considered a stopgap. The proper solution would involve enabling
+ * multiple query condition clauses, connected by an OR, in the bp_has_activities()
+ * API.
+ *
+ * @since 2.1.0
+ *
+ * @param array $args Arguments passed from bp_parse_args() in bp_has_activities().
+ *
+ * @return array $args
+ */
+function bp_blogs_new_blog_comment_query_backpat( $args ) {
+	global $wpdb;
+	$bp = buddypress();
+
+	// Bail if this is not a 'new_blog_comment' query
+	if ( 'new_blog_comment' !== $args['action'] ) {
+		return $args;
+	}
+
+	// Comment synced ?
+	$activity_ids = $wpdb->get_col( $wpdb->prepare( "SELECT activity_id FROM {$bp->activity->table_name_meta} WHERE meta_key = %s", 'bp_blogs_post_comment_id' ) );
+
+	if ( empty( $activity_ids ) ) {
+		return $args;
+	}
+
+	// Init the filter query
+	$filter_query = array();
+
+	if ( 'null' === $args['scope'] ) {
+		$args['scope'] = '';
+	} elseif ( 'just-me' === $args['scope'] ) {
+		$filter_query = array(
+			'relation' => 'AND',
+			array(
+				'column' => 'user_id',
+				'value'  => bp_displayed_user_id(),
+			),
+		);
+		$args['scope'] = '';
+	}
+
+	$filter_query[] = array(
+		'relation' => 'OR',
+		array(
+			'column' => 'type',
+			'value'  => $args['action'],
+		),
+		array(
+			'column'  => 'id',
+			'value'   =>  $activity_ids,
+			'compare' => 'IN'
+		),
+	);
+
+	$args['filter_query'] = $filter_query;
+
+	// Make sure to have comment in stream mode && avoid duplicate content
+	$args['display_comments'] = 'stream';
+
+	// Finally reset the action
+	$args['action'] = '';
+
+	// Return the original arguments
+	return $args;
+}
+add_filter( 'bp_after_has_activities_parse_args', 'bp_blogs_new_blog_comment_query_backpat' );
+
+/**
+ * Utility function to set up some variables for use in the activity loop.
+ *
+ * Grabs the blog's comment depth and the post's open comment status options
+ * for later use in the activity and activity comment loops.
+ *
+ * This is to prevent having to requery these items later on.
+ *
+ * @since 2.0.0
+ *
+ * @see bp_blogs_disable_activity_commenting()
+ * @see bp_blogs_setup_comment_loop_globals_on_ajax()
+ *
+ * @param object $activity The BP_Activity_Activity object.
+ */
+function bp_blogs_setup_activity_loop_globals( $activity ) {
+	if ( ! is_object( $activity ) ) {
+		return;
+	}
+
+	// parent not a blog post? stop now!
+	if ( 'new_blog_post' !== $activity->type ) {
+		return;
+	}
+
+	if ( empty( $activity->id ) ) {
+		return;
+	}
+
+	// if we've already done this before, stop now!
+	if ( isset( buddypress()->blogs->allow_comments[ $activity->id ] ) ) {
+		return;
+	}
+
+	$allow_comments = bp_blogs_comments_open( $activity );
+	$thread_depth   = bp_blogs_get_blogmeta( $activity->item_id, 'thread_comments_depth' );
+
+	// initialize a local object so we won't have to query this again in the
+	// comment loop
+	if ( empty( buddypress()->blogs->allow_comments ) ) {
+		buddypress()->blogs->allow_comments = array();
+	}
+	if ( empty( buddypress()->blogs->thread_depth ) ) {
+		buddypress()->blogs->thread_depth   = array();
+	}
+
+	// cache comment settings in the buddypress() singleton to reference later in
+	// the activity comment loop
+	// @see bp_blogs_disable_activity_replies()
+	//
+	// thread_depth is keyed by activity ID instead of blog ID because when we're
+	// in a comment loop, we don't have access to the blog ID...
+	// should probably object cache these values instead...
+	buddypress()->blogs->allow_comments[ $activity->id ] = $allow_comments;
+	buddypress()->blogs->thread_depth[ $activity->id ]   = $thread_depth;
+}
+
+/**
+ * Set up some globals used in the activity comment loop when AJAX is used.
+ *
+ * @since 2.0.0
+ *
+ * @see bp_blogs_setup_activity_loop_globals()
+ */
+function bp_blogs_setup_comment_loop_globals_on_ajax() {
+	// not AJAX? stop now!
+	if ( ! defined( 'DOING_AJAX' ) ) {
+		return;
+	}
+	if ( false === (bool) constant( 'DOING_AJAX' ) ) {
+		return;
+	}
+
+	// get the parent activity item
+	$comment         = bp_activity_current_comment();
+	$parent_activity = new BP_Activity_Activity( $comment->item_id );
+
+	// setup the globals
+	bp_blogs_setup_activity_loop_globals( $parent_activity );
+}
+add_action( 'bp_before_activity_comment', 'bp_blogs_setup_comment_loop_globals_on_ajax' );
+
+/**
+ * Disable activity commenting for blog posts based on certain criteria.
+ *
+ * If activity commenting is enabled for blog posts, we still need to disable
+ * commenting if:
+ *  - comments are disabled for the WP blog post from the admin dashboard
+ *  - the WP blog post is supposed to be automatically closed from comments
+ *    based on a certain age
+ *  - the activity entry is a 'new_blog_comment' type
+ *
+ * @since 2.0.0
+ *
+ * @param bool $retval Is activity commenting enabled for this activity entry?
+ *
+ * @return bool
+ */
+function bp_blogs_disable_activity_commenting( $retval ) {
+	// if activity commenting is disabled, return current value
+	if ( bp_disable_blogforum_comments() ) {
+		return $retval;
+	}
+
+	// activity commenting is enabled for blog posts
+	switch ( bp_get_activity_action_name() ) {
+
+		// we still have to disable activity commenting for 'new_blog_comment' items
+		// commenting should only be done on the parent 'new_blog_post' item
+		case 'new_blog_comment' :
+			$retval = false;
+
+			break;
+
+		// check if commenting is disabled for the WP blog post
+		// we should extrapolate this and automate this for plugins... or not
+		case 'new_blog_post' :
+			global $activities_template;
+
+			// setup some globals we'll need to reference later
+			bp_blogs_setup_activity_loop_globals( $activities_template->activity );
+
+			// if comments are closed for the WP blog post, we should disable
+			// activity comments for this activity entry
+			if ( empty( buddypress()->blogs->allow_comments[bp_get_activity_id()] ) ) {
+				$retval = false;
+			}
+
+			break;
+	}
+
+	return $retval;
+}
+add_filter( 'bp_activity_can_comment', 'bp_blogs_disable_activity_commenting' );
+
+/**
+ * Check if an activity comment associated with a blog post can be replied to.
+ *
+ * By default, disables replying to activity comments if the corresponding WP
+ * blog post no longer accepts comments.
+ *
+ * This check uses a locally-cached value set in {@link bp_blogs_disable_activity_commenting()}
+ * via {@link bp_blogs_setup_activity_loop_globals()}.
+ *
+ * @since 2.0.0
+ *
+ * @param bool         $retval  Are replies allowed for this activity reply?
+ * @param object|array $comment The activity comment object.
+ *
+ * @return bool
+ */
+function bp_blogs_can_comment_reply( $retval, $comment ) {
+	if ( is_array( $comment ) ) {
+		$comment = (object) $comment;
+	}
+
+	// check comment depth and disable if depth is too large
+	if ( isset( buddypress()->blogs->thread_depth[$comment->item_id] ) ){
+		if ( $comment->mptt_left > buddypress()->blogs->thread_depth[$comment->item_id] ) {
+			$retval = false;
+		}
+	}
+
+	// check if we should disable activity replies based on the parent activity
+	if ( isset( buddypress()->blogs->allow_comments[$comment->item_id] ) ){
+		// the blog post has closed off commenting, so we should disable all activity
+		// comments under the parent 'new_blog_post' activity entry
+		if ( empty( buddypress()->blogs->allow_comments[$comment->item_id] ) ) {
+			$retval = false;
+		}
+	}
+
+	return $retval;
+}
+add_filter( 'bp_activity_can_comment_reply', 'bp_blogs_can_comment_reply', 10, 2 );
+
+/**
+ * Changes activity comment permalinks to use the blog comment permalink
+ * instead of the activity permalink.
+ *
+ * This is only done if activity commenting is allowed and whether the parent
+ * activity item is a 'new_blog_post' entry.
+ *
+ * @since 2.0.0
+ *
+ * @param string $retval The activity comment permalink.
+ *
+ * @return string
+ */
+function bp_blogs_activity_comment_permalink( $retval = '' ) {
+	global $activities_template;
+
+	// Get the current comment ID
+	$item_id = isset( $activities_template->activity->current_comment->item_id )
+		? $activities_template->activity->current_comment->item_id
+		: false;
+
+	// Maybe adjust the link if item ID exists
+	if ( ( false !== $item_id ) && isset( buddypress()->blogs->allow_comments[ $item_id ] ) ) {
+		$retval = $activities_template->activity->current_comment->primary_link;
+	}
+
+	return $retval;
+}
+add_filter( 'bp_get_activity_comment_permalink', 'bp_blogs_activity_comment_permalink' );
+
+/**
+ * Changes single activity comment entries to use the blog comment permalink.
+ *
+ * This is only done if the activity comment is associated with a blog comment.
+ *
+ * @since 2.0.1
+ *
+ * @param string               $retval   The activity permalink.
+ * @param BP_Activity_Activity $activity
+ *
+ * @return string
+ */
+function bp_blogs_activity_comment_single_permalink( $retval, $activity ) {
+	if ( 'activity_comment' !== $activity->type ) {
+		return $retval;
+	}
+
+	$blog_comment_id = bp_activity_get_meta( $activity->id, 'bp_blogs_post_comment_id' );
+
+	if ( ! empty( $blog_comment_id ) ) {
+		$retval = $activity->primary_link;
+	}
+
+	return $retval;
+}
+add_filter( 'bp_activity_get_permalink', 'bp_blogs_activity_comment_single_permalink', 10, 2 );
+
+/**
+ * Formats single activity comment entries to use the blog comment action.
+ *
+ * This is only done if the activity comment is associated with a blog comment.
+ *
+ * @since 2.0.1
+ *
+ * @param string               $retval   The activity action.
+ * @param BP_Activity_Activity $activity
+ *
+ * @return string
+ */
+function bp_blogs_activity_comment_single_action( $retval, $activity ) {
+	if ( 'activity_comment' !== $activity->type ) {
+		return $retval;
+	}
+
+	$blog_comment_id = bp_activity_get_meta( $activity->id, 'bp_blogs_post_comment_id' );
+
+	if ( ! empty( $blog_comment_id ) ) {
+		// fetch the parent blog post activity item
+		$parent_blog_post_activity = new BP_Activity_Activity( $activity->item_id );
+
+		// fake a 'new_blog_comment' activity object
+		$object = $activity;
+
+		// override 'item_id' to use blog ID
+		$object->item_id = $parent_blog_post_activity->item_id;
+
+		// override 'secondary_item_id' to use comment ID
+		$object->secondary_item_id = $blog_comment_id;
+
+		// now format the activity action using the 'new_blog_comment' action callback
+		$retval = bp_blogs_format_activity_action_new_blog_comment( '', $object );
+	}
+
+	return $retval;
+}
+add_filter( 'bp_get_activity_action_pre_meta', 'bp_blogs_activity_comment_single_action', 10, 2 );
Index: /tags/2.4.3/src/bp-blogs/bp-blogs-cache.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/bp-blogs-cache.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/bp-blogs-cache.php	(revision 10410)
@@ -0,0 +1,67 @@
+<?php
+/**
+ * BuddyPress Blogs Caching.
+ *
+ * Caching functions handle the clearing of cached objects and pages on specific
+ * actions throughout BuddyPress.
+ *
+ * @package BuddyPress
+ * @subpackage BlogsCache
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Slurp up blogmeta for a specified set of blogs.
+ *
+ * It grabs all blogmeta associated with all of the blogs passed
+ * in $blog_ids and adds it to the WP cache. This improves efficiency when
+ * using querying blogmeta inline.
+ *
+ * @param int|string|array|bool $blog_ids Accepts a single blog ID, or a comma-
+ *                                        separated list or array of blog IDs.
+ */
+function bp_blogs_update_meta_cache( $blog_ids = false ) {
+	$cache_args = array(
+		'object_ids' 	   => $blog_ids,
+		'object_type' 	   => buddypress()->blogs->id,
+		'object_column'    => 'blog_id',
+		'cache_group'      => 'blog_meta',
+		'meta_table' 	   => buddypress()->blogs->table_name_blogmeta,
+	);
+
+	bp_update_meta_cache( $cache_args );
+}
+/**
+ * Clear the blog object cache.
+ *
+ * @since 1.0.0
+ *
+ * @param int $blog_id ID of the current blog.
+ * @param int $user_id ID of the user whose blog cache should be cleared.
+ */
+function bp_blogs_clear_blog_object_cache( $blog_id = 0, $user_id = 0 ) {
+	if ( ! empty( $user_id ) ) {
+		wp_cache_delete( 'bp_blogs_of_user_'        . $user_id, 'bp' );
+		wp_cache_delete( 'bp_total_blogs_for_user_' . $user_id, 'bp' );
+	}
+
+	wp_cache_delete( 'bp_total_blogs', 'bp' );
+}
+
+// List actions to clear object caches on.
+add_action( 'bp_blogs_remove_blog_for_user', 'bp_blogs_clear_blog_object_cache', 10, 2 );
+add_action( 'wpmu_new_blog',                 'bp_blogs_clear_blog_object_cache', 10, 2 );
+add_action( 'bp_blogs_remove_blog',          'bp_blogs_clear_blog_object_cache' );
+
+// List actions to clear super cached pages on, if super cache is installed.
+add_action( 'bp_blogs_remove_data_for_blog', 'bp_core_clear_cache' );
+add_action( 'bp_blogs_remove_comment',       'bp_core_clear_cache' );
+add_action( 'bp_blogs_remove_post',          'bp_core_clear_cache' );
+add_action( 'bp_blogs_remove_blog_for_user', 'bp_core_clear_cache' );
+add_action( 'bp_blogs_remove_blog',          'bp_core_clear_cache' );
+add_action( 'bp_blogs_new_blog_comment',     'bp_core_clear_cache' );
+add_action( 'bp_blogs_new_blog_post',        'bp_core_clear_cache' );
+add_action( 'bp_blogs_new_blog',             'bp_core_clear_cache' );
+add_action( 'bp_blogs_remove_data',          'bp_core_clear_cache' );
Index: /tags/2.4.3/src/bp-blogs/bp-blogs-classes.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/bp-blogs-classes.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/bp-blogs-classes.php	(revision 10410)
@@ -0,0 +1,12 @@
+<?php
+/**
+ * BuddyPress Blogs Classes.
+ *
+ * @package BuddyPress
+ * @subpackage BlogsClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+require dirname( __FILE__ ) . '/classes/class-bp-blogs-blog.php';
Index: /tags/2.4.3/src/bp-blogs/bp-blogs-filters.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/bp-blogs-filters.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/bp-blogs-filters.php	(revision 10410)
@@ -0,0 +1,126 @@
+<?php
+/**
+ * Filters related to the Blogs component.
+ *
+ * @package BuddyPress
+ * @subpackage BlogFilters
+ * @since 1.6.0
+ */
+
+/** Display Filters **********************************************************/
+
+add_filter( 'bp_get_blog_latest_post_title', 'wptexturize'   );
+add_filter( 'bp_get_blog_latest_post_title', 'convert_chars' );
+add_filter( 'bp_get_blog_latest_post_title', 'trim'          );
+
+add_filter( 'bp_blog_latest_post_content', 'wptexturize'        );
+add_filter( 'bp_blog_latest_post_content', 'convert_smilies'    );
+add_filter( 'bp_blog_latest_post_content', 'convert_chars'      );
+add_filter( 'bp_blog_latest_post_content', 'wpautop'            );
+add_filter( 'bp_blog_latest_post_content', 'shortcode_unautop'  );
+add_filter( 'bp_blog_latest_post_content', 'prepend_attachment' );
+
+/**
+ * Ensure that the 'Create a new site' link at wp-admin/my-sites.php points to the BP blog signup.
+ *
+ * @since 1.6.0
+ *
+ * @uses apply_filters() Filter 'bp_blogs_creation_location' to alter the
+ *       returned value.
+ *
+ * @param string $url The original URL (points to wp-signup.php by default).
+ *
+ * @return string The new URL.
+ */
+function bp_blogs_creation_location( $url ) {
+
+	/**
+	 * Filters the 'Create a new site' link URL.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $value URL for the 'Create a new site' signup page.
+	 */
+	return apply_filters( 'bp_blogs_creation_location', trailingslashit( bp_get_blogs_directory_permalink() . 'create' ), $url );
+}
+add_filter( 'wp_signup_location', 'bp_blogs_creation_location' );
+
+/**
+ * Only select comments by ID instead of all fields when using get_comments().
+ *
+ * @since 2.1.0
+ *
+ * @see bp_blogs_update_post_activity_meta()
+ *
+ * @param array $retval Current SQL clauses in array format.
+ *
+ * @return array
+ */
+function bp_blogs_comments_clauses_select_by_id( $retval ) {
+	$retval['fields'] = 'comment_ID';
+
+	return $retval;
+}
+
+/**
+ * Check whether the current post can be published.
+ *
+ * Abstracted from the deprecated `bp_blogs_record_post()`.
+ *
+ * @since 2.2.0
+ *
+ * @param  bool $return  Whether the post should be published.
+ * @param  int  $blog_id ID of the blog.
+ * @param  int  $post_id ID of the post.
+ * @param  int  $user_id ID of the post author.
+ *
+ * @return bool True to authorize the post to be published, otherwise false.
+ */
+function bp_blogs_post_pre_publish( $return = true, $blog_id = 0, $post_id = 0, $user_id = 0 ) {
+	$bp = buddypress();
+
+	// If blog is not trackable, do not record the activity.
+	if ( ! bp_blogs_is_blog_trackable( $blog_id, $user_id ) ) {
+		return false;
+	}
+
+	/*
+	 * Stop infinite loops with WordPress MU Sitewide Tags.
+	 * That plugin changed the way its settings were stored at some point. Thus the dual check.
+	 */
+	$sitewide_tags_blog_settings = bp_core_get_root_option( 'sitewide_tags_blog' );
+	if ( ! empty( $sitewide_tags_blog_settings ) ) {
+		$st_options = maybe_unserialize( $sitewide_tags_blog_settings );
+		$tags_blog_id = isset( $st_options['tags_blog_id'] ) ? $st_options['tags_blog_id'] : 0;
+	} else {
+		$tags_blog_id = bp_core_get_root_option( 'sitewide_tags_blog' );
+		$tags_blog_id = intval( $tags_blog_id );
+	}
+
+	/**
+	 * Filters whether or not BuddyPress should block sitewide tags activity.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param bool $value Current status of the sitewide tags activity.
+	 */
+	if ( (int) $blog_id == $tags_blog_id && apply_filters( 'bp_blogs_block_sitewide_tags_activity', true ) ) {
+		return false;
+	}
+
+	/**
+	 * Filters whether or not the current blog is public.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param int $value Value from the blog_public option for the current blog.
+	 */
+	$is_blog_public = apply_filters( 'bp_is_blog_public', (int) get_blog_option( $blog_id, 'blog_public' ) );
+
+	if ( 0 === $is_blog_public && is_multisite() ) {
+		return false;
+	}
+
+	return $return;
+}
+add_filter( 'bp_activity_post_pre_publish', 'bp_blogs_post_pre_publish', 10, 4 );
Index: /tags/2.4.3/src/bp-blogs/bp-blogs-functions.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/bp-blogs-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/bp-blogs-functions.php	(revision 10410)
@@ -0,0 +1,1504 @@
+<?php
+/**
+ * Blogs component functions.
+ *
+ * @package BuddyPress
+ * @subpackage BlogsFunctions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Check whether the $bp global lists an activity directory page.
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if set, false if empty.
+ */
+function bp_blogs_has_directory() {
+	$bp = buddypress();
+
+	return (bool) !empty( $bp->pages->blogs->id );
+}
+
+/**
+ * Retrieve a set of blogs.
+ *
+ * @see BP_Blogs_Blog::get() for a description of arguments and return value.
+ *
+ * @param array|string $args {
+ *     Arguments are listed here with their default values. For more
+ *     information about the arguments, see {@link BP_Blogs_Blog::get()}.
+ *     @type string      $type              Default: 'active'.
+ *     @type int|bool    $user_id           Default: false.
+ *     @type array       $include_blog_ids  Default: false.
+ *     @type string|bool $search_terms      Default: false.
+ *     @type int         $per_page          Default: 20.
+ *     @type int         $page              Default: 1.
+ *     @type bool        $update_meta_cache Whether to pre-fetch blogmeta. Default: true.
+ * }
+ * @return array See {@link BP_Blogs_Blog::get()}.
+ */
+function bp_blogs_get_blogs( $args = '' ) {
+
+	// Parse query arguments
+	$r = bp_parse_args( $args, array(
+		'type'              => 'active', // 'active', 'alphabetical', 'newest', or 'random'
+		'include_blog_ids'  => false,    // Array of blog IDs to include
+		'user_id'           => false,    // Limit to blogs this user can post to
+		'search_terms'      => false,    // Limit to blogs matching these search terms
+		'per_page'          => 20,       // The number of results to return per page
+		'page'              => 1,        // The page to return if limiting per page
+		'update_meta_cache' => true      // Whether to pre-fetch blogmeta
+	), 'blogs_get_blogs' );
+
+	// Get the blogs
+	$blogs = BP_Blogs_Blog::get(
+		$r['type'],
+		$r['per_page'],
+		$r['page'],
+		$r['user_id'],
+		$r['search_terms'],
+		$r['update_meta_cache'],
+		$r['include_blog_ids']
+	);
+
+	// Filter and return
+	return apply_filters( 'bp_blogs_get_blogs', $blogs, $r );
+}
+
+/**
+ * Populate the BP blogs table with existing blogs.
+ *
+ * @since 1.0.0
+ *
+ * @global object $wpdb WordPress database object.
+ * @uses get_users()
+ * @uses bp_blogs_record_blog()
+ *
+ * @return bool
+ */
+function bp_blogs_record_existing_blogs() {
+	global $wpdb;
+
+	// Query for all sites in network
+	if ( is_multisite() ) {
+
+		// Get blog ID's if not a large network
+		if ( ! wp_is_large_network() ) {
+			$blog_ids = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM {$wpdb->base_prefix}blogs WHERE mature = 0 AND spam = 0 AND deleted = 0 AND site_id = %d", $wpdb->siteid ) );
+
+			// If error running this query, set blog ID's to false
+			if ( is_wp_error( $blog_ids ) ) {
+				$blog_ids = false;
+			}
+
+		// Large networks are not currently supported
+		} else {
+			$blog_ids = false;
+		}
+
+	// Record a single site
+	} else {
+		$blog_ids = $wpdb->blogid;
+	}
+
+	// Bail if there are no blogs in the network
+	if ( empty( $blog_ids ) ) {
+		return false;
+	}
+
+	// Get BuddyPress
+	$bp = buddypress();
+
+	// Truncate user blogs table
+	$truncate = $wpdb->query( "TRUNCATE {$bp->blogs->table_name}" );
+	if ( is_wp_error( $truncate ) ) {
+		return false;
+	}
+
+	// Truncate user blogsmeta table
+	$truncate = $wpdb->query( "TRUNCATE {$bp->blogs->table_name_blogmeta}" );
+	if ( is_wp_error( $truncate ) ) {
+		return false;
+	}
+
+	// Loop through users of blogs and record the relationship
+	foreach ( (array) $blog_ids as $blog_id ) {
+
+		// Ensure that the cache is clear after the table TRUNCATE above
+		wp_cache_delete( $blog_id, 'blog_meta' );
+
+		// Get all users
+		$users = get_users( array(
+			'blog_id' => $blog_id
+		) );
+
+		// Continue on if no users exist for this site (how did this happen?)
+		if ( empty( $users ) ) {
+			continue;
+		}
+
+		// Loop through users and record their relationship to this blog
+		foreach ( (array) $users as $user ) {
+			bp_blogs_add_user_to_blog( $user->ID, false, $blog_id );
+		}
+	}
+
+	/**
+	 * Fires after the BP blogs tables have been populated with existing blogs.
+	 *
+	 * @since 2.4.0
+	 */
+	do_action( 'bp_blogs_recorded_existing_blogs' );
+
+	// No errors
+	return true;
+}
+
+/**
+ * Check whether a given blog should be recorded in activity streams.
+ *
+ * If $user_id is provided, you can restrict site from being recordable
+ * only to particular users.
+ *
+ * @since 1.7.0
+ *
+ * @uses apply_filters()
+ *
+ * @param int $blog_id ID of the blog being checked.
+ * @param int $user_id Optional. ID of the user for whom access is being checked.
+ *
+ * @return bool True if blog is recordable, otherwise false.
+ */
+function bp_blogs_is_blog_recordable( $blog_id, $user_id = 0 ) {
+
+	$recordable_globally = apply_filters( 'bp_blogs_is_blog_recordable', true, $blog_id );
+
+	if ( !empty( $user_id ) ) {
+		$recordable_for_user = apply_filters( 'bp_blogs_is_blog_recordable_for_user', $recordable_globally, $blog_id, $user_id );
+	} else {
+		$recordable_for_user = $recordable_globally;
+	}
+
+	if ( !empty( $recordable_for_user ) ) {
+		return true;
+	}
+
+	return $recordable_globally;
+}
+
+/**
+ * Check whether a given blog should be tracked by the Blogs component.
+ *
+ * If $user_id is provided, the developer can restrict site from
+ * being trackable only to particular users.
+ *
+ * @since 1.7.0
+ *
+ * @uses bp_blogs_is_blog_recordable
+ * @uses apply_filters()
+ *
+ * @param int $blog_id ID of the blog being checked.
+ * @param int $user_id Optional. ID of the user for whom access is being checked.
+ *
+ * @return bool True if blog is trackable, otherwise false.
+ */
+function bp_blogs_is_blog_trackable( $blog_id, $user_id = 0 ) {
+
+	$trackable_globally = apply_filters( 'bp_blogs_is_blog_trackable', bp_blogs_is_blog_recordable( $blog_id, $user_id ), $blog_id );
+
+	if ( !empty( $user_id ) ) {
+		$trackable_for_user = apply_filters( 'bp_blogs_is_blog_trackable_for_user', $trackable_globally, $blog_id, $user_id );
+	} else {
+		$trackable_for_user = $trackable_globally;
+	}
+
+	if ( !empty( $trackable_for_user ) ) {
+		return $trackable_for_user;
+	}
+
+	return $trackable_globally;
+}
+
+/**
+ * Make BuddyPress aware of a new site so that it can track its activity.
+ *
+ * @since 1.0.0
+ *
+ * @uses BP_Blogs_Blog
+ *
+ * @param int  $blog_id     ID of the blog being recorded.
+ * @param int  $user_id     ID of the user for whom the blog is being recorded.
+ * @param bool $no_activity Optional. Whether to skip recording an activity
+ *                          item about this blog creation. Default: false.
+ *
+ * @return bool|null Returns false on failure.
+ */
+function bp_blogs_record_blog( $blog_id, $user_id, $no_activity = false ) {
+
+	if ( empty( $user_id ) )
+		$user_id = bp_loggedin_user_id();
+
+	// If blog is not recordable, do not record the activity.
+	if ( !bp_blogs_is_blog_recordable( $blog_id, $user_id ) )
+		return false;
+
+	$name = get_blog_option( $blog_id, 'blogname' );
+	$url  = get_home_url( $blog_id );
+
+	if ( empty( $name ) ) {
+		$name = $url;
+	}
+
+	$description     = get_blog_option( $blog_id, 'blogdescription' );
+	$close_old_posts = get_blog_option( $blog_id, 'close_comments_for_old_posts' );
+	$close_days_old  = get_blog_option( $blog_id, 'close_comments_days_old' );
+
+	$thread_depth = get_blog_option( $blog_id, 'thread_comments' );
+	if ( ! empty( $thread_depth ) ) {
+		$thread_depth = get_blog_option( $blog_id, 'thread_comments_depth' );
+	} else {
+		// perhaps filter this?
+		$thread_depth = 1;
+	}
+
+	$recorded_blog          = new BP_Blogs_Blog;
+	$recorded_blog->user_id = $user_id;
+	$recorded_blog->blog_id = $blog_id;
+	$recorded_blog_id       = $recorded_blog->save();
+	$is_recorded            = !empty( $recorded_blog_id ) ? true : false;
+
+	bp_blogs_update_blogmeta( $recorded_blog->blog_id, 'url', $url );
+	bp_blogs_update_blogmeta( $recorded_blog->blog_id, 'name', $name );
+	bp_blogs_update_blogmeta( $recorded_blog->blog_id, 'description', $description );
+	bp_blogs_update_blogmeta( $recorded_blog->blog_id, 'last_activity', bp_core_current_time() );
+	bp_blogs_update_blogmeta( $recorded_blog->blog_id, 'close_comments_for_old_posts', $close_old_posts );
+	bp_blogs_update_blogmeta( $recorded_blog->blog_id, 'close_comments_days_old', $close_days_old );
+	bp_blogs_update_blogmeta( $recorded_blog->blog_id, 'thread_comments_depth', $thread_depth );
+
+	$is_private = !empty( $_POST['blog_public'] ) && (int) $_POST['blog_public'] ? false : true;
+	$is_private = !apply_filters( 'bp_is_new_blog_public', !$is_private );
+
+	// Only record this activity if the blog is public
+	if ( !$is_private && !$no_activity && bp_blogs_is_blog_trackable( $blog_id, $user_id ) ) {
+
+		// Record this in activity streams
+		bp_blogs_record_activity( array(
+			'user_id'      => $recorded_blog->user_id,
+			'primary_link' => apply_filters( 'bp_blogs_activity_created_blog_primary_link', $url, $recorded_blog->blog_id ),
+			'type'         => 'new_blog',
+			'item_id'      => $recorded_blog->blog_id
+		) );
+	}
+
+	/**
+	 * Fires after BuddyPress has been made aware of a new site for activity tracking.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param BP_Blogs_Blog $recorded_blog Current blog being recorded. Passed by reference.
+	 * @param bool          $is_private    Whether or not the current blog being recorded is private.
+	 * @param bool          $is_recorded   Whether or not the current blog was recorded.
+	 */
+	do_action_ref_array( 'bp_blogs_new_blog', array( &$recorded_blog, $is_private, $is_recorded ) );
+}
+add_action( 'wpmu_new_blog', 'bp_blogs_record_blog', 10, 2 );
+
+/**
+ * Update blog name in BuddyPress blogmeta table.
+ *
+ * @global object $wpdb DB Layer.
+ *
+ * @param string $oldvalue Value before save. Passed by do_action() but
+ *                         unused here.
+ * @param string $newvalue Value to change meta to.
+ */
+function bp_blogs_update_option_blogname( $oldvalue, $newvalue ) {
+	global $wpdb;
+
+	bp_blogs_update_blogmeta( $wpdb->blogid, 'name', $newvalue );
+}
+add_action( 'update_option_blogname', 'bp_blogs_update_option_blogname', 10, 2 );
+
+/**
+ * Update blog description in BuddyPress blogmeta table.
+ *
+ * @global object $wpdb DB Layer.
+ *
+ * @param string $oldvalue Value before save. Passed by do_action() but
+ *                         unused here.
+ * @param string $newvalue Value to change meta to.
+ */
+function bp_blogs_update_option_blogdescription( $oldvalue, $newvalue ) {
+	global $wpdb;
+
+	bp_blogs_update_blogmeta( $wpdb->blogid, 'description', $newvalue );
+}
+add_action( 'update_option_blogdescription', 'bp_blogs_update_option_blogdescription', 10, 2 );
+
+/**
+ * Update "Close comments for old posts" option in BuddyPress blogmeta table.
+ *
+ * @since 2.0.0
+ *
+ * @global object $wpdb DB Layer.
+ *
+ * @param string $oldvalue Value before save. Passed by do_action() but
+ *                         unused here.
+ * @param string $newvalue Value to change meta to.
+ */
+function bp_blogs_update_option_close_comments_for_old_posts( $oldvalue, $newvalue ) {
+	global $wpdb;
+
+	bp_blogs_update_blogmeta( $wpdb->blogid, 'close_comments_for_old_posts', $newvalue );
+}
+add_action( 'update_option_close_comments_for_old_posts', 'bp_blogs_update_option_close_comments_for_old_posts', 10, 2 );
+
+/**
+ * Update "Close comments after days old" option in BuddyPress blogmeta table.
+ *
+ * @since 2.0.0
+ *
+ * @global object $wpdb DB Layer.
+ *
+ * @param string $oldvalue Value before save. Passed by do_action() but
+ *                         unused here.
+ * @param string $newvalue Value to change meta to.
+ */
+function bp_blogs_update_option_close_comments_days_old( $oldvalue, $newvalue ) {
+	global $wpdb;
+
+	bp_blogs_update_blogmeta( $wpdb->blogid, 'close_comments_days_old', $newvalue );
+}
+add_action( 'update_option_close_comments_days_old', 'bp_blogs_update_option_close_comments_days_old', 10, 2 );
+
+/**
+ * When toggling threaded comments, update thread depth in blogmeta table.
+ *
+ * @since 2.0.0
+ *
+ * @global object $wpdb DB Layer.
+ *
+ * @param string $oldvalue Value before save. Passed by do_action() but
+ *                         unused here.
+ * @param string $newvalue Value to change meta to.
+ */
+function bp_blogs_update_option_thread_comments( $oldvalue, $newvalue ) {
+	global $wpdb;
+
+	if ( empty( $newvalue ) ) {
+		$thread_depth = 1;
+	} else {
+		$thread_depth = get_option( 'thread_comments_depth' );
+	}
+
+	bp_blogs_update_blogmeta( $wpdb->blogid, 'thread_comments_depth', $thread_depth );
+}
+add_action( 'update_option_thread_comments', 'bp_blogs_update_option_thread_comments', 10, 2 );
+
+/**
+ * When updating comment depth, update thread depth in blogmeta table.
+ *
+ * @since 2.0.0
+ *
+ * @global object $wpdb DB Layer.
+ *
+ * @param string $oldvalue Value before save. Passed by do_action() but
+ *                         unused here.
+ * @param string $newvalue Value to change meta to.
+ */
+function bp_blogs_update_option_thread_comments_depth( $oldvalue, $newvalue ) {
+	global $wpdb;
+
+	$comments_enabled = get_option( 'thread_comments' );
+
+	if (  $comments_enabled ) {
+		bp_blogs_update_blogmeta( $wpdb->blogid, 'thread_comments_depth', $newvalue );
+	}
+}
+add_action( 'update_option_thread_comments_depth', 'bp_blogs_update_option_thread_comments_depth', 10, 2 );
+
+/**
+ * Deletes the 'url' blogmeta for a site.
+ *
+ * Hooked to 'refresh_blog_details', which is notably used when editing a site
+ * under "Network Admin > Sites".
+ *
+ * @since 2.3.0
+ *
+ * @param int $site_id The site ID.
+ */
+function bp_blogs_delete_url_blogmeta( $site_id = 0 ) {
+	bp_blogs_delete_blogmeta( (int) $site_id, 'url' );
+}
+add_action( 'refresh_blog_details', 'bp_blogs_delete_url_blogmeta' );
+
+/**
+ * Record activity metadata about a published blog post.
+ *
+ * @since 2.2.0
+ *
+ * @param int     $activity_id ID of the activity item.
+ * @param WP_Post $post        Post object.
+ * @param array   $args        Array of arguments.
+ */
+function bp_blogs_publish_post_activity_meta( $activity_id, $post, $args ) {
+	if ( empty( $activity_id ) || 'post' != $post->post_type ) {
+		return;
+	}
+
+	bp_activity_update_meta( $activity_id, 'post_title', $post->post_title );
+
+	if ( ! empty( $args['post_url'] ) ) {
+		$post_permalink = $args['post_url'];
+	} else {
+		$post_permalink = $post->guid;
+	}
+
+	bp_activity_update_meta( $activity_id, 'post_url',   $post_permalink );
+
+	// Update the blog's last activity.
+	bp_blogs_update_blogmeta( $args['item_id'], 'last_activity', bp_core_current_time() );
+
+	/**
+	 * Fires after BuddyPress has recorded metadata about a published blog post.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int     $ID    ID of the blog post being recorded.
+	 * @param WP_Post $post  WP_Post object for the current blog post.
+	 * @param string  $value ID of the user associated with the current blog post.
+	 */
+	do_action( 'bp_blogs_new_blog_post', $post->ID, $post, $args['user_id'] );
+}
+add_action( 'bp_activity_post_type_published', 'bp_blogs_publish_post_activity_meta', 10, 3 );
+
+/**
+ * Updates a blog post's activity meta entry during a post edit.
+ *
+ * @since 2.2.0
+ *
+ * @param WP_Post              $post     Post object.
+ * @param BP_Activity_Activity $activity Activity object.
+ */
+function bp_blogs_update_post_activity_meta( $post, $activity ) {
+	if ( empty( $activity->id ) || 'post' != $post->post_type ) {
+		return;
+	}
+
+	// Update post title in activity meta.
+	$existing_title = bp_activity_get_meta( $activity->id, 'post_title' );
+	if ( $post->post_title !== $existing_title ) {
+		bp_activity_update_meta( $activity->id, 'post_title', $post->post_title );
+
+		// Now update activity meta for post comments... sigh.
+		add_filter( 'comments_clauses', 'bp_blogs_comments_clauses_select_by_id' );
+		$comments = get_comments( array( 'post_id' => $post->ID ) );
+		remove_filter( 'comments_clauses', 'bp_blogs_comments_clauses_select_by_id' );
+
+		if ( ! empty( $comments ) ) {
+			$activity_ids = array();
+			$comment_ids  = wp_list_pluck( $comments, 'comment_ID' );
+
+			// Set up activity args.
+			$args = array(
+				'update_meta_cache' => false,
+				'show_hidden'       => true,
+				'per_page'          => 99999,
+			);
+
+			// Query for old-style "new_blog_comment" activity items.
+			$args['filter'] = array(
+				'object'       => buddypress()->blogs->id,
+				'action'       => 'new_blog_comment',
+				'secondary_id' => implode( ',', $comment_ids ),
+			);
+
+			$activities = bp_activity_get( $args );
+			if ( ! empty( $activities['activities'] ) ) {
+				$activity_ids = (array) wp_list_pluck( $activities['activities'], 'id' );
+			}
+
+			// Query for activity comments connected to a blog post.
+			unset( $args['filter'] );
+			$args['meta_query'] = array( array(
+				'key'     => 'bp_blogs_post_comment_id',
+				'value'   => $comment_ids,
+				'compare' => 'IN',
+			) );
+			$args['type'] = 'activity_comment';
+			$args['display_comments'] = 'stream';
+
+			$activities = bp_activity_get( $args );
+			if ( ! empty( $activities['activities'] ) ) {
+				$activity_ids = array_merge( $activity_ids, (array) wp_list_pluck( $activities['activities'], 'id' ) );
+			}
+
+			// Update activity meta for all found activity items.
+			if ( ! empty( $activity_ids ) ) {
+				foreach ( $activity_ids as $aid ) {
+					bp_activity_update_meta( $aid, 'post_title', $post->post_title );
+				}
+			}
+
+			unset( $activities, $activity_ids, $comment_ids, $comments );
+		}
+	}
+
+	// Add post comment status to activity meta if closed.
+	if( 'closed' == $post->comment_status ) {
+		bp_activity_update_meta( $activity->id, 'post_comment_status', $post->comment_status );
+	} else {
+		bp_activity_delete_meta( $activity->id, 'post_comment_status' );
+	}
+}
+add_action( 'bp_activity_post_type_updated', 'bp_blogs_update_post_activity_meta', 10, 2 );
+
+/**
+ * Record a new blog comment in the BuddyPress activity stream.
+ *
+ * Only posts the item if blog is public and post is not password-protected.
+ *
+ * @param int         $comment_id  ID of the comment being recorded.
+ * @param bool|string $is_approved Optional. The $is_approved value passed to
+ *                                 the 'comment_post' action. Default: true.
+ *
+ * @return bool|object Returns false on failure, the comment object on success.
+ */
+function bp_blogs_record_comment( $comment_id, $is_approved = true ) {
+	// bail if activity component is not active
+	if ( ! bp_is_active( 'activity' ) ) {
+		return;
+	}
+
+	// Get the users comment
+	$recorded_comment = get_comment( $comment_id );
+
+	// Don't record activity if the comment hasn't been approved
+	if ( empty( $is_approved ) )
+		return false;
+
+	// Don't record activity if no email address has been included
+	if ( empty( $recorded_comment->comment_author_email ) )
+		return false;
+
+	// Don't record activity if the comment has already been marked as spam
+	if ( 'spam' === $is_approved )
+		return false;
+
+	// Get the user by the comment author email.
+	$user = get_user_by( 'email', $recorded_comment->comment_author_email );
+
+	// If user isn't registered, don't record activity
+	if ( empty( $user ) )
+		return false;
+
+	// Get the user_id
+	$user_id = (int) $user->ID;
+
+	// Get blog and post data
+	$blog_id = get_current_blog_id();
+
+	// If blog is not trackable, do not record the activity.
+	if ( ! bp_blogs_is_blog_trackable( $blog_id, $user_id ) )
+		return false;
+
+	$recorded_comment->post = get_post( $recorded_comment->comment_post_ID );
+
+	if ( empty( $recorded_comment->post ) || is_wp_error( $recorded_comment->post ) )
+		return false;
+
+	// If this is a password protected post, don't record the comment
+	if ( !empty( $recorded_comment->post->post_password ) )
+		return false;
+
+	// Don't record activity if the comment's associated post isn't a WordPress Post
+	if ( !in_array( $recorded_comment->post->post_type, apply_filters( 'bp_blogs_record_comment_post_types', array( 'post' ) ) ) )
+		return false;
+
+	$is_blog_public = apply_filters( 'bp_is_blog_public', (int)get_blog_option( $blog_id, 'blog_public' ) );
+
+	// If blog is public allow activity to be posted
+	if ( $is_blog_public ) {
+
+		// Get activity related links
+		$post_permalink = get_permalink( $recorded_comment->comment_post_ID );
+		$comment_link   = get_comment_link( $recorded_comment->comment_ID );
+
+		// Setup activity args
+		$args = array();
+
+		$args['user_id']       = $user_id;
+		$args['content']       = apply_filters_ref_array( 'bp_blogs_activity_new_comment_content', array( $recorded_comment->comment_content, &$recorded_comment, $comment_link ) );
+		$args['primary_link']  = apply_filters_ref_array( 'bp_blogs_activity_new_comment_primary_link', array( $comment_link,     &$recorded_comment ) );
+		$args['recorded_time'] = $recorded_comment->comment_date_gmt;
+
+		// Setup some different activity args depending if activity commenting is
+		// enabled or not
+
+		// if cannot comment, record separate activity entry
+		// this is the old way of doing things
+		if ( bp_disable_blogforum_comments() ) {
+			$args['type']              = 'new_blog_comment';
+			$args['item_id']           = $blog_id;
+			$args['secondary_item_id'] = $comment_id;
+
+			// record the activity entry
+			$activity_id = bp_blogs_record_activity( $args );
+
+			// add some post info in activity meta
+			bp_activity_update_meta( $activity_id, 'post_title', $recorded_comment->post->post_title );
+			bp_activity_update_meta( $activity_id, 'post_url',   add_query_arg( 'p', $recorded_comment->post->ID, home_url( '/' ) ) );
+
+		// record comment as BP activity comment under the parent 'new_blog_post'
+		// activity item
+		} else {
+			// this is a comment edit
+			// check to see if corresponding activity entry already exists
+			if ( ! empty( $_REQUEST['action'] ) ) {
+				$existing_activity_id = get_comment_meta( $comment_id, 'bp_activity_comment_id', true );
+
+				if ( ! empty( $existing_activity_id ) ) {
+					$args['id'] = $existing_activity_id;
+				}
+			}
+
+			// find the parent 'new_blog_post' activity entry
+			$parent_activity_id = bp_activity_get_activity_id( array(
+				'component'         => 'blogs',
+				'type'              => 'new_blog_post',
+				'item_id'           => $blog_id,
+				'secondary_item_id' => $recorded_comment->comment_post_ID
+			) );
+
+			// Try to create a new activity item for the parent blog post
+			if ( empty( $parent_activity_id ) ) {
+				$parent_activity_id = bp_activity_post_type_publish( $recorded_comment->comment_post_ID, $recorded_comment->post );
+			}
+
+			// we found the parent activity entry
+			// so let's go ahead and reconfigure some activity args
+			if ( ! empty( $parent_activity_id ) ) {
+				// set the 'item_id' with the parent activity entry ID
+				$args['item_id'] = $parent_activity_id;
+
+				// now see if the WP parent comment has a BP activity ID
+				$comment_parent = 0;
+				if ( ! empty( $recorded_comment->comment_parent ) ) {
+					$comment_parent = get_comment_meta( $recorded_comment->comment_parent, 'bp_activity_comment_id', true );
+				}
+
+				// WP parent comment does not have a BP activity ID
+				// so set to 'new_blog_post' activity ID
+				if ( empty( $comment_parent ) ) {
+					$comment_parent = $parent_activity_id;
+				}
+
+				$args['secondary_item_id'] = $comment_parent;
+				$args['component']         = 'activity';
+				$args['type']              = 'activity_comment';
+
+			// could not find corresponding parent activity entry
+			// so wipe out $args array
+			} else {
+				$args = array();
+			}
+
+			// Record in activity streams
+			if ( ! empty( $args ) ) {
+				// @todo should we use bp_activity_new_comment()? that function will also send
+				// an email to people in the activity comment thread
+				//
+				// what if a site already has some comment email notification plugin setup?
+				// this is why I decided to go with bp_activity_add() to avoid any conflict
+				// with existing comment email notification plugins
+				$comment_activity_id = bp_activity_add( $args );
+
+				if ( empty( $args['id'] ) ) {
+					// add meta to activity comment
+					bp_activity_update_meta( $comment_activity_id, 'bp_blogs_post_comment_id', $comment_id );
+					bp_activity_update_meta( $comment_activity_id, 'post_title', $recorded_comment->post->post_title );
+					bp_activity_update_meta( $comment_activity_id, 'post_url', add_query_arg( 'p', $recorded_comment->post->ID, home_url( '/' ) ) );
+
+					// add meta to comment
+					add_comment_meta( $comment_id, 'bp_activity_comment_id', $comment_activity_id );
+				}
+			}
+		}
+
+		// Update the blogs last active date
+		bp_blogs_update_blogmeta( $blog_id, 'last_activity', bp_core_current_time() );
+	}
+
+	return $recorded_comment;
+}
+add_action( 'comment_post', 'bp_blogs_record_comment', 10, 2 );
+add_action( 'edit_comment', 'bp_blogs_record_comment', 10    );
+
+/**
+ * Record a user's association with a blog.
+ *
+ * This function is hooked to several WordPress actions where blog roles are
+ * set/changed ('add_user_to_blog', 'profile_update', 'user_register'). It
+ * parses the changes, and records them as necessary in the BP blog tracker.
+ *
+ * BuddyPress does not track blogs for users with the 'subscriber' role by
+ * default, though as of 2.1.0 you can filter 'bp_blogs_get_allowed_roles' to
+ * modify this behavior.
+ *
+ * @param int         $user_id The ID of the user.
+ * @param string|bool $role    User's WordPress role for this blog ID.
+ * @param int         $blog_id Blog ID user is being added to.
+ *
+ * @return bool|null False on failure.
+ */
+function bp_blogs_add_user_to_blog( $user_id, $role = false, $blog_id = 0 ) {
+	global $wpdb;
+
+	// If no blog ID was passed, use the root blog ID
+	if ( empty( $blog_id ) ) {
+		$blog_id = isset( $wpdb->blogid ) ? $wpdb->blogid : bp_get_root_blog_id();
+	}
+
+	// If no role was passed, try to find the blog role
+	if ( empty( $role ) ) {
+
+		// Get user capabilities
+		$key        = $wpdb->get_blog_prefix( $blog_id ). 'capabilities';
+		$user_roles = array_keys( (array) bp_get_user_meta( $user_id, $key, true ) );
+
+		// User has roles so lets
+		if ( ! empty( $user_roles ) ) {
+
+			// Get blog roles
+			$blog_roles      = array_keys( bp_get_current_blog_roles() );
+
+			// Look for blog only roles of the user
+			$intersect_roles = array_intersect( $user_roles, $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( $intersect_roles ) ) {
+				$role = array_shift( $intersect_roles );
+			}
+		}
+	}
+
+	// Bail if no role was found or role is not in the allowed roles array
+	if ( empty( $role ) || ! in_array( $role, bp_blogs_get_allowed_roles() ) ) {
+		return false;
+	}
+
+	// Record the blog activity for this user being added to this blog
+	bp_blogs_record_blog( $blog_id, $user_id, true );
+}
+add_action( 'add_user_to_blog', 'bp_blogs_add_user_to_blog', 10, 3 );
+add_action( 'profile_update',   'bp_blogs_add_user_to_blog'        );
+add_action( 'user_register',    'bp_blogs_add_user_to_blog'        );
+
+/**
+ * The allowed blog roles a member must have to be recorded into the
+ * `bp_user_blogs` pointer table.
+ *
+ * This added and was made filterable in BuddyPress 2.1.0 to make it easier
+ * to extend the functionality of the Blogs component.
+ *
+ * @since 2.1.0
+ *
+ * @return string
+ */
+function bp_blogs_get_allowed_roles() {
+	return apply_filters( 'bp_blogs_get_allowed_roles', array( 'contributor', 'author', 'editor', 'administrator' ) );
+}
+
+/**
+ * Remove a blog-user pair from BP's blog tracker.
+ *
+ * @param int $user_id ID of the user whose blog is being removed.
+ * @param int $blog_id Optional. ID of the blog being removed. Default: current blog ID.
+ */
+function bp_blogs_remove_user_from_blog( $user_id, $blog_id = 0 ) {
+	global $wpdb;
+
+	if ( empty( $blog_id ) ) {
+		$blog_id = $wpdb->blogid;
+	}
+
+	bp_blogs_remove_blog_for_user( $user_id, $blog_id );
+}
+add_action( 'remove_user_from_blog', 'bp_blogs_remove_user_from_blog', 10, 2 );
+
+/**
+ * Rehook WP's maybe_add_existing_user_to_blog with a later priority.
+ *
+ * WordPress catches add-user-to-blog requests at init:10. In some cases, this
+ * can precede BP's Blogs component. This function bumps the priority of the
+ * core function, so that we can be sure that the Blogs component is loaded
+ * first. See https://buddypress.trac.wordpress.org/ticket/3916.
+ *
+ * @since 1.6.0
+ */
+function bp_blogs_maybe_add_user_to_blog() {
+	if ( ! is_multisite() )
+		return;
+
+	remove_action( 'init', 'maybe_add_existing_user_to_blog' );
+	add_action( 'init', 'maybe_add_existing_user_to_blog', 20 );
+}
+add_action( 'init', 'bp_blogs_maybe_add_user_to_blog', 1 );
+
+/**
+ * Remove the "blog created" item from the BP blogs tracker and activity stream.
+ *
+ * @param int $blog_id ID of the blog being removed.
+ */
+function bp_blogs_remove_blog( $blog_id ) {
+
+	$blog_id = (int) $blog_id;
+
+	/**
+	 * Fires before a "blog created" item is removed from blogs
+	 * tracker and activity stream.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $blog_id ID of the blog having its item removed.
+	 */
+	do_action( 'bp_blogs_before_remove_blog', $blog_id );
+
+	BP_Blogs_Blog::delete_blog_for_all( $blog_id );
+
+	// Delete activity stream item
+	bp_blogs_delete_activity( array(
+		'item_id'   => $blog_id,
+		'component' => buddypress()->blogs->id,
+		'type'      => 'new_blog'
+	) );
+
+	/**
+	 * Fires after a "blog created" item has been removed from blogs
+	 * tracker and activity stream.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $blog_id ID of the blog who had its item removed.
+	 */
+	do_action( 'bp_blogs_remove_blog', $blog_id );
+}
+add_action( 'delete_blog', 'bp_blogs_remove_blog' );
+
+/**
+ * Remove a blog from the tracker for a specific user.
+ *
+ * @param int $user_id ID of the user for whom the blog is being removed.
+ * @param int $blog_id ID of the blog being removed.
+ */
+function bp_blogs_remove_blog_for_user( $user_id, $blog_id ) {
+
+	$blog_id = (int) $blog_id;
+	$user_id = (int) $user_id;
+
+	/**
+	 * Fires before a blog is removed from the tracker for a specific user.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $blog_id ID of the blog being removed.
+	 * @param int $user_id ID of the user having the blog removed for.
+	 */
+	do_action( 'bp_blogs_before_remove_blog_for_user', $blog_id, $user_id );
+
+	BP_Blogs_Blog::delete_blog_for_user( $blog_id, $user_id );
+
+	// Delete activity stream item
+	bp_blogs_delete_activity( array(
+		'item_id'   => $blog_id,
+		'component' => buddypress()->blogs->id,
+		'type'      => 'new_blog'
+	) );
+
+	/**
+	 * Fires after a blog has been removed from the tracker for a specific user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $blog_id ID of the blog that was removed.
+	 * @param int $user_id ID of the user having the blog removed for.
+	 */
+	do_action( 'bp_blogs_remove_blog_for_user', $blog_id, $user_id );
+}
+add_action( 'remove_user_from_blog', 'bp_blogs_remove_blog_for_user', 10, 2 );
+
+/**
+ * Remove a blog post activity item from the activity stream.
+ *
+ * @param int $post_id ID of the post to be removed.
+ * @param int $blog_id Optional. Defaults to current blog ID.
+ * @param int $user_id Optional. Defaults to the logged-in user ID. This param
+ *                     is currently unused in the function (but is passed to hooks).
+ *
+ * @return bool
+ */
+function bp_blogs_remove_post( $post_id, $blog_id = 0, $user_id = 0 ) {
+	global $wpdb;
+
+	if ( empty( $wpdb->blogid ) )
+		return false;
+
+	$post_id = (int) $post_id;
+
+	if ( !$blog_id )
+		$blog_id = (int) $wpdb->blogid;
+
+	if ( !$user_id )
+		$user_id = bp_loggedin_user_id();
+
+	/**
+	 * Fires before removal of a blog post activity item from the activity stream.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $blog_id ID of the blog associated with the post that was removed.
+	 * @param int $post_id ID of the post that was removed.
+	 * @param int $user_id ID of the user having the blog removed for.
+	 */
+	do_action( 'bp_blogs_before_remove_post', $blog_id, $post_id, $user_id );
+
+	// Delete activity stream item
+	bp_blogs_delete_activity( array(
+		'item_id'           => $blog_id,
+		'secondary_item_id' => $post_id,
+		'component'         => buddypress()->blogs->id,
+		'type'              => 'new_blog_post'
+	) );
+
+	/**
+	 * Fires after removal of a blog post activity item from the activity stream.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $blog_id ID of the blog associated with the post that was removed.
+	 * @param int $post_id ID of the post that was removed.
+	 * @param int $user_id ID of the user having the blog removed for.
+	 */
+	do_action( 'bp_blogs_remove_post', $blog_id, $post_id, $user_id );
+}
+add_action( 'delete_post', 'bp_blogs_remove_post' );
+
+/**
+ * Remove a blog comment activity item from the activity stream.
+ *
+ * @param int $comment_id ID of the comment to be removed.
+ */
+function bp_blogs_remove_comment( $comment_id ) {
+	global $wpdb;
+
+	// activity comments are disabled for blog posts
+	// which means that individual activity items exist for blog comments
+	if ( bp_disable_blogforum_comments() ) {
+		// Delete the individual activity stream item
+		bp_blogs_delete_activity( array(
+			'item_id'           => $wpdb->blogid,
+			'secondary_item_id' => $comment_id,
+			'type'              => 'new_blog_comment'
+		) );
+
+	// activity comments are enabled for blog posts
+	// remove the associated activity item
+	} else {
+		// get associated activity ID from comment meta
+		$activity_id = get_comment_meta( $comment_id, 'bp_activity_comment_id', true );
+
+		// delete the associated activity comment
+		//
+		// also removes child post comments and associated activity comments
+		if ( ! empty( $activity_id ) && bp_is_active( 'activity' ) ) {
+			// fetch the activity comments for the activity item
+			$activity = bp_activity_get( array(
+				'in'               => $activity_id,
+				'display_comments' => 'stream',
+				'spam'             => 'all',
+			) );
+
+			// get all activity comment IDs for the pending deleted item
+			if ( ! empty( $activity['activities'] ) ) {
+				$activity_ids   = bp_activity_recurse_comments_activity_ids( $activity );
+				$activity_ids[] = $activity_id;
+
+				// delete activity items
+				foreach ( $activity_ids as $activity_id ) {
+					bp_activity_delete( array(
+						'id' => $activity_id
+					) );
+				}
+
+				// remove associated blog comments
+				bp_blogs_remove_associated_blog_comments( $activity_ids );
+
+				// rebuild activity comment tree
+				BP_Activity_Activity::rebuild_activity_comment_tree( $activity['activities'][0]->item_id );
+			}
+		}
+	}
+
+	/**
+	 * Fires after a blog comment activity item was removed from activity stream.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $blogid     Item ID for the blog associated with the removed comment.
+	 * @param int $comment_id ID of the comment being removed.
+	 * @param int $value      ID of the current logged in user.
+	 */
+	do_action( 'bp_blogs_remove_comment', $wpdb->blogid, $comment_id, bp_loggedin_user_id() );
+}
+add_action( 'delete_comment', 'bp_blogs_remove_comment' );
+
+/**
+ * Removes blog comments that are associated with activity comments.
+ *
+ * @since 2.0.0
+ *
+ * @see bp_blogs_remove_comment()
+ * @see bp_blogs_sync_delete_from_activity_comment()
+ *
+ * @param array $activity_ids The activity IDs to check association with blog
+ *                            comments.
+ * @param bool $force_delete  Whether to force delete the comments. If false,
+ *                            comments are trashed instead.
+ */
+function bp_blogs_remove_associated_blog_comments( $activity_ids = array(), $force_delete = true ) {
+	// query args
+	$query_args = array(
+		'meta_query' => array(
+			array(
+				'key'     => 'bp_activity_comment_id',
+				'value'   => implode( ',', (array) $activity_ids ),
+				'compare' => 'IN',
+			)
+		)
+	);
+
+	// get comment
+	$comment_query = new WP_Comment_Query;
+	$comments = $comment_query->query( $query_args );
+
+	// found the corresponding comments
+	// let's delete them!
+	foreach ( $comments as $comment ) {
+		wp_delete_comment( $comment->comment_ID, $force_delete );
+
+		// if we're trashing the comment, remove the meta key as well
+		if ( empty( $force_delete ) ) {
+			delete_comment_meta( $comment->comment_ID, 'bp_activity_comment_id' );
+		}
+	}
+}
+
+/**
+ * When a blog comment status transition occurs, update the relevant activity's status.
+ *
+ * @since 1.6.0
+ *
+ * @param string $new_status New comment status.
+ * @param string $old_status Previous comment status.
+ * @param object $comment    Comment data.
+ */
+function bp_blogs_transition_activity_status( $new_status, $old_status, $comment ) {
+
+	// Check the Activity component is active
+	if ( ! bp_is_active( 'activity' ) )
+		return;
+
+	/**
+	 * Activity currently doesn't have any concept of a trash, or an unapproved/approved state.
+	 *
+	 * If a blog comment transitions to a "delete" or "hold" status, delete the activity item.
+	 * If a blog comment transitions to trashed, or spammed, mark the activity as spam.
+	 * If a blog comment transitions to approved (and the activity exists), mark the activity as ham.
+	 * If a blog comment transitions to unapproved (and the activity exists), mark the activity as spam.
+	 * Otherwise, record the comment into the activity stream.
+	 */
+
+	// This clause was moved in from bp_blogs_remove_comment() in BuddyPress 1.6. It handles delete/hold.
+	if ( in_array( $new_status, array( 'delete', 'hold' ) ) ) {
+		return bp_blogs_remove_comment( $comment->comment_ID );
+
+	// These clauses handle trash, spam, and un-spams.
+	} elseif ( in_array( $new_status, array( 'trash', 'spam', 'unapproved' ) ) ) {
+		$action = 'spam_activity';
+	} elseif ( 'approved' == $new_status ) {
+		$action = 'ham_activity';
+	}
+
+	// Get the activity
+	if ( bp_disable_blogforum_comments() ) {
+		$activity_id = bp_activity_get_activity_id( array(
+			'component'         => buddypress()->blogs->id,
+			'item_id'           => get_current_blog_id(),
+			'secondary_item_id' => $comment->comment_ID,
+			'type'              => 'new_blog_comment'
+		) );
+	} else {
+		$activity_id = get_comment_meta( $comment->comment_ID, 'bp_activity_comment_id', true );
+	}
+
+	// Check activity item exists
+	if ( empty( $activity_id ) ) {
+		// If no activity exists, but the comment has been approved, record it into the activity table.
+		if ( 'approved' == $new_status ) {
+			return bp_blogs_record_comment( $comment->comment_ID, true );
+		}
+
+		return;
+	}
+
+	// Create an activity object
+	$activity = new BP_Activity_Activity( $activity_id );
+	if ( empty( $activity->component ) )
+		return;
+
+	// Spam/ham the activity if it's not already in that state
+	if ( 'spam_activity' == $action && ! $activity->is_spam ) {
+		bp_activity_mark_as_spam( $activity );
+	} elseif ( 'ham_activity' == $action) {
+		bp_activity_mark_as_ham( $activity );
+	}
+
+	// Add "new_blog_comment" to the whitelisted activity types, so that the activity's Akismet history is generated
+	$comment_akismet_history = create_function( '$t', '$t[] = "new_blog_comment"; return $t;' );
+	add_filter( 'bp_akismet_get_activity_types', $comment_akismet_history );
+
+	// Save the updated activity
+	$activity->save();
+
+	// Remove the "new_blog_comment" activity type whitelist so we don't break anything
+	remove_filter( 'bp_akismet_get_activity_types', $comment_akismet_history );
+}
+add_action( 'transition_comment_status', 'bp_blogs_transition_activity_status', 10, 3 );
+
+/**
+ * Get the total number of blogs being tracked by BuddyPress.
+ *
+ * @return int $count Total blog count.
+ */
+function bp_blogs_total_blogs() {
+	$count = wp_cache_get( 'bp_total_blogs', 'bp' );
+
+	if ( false === $count ) {
+		$blogs = BP_Blogs_Blog::get_all();
+		$count = $blogs['total'];
+		wp_cache_set( 'bp_total_blogs', $count, 'bp' );
+	}
+	return $count;
+}
+
+/**
+ * Get the total number of blogs being tracked by BP for a specific user.
+ *
+ * @since 1.2.0
+ *
+ * @param int $user_id ID of the user being queried. Default: on a user page,
+ *                     the displayed user. Otherwise, the logged-in user.
+ *
+ * @return int $count Total blog count for the user.
+ */
+function bp_blogs_total_blogs_for_user( $user_id = 0 ) {
+	if ( empty( $user_id ) ) {
+		$user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+	}
+
+	// no user ID? do not attempt to look at cache
+	if ( empty( $user_id ) ) {
+		return 0;
+	}
+
+	$count = wp_cache_get( 'bp_total_blogs_for_user_' . $user_id, 'bp' );
+	if ( false === $count ) {
+		$count = BP_Blogs_Blog::total_blog_count_for_user( $user_id );
+		wp_cache_set( 'bp_total_blogs_for_user_' . $user_id, $count, 'bp' );
+	}
+
+	return $count;
+}
+
+/**
+ * Remove the all data related to a given blog from the BP blogs tracker and activity stream.
+ *
+ * @param int $blog_id The ID of the blog to expunge.
+ */
+function bp_blogs_remove_data_for_blog( $blog_id ) {
+
+	/**
+	 * Fires before all data related to a given blog is removed from blogs tracker
+	 * and activity stream.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $blog_id ID of the blog whose data is being removed.
+	 */
+	do_action( 'bp_blogs_before_remove_data_for_blog', $blog_id );
+
+	// If this is regular blog, delete all data for that blog.
+	BP_Blogs_Blog::delete_blog_for_all( $blog_id );
+
+	// Delete activity stream item
+	bp_blogs_delete_activity( array(
+		'item_id'   => $blog_id,
+		'component' => buddypress()->blogs->id,
+		'type'      => false
+	) );
+
+	/**
+	 * Fires after all data related to a given blog has been removed from blogs tracker
+	 * and activity stream.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $blog_id ID of the blog whose data is being removed.
+	 */
+	do_action( 'bp_blogs_remove_data_for_blog', $blog_id );
+}
+add_action( 'delete_blog', 'bp_blogs_remove_data_for_blog', 1 );
+
+/**
+ * Get all of a user's blogs, as tracked by BuddyPress.
+ *
+ * @see BP_Blogs_Blog::get_blogs_for_user() for a description of parameters
+ *      and return values.
+ *
+ * @param int  $user_id     See {@BP_Blogs_Blog::get_blogs_for_user()}.
+ * @param bool $show_hidden See {@BP_Blogs_Blog::get_blogs_for_user()}.
+ *
+ * @return array See {@BP_Blogs_Blog::get_blogs_for_user()}.
+ */
+function bp_blogs_get_blogs_for_user( $user_id, $show_hidden = false ) {
+	return BP_Blogs_Blog::get_blogs_for_user( $user_id, $show_hidden );
+}
+
+/**
+ * Retrieve a list of all blogs.
+ *
+ * @see BP_Blogs_Blog::get_all() for a description of parameters and return values.
+ *
+ * @param int $limit See {@BP_Blogs_Blog::get_all()}.
+ * @param int $page  See {@BP_Blogs_Blog::get_all()}.
+ *
+ * @return array See {@BP_Blogs_Blog::get_all()}.
+ */
+function bp_blogs_get_all_blogs( $limit = null, $page = null ) {
+	return BP_Blogs_Blog::get_all( $limit, $page );
+}
+
+/**
+ * Retrieve a random list of blogs.
+ *
+ * @see BP_Blogs_Blog::get() for a description of parameters and return values.
+ *
+ * @param int $limit See {@BP_Blogs_Blog::get()}.
+ * @param int $page  See {@BP_Blogs_Blog::get()}.
+ *
+ * @return array See {@BP_Blogs_Blog::get()}.
+ */
+function bp_blogs_get_random_blogs( $limit = null, $page = null ) {
+	return BP_Blogs_Blog::get( 'random', $limit, $page );
+}
+
+/**
+ * Check whether a given blog is hidden.
+ *
+ * @see BP_Blogs_Blog::is_hidden() for a description of parameters and return values.
+ *
+ * @param int $blog_id See {@BP_Blogs_Blog::is_hidden()}.
+ *
+ * @return bool See {@BP_Blogs_Blog::is_hidden()}.
+ */
+function bp_blogs_is_blog_hidden( $blog_id ) {
+	return BP_Blogs_Blog::is_hidden( $blog_id );
+}
+
+/*******************************************************************************
+ * Blog meta functions
+ *
+ * These functions are used to store specific blogmeta in one global table,
+ * rather than in each blog's options table. Significantly speeds up global blog
+ * queries. By default each blog's name, description and last updated time are
+ * stored and synced here.
+ */
+
+/**
+ * Delete a metadata from the DB for a blog.
+ *
+ * @global object $wpdb WordPress database access object.
+ *
+ * @param int         $blog_id    ID of the blog whose metadata is being deleted.
+ * @param string|bool $meta_key   Optional. The key of the metadata being deleted. If
+ *                                omitted, all BP metadata associated with the blog will
+ *                                be deleted.
+ * @param string|bool $meta_value Optional. If present, the metadata will only be
+ *                                deleted if the meta_value matches this parameter.
+ * @param bool        $delete_all Optional. If true, delete matching metadata entries for
+ * 	                             all objects, ignoring the specified blog_id. Otherwise, only
+ * 	                             delete matching metadata entries for the specified blog.
+ * 	                             Default: false.
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_blogs_delete_blogmeta( $blog_id, $meta_key = false, $meta_value = false, $delete_all = false ) {
+	global $wpdb;
+
+	// Legacy - if no meta_key is passed, delete all for the blog_id
+	if ( empty( $meta_key ) ) {
+		$keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->blogmeta} WHERE blog_id = %d", $blog_id ) );
+		$delete_all = false;
+	} else {
+		$keys = array( $meta_key );
+	}
+
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	$retval = false;
+	foreach ( $keys as $key ) {
+		$retval = delete_metadata( 'blog', $blog_id, $key, $meta_value, $delete_all );
+	}
+
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Get metadata for a given blog.
+ *
+ * @since 1.2.0
+ *
+ * @global object $wpdb WordPress database access object.
+ *
+ * @param int    $blog_id  ID of the blog whose metadata is being requested.
+ * @param string $meta_key Optional. If present, only the metadata matching
+ *                         that meta key will be returned. Otherwise, all
+ *                         metadata for the blog will be fetched.
+ * @param bool   $single   Optional. If true, return only the first value of the
+ *	                        specified meta_key. This parameter has no effect if
+ *	                        meta_key is not specified. Default: true.
+ *
+ * @return mixed The meta value(s) being requested.
+ */
+function bp_blogs_get_blogmeta( $blog_id, $meta_key = '', $single = true ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = get_metadata( 'blog', $blog_id, $meta_key, $single );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Update a piece of blog meta.
+ *
+ * @global object $wpdb WordPress database access object.
+ *
+ * @param int    $blog_id    ID of the blog whose metadata is being updated.
+ * @param string $meta_key   Key of the metadata being updated.
+ * @param mixed  $meta_value Value to be set.
+ * @param mixed  $prev_value Optional. If specified, only update existing
+ *                           metadata entries with the specified value.
+ *                           Otherwise, update all entries.
+ *
+ * @return bool|int Returns false on failure. On successful update of existing
+ *                  metadata, returns true. On successful creation of new metadata,
+ *                  returns the integer ID of the new metadata row.
+ */
+function bp_blogs_update_blogmeta( $blog_id, $meta_key, $meta_value, $prev_value = '' ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = update_metadata( 'blog', $blog_id, $meta_key, $meta_value, $prev_value );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Add a piece of blog metadata.
+ *
+ * @since 2.0.0
+ *
+ * @param int    $blog_id    ID of the blog.
+ * @param string $meta_key   Metadata key.
+ * @param mixed  $meta_value Metadata value.
+ * @param bool   $unique     Optional. Whether to enforce a single metadata value
+ *                           for the given key. If true, and the object already has a value for
+ *                           the key, no change will be made. Default: false.
+ *
+ * @return int|bool The meta ID on successful update, false on failure.
+ */
+function bp_blogs_add_blogmeta( $blog_id, $meta_key, $meta_value, $unique = false ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = add_metadata( 'blog', $blog_id, $meta_key, $meta_value, $unique );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+/**
+ * Remove all blog associations for a given user.
+ *
+ * @param int $user_id ID whose blog data should be removed.
+ *
+ * @return bool|null Returns false on failure.
+ */
+function bp_blogs_remove_data( $user_id ) {
+	if ( !is_multisite() )
+		return false;
+
+	/**
+	 * Fires before all blog associations are removed for a given user.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $user_id ID of the user whose blog associations are being removed.
+	 */
+	do_action( 'bp_blogs_before_remove_data', $user_id );
+
+	// If this is regular blog, delete all data for that blog.
+	BP_Blogs_Blog::delete_blogs_for_user( $user_id );
+
+	/**
+	 * Fires after all blog associations are removed for a given user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $user_id ID of the user whose blog associations were removed.
+	 */
+	do_action( 'bp_blogs_remove_data', $user_id );
+}
+add_action( 'wpmu_delete_user',  'bp_blogs_remove_data' );
+add_action( 'delete_user',       'bp_blogs_remove_data' );
+add_action( 'bp_make_spam_user', 'bp_blogs_remove_data' );
+
+/**
+ * Restore all blog associations for a given user.
+ *
+ * @since 2.2.0
+ *
+ * @param int $user_id ID whose blog data should be restored.
+ */
+function bp_blogs_restore_data( $user_id = 0 ) {
+	if ( ! is_multisite() ) {
+		return;
+	}
+
+	// Get the user's blogs
+	$user_blogs = get_blogs_of_user( $user_id );
+	if ( empty( $user_blogs ) ) {
+		return;
+	}
+
+	$blogs = array_keys( $user_blogs );
+
+	foreach ( $blogs as $blog_id ) {
+		bp_blogs_add_user_to_blog( $user_id, false, $blog_id );
+	}
+}
+add_action( 'bp_make_ham_user', 'bp_blogs_restore_data', 10, 1 );
Index: /tags/2.4.3/src/bp-blogs/bp-blogs-loader.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/bp-blogs-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/bp-blogs-loader.php	(revision 10410)
@@ -0,0 +1,353 @@
+<?php
+/**
+ * BuddyPress Blogs Loader
+ *
+ * The blogs component tracks posts and comments to member activity streams,
+ * shows blogs the member can post to in their profiles, and caches useful
+ * information from those blogs to make querying blogs in bulk more performant.
+ *
+ * @package BuddyPress
+ * @subpackage BlogsCore
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+class BP_Blogs_Component extends BP_Component {
+
+	/**
+	 * Start the blogs component creation process.
+	 *
+	 * @since 1.5.0
+	 */
+	public function __construct() {
+		parent::start(
+			'blogs',
+			__( 'Site Directory', 'buddypress' ),
+			buddypress()->plugin_dir,
+			array(
+				'adminbar_myaccount_order' => 30,
+				'search_query_arg' => 'sites_search',
+			)
+		);
+	}
+
+	/**
+	 * Set up global settings for the blogs component.
+	 *
+	 * The BP_BLOGS_SLUG constant is deprecated, and only used here for
+	 * backwards compatibility.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_globals() for description of parameters.
+	 *
+	 * @param array $args See {@link BP_Component::setup_globals()}.
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		if ( ! defined( 'BP_BLOGS_SLUG' ) ) {
+			define ( 'BP_BLOGS_SLUG', $this->id );
+		}
+
+		// Global tables for messaging component
+		$global_tables = array(
+			'table_name'          => $bp->table_prefix . 'bp_user_blogs',
+			'table_name_blogmeta' => $bp->table_prefix . 'bp_user_blogs_blogmeta',
+		);
+
+		$meta_tables = array(
+			'blog' => $bp->table_prefix . 'bp_user_blogs_blogmeta',
+		);
+
+		// All globals for blogs component.
+		$args = array(
+			'slug'                  => BP_BLOGS_SLUG,
+			'root_slug'             => isset( $bp->pages->blogs->slug ) ? $bp->pages->blogs->slug : BP_BLOGS_SLUG,
+			'has_directory'         => is_multisite(), // Non-multisite installs don't need a top-level Sites directory, since there's only one site
+			'directory_title'       => _x( 'Sites', 'component directory title', 'buddypress' ),
+			'notification_callback' => 'bp_blogs_format_notifications',
+			'search_string'         => __( 'Search sites...', 'buddypress' ),
+			'autocomplete_all'      => defined( 'BP_MESSAGES_AUTOCOMPLETE_ALL' ),
+			'global_tables'         => $global_tables,
+			'meta_tables'           => $meta_tables,
+		);
+
+		// Setup the globals
+		parent::setup_globals( $args );
+
+		/*
+		 * Filters if a blog is public.
+		 *
+		 * In case the config is not multisite, the blog_public option is ignored.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @oaram int $value Whether or not the blog is public.
+		 */
+		if ( 0 !== apply_filters( 'bp_is_blog_public', (int) get_option( 'blog_public' ) ) || ! is_multisite() ) {
+
+			/**
+			 * Filters the post types to track for the Blogs component.
+			 *
+			 * @since 1.5.0
+			 * @deprecated 2.3.0
+			 *
+			 * @param array $value Array of post types to track.
+			 */
+			$post_types = apply_filters( 'bp_blogs_record_post_post_types', array( 'post' ) );
+
+			foreach ( $post_types as $post_type ) {
+				add_post_type_support( $post_type, 'buddypress-activity' );
+			}
+		}
+
+		// Filter the generic track parameters for the 'post' post type.
+		add_filter( 'bp_activity_get_post_type_tracking_args', array( $this, 'post_tracking_args' ), 10, 2 );
+	}
+
+	/**
+	 * Include bp-blogs files.
+	 *
+	 * @see BP_Component::includes() for description of parameters.
+	 *
+	 * @param array $includes See {@link BP_Component::includes()}.
+	 */
+	public function includes( $includes = array() ) {
+
+		// Files to include
+		$includes = array(
+			'cache',
+			'actions',
+			'screens',
+			'classes',
+			'template',
+			'filters',
+			'activity',
+			'functions',
+		);
+
+		if ( is_multisite() ) {
+			$includes[] = 'widgets';
+		}
+
+		// Include the files
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Set up component navigation for bp-blogs.
+	 *
+	 * @see BP_Component::setup_nav() for a description of arguments.
+	 *
+	 * @param array $main_nav Optional. See BP_Component::setup_nav() for
+	 *                        description.
+	 * @param array $sub_nav  Optional. See BP_Component::setup_nav() for
+	 *                        description.
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		/**
+		 * Blog/post/comment menus should not appear on single WordPress setups.
+		 * Although comments and posts made by users will still show on their
+		 * activity stream.
+		 */
+		if ( ! is_multisite() ) {
+			return false;
+		}
+
+		// Determine user to use
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		$slug       = bp_get_blogs_slug();
+		$parent_url = trailingslashit( $user_domain . $slug );
+
+		// Add 'Sites' to the main navigation
+		$count    = (int) bp_get_total_blog_count_for_user();
+		$class    = ( 0 === $count ) ? 'no-count' : 'count';
+		$nav_text = sprintf( __( 'Sites <span class="%s">%s</span>', 'buddypress' ), esc_attr( $class ), bp_core_number_format( $count )  );
+		$main_nav = array(
+			'name'                => $nav_text,
+			'slug'                => $slug,
+			'position'            => 30,
+			'screen_function'     => 'bp_blogs_screen_my_blogs',
+			'default_subnav_slug' => 'my-sites',
+			'item_css_id'         => $this->id
+		);
+
+		$sub_nav[] = array(
+			'name'            => __( 'My Sites', 'buddypress' ),
+			'slug'            => 'my-sites',
+			'parent_url'      => $parent_url,
+			'parent_slug'     => $slug,
+			'screen_function' => 'bp_blogs_screen_my_blogs',
+			'position'        => 10
+		);
+
+		// Setup navigation
+		parent::setup_nav( $main_nav, $sub_nav );
+	}
+
+	/**
+	 * Set up bp-blogs integration with the WordPress admin bar.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_admin_bar() for a description of arguments.
+	 *
+	 * @param array $wp_admin_nav See BP_Component::setup_admin_bar()
+	 *                            for description.
+	 *
+	 * @return bool
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		/**
+		 * Site/post/comment menus should not appear on single WordPress setups.
+		 *
+		 * Comments and posts made by users will still show in their activity.
+		 */
+		if ( ! is_multisite() ) {
+			return false;
+		}
+
+		// Menus for logged in user
+		if ( is_user_logged_in() ) {
+
+			// Setup the logged in user variables
+			$blogs_link = trailingslashit( bp_loggedin_user_domain() . bp_get_blogs_slug() );
+
+			// Add the "Sites" sub menu
+			$wp_admin_nav[] = array(
+				'parent' => buddypress()->my_account_menu_id,
+				'id'     => 'my-account-' . $this->id,
+				'title'  => __( 'Sites', 'buddypress' ),
+				'href'   => $blogs_link
+			);
+
+			// My Sites
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-my-sites',
+				'title'  => __( 'My Sites', 'buddypress' ),
+				'href'   => $blogs_link
+			);
+
+			// Create a Site
+			if ( bp_blog_signup_enabled() ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-create',
+					'title'  => __( 'Create a Site', 'buddypress' ),
+					'href'   => trailingslashit( bp_get_blogs_directory_permalink() . 'create' )
+				);
+			}
+		}
+
+		parent::setup_admin_bar( $wp_admin_nav );
+	}
+
+	/**
+	 * Set up the title for pages and <title>
+	 */
+	public function setup_title() {
+
+		// Set up the component options navigation for Site
+		if ( bp_is_blogs_component() ) {
+			$bp = buddypress();
+
+			if ( bp_is_my_profile() ) {
+				if ( bp_is_active( 'xprofile' ) ) {
+					$bp->bp_options_title = __( 'My Sites', 'buddypress' );
+				}
+
+			// If we are not viewing the logged in user, set up the current
+			// users avatar and name
+			} else {
+				$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+					'item_id' => bp_displayed_user_id(),
+					'type'    => 'thumb',
+					'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_displayed_user_fullname() )
+				) );
+				$bp->bp_options_title = bp_get_displayed_user_fullname();
+			}
+		}
+
+		parent::setup_title();
+	}
+
+	/**
+	 * Setup cache groups
+	 *
+	 * @since 2.2.0
+	 */
+	public function setup_cache_groups() {
+
+		// Global groups
+		wp_cache_add_global_groups( array(
+			'blog_meta'
+		) );
+
+		parent::setup_cache_groups();
+	}
+
+	/**
+	 * Set up the tracking arguments for the 'post' post type.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @see bp_activity_get_post_type_tracking_args() for information on parameters.
+	 *
+	 * @param object|null $params
+	 * @param string|int  $post_type
+	 *
+	 * @return object
+	 */
+	public function post_tracking_args( $params = null, $post_type = 0 ) {
+
+		/**
+		 * Filters the post types to track for the Blogs component.
+		 *
+		 * @since 1.5.0
+		 * @deprecated 2.3.0
+		 *
+		 * Make sure plugins still using 'bp_blogs_record_post_post_types'
+		 * to track their post types will generate new_blog_post activities
+		 * See https://buddypress.trac.wordpress.org/ticket/6306
+		 *
+		 * @param array $value Array of post types to track.
+		 */
+		$post_types = apply_filters( 'bp_blogs_record_post_post_types', array( 'post' ) );
+		$post_types_array = array_flip( $post_types );
+
+		if ( ! isset( $post_types_array[ $post_type ] ) ) {
+			return $params;
+		}
+
+		// Set specific params for the 'post' post type.
+		$params->component_id    = $this->id;
+		$params->action_id       = 'new_blog_post';
+		$params->admin_filter    = __( 'New post published', 'buddypress' );
+		$params->format_callback = 'bp_blogs_format_activity_action_new_blog_post';
+		$params->front_filter    = __( 'Posts', 'buddypress' );
+		$params->contexts        = array( 'activity', 'member' );
+		$params->position        = 5;
+
+		return $params;
+	}
+}
+
+/**
+ * Set up the bp-blogs component.
+ */
+function bp_setup_blogs() {
+	buddypress()->blogs = new BP_Blogs_Component();
+}
+add_action( 'bp_setup_components', 'bp_setup_blogs', 6 );
Index: /tags/2.4.3/src/bp-blogs/bp-blogs-screens.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/bp-blogs-screens.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/bp-blogs-screens.php	(revision 10410)
@@ -0,0 +1,263 @@
+<?php
+/**
+ * BuddyPress Blogs Screens.
+ *
+ * @package BuddyPress
+ * @subpackage BlogsScreens
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Load the "My Blogs" screen.
+ */
+function bp_blogs_screen_my_blogs() {
+	if ( !is_multisite() )
+		return false;
+
+	/**
+	 * Fires right before the loading of the My Blogs screen template file.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'bp_blogs_screen_my_blogs' );
+
+	bp_core_load_template( apply_filters( 'bp_blogs_template_my_blogs', 'members/single/home' ) );
+}
+
+/**
+ * Load the "Create a Blog" screen.
+ */
+function bp_blogs_screen_create_a_blog() {
+
+	if ( !is_multisite() ||  !bp_is_blogs_component() || !bp_is_current_action( 'create' ) )
+		return false;
+
+	if ( !is_user_logged_in() || !bp_blog_signup_enabled() )
+		return false;
+
+	/**
+	 * Fires right before the loading of the Create A Blog screen template file.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'bp_blogs_screen_create_a_blog' );
+
+	bp_core_load_template( apply_filters( 'bp_blogs_template_create_a_blog', 'blogs/create' ) );
+}
+add_action( 'bp_screens', 'bp_blogs_screen_create_a_blog', 3 );
+
+/**
+ * Load the top-level Blogs directory.
+ */
+function bp_blogs_screen_index() {
+	if ( bp_is_blogs_directory() ) {
+		bp_update_is_directory( true, 'blogs' );
+
+		/**
+		 * Fires right before the loading of the top-level Blogs screen template file.
+		 *
+		 * @since 1.0.0
+		 */
+		do_action( 'bp_blogs_screen_index' );
+
+		bp_core_load_template( apply_filters( 'bp_blogs_screen_index', 'blogs/index' ) );
+	}
+}
+add_action( 'bp_screens', 'bp_blogs_screen_index', 2 );
+
+/** Theme Compatibility *******************************************************/
+
+/**
+ * The main theme compat class for BuddyPress Blogs.
+ *
+ * This class sets up the necessary theme compatibility actions to safely output
+ * group template parts to the_title and the_content areas of a theme.
+ *
+ * @since 1.7.0
+ */
+class BP_Blogs_Theme_Compat {
+
+	/**
+	 * Set up theme compatibility for the Blogs component.
+	 *
+	 * @since 1.7.0
+	 */
+	public function __construct() {
+		add_action( 'bp_setup_theme_compat', array( $this, 'is_blogs' ) );
+	}
+
+	/**
+	 * Are we looking at something that needs Blogs theme compatibility?
+	 *
+	 * @since 1.7.0
+	 */
+	public function is_blogs() {
+
+		// Bail if not looking at a group
+		if ( ! bp_is_blogs_component() )
+			return;
+
+		// Bail if looking at a users sites
+		if ( bp_is_user() )
+			return;
+
+		// Blog Directory
+		if ( is_multisite() && ! bp_current_action() ) {
+			bp_update_is_directory( true, 'blogs' );
+
+			/**
+			 * Fires if in the blog directory and BuddyPress needs Blog theme compatibility,
+			 * before the actions and filters are added.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'bp_blogs_screen_index' );
+
+			add_filter( 'bp_get_buddypress_template',                array( $this, 'directory_template_hierarchy' ) );
+			add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'directory_dummy_post' ) );
+			add_filter( 'bp_replace_the_content',                    array( $this, 'directory_content'    ) );
+
+		// Create blog
+		} elseif ( is_user_logged_in() && bp_blog_signup_enabled() ) {
+			add_filter( 'bp_get_buddypress_template',                array( $this, 'create_template_hierarchy' ) );
+			add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'create_dummy_post' ) );
+			add_filter( 'bp_replace_the_content',                    array( $this, 'create_content'    ) );
+		}
+	}
+
+	/** Directory *************************************************************/
+
+	/**
+	 * Add template hierarchy to theme compat for the blog directory page.
+	 *
+	 * This is to mirror how WordPress has
+	 * {@link https://codex.wordpress.org/Template_Hierarchy template hierarchy}.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $templates The templates from bp_get_theme_compat_templates().
+	 *
+	 * @return array $templates Array of custom templates to look for.
+	 */
+	public function directory_template_hierarchy( $templates ) {
+
+		/**
+		 * Filters the custom templates used for theme compat with the blog directory page.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param array $value Array of template paths to add to template list to look for.
+		 */
+		$new_templates = apply_filters( 'bp_template_hierarchy_blogs_create', array(
+			'blogs/index-directory.php'
+		) );
+
+		// Merge new templates with existing stack
+		// @see bp_get_theme_compat_templates()
+		$templates = array_merge( (array) $new_templates, $templates );
+
+		return $templates;
+	}
+
+	/**
+	 * Update the global $post with directory data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function directory_dummy_post() {
+
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => __( 'Sites', 'buddypress' ),
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with the groups index template part.
+	 *
+	 * @since 1.7.0
+	 */
+	public function directory_content() {
+		return bp_buffer_template_part( 'blogs/index', null, false );
+	}
+
+	/** Create ****************************************************************/
+
+	/**
+	 * Add custom template hierarchy to theme compat for the blog create page.
+	 *
+	 * This is to mirror how WordPress has
+	 * {@link https://codex.wordpress.org/Template_Hierarchy template hierarchy}.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $templates The templates from bp_get_theme_compat_templates().
+	 *
+	 * @return array $templates Array of custom templates to look for.
+	 */
+	public function create_template_hierarchy( $templates ) {
+
+		/**
+		 * Filters the custom templates used for theme compat with the blog create page.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param array $value Array of template paths to add to template list to look for.
+		 */
+		$new_templates = apply_filters( 'bp_template_hierarchy_blogs_create', array(
+			'blogs/index-create.php'
+		) );
+
+		// Merge new templates with existing stack
+		// @see bp_get_theme_compat_templates()
+		$templates = array_merge( (array) $new_templates, $templates );
+
+		return $templates;
+	}
+
+	/**
+	 * Update the global $post with create screen data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function create_dummy_post() {
+
+		// Title based on ability to create blogs
+		if ( is_user_logged_in() && bp_blog_signup_enabled() ) {
+			$title = __( 'Create a Site', 'buddypress' );
+		} else {
+			$title = __( 'Sites', 'buddypress' );
+		}
+
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => $title,
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with the create screen template part.
+	 *
+	 * @since 1.7.0
+	 */
+	public function create_content() {
+		return bp_buffer_template_part( 'blogs/create', null, false );
+	}
+}
+new BP_Blogs_Theme_Compat();
Index: /tags/2.4.3/src/bp-blogs/bp-blogs-template.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/bp-blogs-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/bp-blogs-template.php	(revision 10410)
@@ -0,0 +1,1688 @@
+<?php
+/**
+ * BuddyPress Blogs Template Tags.
+ *
+ * @package BuddyPress
+ * @subpackage BlogsTemplate
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the blogs component slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_blogs_slug()
+ */
+function bp_blogs_slug() {
+	echo bp_get_blogs_slug();
+}
+	/**
+	 * Return the blogs component slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string The 'blogs' slug.
+	 */
+	function bp_get_blogs_slug() {
+
+		/**
+		 * Filters the blogs component slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $slug Slug for the blogs component.
+		 */
+		return apply_filters( 'bp_get_blogs_slug', buddypress()->blogs->slug );
+	}
+
+/**
+ * Output the blogs component root slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_blogs_root_slug()
+ */
+function bp_blogs_root_slug() {
+	echo bp_get_blogs_root_slug();
+}
+	/**
+	 * Return the blogs component root slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string The 'blogs' root slug.
+	 */
+	function bp_get_blogs_root_slug() {
+
+		/**
+		 * Filters the blogs component root slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $root_slug Root slug for the blogs component.
+		 */
+		return apply_filters( 'bp_get_blogs_root_slug', buddypress()->blogs->root_slug );
+	}
+
+/**
+ * Output blog directory permalink.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_blogs_directory_permalink()
+ */
+function bp_blogs_directory_permalink() {
+	echo esc_url( bp_get_blogs_directory_permalink() );
+}
+	/**
+	 * Return blog directory permalink.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses apply_filters()
+	 * @uses trailingslashit()
+	 * @uses bp_get_root_domain()
+	 * @uses bp_get_blogs_root_slug()
+	 *
+	 * @return string The URL of the Blogs directory.
+	 */
+	function bp_get_blogs_directory_permalink() {
+
+		/**
+		 * Filters the blog directory permalink.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Permalink URL for the blog directory.
+		 */
+		return apply_filters( 'bp_get_blogs_directory_permalink', trailingslashit( bp_get_root_domain() . '/' . bp_get_blogs_root_slug() ) );
+	}
+
+/**
+ * The main blog template loop class.
+ *
+ * Responsible for loading a group of blogs into a loop for display.
+ */
+class BP_Blogs_Template {
+
+	/**
+	 * The loop iterator.
+	 *
+	 * @var int
+	 */
+	public $current_blog = -1;
+
+	/**
+	 * The number of blogs returned by the paged query.
+	 *
+	 * @var int
+	 */
+	public $blog_count = 0;
+
+	/**
+	 * Array of blogs located by the query..
+	 *
+	 * @var array
+	 */
+	public $blogs = array();
+
+	/**
+	 * The blog object currently being iterated on.
+	 *
+	 * @var object
+	 */
+	public $blog;
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @var bool
+	 */
+	public $in_the_loop = false;
+
+	/**
+	 * The page number being requested.
+	 *
+	 * @var int
+	 */
+	public $pag_page = 1;
+
+	/**
+	 * The number of items being requested per page.
+	 *
+	 * @var int
+	 */
+	public $pag_num = 20;
+
+	/**
+	 * An HTML string containing pagination links.
+	 *
+	 * @var string
+	 */
+	public $pag_links = '';
+
+	/**
+	 * The total number of blogs matching the query parameters.
+	 *
+	 * @var int
+	 */
+	public $total_blog_count = 0;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @see BP_Blogs_Blog::get() for a description of parameters.
+	 *
+	 * @param string     $type              See {@link BP_Blogs_Blog::get()}.
+	 * @param string     $page              See {@link BP_Blogs_Blog::get()}.
+	 * @param string     $per_page          See {@link BP_Blogs_Blog::get()}.
+	 * @param string     $max               See {@link BP_Blogs_Blog::get()}.
+	 * @param string     $user_id           See {@link BP_Blogs_Blog::get()}.
+	 * @param string     $search_terms      See {@link BP_Blogs_Blog::get()}.
+	 * @param string     $page_arg          The string used as a query parameter in
+	 *                                      pagination links. Default: 'bpage'.
+	 * @param bool       $update_meta_cache Whether to pre-fetch metadata for
+	 *                                      queried blogs.
+	 * @param array|bool $include_blog_ids  Array of blog IDs to include.
+	 */
+	public function __construct( $type, $page, $per_page, $max, $user_id, $search_terms, $page_arg = 'bpage', $update_meta_cache = true, $include_blog_ids = false ) {
+
+		$this->pag_arg  = sanitize_key( $page_arg );
+		$this->pag_page = bp_sanitize_pagination_arg( $this->pag_arg, $page     );
+		$this->pag_num  = bp_sanitize_pagination_arg( 'num',          $per_page );
+
+		// Backwards compatibility support for blogs by first letter
+		if ( ! empty( $_REQUEST['letter'] ) ) {
+			$this->blogs = BP_Blogs_Blog::get_by_letter( $_REQUEST['letter'], $this->pag_num, $this->pag_page );
+
+		// Typical blogs query
+		} else {
+			$this->blogs = bp_blogs_get_blogs( array(
+				'type'              => $type,
+				'per_page'          => $this->pag_num,
+				'page'              => $this->pag_page,
+				'user_id'           => $user_id,
+				'search_terms'      => $search_terms,
+				'update_meta_cache' => $update_meta_cache,
+				'include_blog_ids'  => $include_blog_ids,
+			) );
+		}
+
+		// Set the total blog count
+		if ( empty( $max ) || ( $max >= (int) $this->blogs['total'] ) ) {
+			$this->total_blog_count = (int) $this->blogs['total'];
+		} else {
+			$this->total_blog_count = (int) $max;
+		}
+
+		// Set the blogs array (to loop through later
+		$this->blogs = $this->blogs['blogs'];
+
+		// Get the current blog count to compare maximum against
+		$blog_count = count( $this->blogs );
+
+		// Set the current blog count
+		if ( empty( $max ) || ( $max >= (int) $blog_count ) ) {
+			$this->blog_count = (int) $blog_count;
+		} else {
+			$this->blog_count = (int) $max;
+		}
+
+		// Build pagination links based on total blogs and current page number
+		if ( ! empty( $this->total_blog_count ) && ! empty( $this->pag_num ) ) {
+			$this->pag_links = paginate_links( array(
+				'base'      => add_query_arg( $this->pag_arg, '%#%' ),
+				'format'    => '',
+				'total'     => ceil( (int) $this->total_blog_count / (int) $this->pag_num ),
+				'current'   => (int) $this->pag_page,
+				'prev_text' => _x( '&larr;', 'Blog pagination previous text', 'buddypress' ),
+				'next_text' => _x( '&rarr;', 'Blog pagination next text',     'buddypress' ),
+				'mid_size'  => 1,
+				'add_args'  => array(),
+			) );
+		}
+	}
+
+	/**
+	 * Whether there are blogs available in the loop.
+	 *
+	 * @see bp_has_blogs()
+	 *
+	 * @return bool True if there are items in the loop, otherwise false.
+	 */
+	public function has_blogs() {
+		return (bool) ! empty( $this->blog_count );
+	}
+
+	/**
+	 * Set up the next blog and iterate index.
+	 *
+	 * @return object The next blog to iterate over.
+	 */
+	public function next_blog() {
+		$this->current_blog++;
+		$this->blog = $this->blogs[ $this->current_blog ];
+
+		return $this->blog;
+	}
+
+	/**
+	 * Rewind the blogs and reset blog index.
+	 */
+	public function rewind_blogs() {
+		$this->current_blog = -1;
+		if ( $this->blog_count > 0 ) {
+			$this->blog = $this->blogs[0];
+		}
+	}
+
+	/**
+	 * Whether there are blogs left in the loop to iterate over.
+	 *
+	 * This method is used by {@link bp_blogs()} as part of the while loop
+	 * that controls iteration inside the blogs loop, eg:
+	 *     while ( bp_blogs() ) { ...
+	 *
+	 * @see bp_blogs()
+	 *
+	 * @return bool True if there are more blogs to show, otherwise false.
+	 */
+	public function blogs() {
+		if ( ( $this->current_blog + 1 ) < $this->blog_count ) {
+			return true;
+		} elseif ( ( $this->current_blog + 1 ) === $this->blog_count ) {
+
+			/**
+			 * Fires right before the rewinding of blogs listing after all are shown.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'blog_loop_end' );
+			// Do some cleaning up after the loop
+			$this->rewind_blogs();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	/**
+	 * Set up the current blog inside the loop.
+	 *
+	 * Used by {@link bp_the_blog()} to set up the current blog data while
+	 * looping, so that template tags used during that iteration make
+	 * reference to the current blog.
+	 *
+	 * @see bp_the_blog()
+	 */
+	public function the_blog() {
+
+		$this->in_the_loop = true;
+		$this->blog        = $this->next_blog();
+
+		// loop has just started
+		if ( 0 === $this->current_blog ) {
+
+			/**
+			 * Fires if on the first blog in the loop.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'blog_loop_start' );
+		}
+	}
+}
+
+/**
+ * Rewind the blogs and reset blog index.
+ */
+function bp_rewind_blogs() {
+	global $blogs_template;
+
+	$blogs_template->rewind_blogs();
+}
+
+/**
+ * Initialize the blogs loop.
+ *
+ * Based on the $args passed, bp_has_blogs() populates the $blogs_template
+ * global, enabling the use of BuddyPress templates and template functions to
+ * display a list of activity items.
+ *
+ * @global object $blogs_template {@link BP_Blogs_Template}
+ *
+ * @param array|string $args {
+ *     Arguments for limiting the contents of the blogs loop. Most arguments
+ *     are in the same format as {@link BP_Blogs_Blog::get()}. However, because
+ *     the format of the arguments accepted here differs in a number of ways,
+ *     and because bp_has_blogs() determines some default arguments in a
+ *     dynamic fashion, we list all accepted arguments here as well.
+ *
+ *     Arguments can be passed as an associative array, or as a URL query
+ *     string (eg, 'user_id=4&per_page=3').
+ *
+ *     @type int      $page             Which page of results to fetch. Using page=1 without
+ *                                      per_page will result in no pagination. Default: 1.
+ *     @type int|bool $per_page         Number of results per page. Default: 20.
+ *     @type string   $page_arg         The string used as a query parameter in
+ *                                      pagination links. Default: 'bpage'.
+ *     @type int|bool $max              Maximum number of results to return.
+ *                                      Default: false (unlimited).
+ *     @type string   $type             The order in which results should be fetched.
+ *                                      'active', 'alphabetical', 'newest', or 'random'.
+ *     @type array    $include_blog_ids Array of blog IDs to limit results to.
+ *     @type string   $sort             'ASC' or 'DESC'. Default: 'DESC'.
+ *     @type string   $search_terms     Limit results by a search term. Default: the value of `$_REQUEST['s']` or
+ *                                      `$_REQUEST['sites_search']`, if present.
+ *     @type int      $user_id          The ID of the user whose blogs should be retrieved.
+ *                                      When viewing a user profile page, 'user_id' defaults to the
+ *                                      ID of the displayed user. Otherwise the default is false.
+ * }
+ * @return bool Returns true when blogs are found, otherwise false.
+ */
+function bp_has_blogs( $args = '' ) {
+	global $blogs_template;
+
+	// Check for and use search terms.
+	$search_terms_default = false;
+	$search_query_arg = bp_core_get_component_search_query_arg( 'blogs' );
+	if ( ! empty( $_REQUEST[ $search_query_arg ] ) ) {
+		$search_terms_default = stripslashes( $_REQUEST[ $search_query_arg ] );
+	} elseif ( ! empty( $_REQUEST['s'] ) ) {
+		$search_terms_default = stripslashes( $_REQUEST['s'] );
+	}
+
+	// Parse arguments
+	$r = bp_parse_args( $args, array(
+		'type'              => 'active',
+		'page_arg'          => 'bpage',                // See https://buddypress.trac.wordpress.org/ticket/3679
+		'page'              => 1,
+		'per_page'          => 20,
+		'max'               => false,
+		'user_id'           => bp_displayed_user_id(), // Pass a user_id to limit to only blogs this user is a member of
+		'include_blog_ids'  => false,
+		'search_terms'      => $search_terms_default,
+		'update_meta_cache' => true
+	), 'has_blogs' );
+
+	// Set per_page to maximum if max is enforced
+	if ( ! empty( $r['max'] ) && ( (int) $r['per_page'] > (int) $r['max'] ) ) {
+		$r['per_page'] = (int) $r['max'];
+	}
+
+	// Get the blogs
+	$blogs_template = new BP_Blogs_Template( $r['type'], $r['page'], $r['per_page'], $r['max'], $r['user_id'], $r['search_terms'], $r['page_arg'], $r['update_meta_cache'], $r['include_blog_ids'] );
+
+	/**
+	 * Filters whether or not there are blogs to list.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool              $value          Whether or not there are blogs to list.
+	 * @param BP_Blogs_Template $blogs_template Current blogs template object.
+	 * @param array             $r              Parsed arguments used in blogs template query.
+	 */
+	return apply_filters( 'bp_has_blogs', $blogs_template->has_blogs(), $blogs_template, $r );
+}
+
+/**
+ * Determine if there are still blogs left in the loop.
+ *
+ * @global object $blogs_template {@link BP_Blogs_Template}
+ *
+ * @return bool Returns true when blogs are found.
+ */
+function bp_blogs() {
+	global $blogs_template;
+
+	return $blogs_template->blogs();
+}
+
+/**
+ * Get the current blog object in the loop.
+ *
+ * @global object $blogs_template {@link BP_Blogs_Template}
+ *
+ * @return object The current blog within the loop.
+ */
+function bp_the_blog() {
+	global $blogs_template;
+
+	return $blogs_template->the_blog();
+}
+
+/**
+ * Output the blogs pagination count.
+ *
+ * @global object $blogs_template {@link BP_Blogs_Template}
+ */
+function bp_blogs_pagination_count() {
+	global $blogs_template;
+
+	$start_num = intval( ( $blogs_template->pag_page - 1 ) * $blogs_template->pag_num ) + 1;
+	$from_num  = bp_core_number_format( $start_num );
+	$to_num    = bp_core_number_format( ( $start_num + ( $blogs_template->pag_num - 1 ) > $blogs_template->total_blog_count ) ? $blogs_template->total_blog_count : $start_num + ( $blogs_template->pag_num - 1 ) );
+	$total     = bp_core_number_format( $blogs_template->total_blog_count );
+
+	if ( 1 == $blogs_template->total_blog_count ) {
+		$message = __( 'Viewing 1 site', 'buddypress' );
+	} else {
+		$message = sprintf( _n( 'Viewing %1$s - %2$s of %3$s site', 'Viewing %1$s - %2$s of %3$s sites', $blogs_template->total_blog_count, 'buddypress' ), $from_num, $to_num, $total );
+	}
+
+	echo $message;
+}
+
+/**
+ * Output the blogs pagination links.
+ */
+function bp_blogs_pagination_links() {
+	echo bp_get_blogs_pagination_links();
+}
+	/**
+	 * Return the blogs pagination links.
+	 *
+	 * @global object $blogs_template {@link BP_Blogs_Template}
+	 *
+	 * @return string HTML pagination links.
+	 */
+	function bp_get_blogs_pagination_links() {
+		global $blogs_template;
+
+		/**
+		 * Filters the blogs pagination links.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $pag_links HTML pagination links.
+		 */
+		return apply_filters( 'bp_get_blogs_pagination_links', $blogs_template->pag_links );
+	}
+
+/**
+ * Output a blog's avatar.
+ *
+ * @see bp_get_blog_avatar() for description of arguments.
+ *
+ * @param array|string $args See {@link bp_get_blog_avatar()}.
+ */
+function bp_blog_avatar( $args = '' ) {
+	echo bp_get_blog_avatar( $args );
+}
+	/**
+	 * Get a blog's avatar.
+	 *
+	 * At the moment, blog avatars are simply the user avatars of the blog
+	 * admin. Filter 'bp_get_blog_avatar_' . $blog_id to customize.
+	 *
+	 * @since 2.4.0 Introduced `$title` argument.
+	 *
+	 * @see bp_core_fetch_avatar() For a description of arguments and
+	 *      return values.
+	 *
+	 * @param array|string $args  {
+	 *     Arguments are listed here with an explanation of their defaults.
+	 *     For more information about the arguments, see
+	 *     {@link bp_core_fetch_avatar()}.
+	 *     @type string   $alt     Default: 'Profile picture of site author [user name]'.
+	 *     @type string   $class   Default: 'avatar'.
+	 *     @type string   $title   Default: 'Profile picture of site author [user name]'.
+	 *     @type string   $type    Default: 'full'.
+	 *     @type int|bool $width   Default: false.
+	 *     @type int|bool $height  Default: false.
+	 *     @type bool     $id      Currently unused.
+	 *     @type bool     $no_grav Default: true.
+	 * }
+	 * @return string User avatar string.
+	 */
+	function bp_get_blog_avatar( $args = '' ) {
+		global $blogs_template;
+
+		// Bail if avatars are turned off
+		// @todo Should we maybe still filter this?
+		if ( ! buddypress()->avatar->show_avatars ) {
+			return false;
+		}
+
+		$author_displayname = bp_core_get_user_displayname( $blogs_template->blog->admin_user_id );
+
+		// Parse the arguments
+		$r = bp_parse_args( $args, array(
+			'type'    => 'full',
+			'width'   => false,
+			'height'  => false,
+			'class'   => 'avatar',
+			'title'   => sprintf( __( 'Profile picture of site author %s', 'buddypress' ), esc_attr( $author_displayname ) ),
+			'id'      => false,
+			'alt'     => sprintf( __( 'Profile picture of site author %s', 'buddypress' ), esc_attr( $author_displayname ) ),
+			'no_grav' => true,
+		) );
+
+		// Fetch the avatar
+		$avatar = bp_core_fetch_avatar( array(
+			'item_id'    => $blogs_template->blog->admin_user_id,
+			'title'      => $r['title'],
+			//'avatar_dir' => 'blog-avatars',
+			//'object'     => 'blog',
+			'type'       => $r['type'],
+			'alt'        => $r['alt'],
+			'css_id'     => $r['id'],
+			'class'      => $r['class'],
+			'width'      => $r['width'],
+			'height'     => $r['height']
+		) );
+
+		/***
+		 * In future BuddyPress versions you will be able to set the avatar for a blog.
+		 * Right now you can use a filter with the ID of the blog to change it if you wish.
+		 * By default it will return the avatar for the primary blog admin.
+		 *
+		 * This filter is deprecated as of BuddyPress 1.5 and may be removed in a future version.
+		 * Use the 'bp_get_blog_avatar' filter instead.
+		 */
+		$avatar = apply_filters( 'bp_get_blog_avatar_' . $blogs_template->blog->blog_id, $avatar );
+
+		/**
+		 * Filters a blog's avatar.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $avatar  Formatted HTML <img> element, or raw avatar
+		 *                        URL based on $html arg.
+		 * @param int    $blog_id ID of the blog whose avatar is being displayed.
+		 * @param array  $r       Array of arguments used when fetching avatar.
+		 */
+		return apply_filters( 'bp_get_blog_avatar', $avatar, $blogs_template->blog->blog_id, $r );
+	}
+
+function bp_blog_permalink() {
+	echo bp_get_blog_permalink();
+}
+	function bp_get_blog_permalink() {
+		global $blogs_template;
+
+		if ( empty( $blogs_template->blog->domain ) )
+			$permalink = bp_get_root_domain() . $blogs_template->blog->path;
+		else {
+			$protocol = 'http://';
+			if ( is_ssl() )
+				$protocol = 'https://';
+
+			$permalink = $protocol . $blogs_template->blog->domain . $blogs_template->blog->path;
+		}
+
+		/**
+		 * Filters the blog permalink.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $permalink Permalink URL for the blog.
+		 */
+		return apply_filters( 'bp_get_blog_permalink', $permalink );
+	}
+
+/**
+ * Output the name of the current blog in the loop.
+ */
+function bp_blog_name() {
+	echo bp_get_blog_name();
+}
+	/**
+	 * Return the name of the current blog in the loop.
+	 *
+	 * @return string The name of the current blog in the loop.
+	 */
+	function bp_get_blog_name() {
+		global $blogs_template;
+
+		/**
+		 * Filters the name of the current blog in the loop.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $name Name of the current blog in the loop.
+		 */
+		return apply_filters( 'bp_get_blog_name', $blogs_template->blog->name );
+	}
+
+/**
+ * Output the ID of the current blog in the loop.
+ *
+ * @since 1.7.0
+ */
+function bp_blog_id() {
+	echo bp_get_blog_id();
+}
+	/**
+	 * Return the ID of the current blog in the loop.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @return int ID of the current blog in the loop.
+	 */
+	function bp_get_blog_id() {
+		global $blogs_template;
+
+		/**
+		 * Filters the ID of the current blog in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param int $blog_id ID of the current blog in the loop.
+		 */
+		return apply_filters( 'bp_get_blog_id', $blogs_template->blog->blog_id );
+	}
+
+/**
+ * Output the description of the current blog in the loop.
+ */
+function bp_blog_description() {
+
+	/**
+	 * Filters the description of the current blog in the loop.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value Description of the current blog in the loop.
+	 */
+	echo apply_filters( 'bp_blog_description', bp_get_blog_description() );
+}
+	/**
+	 * Return the description of the current blog in the loop.
+	 *
+	 * @return string Description of the current blog in the loop.
+	 */
+	function bp_get_blog_description() {
+		global $blogs_template;
+
+		/**
+		 * Filters the description of the current blog in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Description of the current blog in the loop.
+		 */
+		return apply_filters( 'bp_get_blog_description', $blogs_template->blog->description );
+	}
+
+/**
+ * Output the row class of the current blog in the loop.
+ *
+ * @since 1.7.0
+ *
+ * @param array $classes Array of custom classes
+ */
+function bp_blog_class( $classes = array() ) {
+	echo bp_get_blog_class( $classes );
+}
+	/**
+	 * Return the row class of the current blog in the loop.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @global BP_Blogs_Template $blogs_template
+	 * @param array $classes Array of custom classes
+	 *
+	 * @return string Row class of the site.
+	 */
+	function bp_get_blog_class( $classes = array() ) {
+		global $blogs_template;
+
+		// Add even/odd classes, but only if there's more than 1 group
+		if ( $blogs_template->blog_count > 1 ) {
+			$pos_in_loop = (int) $blogs_template->current_blog;
+			$classes[]   = ( $pos_in_loop % 2 ) ? 'even' : 'odd';
+
+		// If we've only one site in the loop, don't bother with odd and even
+		} else {
+			$classes[] = 'bp-single-blog';
+		}
+
+		/**
+		 * Filters the row class of the current blog in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param array $classes Array of classes to be applied to row.
+		 */
+		$classes = apply_filters( 'bp_get_blog_class', $classes );
+		$classes = array_merge( $classes, array() );
+		$retval  = 'class="' . join( ' ', $classes ) . '"';
+
+		return $retval;
+	}
+
+/**
+ * Output the last active date of the current blog in the loop.
+ *
+ * @param array $args See {@link bp_get_blog_last_active()}.
+ */
+function bp_blog_last_active( $args = array() ) {
+	echo bp_get_blog_last_active( $args );
+}
+	/**
+	 * Return the last active date of the current blog in the loop.
+	 *
+	 * @param array $args {
+	 *     Array of optional arguments.
+	 *     @type bool $active_format If true, formatted "Active 5 minutes ago".
+	 *                               If false, formatted "5 minutes ago".
+	 *                               Default: true.
+	 * }
+	 * @return string Last active date.
+	 */
+	function bp_get_blog_last_active( $args = array() ) {
+		global $blogs_template;
+
+		// Parse the activity format
+		$r = bp_parse_args( $args, array(
+			'active_format' => true
+		) );
+
+		// Backwards compatibility for anyone forcing a 'true' active_format
+		if ( true === $r['active_format'] ) {
+			$r['active_format'] = __( 'active %s', 'buddypress' );
+		}
+
+		// Blog has been posted to at least once
+		if ( isset( $blogs_template->blog->last_activity ) ) {
+
+			// Backwards compatibility for pre 1.5 'ago' strings
+			$last_activity = ! empty( $r['active_format'] )
+				? bp_core_get_last_activity( $blogs_template->blog->last_activity, $r['active_format'] )
+				: bp_core_time_since( $blogs_template->blog->last_activity );
+
+		// Blog has never been posted to
+		} else {
+			$last_activity = __( 'Never active', 'buddypress' );
+		}
+
+		/**
+		 * Filters the last active date of the current blog in the loop.
+		 *
+		 * @since
+		 *
+		 * @param string $last_activity Last active date.
+		 * @param array  $r             Array of parsed args used to determine formatting.
+		 */
+		return apply_filters( 'bp_blog_last_active', $last_activity, $r );
+	}
+
+/**
+ * Output the latest post from the current blog in the loop.
+ *
+ * @param array $args See {@link bp_get_blog_latest_post()}.
+ */
+function bp_blog_latest_post( $args = array() ) {
+	echo bp_get_blog_latest_post( $args );
+}
+	/**
+	 * Return the latest post from the current blog in the loop.
+	 *
+	 * @param array $args {
+	 *     Array of optional arguments.
+	 *     @type bool $latest_format If true, formatted "Latest post: [link to post]".
+	 *                               If false, formatted "[link to post]".
+	 *                               Default: true.
+	 * }
+	 * @return string $retval String of the form 'Latest Post: [link to post]'.
+	 */
+	function bp_get_blog_latest_post( $args = array() ) {
+		global $blogs_template;
+
+		$r = wp_parse_args( $args, array(
+			'latest_format' => true,
+		) );
+
+		$retval = bp_get_blog_latest_post_title();
+
+		if ( ! empty( $retval ) ) {
+			if ( ! empty( $r['latest_format'] ) ) {
+
+				/**
+				 * Filters the title text of the latest post for the current blog in loop.
+				 *
+				 * @since 1.0.0
+				 *
+				 * @param string $retval Title of the latest post.
+				 */
+				$retval = sprintf( __( 'Latest Post: %s', 'buddypress' ), '<a href="' . $blogs_template->blog->latest_post->guid . '">' . apply_filters( 'the_title', $retval ) . '</a>' );
+			} else {
+
+				/** This filter is documented in bp-blogs/bp-blogs-template.php */
+				$retval = '<a href="' . $blogs_template->blog->latest_post->guid . '">' . apply_filters( 'the_title', $retval ) . '</a>';
+			}
+		}
+
+		/**
+		 * Filters the HTML markup result for the latest blog post in loop.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $retval HTML markup for the latest post.
+		 */
+		return apply_filters( 'bp_get_blog_latest_post', $retval );
+	}
+
+/**
+ * Output the title of the latest post on the current blog in the loop.
+ *
+ * @since 1.7.0
+ *
+ * @see bp_get_blog_latest_post_title()
+ */
+function bp_blog_latest_post_title() {
+	echo bp_get_blog_latest_post_title();
+}
+	/**
+	 * Return the title of the latest post on the current blog in the loop.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @global BP_Blogs_Template
+	 *
+	 * @return string Post title.
+	 */
+	function bp_get_blog_latest_post_title() {
+		global $blogs_template;
+
+		$retval = '';
+
+		if ( ! empty( $blogs_template->blog->latest_post ) && ! empty( $blogs_template->blog->latest_post->post_title ) )
+			$retval = $blogs_template->blog->latest_post->post_title;
+
+		/**
+		 * Filters the title text of the latest post on the current blog in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $retval Title text for the latest post.
+		 */
+		return apply_filters( 'bp_get_blog_latest_post_title', $retval );
+	}
+
+/**
+ * Output the permalink of the latest post on the current blog in the loop.
+ *
+ * @since 1.7.0
+ *
+ * @see bp_get_blog_latest_post_title()
+ */
+function bp_blog_latest_post_permalink() {
+	echo esc_url( bp_get_blog_latest_post_permalink() );
+}
+	/**
+	 * Return the permalink of the latest post on the current blog in the loop.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @global BP_Blogs_Template
+	 *
+	 * @return string URL of the blog's latest post.
+	 */
+	function bp_get_blog_latest_post_permalink() {
+		global $blogs_template;
+
+		$retval = '';
+
+		if ( ! empty( $blogs_template->blog->latest_post ) && ! empty( $blogs_template->blog->latest_post->ID ) )
+			$retval = add_query_arg( 'p', $blogs_template->blog->latest_post->ID, bp_get_blog_permalink() );
+
+		/**
+		 * Filters the permalink of the latest post on the current blog in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $retval Permalink URL of the latest post.
+		 */
+		return apply_filters( 'bp_get_blog_latest_post_permalink', $retval );
+	}
+
+/**
+ * Output the content of the latest post on the current blog in the loop.
+ *
+ * @since 1.7.0
+ *
+ * @uses bp_get_blog_latest_post_content()
+ */
+function bp_blog_latest_post_content() {
+	echo bp_get_blog_latest_post_content();
+}
+	/**
+	 * Return the content of the latest post on the current blog in the loop.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @global BP_Blogs_Template
+	 *
+	 * @return string Content of the blog's latest post.
+	 */
+	function bp_get_blog_latest_post_content() {
+		global $blogs_template;
+
+		$retval = '';
+
+		if ( ! empty( $blogs_template->blog->latest_post ) && ! empty( $blogs_template->blog->latest_post->post_content ) )
+			$retval = $blogs_template->blog->latest_post->post_content;
+
+		/**
+		 * Filters the content of the latest post on the current blog in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $retval Content of the latest post on the current blog in the loop.
+		 */
+		return apply_filters( 'bp_get_blog_latest_post_content', $retval );
+	}
+
+/**
+ * Output the featured image of the latest post on the current blog in the loop.
+ *
+ * @since 1.7.0
+ *
+ * @see bp_get_blog_latest_post_content() For description of parameters.
+ *
+ * @param string $size See {@link bp_get_blog_latest_post_featured_image()}.
+ */
+function bp_blog_latest_post_featured_image( $size = 'thumbnail' ) {
+	echo bp_get_blog_latest_post_featured_image( $size );
+}
+	/**
+	 * Return the featured image of the latest post on the current blog in the loop.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @global BP_Blogs_Template
+	 *
+	 * @param string $size Image version to return. 'thumbnail', 'medium',
+	 *                     'large', or 'post-thumbnail'. Default: 'thumbnail'.
+	 *
+	 * @return string URL of the image.
+	 */
+	function bp_get_blog_latest_post_featured_image( $size = 'thumbnail' ) {
+		global $blogs_template;
+
+		$retval = '';
+
+		if ( ! empty( $blogs_template->blog->latest_post ) && ! empty( $blogs_template->blog->latest_post->images[$size] ) )
+			$retval = $blogs_template->blog->latest_post->images[$size];
+
+		/**
+		 * Filters the featured image of the latest post on the current blog in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $retval The featured image of the latest post on the current blog in the loop.
+		 */
+		return apply_filters( 'bp_get_blog_latest_post_featured_image', $retval );
+	}
+
+/**
+ * Does the latest blog post have a featured image?
+ *
+ * @since 1.7.0
+ *
+ * @param string $thumbnail Image version to return. 'thumbnail', 'medium', 'large',
+ *                          or 'post-thumbnail'. Default: 'thumbnail'.
+ *
+ * @return bool True if the latest blog post from the current blog has a
+ *              featured image of the given size.
+ */
+function bp_blog_latest_post_has_featured_image( $thumbnail = 'thumbnail' ) {
+	$image  = bp_get_blog_latest_post_featured_image( $thumbnail );
+
+	/**
+	 * Filters whether or not the latest blog post has a featured image.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param bool   $value     Whether or not the latest blog post has a featured image.
+	 * @param string $thumbnail Image version to return.
+	 * @param string $image     Returned value from bp_get_blog_latest_post_featured_image.
+	 */
+	return apply_filters( 'bp_blog_latest_post_has_featured_image', ! empty( $image ), $thumbnail, $image );
+}
+
+/**
+ * Output hidden fields to help with form submissions in Sites directory.
+ *
+ * This function detects whether 's', 'letter', or 'blogs_search' requests are
+ * currently being made (as in a URL parameter), and creates corresponding
+ * hidden fields.
+ */
+function bp_blog_hidden_fields() {
+	if ( isset( $_REQUEST['s'] ) )
+		echo '<input type="hidden" id="search_terms" value="' . esc_attr( $_REQUEST['s'] ). '" name="search_terms" />';
+
+	if ( isset( $_REQUEST['letter'] ) )
+		echo '<input type="hidden" id="selected_letter" value="' . esc_attr( $_REQUEST['letter'] ) . '" name="selected_letter" />';
+
+	if ( isset( $_REQUEST['blogs_search'] ) )
+		echo '<input type="hidden" id="search_terms" value="' . esc_attr( $_REQUEST['blogs_search'] ) . '" name="search_terms" />';
+}
+
+/**
+ * Output the total number of blogs on the site.
+ */
+function bp_total_blog_count() {
+	echo bp_get_total_blog_count();
+}
+	/**
+	 * Return the total number of blogs on the site.
+	 *
+	 * @return int Total number of blogs.
+	 */
+	function bp_get_total_blog_count() {
+
+		/**
+		 * Filters the total number of blogs on the site.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $value Total number of blogs on the site.
+		 */
+		return apply_filters( 'bp_get_total_blog_count', bp_blogs_total_blogs() );
+	}
+	add_filter( 'bp_get_total_blog_count', 'bp_core_number_format' );
+
+/**
+ * Output the total number of blogs for a given user.
+ *
+ * @param int $user_id ID of the user.
+ */
+function bp_total_blog_count_for_user( $user_id = 0 ) {
+	echo bp_get_total_blog_count_for_user( $user_id );
+}
+	/**
+	 * Return the total number of blogs for a given user.
+	 *
+	 * @param int $user_id ID of the user.
+	 *
+	 * @return int Total number of blogs for the user.
+	 */
+	function bp_get_total_blog_count_for_user( $user_id = 0 ) {
+
+		/**
+		 * Filters the total number of blogs for a given user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $value Total number of blogs for a given user.
+		 */
+		return apply_filters( 'bp_get_total_blog_count_for_user', bp_blogs_total_blogs_for_user( $user_id ) );
+	}
+	add_filter( 'bp_get_total_blog_count_for_user', 'bp_core_number_format' );
+
+
+/** Blog Registration ********************************************************/
+
+/**
+ * Checks whether blog creation is enabled.
+ *
+ * Returns true when blog creation is enabled for logged-in users only, or
+ * when it's enabled for new registrations.
+ *
+ * @return bool True if blog registration is enabled.
+ */
+function bp_blog_signup_enabled() {
+	$bp = buddypress();
+
+	$active_signup = isset( $bp->site_options['registration'] )
+		? $bp->site_options['registration']
+		: 'all';
+
+	/**
+	 * Filters whether or not blog creation is enabled.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $active_signup Value of the registration site option creation status.
+	 */
+	$active_signup = apply_filters( 'wpmu_active_signup', $active_signup ); // return "all", "none", "blog" or "user"
+
+	if ( 'none' == $active_signup || 'user' == $active_signup )
+		return false;
+
+	return true;
+}
+
+/**
+ * Output the wrapper markup for the blog signup form.
+ *
+ * @param string          $blogname   Optional. The default blog name (path or domain).
+ * @param string          $blog_title Optional. The default blog title.
+ * @param string|WP_Error $errors     Optional. The WP_Error object returned by a previous
+ *                                    submission attempt.
+ */
+function bp_show_blog_signup_form($blogname = '', $blog_title = '', $errors = '') {
+	global $current_user;
+
+	if ( isset($_POST['submit']) ) {
+		bp_blogs_validate_blog_signup();
+	} else {
+		if ( ! is_wp_error($errors) ) {
+			$errors = new WP_Error();
+		}
+
+		/**
+		 * Filters the default values for Blog name, title, and any current errors.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param array $value {
+		 *      string   $blogname   Default blog name provided.
+		 *      string   $blog_title Default blog title provided.
+		 *      WP_Error $errors     WP_Error object.
+		 * }
+		 */
+		$filtered_results = apply_filters('signup_another_blog_init', array('blogname' => $blogname, 'blog_title' => $blog_title, 'errors' => $errors ));
+		$blogname = $filtered_results['blogname'];
+		$blog_title = $filtered_results['blog_title'];
+		$errors = $filtered_results['errors'];
+
+		if ( $errors->get_error_code() ) {
+			echo "<p>" . __('There was a problem; please correct the form below and try again.', 'buddypress') . "</p>";
+		}
+		?>
+		<p><?php printf(__("By filling out the form below, you can <strong>add a site to your account</strong>. There is no limit to the number of sites that you can have, so create to your heart's content, but blog responsibly!", 'buddypress'), $current_user->display_name) ?></p>
+
+		<p><?php _e("If you&#8217;re not going to use a great domain, leave it for a new user. Now have at it!", 'buddypress') ?></p>
+
+		<form class="standard-form" id="setupform" method="post" action="">
+
+			<input type="hidden" name="stage" value="gimmeanotherblog" />
+			<?php
+
+			/**
+			 * Fires after the default hidden fields in blog signup form markup.
+			 *
+			 * @since 1.0.0
+			 */
+			do_action( 'signup_hidden_fields' ); ?>
+
+			<?php bp_blogs_signup_blog($blogname, $blog_title, $errors); ?>
+			<p>
+				<input id="submit" type="submit" name="submit" class="submit" value="<?php esc_attr_e('Create Site', 'buddypress') ?>" />
+			</p>
+
+			<?php wp_nonce_field( 'bp_blog_signup_form' ) ?>
+		</form>
+		<?php
+	}
+}
+
+/**
+ * Output the input fields for the blog creation form.
+ *
+ * @param string          $blogname   Optional. The default blog name (path or domain).
+ * @param string          $blog_title Optional. The default blog title.
+ * @param string|WP_Error $errors     Optional. The WP_Error object returned by a previous
+ *                                    submission attempt.
+ */
+function bp_blogs_signup_blog( $blogname = '', $blog_title = '', $errors = '' ) {
+	global $current_site;
+
+	// Blog name
+	if( !is_subdomain_install() )
+		echo '<label for="blogname">' . __('Site Name:', 'buddypress') . '</label>';
+	else
+		echo '<label for="blogname">' . __('Site Domain:', 'buddypress') . '</label>';
+
+	if ( $errmsg = $errors->get_error_message('blogname') ) { ?>
+
+		<p class="error"><?php echo $errmsg ?></p>
+
+	<?php }
+
+	if ( !is_subdomain_install() )
+		echo '<span class="prefix_address">' . $current_site->domain . $current_site->path . '</span> <input name="blogname" type="text" id="blogname" value="'.$blogname.'" maxlength="63" /><br />';
+	else
+		echo '<input name="blogname" type="text" id="blogname" value="'.$blogname.'" maxlength="63" ' . bp_get_form_field_attributes( 'blogname' ) . '/> <span class="suffix_address">.' . bp_blogs_get_subdomain_base() . '</span><br />';
+
+	if ( !is_user_logged_in() ) {
+		print '(<strong>' . __( 'Your address will be ' , 'buddypress');
+
+		if ( !is_subdomain_install() ) {
+			print $current_site->domain . $current_site->path . __( 'blogname' , 'buddypress');
+		} else {
+			print __( 'domain.' , 'buddypress') . $current_site->domain . $current_site->path;
+		}
+
+		echo '.</strong> ' . __( 'Must be at least 4 characters, letters and numbers only. It cannot be changed so choose carefully!)' , 'buddypress') . '</p>';
+	}
+
+	// Blog Title
+	?>
+
+	<label for="blog_title"><?php _e('Site Title:', 'buddypress') ?></label>
+
+	<?php if ( $errmsg = $errors->get_error_message('blog_title') ) { ?>
+
+		<p class="error"><?php echo $errmsg ?></p>
+
+	<?php }
+	echo '<input name="blog_title" type="text" id="blog_title" value="'.esc_html($blog_title, 1).'" /></p>';
+	?>
+
+	<p>
+		<label for="blog_public_on"><?php _e('Privacy:', 'buddypress') ?></label>
+		<?php _e( 'I would like my site to appear in search engines, and in public listings around this network.', 'buddypress' ); ?>
+
+		<label class="checkbox" for="blog_public_on">
+			<input type="radio" id="blog_public_on" name="blog_public" value="1" <?php if( !isset( $_POST['blog_public'] ) || '1' == $_POST['blog_public'] ) { ?>checked="checked"<?php } ?> />
+			<strong><?php _e( 'Yes' , 'buddypress'); ?></strong>
+		</label>
+		<label class="checkbox" for="blog_public_off">
+			<input type="radio" id="blog_public_off" name="blog_public" value="0" <?php if( isset( $_POST['blog_public'] ) && '0' == $_POST['blog_public'] ) { ?>checked="checked"<?php } ?> />
+			<strong><?php _e( 'No' , 'buddypress'); ?></strong>
+		</label>
+	</p>
+
+	<?php
+
+	/**
+	 * Fires at the end of all of the default input fields for blog creation form.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param WP_Error $errors WP_Error object if any present.
+	 */
+	do_action('signup_blogform', $errors);
+}
+
+/**
+ * Process a blog registration submission.
+ *
+ * Passes submitted values to {@link wpmu_create_blog()}.
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_blogs_validate_blog_signup() {
+	global $wpdb, $current_user, $blogname, $blog_title, $errors, $domain, $path, $current_site;
+
+	if ( !check_admin_referer( 'bp_blog_signup_form' ) )
+		return false;
+
+	$current_user = wp_get_current_user();
+
+	if( !is_user_logged_in() )
+		die();
+
+	$result = bp_blogs_validate_blog_form();
+	extract($result);
+
+	if ( $errors->get_error_code() ) {
+		unset($_POST['submit']);
+		bp_show_blog_signup_form( $blogname, $blog_title, $errors );
+		return false;
+	}
+
+	$public = (int) $_POST['blog_public'];
+
+	$meta = apply_filters( 'signup_create_blog_meta', array( 'lang_id' => 1, 'public' => $public ) ); // depreciated
+
+	/**
+	 * Filters the default values for Blog meta.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param array $meta {
+	 *      string $value  Default blog language ID.
+	 *      string $public Default public status.
+	 * }
+	 */
+	$meta = apply_filters( 'add_signup_meta', $meta );
+
+	// If this is a subdomain install, set up the site inside the root domain.
+	if ( is_subdomain_install() )
+		$domain = $blogname . '.' . preg_replace( '|^www\.|', '', $current_site->domain );
+
+	wpmu_create_blog( $domain, $path, $blog_title, $current_user->ID, $meta, $wpdb->siteid );
+	bp_blogs_confirm_blog_signup($domain, $path, $blog_title, $current_user->user_login, $current_user->user_email, $meta);
+	return true;
+}
+
+/**
+ * Validate a blog creation submission.
+ *
+ * Essentially, a wrapper for {@link wpmu_validate_blog_signup()}.
+ *
+ * @return array Contains the new site data and error messages.
+ */
+function bp_blogs_validate_blog_form() {
+	$user = '';
+	if ( is_user_logged_in() )
+		$user = wp_get_current_user();
+
+	return wpmu_validate_blog_signup($_POST['blogname'], $_POST['blog_title'], $user);
+}
+
+/**
+ * Display a message after successful blog registration.
+ *
+ * @param string       $domain     The new blog's domain.
+ * @param string       $path       The new blog's path.
+ * @param string       $blog_title The new blog's title.
+ * @param string       $user_name  The user name of the user who created the blog. Unused.
+ * @param string       $user_email The email of the user who created the blog. Unused.
+ * @param string|array $meta       Meta values associated with the new blog. Unused.
+ */
+function bp_blogs_confirm_blog_signup( $domain, $path, $blog_title, $user_name, $user_email = '', $meta = '' ) {
+	$protocol = is_ssl() ? 'https://' : 'http://';
+	$blog_url = $protocol . $domain . $path; ?>
+
+	<p><?php _e( 'Congratulations! You have successfully registered a new site.', 'buddypress' ) ?></p>
+	<p>
+		<?php printf(__( '<a href="%1$s">%2$s</a> is your new site.  <a href="%3$s">Login</a> as "%4$s" using your existing password.', 'buddypress' ), $blog_url, $blog_url, $blog_url . "wp-login.php", $user_name ); ?>
+	</p>
+
+<?php
+
+	/**
+	 * Fires after the default successful blog registration message markup.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action('signup_finished');
+}
+
+/**
+ * Output a "Create a Site" link for users viewing their own profiles.
+ *
+ * This function is not used by BuddyPress as of 1.2, but is kept here for older
+ * themes that may still be using it.
+ */
+function bp_create_blog_link() {
+
+	// Don't show this link when not on your own profile
+	if ( ! bp_is_my_profile() ) {
+		return;
+	}
+
+	/**
+	 * Filters "Create a Site" links for users viewing their own profiles.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value HTML link for creating a site.
+	 */
+	echo apply_filters( 'bp_create_blog_link', '<a href="' . trailingslashit( bp_get_blogs_directory_permalink() . 'create' ) . '">' . __( 'Create a Site', 'buddypress' ) . '</a>' );
+}
+
+/**
+ * Output navigation tabs for a user Blogs page.
+ *
+ * Currently unused by BuddyPress.
+ */
+function bp_blogs_blog_tabs() {
+
+	// Don't show these tabs on a user's own profile
+	if ( bp_is_my_profile() ) {
+		return false;
+	} ?>
+
+	<ul class="content-header-nav">
+		<li<?php if ( bp_is_current_action( 'my-blogs'        ) || !bp_current_action() ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( bp_displayed_user_domain() . bp_get_blogs_slug() . '/my-blogs'        ); ?>"><?php printf( __( "%s's Sites", 'buddypress' ),           bp_get_displayed_user_fullname() ); ?></a></li>
+		<li<?php if ( bp_is_current_action( 'recent-posts'    )                         ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( bp_displayed_user_domain() . bp_get_blogs_slug() . '/recent-posts'    ); ?>"><?php printf( __( "%s's Recent Posts", 'buddypress' ),    bp_get_displayed_user_fullname() ); ?></a></li>
+		<li<?php if ( bp_is_current_action( 'recent-comments' )                         ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( bp_displayed_user_domain() . bp_get_blogs_slug() . '/recent-comments' ); ?>"><?php printf( __( "%s's Recent Comments", 'buddypress' ), bp_get_displayed_user_fullname() ); ?></a></li>
+	</ul>
+
+<?php
+
+	/**
+	 * Fires after the markup for the navigation tabs for a user Blogs page.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'bp_blogs_blog_tabs' );
+}
+
+/**
+ * Output the blog directory search form.
+ */
+function bp_directory_blogs_search_form() {
+
+	$query_arg = bp_core_get_component_search_query_arg( 'blogs' );
+
+	if ( ! empty( $_REQUEST[ $query_arg ] ) ) {
+		$search_value = stripslashes( $_REQUEST[ $query_arg ] );
+	} else {
+		$search_value = bp_get_search_default_text( 'blogs' );
+	}
+
+	$search_form_html = '<form action="" method="get" id="search-blogs-form">
+		<label for="blogs_search"><input type="text" name="' . esc_attr( $query_arg ) . '" id="blogs_search" placeholder="'. esc_attr( $search_value ) .'" /></label>
+		<input type="submit" id="blogs_search_submit" name="blogs_search_submit" value="' . __( 'Search', 'buddypress' ) . '" />
+	</form>';
+
+	/**
+	 * Filters the output for the blog directory search form.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $search_form_html HTML markup for blog directory search form.
+	 */
+	echo apply_filters( 'bp_directory_blogs_search_form', $search_form_html );
+}
+
+/**
+ * Output the Create a Site button.
+ *
+ * @since 2.0.0
+ */
+function bp_blog_create_button() {
+	echo bp_get_blog_create_button();
+}
+	/**
+	 * Get the Create a Site button.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @return string
+	 */
+	function bp_get_blog_create_button() {
+		if ( ! is_user_logged_in() ) {
+			return false;
+		}
+
+		if ( ! bp_blog_signup_enabled() ) {
+			return false;
+		}
+
+		$button_args = array(
+			'id'         => 'create_blog',
+			'component'  => 'blogs',
+			'link_text'  => __( 'Create a Site', 'buddypress' ),
+			'link_title' => __( 'Create a Site', 'buddypress' ),
+			'link_class' => 'blog-create no-ajax',
+			'link_href'  => trailingslashit( bp_get_blogs_directory_permalink() . 'create' ),
+			'wrapper'    => false,
+			'block_self' => false,
+		);
+
+		/**
+		 * Filters the Create a Site button.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $button_args Array of arguments to be used for the Create a Site button.
+		 */
+		return bp_get_button( apply_filters( 'bp_get_blog_create_button', $button_args ) );
+	}
+
+/**
+ * Output the Create a Site nav item.
+ *
+ * @since 2.2.0
+ */
+function bp_blog_create_nav_item() {
+	echo bp_get_blog_create_nav_item();
+}
+
+	/**
+	 * Get the Create a Site nav item.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @return string
+	 */
+	function bp_get_blog_create_nav_item() {
+		// Get the create a site button
+		$create_blog_button = bp_get_blog_create_button();
+
+		// Make sure the button is available
+		if ( empty( $create_blog_button ) ) {
+			return;
+		}
+
+		$output = '<li id="blog-create-nav">' . $create_blog_button . '</li>';
+
+		return apply_filters( 'bp_get_blog_create_nav_item', $output );
+	}
+
+/**
+ * Checks if a specific theme is still filtering the Blogs directory title
+ * if so, transform the title button into a Blogs directory nav item.
+ *
+ * @since 2.2.0
+ *
+ * @uses bp_blog_create_nav_item() to output the Create a Site nav item.
+ *
+ * @return string HTML Output
+ */
+function bp_blog_backcompat_create_nav_item() {
+	// Bail if Blogs nav item is already used by bp-legacy
+	if ( has_action( 'bp_blogs_directory_blog_types', 'bp_legacy_theme_blog_create_nav', 999 ) ) {
+		return;
+	}
+
+	// Bail if the theme is not filtering the Blogs directory title
+	if ( ! has_filter( 'bp_blogs_directory_header' ) ) {
+		return;
+	}
+
+	bp_blog_create_nav_item();
+}
+add_action( 'bp_blogs_directory_blog_types', 'bp_blog_backcompat_create_nav_item', 1000 );
+
+/**
+ * Output button for visiting a blog in a loop.
+ *
+ * @see bp_get_blogs_visit_blog_button() for description of arguments.
+ *
+ * @param array|string $args See {@link bp_get_blogs_visit_blog_button()}.
+ */
+function bp_blogs_visit_blog_button( $args = '' ) {
+	echo bp_get_blogs_visit_blog_button( $args );
+}
+	/**
+	 * Return button for visiting a blog in a loop.
+	 *
+	 * @see BP_Button for a complete description of arguments and return
+	 *      value.
+	 *
+	 * @param array|string $args {
+	 *     Arguments are listed below, with their default values. For a
+	 *     complete description of arguments, see {@link BP_Button}.
+	 *     @type string $id                Default: 'visit_blog'.
+	 *     @type string $component         Default: 'blogs'.
+	 *     @type bool   $must_be_logged_in Default: false.
+	 *     @type bool   $block_self        Default: false.
+	 *     @type string $wrapper_class     Default: 'blog-button visit'.
+	 *     @type string $link_href         Permalink of the current blog in the loop.
+	 *     @type string $link_class        Default: 'blog-button visit'.
+	 *     @type string $link_text         Default: 'Visit Site'.
+	 *     @type string $link_title        Default: 'Visit Site'.
+	 * }
+	 * @return string The HTML for the Visit button.
+	 */
+	function bp_get_blogs_visit_blog_button( $args = '' ) {
+		$defaults = array(
+			'id'                => 'visit_blog',
+			'component'         => 'blogs',
+			'must_be_logged_in' => false,
+			'block_self'        => false,
+			'wrapper_class'     => 'blog-button visit',
+			'link_href'         => bp_get_blog_permalink(),
+			'link_class'        => 'blog-button visit',
+			'link_text'         => __( 'Visit Site', 'buddypress' ),
+			'link_title'        => __( 'Visit Site', 'buddypress' ),
+		);
+
+		$button = wp_parse_args( $args, $defaults );
+
+		/**
+		 * Filters the button for visiting a blog in a loop.
+		 *
+		 * @since 1.2.10
+		 *
+		 * @param array $button Array of arguments to be used for the button to visit a blog.
+		 */
+		return bp_get_button( apply_filters( 'bp_get_blogs_visit_blog_button', $button ) );
+	}
+
+/** Stats **********************************************************************/
+
+/**
+ * Display the number of blogs in user's profile.
+ *
+ * @since 2.0.0
+ *
+ * @uses bp_blogs_admin_get_profile_stats() to get the stats.
+ *
+ * @param array|string $args before|after|user_id
+ */
+function bp_blogs_profile_stats( $args = '' ) {
+	echo bp_blogs_get_profile_stats( $args );
+}
+add_action( 'bp_members_admin_user_stats', 'bp_blogs_profile_stats', 9, 1 );
+
+/**
+ * Return the number of blogs in user's profile.
+ *
+ * @since 2.0.0
+ *
+ * @param array|string $args before|after|user_id
+ *
+ * @return string HTML for stats output.
+ */
+function bp_blogs_get_profile_stats( $args = '' ) {
+
+	// Parse the args
+	$r = bp_parse_args( $args, array(
+		'before'  => '<li class="bp-blogs-profile-stats">',
+		'after'   => '</li>',
+		'user_id' => bp_displayed_user_id(),
+		'blogs'   => 0,
+		'output'  => ''
+	), 'blogs_get_profile_stats' );
+
+	// Allow completely overloaded output
+	if ( is_multisite() && empty( $r['output'] ) ) {
+
+		// Only proceed if a user ID was passed
+		if ( ! empty( $r['user_id'] ) ) {
+
+			// Get the user's blogs
+			if ( empty( $r['blogs'] ) ) {
+				$r['blogs'] = absint( bp_blogs_total_blogs_for_user( $r['user_id'] ) );
+			}
+
+			// If blogs exist, show some formatted output
+			$r['output'] = $r['before'] . sprintf( _n( '%s site', '%s sites', $r['blogs'], 'buddypress' ), '<strong>' . $r['blogs'] . '</strong>' ) . $r['after'];
+		}
+	}
+
+	/**
+	 * Filters the number of blogs in user's profile.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $value Output determined for the profile stats.
+	 * @param array  $r     Array of arguments used for default output if none provided.
+	 */
+	return apply_filters( 'bp_blogs_get_profile_stats', $r['output'], $r );
+}
Index: /tags/2.4.3/src/bp-blogs/bp-blogs-widgets.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/bp-blogs-widgets.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/bp-blogs-widgets.php	(revision 10410)
@@ -0,0 +1,167 @@
+<?php
+/**
+ * BuddyPress Blogs Widgets.
+ *
+ * @package BuddyPress
+ * @subpackage BlogsWidgets
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register the widgets for the Blogs component.
+ */
+function bp_blogs_register_widgets() {
+	global $wpdb;
+
+	if ( bp_is_active( 'activity' ) && bp_is_root_blog( $wpdb->blogid ) ) {
+		add_action( 'widgets_init', create_function( '', 'return register_widget("BP_Blogs_Recent_Posts_Widget");' ) );
+	}
+}
+add_action( 'bp_register_widgets', 'bp_blogs_register_widgets' );
+
+/**
+ * The Recent Networkwide Posts widget.
+ */
+class BP_Blogs_Recent_Posts_Widget extends WP_Widget {
+
+	/**
+	 * Constructor method.
+	 */
+	public function __construct() {
+		$widget_ops = array(
+			'description' => __( 'A list of recently published posts from across your network.', 'buddypress' ),
+			'classname'   => 'widget_bp_blogs_widget buddypress widget',
+		);
+		parent::__construct( false, $name = _x( '(BuddyPress) Recent Networkwide Posts', 'widget name', 'buddypress' ), $widget_ops );
+	}
+
+	/**
+	 * Display the networkwide posts widget.
+	 *
+	 * @see WP_Widget::widget() for description of parameters.
+	 *
+	 * @param array $args     Widget arguments.
+	 * @param array $instance Widget settings, as saved by the user.
+	 */
+	public function widget( $args, $instance ) {
+
+		$title = ! empty( $instance['title'] )
+			? esc_html( $instance['title'] )
+			: __( 'Recent Networkwide Posts', 'buddypress' );
+
+		if ( ! empty( $instance['link_title'] ) ) {
+			$title = '<a href="' . bp_get_blogs_directory_permalink() . '">' . esc_html( $title ) . '</a>';
+		}
+
+		/**
+		 * Filters the Blogs Recent Posts widget title.
+		 *
+		 * @since 2.2.0
+		 * @since 2.3.0 Added 'instance' and 'id_base' to arguments passed to filter.
+		 *
+		 * @param string $title    The widget title.
+		 * @param array  $instance The settings for the particular instance of the widget.
+		 * @param string $id_base  Root ID for all widgets of this type.
+		 */
+		$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
+
+		echo $args['before_widget'];
+		echo $args['before_title'] . $title . $args['after_title'];
+
+		if ( empty( $instance['max_posts'] ) || empty( $instance['max_posts'] ) ) {
+			$instance['max_posts'] = 10;
+		}
+
+		$after_widget = $args['after_widget'];
+
+		// Override some of the contextually set parameters for bp_has_activities()
+		$args = array(
+			'action'     => 'new_blog_post',
+			'max'        => $instance['max_posts'],
+			'per_page'   => $instance['max_posts'],
+			'user_id'    => 0,
+			'scope'      => false,
+			'object'     => false,
+			'primary_id' => false
+		); ?>
+
+		<?php if ( bp_has_activities( $args ) ) : ?>
+
+			<ul id="blog-post-list" class="activity-list item-list">
+
+				<?php while ( bp_activities() ) : bp_the_activity(); ?>
+
+					<li>
+						<div class="activity-content" style="margin: 0">
+							<div class="activity-header"><?php bp_activity_action(); ?></div>
+
+							<?php if ( bp_get_activity_content_body() ) : ?>
+
+								<div class="activity-inner"><?php bp_activity_content_body(); ?></div>
+
+							<?php endif; ?>
+
+						</div>
+					</li>
+
+				<?php endwhile; ?>
+
+			</ul>
+
+		<?php else : ?>
+
+			<div id="message" class="info">
+				<p><?php _e( 'Sorry, there were no posts found. Why not write one?', 'buddypress' ); ?></p>
+			</div>
+
+		<?php endif; ?>
+
+		<?php echo $after_widget; ?>
+	<?php
+	}
+
+	/**
+	 * Update the networkwide posts widget options.
+	 *
+	 * @param array $new_instance The new instance options.
+	 * @param array $old_instance The old instance options.
+	 *
+	 * @return array $instance The parsed options to be saved.
+	 */
+	public function update( $new_instance, $old_instance ) {
+		$instance               = $old_instance;
+		$instance['title']      = strip_tags( $new_instance['title'] );
+		$instance['max_posts']  = strip_tags( $new_instance['max_posts'] );
+		$instance['link_title'] = (bool) $new_instance['link_title'];
+
+		return $instance;
+	}
+
+	/**
+	 * Output the networkwide posts widget options form.
+	 *
+	 * @param array $instance Settings for this widget.
+	 *
+	 * @return string
+	 */
+	public function form( $instance ) {
+		$instance = wp_parse_args( (array) $instance, array(
+			'title'      => __( 'Recent Networkwide Posts', 'buddypress' ),
+			'max_posts'  => 10,
+			'link_title' => false,
+		) );
+
+		$title      = strip_tags( $instance['title'] );
+		$max_posts  = strip_tags( $instance['max_posts'] );
+		$link_title = (bool) $instance['link_title'];
+
+		?>
+
+		<p><label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _ex( 'Title:', 'Label for the Title field of the Recent Networkwide Posts widget', 'buddypress' ); ?> <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( $title ); ?>" style="width: 100%;" /></label></p>
+		<p><label for="<?php echo $this->get_field_id( 'link_title' ); ?>"><input type="checkbox" name="<?php echo $this->get_field_name( 'link_title' ); ?>" value="1" <?php checked( $link_title ); ?> /> <?php _e( 'Link widget title to Blogs directory', 'buddypress' ); ?></label></p>
+		<p><label for="<?php echo $this->get_field_id( 'max_posts' ); ?>"><?php _e( 'Max posts to show:', 'buddypress' ); ?> <input class="widefat" id="<?php echo $this->get_field_id( 'max_posts' ); ?>" name="<?php echo $this->get_field_name( 'max_posts' ); ?>" type="text" value="<?php echo esc_attr( $max_posts ); ?>" style="width: 30%" /></label></p>
+		<?php
+	}
+}
Index: /tags/2.4.3/src/bp-blogs/classes/class-bp-blogs-blog.php
===================================================================
--- /tags/2.4.3/src/bp-blogs/classes/class-bp-blogs-blog.php	(revision 10410)
+++ /tags/2.4.3/src/bp-blogs/classes/class-bp-blogs-blog.php	(revision 10410)
@@ -0,0 +1,619 @@
+<?php
+/**
+ * BuddyPress Blogs Classes.
+ *
+ * @package BuddyPress
+ * @subpackage BlogsClasses
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * The main BuddyPress blog class.
+ *
+ * A BP_Blogs_Object represents a link between a specific WordPress blog on a
+ * network and a specific user on that blog.
+ *
+ * @since 1.0.0
+ */
+class BP_Blogs_Blog {
+	public $id;
+	public $user_id;
+	public $blog_id;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @param int $id Optional. The ID of the blog.
+	 */
+	public function __construct( $id = null ) {
+		if ( !empty( $id ) ) {
+			$this->id = $id;
+			$this->populate();
+		}
+	}
+
+	/**
+	 * Populate the object with data about the specific activity item.
+	 */
+	public function populate() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$blog = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->blogs->table_name} WHERE id = %d", $this->id ) );
+
+		$this->user_id = $blog->user_id;
+		$this->blog_id = $blog->blog_id;
+	}
+
+	/**
+	 * Save the BP blog data to the database.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function save() {
+		global $wpdb;
+
+		$this->user_id = apply_filters( 'bp_blogs_blog_user_id_before_save', $this->user_id, $this->id );
+		$this->blog_id = apply_filters( 'bp_blogs_blog_id_before_save', $this->blog_id, $this->id );
+
+		/**
+		 * Fires before the current blog item gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Blogs_Blog $this Current instance of the blog item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_blogs_blog_before_save', array( &$this ) );
+
+		// Don't try and save if there is no user ID or blog ID set.
+		if ( !$this->user_id || !$this->blog_id )
+			return false;
+
+		// Don't save if this blog has already been recorded for the user.
+		if ( !$this->id && $this->exists() )
+			return false;
+
+		$bp = buddypress();
+
+		if ( $this->id ) {
+			// Update
+			$sql = $wpdb->prepare( "UPDATE {$bp->blogs->table_name} SET user_id = %d, blog_id = %d WHERE id = %d", $this->user_id, $this->blog_id, $this->id );
+		} else {
+			// Save
+			$sql = $wpdb->prepare( "INSERT INTO {$bp->blogs->table_name} ( user_id, blog_id ) VALUES ( %d, %d )", $this->user_id, $this->blog_id );
+		}
+
+		if ( !$wpdb->query($sql) )
+			return false;
+
+		/**
+		 * Fires after the current blog item gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Blogs_Blog $this Current instance of the blog item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_blogs_blog_after_save', array( &$this ) );
+
+		if ( $this->id )
+			return $this->id;
+		else
+			return $wpdb->insert_id;
+	}
+
+	/**
+	 * Check whether an association between this user and this blog exists.
+	 *
+	 * @return int $value The number of associations between the user and blog
+	 *                    saved in the blog component tables.
+	 */
+	public function exists() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->blogs->table_name} WHERE user_id = %d AND blog_id = %d", $this->user_id, $this->blog_id ) );
+	}
+
+	/** Static Methods ***************************************************/
+
+	/**
+	 * Retrieve a set of blog-user associations.
+	 *
+	 * @param string      $type              The order in which results should be returned.
+	 *                                       'active', 'alphabetical', 'newest', or 'random'.
+	 * @param int|bool    $limit             Optional. The maximum records to return.
+	 *                                       Default: false.
+	 * @param int|bool    $page              Optional. The page of records to return.
+	 *                                       Default: false (unlimited results).
+	 * @param int         $user_id           Optional. ID of the user whose blogs are being
+	 *                                       retrieved. Default: 0.
+	 * @param string|bool $search_terms      Optional. Search by text stored in
+	 *                                       blogmeta (such as the blog name). Default: false.
+	 * @param bool        $update_meta_cache Whether to pre-fetch metadata for
+	 *                                       blogs. Default: true.
+	 * @param array|bool  $include_blog_ids  Array of blog IDs to include.
+	 *
+	 * @return array Multidimensional results array, structured as follows:
+	 *               'blogs' - Array of located blog objects
+	 *               'total' - A count of the total blogs matching the filter params
+	 */
+	public static function get( $type, $limit = false, $page = false, $user_id = 0, $search_terms = false, $update_meta_cache = true, $include_blog_ids = false ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( !is_user_logged_in() || ( !bp_current_user_can( 'bp_moderate' ) && ( $user_id != bp_loggedin_user_id() ) ) )
+			$hidden_sql = "AND wb.public = 1";
+		else
+			$hidden_sql = '';
+
+		$pag_sql = ( $limit && $page ) ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ) : '';
+
+		$user_sql = !empty( $user_id ) ? $wpdb->prepare( " AND b.user_id = %d", $user_id ) : '';
+
+		switch ( $type ) {
+			case 'active': default:
+				$order_sql = "ORDER BY bm.meta_value DESC";
+				break;
+			case 'alphabetical':
+				$order_sql = "ORDER BY bm_name.meta_value ASC";
+				break;
+			case 'newest':
+				$order_sql = "ORDER BY wb.registered DESC";
+				break;
+			case 'random':
+				$order_sql = "ORDER BY RAND()";
+				break;
+		}
+
+		$include_sql = '';
+		$include_blog_ids = array_filter( wp_parse_id_list( $include_blog_ids ) );
+		if ( ! empty( $include_blog_ids ) ) {
+			$blog_ids_sql = implode( ',', $include_blog_ids );
+			$include_sql  = " AND b.blog_id IN ({$blog_ids_sql})";
+		}
+
+		if ( ! empty( $search_terms ) ) {
+			$search_terms_like = '%' . bp_esc_like( $search_terms ) . '%';
+			$search_terms_sql  = $wpdb->prepare( 'AND (bm_name.meta_value LIKE %s OR bm_description.meta_value LIKE %s)', $search_terms_like, $search_terms_like );
+		} else {
+			$search_terms_sql = '';
+		}
+
+		$paged_blogs = $wpdb->get_results( "
+			SELECT b.blog_id, b.user_id as admin_user_id, u.user_email as admin_user_email, wb.domain, wb.path, bm.meta_value as last_activity, bm_name.meta_value as name
+			FROM
+			  {$bp->blogs->table_name} b
+			  LEFT JOIN {$bp->blogs->table_name_blogmeta} bm ON (b.blog_id = bm.blog_id)
+			  LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_name ON (b.blog_id = bm_name.blog_id)
+			  LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_description ON (b.blog_id = bm_description.blog_id)
+			  LEFT JOIN {$wpdb->base_prefix}blogs wb ON (b.blog_id = wb.blog_id)
+			  LEFT JOIN {$wpdb->users} u ON (b.user_id = u.ID)
+			WHERE
+			  wb.archived = '0' AND wb.spam = 0 AND wb.mature = 0 AND wb.deleted = 0 {$hidden_sql}
+			  AND bm.meta_key = 'last_activity' AND bm_name.meta_key = 'name' AND bm_description.meta_key = 'description'
+			  {$search_terms_sql} {$user_sql} {$include_sql}
+			GROUP BY b.blog_id {$order_sql} {$pag_sql}
+		" );
+
+		$total_blogs = $wpdb->get_var( "
+			SELECT COUNT(DISTINCT b.blog_id)
+			FROM
+			  {$bp->blogs->table_name} b
+			  LEFT JOIN {$wpdb->base_prefix}blogs wb ON (b.blog_id = wb.blog_id)
+			  LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_name ON (b.blog_id = bm_name.blog_id)
+			  LEFT JOIN {$bp->blogs->table_name_blogmeta} bm_description ON (b.blog_id = bm_description.blog_id)
+			WHERE
+			  wb.archived = '0' AND wb.spam = 0 AND wb.mature = 0 AND wb.deleted = 0 {$hidden_sql}
+			  AND
+			  bm_name.meta_key = 'name' AND bm_description.meta_key = 'description'
+			  {$search_terms_sql} {$user_sql} {$include_sql}
+		" );
+
+		$blog_ids = array();
+		foreach ( (array) $paged_blogs as $blog ) {
+			$blog_ids[] = (int) $blog->blog_id;
+		}
+
+		$paged_blogs = BP_Blogs_Blog::get_blog_extras( $paged_blogs, $blog_ids, $type );
+
+		if ( $update_meta_cache ) {
+			bp_blogs_update_meta_cache( $blog_ids );
+		}
+
+		return array( 'blogs' => $paged_blogs, 'total' => $total_blogs );
+	}
+
+	/**
+	 * Delete the record of a given blog for all users.
+	 *
+	 * @param int $blog_id The blog being removed from all users.
+	 * @return int|bool Number of rows deleted on success, false on failure.
+	 */
+	public static function delete_blog_for_all( $blog_id ) {
+		global $wpdb;
+
+		bp_blogs_delete_blogmeta( $blog_id );
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->blogs->table_name} WHERE blog_id = %d", $blog_id ) );
+	}
+
+	/**
+	 * Delete the record of a given blog for a specific user.
+	 *
+	 * @param int $blog_id The blog being removed.
+	 * @param int $user_id Optional. The ID of the user from whom the blog is
+	 *                     being removed. If absent, defaults to the logged-in user ID.
+	 *
+	 * @return int|bool Number of rows deleted on success, false on failure.
+	 */
+	public static function delete_blog_for_user( $blog_id, $user_id = null ) {
+		global $wpdb;
+
+		if ( !$user_id )
+			$user_id = bp_loggedin_user_id();
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->blogs->table_name} WHERE user_id = %d AND blog_id = %d", $user_id, $blog_id ) );
+	}
+
+	/**
+	 * Delete all of a user's blog associations in the BP tables.
+	 *
+	 * @param int $user_id Optional. The ID of the user whose blog associations
+	 *                     are being deleted. If absent, defaults to logged-in user ID.
+	 *
+	 * @return int|bool Number of rows deleted on success, false on failure.
+	 */
+	public static function delete_blogs_for_user( $user_id = null ) {
+		global $wpdb;
+
+		if ( !$user_id )
+			$user_id = bp_loggedin_user_id();
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->blogs->table_name} WHERE user_id = %d", $user_id ) );
+	}
+
+	/**
+	 * Get all of a user's blogs, as tracked by BuddyPress.
+	 *
+	 * Note that this is different from the WordPress function
+	 * {@link get_blogs_of_user()}; the current method returns only those
+	 * blogs that have been recorded by BuddyPress, while the WP function
+	 * does a true query of a user's blog capabilities.
+	 *
+	 * @param int  $user_id     Optional. ID of the user whose blogs are being
+	 *                          queried. Defaults to logged-in user.
+	 * @param bool $show_hidden Optional. Whether to include blogs that are not marked
+	 *                          public. Defaults to true when viewing one's own profile.
+	 *
+	 * @return array Multidimensional results array, structured as follows:
+	 *               'blogs' - Array of located blog objects.
+	 *               'total' - A count of the total blogs for the user.
+	 */
+	public static function get_blogs_for_user( $user_id = 0, $show_hidden = false ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( !$user_id )
+			$user_id = bp_displayed_user_id();
+
+		// Show logged in users their hidden blogs.
+		if ( !bp_is_my_profile() && !$show_hidden )
+			$blogs = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT b.blog_id, b.id, bm1.meta_value as name, wb.domain, wb.path FROM {$bp->blogs->table_name} b, {$wpdb->base_prefix}blogs wb, {$bp->blogs->table_name_blogmeta} bm1 WHERE b.blog_id = wb.blog_id AND b.blog_id = bm1.blog_id AND bm1.meta_key = 'name' AND wb.public = 1 AND wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND b.user_id = %d ORDER BY b.blog_id", $user_id ) );
+		else
+			$blogs = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT b.blog_id, b.id, bm1.meta_value as name, wb.domain, wb.path FROM {$bp->blogs->table_name} b, {$wpdb->base_prefix}blogs wb, {$bp->blogs->table_name_blogmeta} bm1 WHERE b.blog_id = wb.blog_id AND b.blog_id = bm1.blog_id AND bm1.meta_key = 'name' AND wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND b.user_id = %d ORDER BY b.blog_id", $user_id ) );
+
+		$total_blog_count = BP_Blogs_Blog::total_blog_count_for_user( $user_id );
+
+		$user_blogs = array();
+		foreach ( (array) $blogs as $blog ) {
+			$user_blogs[$blog->blog_id] = new stdClass;
+			$user_blogs[$blog->blog_id]->id = $blog->id;
+			$user_blogs[$blog->blog_id]->blog_id = $blog->blog_id;
+			$user_blogs[$blog->blog_id]->siteurl = ( is_ssl() ) ? 'https://' . $blog->domain . $blog->path : 'http://' . $blog->domain . $blog->path;
+			$user_blogs[$blog->blog_id]->name = $blog->name;
+		}
+
+		return array( 'blogs' => $user_blogs, 'count' => $total_blog_count );
+	}
+
+	/**
+	 * Get IDs of all of a user's blogs, as tracked by BuddyPress.
+	 *
+	 * This method always includes hidden blogs.
+	 *
+	 * @param int $user_id Optional. ID of the user whose blogs are being
+	 *                     queried. Defaults to logged-in user.
+	 *
+	 * @return int The number of blogs associated with the user.
+	 */
+	public static function get_blog_ids_for_user( $user_id = 0 ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( !$user_id )
+			$user_id = bp_displayed_user_id();
+
+		return $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM {$bp->blogs->table_name} WHERE user_id = %d", $user_id ) );
+	}
+
+	/**
+	 * Check whether a blog has been recorded by BuddyPress.
+	 *
+	 * @param int $blog_id ID of the blog being queried.
+	 *
+	 * @return int|null The ID of the first located entry in the BP table
+	 *                  on success, otherwise null.
+	 */
+	public static function is_recorded( $blog_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->blogs->table_name} WHERE blog_id = %d", $blog_id ) );
+	}
+
+	/**
+	 * Return a count of associated blogs for a given user.
+	 *
+	 * Includes hidden blogs when the logged-in user is the same as the
+	 * $user_id parameter, or when the logged-in user has the bp_moderate
+	 * cap.
+	 *
+	 * @param int $user_id Optional. ID of the user whose blogs are being
+	 *                     queried. Defaults to logged-in user.
+	 *
+	 * @return int Blog count for the user.
+	 */
+	public static function total_blog_count_for_user( $user_id = null ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( !$user_id )
+			$user_id = bp_displayed_user_id();
+
+		// If the user is logged in return the blog count including their hidden blogs.
+		if ( ( is_user_logged_in() && $user_id == bp_loggedin_user_id() ) || bp_current_user_can( 'bp_moderate' ) ) {
+			return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT b.blog_id) FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND user_id = %d", $user_id ) );
+		} else {
+			return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT b.blog_id) FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.public = 1 AND wb.deleted = 0 AND wb.spam = 0 AND wb.mature = 0 AND wb.archived = '0' AND user_id = %d", $user_id ) );
+		}
+	}
+
+	/**
+	 * Return a list of blogs matching a search term.
+	 *
+	 * Matches against blog names and descriptions, as stored in the BP
+	 * blogmeta table.
+	 *
+	 * @param string $filter The search term.
+	 * @param int    $limit  Optional. The maximum number of items to return.
+	 *                       Default: null (no limit).
+	 * @param int    $page   Optional. The page of results to return. Default:
+	 *                       null (no limit).
+	 *
+	 * @return array Multidimensional results array, structured as follows:
+	 *               'blogs' - Array of located blog objects.
+	 *               'total' - A count of the total blogs matching the query.
+	 */
+	public static function search_blogs( $filter, $limit = null, $page = null ) {
+		global $wpdb;
+
+		$search_terms_like = '%' . bp_esc_like( $filter ) . '%';
+		$search_terms_sql  = $wpdb->prepare( 'bm.meta_value LIKE %s', $search_terms_like );
+
+		$hidden_sql = '';
+		if ( !bp_current_user_can( 'bp_moderate' ) )
+			$hidden_sql = "AND wb.public = 1";
+
+		$pag_sql = '';
+		if ( $limit && $page ) {
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+		}
+
+		$bp = buddypress();
+
+		$paged_blogs = $wpdb->get_results( "SELECT DISTINCT bm.blog_id FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE ( ( bm.meta_key = 'name' OR bm.meta_key = 'description' ) AND {$search_terms_sql} ) {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY meta_value ASC{$pag_sql}" );
+		$total_blogs = $wpdb->get_var( "SELECT COUNT(DISTINCT bm.blog_id) FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE ( ( bm.meta_key = 'name' OR bm.meta_key = 'description' ) AND {$search_terms_sql} ) {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY meta_value ASC" );
+
+		return array( 'blogs' => $paged_blogs, 'total' => $total_blogs );
+	}
+
+	/**
+	 * Retrieve a list of all blogs.
+	 *
+	 * Query will include hidden blogs if the logged-in user has the
+	 * 'bp_moderate' cap.
+	 *
+	 * @param int $limit Optional. The maximum number of items to return.
+	 *                   Default: null (no limit).
+	 * @param int $page  Optional. The page of results to return. Default:
+	 *                   null (no limit).
+	 *
+	 * @return array Multidimensional results array, structured as follows:
+	 *               'blogs' - Array of located blog objects.
+	 *               'total' - A count of the total blogs.
+	 */
+	public static function get_all( $limit = null, $page = null ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$hidden_sql = !bp_current_user_can( 'bp_moderate' ) ? "AND wb.public = 1" : '';
+		$pag_sql    = ( $limit && $page ) ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ) : '';
+
+		$paged_blogs = $wpdb->get_results( "SELECT DISTINCT b.blog_id FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 {$hidden_sql} {$pag_sql}" );
+		$total_blogs = $wpdb->get_var( "SELECT COUNT(DISTINCT b.blog_id) FROM {$bp->blogs->table_name} b LEFT JOIN {$wpdb->base_prefix}blogs wb ON b.blog_id = wb.blog_id WHERE wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 {$hidden_sql}" );
+
+		return array( 'blogs' => $paged_blogs, 'total' => $total_blogs );
+	}
+
+	/**
+	 * Retrieve a list of blogs whose names start with a given letter.
+	 *
+	 * Query will include hidden blogs if the logged-in user has the
+	 * 'bp_moderate' cap.
+	 *
+	 * @param string $letter. The letter you're looking for.
+	 * @param int    $limit   Optional. The maximum number of items to return.
+	 *                        Default: null (no limit).
+	 * @param int    $page    Optional. The page of results to return. Default:
+	 *                        null (no limit).
+	 *
+	 * @return array Multidimensional results array, structured as follows:
+	 *               'blogs' - Array of located blog objects.
+	 *               'total' - A count of the total blogs matching the query.
+	 */
+	public static function get_by_letter( $letter, $limit = null, $page = null ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$letter_like = '%' . bp_esc_like( $letter ) . '%';
+		$letter_sql  = $wpdb->prepare( 'bm.meta_value LIKE %s', $letter_like );
+
+		$hidden_sql = '';
+		if ( !bp_current_user_can( 'bp_moderate' ) )
+			$hidden_sql = "AND wb.public = 1";
+
+		$pag_sql = '';
+		if ( $limit && $page )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		$paged_blogs = $wpdb->get_results( "SELECT DISTINCT bm.blog_id FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE bm.meta_key = 'name' AND {$letter_sql} {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY bm.meta_value ASC{$pag_sql}" );
+		$total_blogs = $wpdb->get_var( "SELECT COUNT(DISTINCT bm.blog_id) FROM {$bp->blogs->table_name_blogmeta} bm LEFT JOIN {$wpdb->base_prefix}blogs wb ON bm.blog_id = wb.blog_id WHERE bm.meta_key = 'name' AND {$letter_sql} {$hidden_sql} AND wb.mature = 0 AND wb.spam = 0 AND wb.archived = '0' AND wb.deleted = 0 ORDER BY bm.meta_value ASC" );
+
+		return array( 'blogs' => $paged_blogs, 'total' => $total_blogs );
+	}
+
+	/**
+	 * Fetch blog data not caught in the main query and append it to results array.
+	 *
+	 * Gets the following information, which is either unavailable at the
+	 * time of the original query, or is more efficient to look up in one
+	 * fell swoop:
+	 *   - The latest post for each blog, include Featured Image data
+	 *   - The blog description
+	 *
+	 * @param array       $paged_blogs Array of results from the original query.
+	 * @param array       $blog_ids    Array of IDs returned from the original query.
+	 * @param string|bool $type        Not currently used. Default: false.
+	 *
+	 * @return array $paged_blogs The located blogs array, with the extras added.
+	 */
+	public static function get_blog_extras( &$paged_blogs, &$blog_ids, $type = false ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( empty( $blog_ids ) )
+			return $paged_blogs;
+
+		$blog_ids = implode( ',', wp_parse_id_list( $blog_ids ) );
+
+		for ( $i = 0, $count = count( $paged_blogs ); $i < $count; ++$i ) {
+			$blog_prefix = $wpdb->get_blog_prefix( $paged_blogs[$i]->blog_id );
+			$paged_blogs[$i]->latest_post = $wpdb->get_row( "SELECT ID, post_content, post_title, post_excerpt, guid FROM {$blog_prefix}posts WHERE post_status = 'publish' AND post_type = 'post' AND id != 1 ORDER BY id DESC LIMIT 1" );
+			$images = array();
+
+			// Add URLs to any Featured Image this post might have
+			if ( ! empty( $paged_blogs[$i]->latest_post ) && has_post_thumbnail( $paged_blogs[$i]->latest_post->ID ) ) {
+
+				// Grab 4 sizes of the image. Thumbnail.
+				$image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'thumbnail', false );
+				if ( ! empty( $image ) )
+					$images['thumbnail'] = $image[0];
+
+				// Medium
+				$image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'medium', false );
+				if ( ! empty( $image ) )
+					$images['medium'] = $image[0];
+
+				// Large
+				$image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'large', false );
+				if ( ! empty( $image ) )
+					$images['large'] = $image[0];
+
+				// Post thumbnail
+				$image = wp_get_attachment_image_src( get_post_thumbnail_id( $paged_blogs[$i]->latest_post->ID ), 'post-thumbnail', false );
+				if ( ! empty( $image ) )
+					$images['post-thumbnail'] = $image[0];
+
+				// Add the images to the latest_post object
+				$paged_blogs[$i]->latest_post->images = $images;
+			}
+		}
+
+		/* Fetch the blog description for each blog (as it may be empty we can't fetch it in the main query). */
+		$blog_descs = $wpdb->get_results( "SELECT blog_id, meta_value as description FROM {$bp->blogs->table_name_blogmeta} WHERE meta_key = 'description' AND blog_id IN ( {$blog_ids} )" );
+
+		for ( $i = 0, $count = count( $paged_blogs ); $i < $count; ++$i ) {
+			foreach ( (array) $blog_descs as $desc ) {
+				if ( $desc->blog_id == $paged_blogs[$i]->blog_id )
+					$paged_blogs[$i]->description = $desc->description;
+			}
+		}
+
+		return $paged_blogs;
+	}
+
+	/**
+	 * Check whether a given blog is hidden.
+	 *
+	 * Checks the 'public' column in the wp_blogs table.
+	 *
+	 * @param int $blog_id The ID of the blog being checked.
+	 *
+	 * @return bool True if hidden (public = 0), false otherwise.
+	 */
+	public static function is_hidden( $blog_id ) {
+		global $wpdb;
+
+		if ( !(int) $wpdb->get_var( $wpdb->prepare( "SELECT public FROM {$wpdb->base_prefix}blogs WHERE blog_id = %d", $blog_id ) ) ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Get ID of user-blog link.
+	 *
+	 * @param int $user_id ID of user.
+	 * @param int $blog_id ID of blog.
+	 *
+	 * @return int|bool ID of user-blog link, or false if not found.
+	 */
+	public static function get_user_blog( $user_id, $blog_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$user_blog = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->blogs->table_name} WHERE user_id = %d AND blog_id = %d", $user_id, $blog_id ) );
+
+		if ( empty( $user_blog ) ) {
+			$user_blog = false;
+		} else {
+			$user_blog = intval( $user_blog );
+		}
+
+		return $user_blog;
+	}
+}
Index: /tags/2.4.3/src/bp-core/admin/bp-core-admin-actions.php
===================================================================
--- /tags/2.4.3/src/bp-core/admin/bp-core-admin-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/admin/bp-core-admin-actions.php	(revision 10410)
@@ -0,0 +1,236 @@
+<?php
+/**
+ * BuddyPress Admin Actions.
+ *
+ * This file contains the actions that are used through-out BuddyPress 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.
+ *
+ *  - BuddyPress: In {@link BuddyPress::setup_actions()} in BuddyPress.php
+ *  - Admin: More in {@link bp_Admin::setup_actions()} in admin.php
+ *
+ * @package BuddyPress
+ * @subpackage Admin
+ * @see bp-core-actions.php
+ * @see bp-core-filters.php
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Attach BuddyPress to WordPress.
+ *
+ * BuddyPress 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 BuddyPress 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--BuddyPress Sub-actions
+ */
+add_action( 'admin_menu',              'bp_admin_menu'                    );
+add_action( 'admin_init',              'bp_admin_init'                    );
+add_action( 'admin_head',              'bp_admin_head'                    );
+add_action( 'admin_notices',           'bp_admin_notices'                 );
+add_action( 'admin_enqueue_scripts',   'bp_admin_enqueue_scripts'         );
+add_action( 'network_admin_menu',      'bp_admin_menu'                    );
+add_action( 'custom_menu_order',       'bp_admin_custom_menu_order'       );
+add_action( 'menu_order',              'bp_admin_menu_order'              );
+add_action( 'wpmu_new_blog',           'bp_new_site',               10, 6 );
+
+// Hook on to admin_init
+add_action( 'bp_admin_init', 'bp_setup_updater',          1000 );
+add_action( 'bp_admin_init', 'bp_core_activation_notice', 1010 );
+add_action( 'bp_admin_init', 'bp_register_importers'           );
+add_action( 'bp_admin_init', 'bp_register_admin_style'         );
+add_action( 'bp_admin_init', 'bp_register_admin_settings'      );
+add_action( 'bp_admin_init', 'bp_do_activation_redirect', 1    );
+
+// Add a new separator
+add_action( 'bp_admin_menu', 'bp_admin_separator' );
+
+/**
+ * When a new site is created in a multisite installation, run the activation
+ * routine on that site.
+ *
+ * @since 1.7.0
+ *
+ * @param int    $blog_id
+ * @param int    $user_id
+ * @param string $domain
+ * @param string $path
+ * @param int    $site_id
+ * @param array  $meta
+ */
+function bp_new_site( $blog_id, $user_id, $domain, $path, $site_id, $meta ) {
+
+	// Bail if plugin is not network activated
+	if ( ! is_plugin_active_for_network( buddypress()->basename ) )
+		return;
+
+	// Switch to the new blog
+	switch_to_blog( $blog_id );
+
+	/**
+	 * Fires the activation routine for a new site created in a multisite installation.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param int    $blog_id ID of the blog being installed to.
+	 * @param int    $user_id ID of the user the install is for.
+	 * @param string $domain  Domain to use with the install.
+	 * @param string $path    Path to use with the install.
+	 * @param int    $site_id ID of the site being installed to.
+	 * @param array  $meta    Metadata to use with the site creation.
+	 */
+	do_action( 'bp_new_site', $blog_id, $user_id, $domain, $path, $site_id, $meta );
+
+	// restore original blog
+	restore_current_blog();
+}
+
+/** Sub-Actions ***************************************************************/
+
+/**
+ * Piggy back admin_init action.
+ *
+ * @since 1.7.0
+ * @uses do_action() Calls 'bp_admin_init'.
+ */
+function bp_admin_init() {
+
+	/**
+	 * Fires inside the bp_admin_init function.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_admin_init' );
+}
+
+/**
+ * Piggy back admin_menu action.
+ *
+ * @since 1.7.0
+ * @uses do_action() Calls 'bp_admin_menu'.
+ */
+function bp_admin_menu() {
+
+	/**
+	 * Fires inside the bp_admin_menu function.
+	 *
+	 * @since 1.7.0
+	 */
+	do_action( 'bp_admin_menu' );
+}
+
+/**
+ * Piggy back admin_head action.
+ *
+ * @since 1.7.0
+ * @uses do_action() Calls 'bp_admin_head'.
+ */
+function bp_admin_head() {
+
+	/**
+	 * Fires inside the bp_admin_head function.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_admin_head' );
+}
+
+/**
+ * Piggy back admin_notices action.
+ *
+ * @since 1.7.0
+ * @uses do_action() Calls 'bp_admin_notices'.
+ */
+function bp_admin_notices() {
+
+	/**
+	 * Fires inside the bp_admin_notices function.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_admin_notices' );
+}
+
+/**
+ * Piggy back admin_enqueue_scripts action.
+ *
+ * @since 1.7.0
+ *
+ * @uses do_action() Calls 'bp_admin_enqueue_scripts''.
+ *
+ * @param string $hook_suffix The current admin page, passed to
+ *                            'admin_enqueue_scripts'.
+ */
+function bp_admin_enqueue_scripts( $hook_suffix = '' ) {
+
+	/**
+	 * Fires inside the bp_admin_enqueue_scripts function.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $hook_suffix The current admin page, passed to admin_enqueue_scripts.
+	 */
+	do_action( 'bp_admin_enqueue_scripts', $hook_suffix );
+}
+
+/**
+ * Dedicated action to register BuddyPress importers.
+ *
+ * @since 1.7.0
+ * @uses do_action() Calls 'bp_admin_notices'.
+ */
+function bp_register_importers() {
+
+	/**
+	 * Fires inside the bp_register_importers function.
+	 *
+	 * Used to register a BuddyPress importer.
+	 *
+	 * @since 1.7.0
+	 */
+	do_action( 'bp_register_importers' );
+}
+
+/**
+ * Dedicated action to register admin styles.
+ *
+ * @since 1.7.0
+ * @uses do_action() Calls 'bp_admin_notices'.
+ */
+function bp_register_admin_style() {
+
+	/**
+	 * Fires inside the bp_register_admin_style function.
+	 *
+	 * @since 1.7.0
+	 */
+	do_action( 'bp_register_admin_style' );
+}
+
+/**
+ * Dedicated action to register admin settings.
+ *
+ * @since 1.7.0
+ * @uses do_action() Calls 'bp_register_admin_settings'.
+ */
+function bp_register_admin_settings() {
+
+	/**
+	 * Fires inside the bp_register_admin_settings function.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_register_admin_settings' );
+}
Index: /tags/2.4.3/src/bp-core/admin/bp-core-admin-components.php
===================================================================
--- /tags/2.4.3/src/bp-core/admin/bp-core-admin-components.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/admin/bp-core-admin-components.php	(revision 10410)
@@ -0,0 +1,445 @@
+<?php
+/**
+ * BuddyPress Admin Component Functions.
+ *
+ * @package BuddyPress
+ * @subpackage CoreAdministration
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Renders the Component Setup admin panel.
+ *
+ * @package BuddyPress
+ * @since 1.6.0
+ * @uses bp_core_admin_component_options()
+ */
+function bp_core_admin_components_settings() {
+?>
+
+	<div class="wrap">
+
+		<h2 class="nav-tab-wrapper"><?php bp_core_admin_tabs( __( 'Components', 'buddypress' ) ); ?></h2>
+		<form action="" method="post" id="bp-admin-component-form">
+
+			<?php bp_core_admin_components_options(); ?>
+
+			<p class="submit clear">
+				<input class="button-primary" type="submit" name="bp-admin-component-submit" id="bp-admin-component-submit" value="<?php esc_attr_e( 'Save Settings', 'buddypress' ) ?>"/>
+			</p>
+
+			<?php wp_nonce_field( 'bp-admin-component-setup' ); ?>
+
+		</form>
+	</div>
+
+<?php
+}
+
+/**
+ * Creates reusable markup for component setup on the Components and Pages dashboard panel.
+ *
+ * @package BuddyPress
+ * @since 1.6.0
+ * @todo Use settings API
+ */
+function bp_core_admin_components_options() {
+
+	// Declare local variables
+	$deactivated_components = array();
+
+	/**
+	 * Filters the array of available components.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param mixed $value Active components.
+	 */
+	$active_components      = apply_filters( 'bp_active_components', bp_get_option( 'bp-active-components' ) );
+
+	// The default components (if none are previously selected)
+	$default_components = array(
+		'xprofile' => array(
+			'title'       => __( 'Extended Profiles', 'buddypress' ),
+			'description' => __( 'Customize your community with fully editable profile fields that allow your users to describe themselves.', 'buddypress' )
+		),
+		'settings' => array(
+			'title'       => __( 'Account Settings', 'buddypress' ),
+			'description' => __( 'Allow your users to modify their account and notification settings directly from within their profiles.', 'buddypress' )
+		),
+		'notifications' => array(
+			'title'       => __( 'Notifications', 'buddypress' ),
+			'description' => __( 'Notify members of relevant activity with a toolbar bubble and/or via email, and allow them to customize their notification settings.', 'buddypress' )
+		),
+	);
+
+	$optional_components = bp_core_admin_get_components( 'optional' );
+	$required_components = bp_core_admin_get_components( 'required' );
+	$retired_components  = bp_core_admin_get_components( 'retired'  );
+
+	// Don't show Forums component in optional components if it's disabled
+	if ( ! bp_is_active( 'forums' ) ) {
+		unset( $optional_components['forums'] );
+	}
+
+	// Merge optional and required together
+	$all_components = $optional_components + $required_components;
+
+	// If this is an upgrade from before BuddyPress 1.5, we'll have to convert
+	// deactivated components into activated ones.
+	if ( empty( $active_components ) ) {
+		$deactivated_components = bp_get_option( 'bp-deactivated-components' );
+		if ( !empty( $deactivated_components ) ) {
+
+			// Trim off namespace and filename
+			$trimmed = array();
+			foreach ( array_keys( (array) $deactivated_components ) as $component ) {
+				$trimmed[] = str_replace( '.php', '', str_replace( 'bp-', '', $component ) );
+			}
+
+			// Loop through the optional components to create an active component array
+			foreach ( array_keys( (array) $optional_components ) as $ocomponent ) {
+				if ( !in_array( $ocomponent, $trimmed ) ) {
+					$active_components[$ocomponent] = 1;
+				}
+			}
+		}
+	}
+
+	// On new install, set active components to default
+	if ( empty( $active_components ) ) {
+		$active_components = $default_components;
+	}
+
+	// Core component is always active
+	$active_components['core'] = $all_components['core'];
+	$inactive_components       = array_diff( array_keys( $all_components ) , array_keys( $active_components ) );
+
+	/** Display ***************************************************************/
+
+	// Get the total count of all plugins
+	$all_count = count( $all_components );
+	$page      = bp_core_do_network_admin()  ? 'settings.php' : 'options-general.php';
+	$action    = !empty( $_GET['action'] ) ? $_GET['action'] : 'all';
+
+	switch( $action ) {
+		case 'all' :
+			$current_components = $all_components;
+			break;
+		case 'active' :
+			foreach ( array_keys( $active_components ) as $component ) {
+				$current_components[$component] = $all_components[$component];
+			}
+			break;
+		case 'inactive' :
+			foreach ( $inactive_components as $component ) {
+				$current_components[$component] = $all_components[$component];
+			}
+			break;
+		case 'mustuse' :
+			$current_components = $required_components;
+			break;
+		case 'retired' :
+			$current_components = $retired_components;
+			break;
+	} ?>
+
+	<ul class="subsubsub">
+		<li><a href="<?php echo esc_url( add_query_arg( array( 'page' => 'bp-components', 'action' => 'all'      ), bp_get_admin_url( $page ) ) ); ?>" <?php if ( $action === 'all'      ) : ?>class="current"<?php endif; ?>><?php printf( _nx( 'All <span class="count">(%s)</span>',      'All <span class="count">(%s)</span>',      $all_count,         'plugins', 'buddypress' ), number_format_i18n( $all_count                    ) ); ?></a> | </li>
+		<li><a href="<?php echo esc_url( add_query_arg( array( 'page' => 'bp-components', 'action' => 'active'   ), bp_get_admin_url( $page ) ) ); ?>" <?php if ( $action === 'active'   ) : ?>class="current"<?php endif; ?>><?php printf( _n(  'Active <span class="count">(%s)</span>',   'Active <span class="count">(%s)</span>',   count( $active_components   ), 'buddypress' ), number_format_i18n( count( $active_components   ) ) ); ?></a> | </li>
+		<li><a href="<?php echo esc_url( add_query_arg( array( 'page' => 'bp-components', 'action' => 'inactive' ), bp_get_admin_url( $page ) ) ); ?>" <?php if ( $action === 'inactive' ) : ?>class="current"<?php endif; ?>><?php printf( _n(  'Inactive <span class="count">(%s)</span>', 'Inactive <span class="count">(%s)</span>', count( $inactive_components ), 'buddypress' ), number_format_i18n( count( $inactive_components ) ) ); ?></a> | </li>
+		<li><a href="<?php echo esc_url( add_query_arg( array( 'page' => 'bp-components', 'action' => 'mustuse'  ), bp_get_admin_url( $page ) ) ); ?>" <?php if ( $action === 'mustuse'  ) : ?>class="current"<?php endif; ?>><?php printf( _n(  'Must-Use <span class="count">(%s)</span>', 'Must-Use <span class="count">(%s)</span>', count( $required_components ), 'buddypress' ), number_format_i18n( count( $required_components ) ) ); ?></a> | </li>
+		<li><a href="<?php echo esc_url( add_query_arg( array( 'page' => 'bp-components', 'action' => 'retired'  ), bp_get_admin_url( $page ) ) ); ?>" <?php if ( $action === 'retired'  ) : ?>class="current"<?php endif; ?>><?php printf( _n(  'Retired <span class="count">(%s)</span>',  'Retired <span class="count">(%s)</span>',  count( $retired_components ),  'buddypress' ), number_format_i18n( count( $retired_components  ) ) ); ?></a></li>
+	</ul>
+
+	<table class="widefat fixed plugins" cellspacing="0">
+		<thead>
+			<tr>
+				<th scope="col" id="cb" class="manage-column column-cb check-column">&nbsp;</th>
+				<th scope="col" id="name" class="manage-column column-name" style="width: 190px;"><?php _e( 'Component', 'buddypress' ); ?></th>
+				<th scope="col" id="description" class="manage-column column-description"><?php _e( 'Description', 'buddypress' ); ?></th>
+			</tr>
+		</thead>
+
+		<tfoot>
+			<tr>
+				<th scope="col" class="manage-column column-cb check-column">&nbsp;</th>
+				<th scope="col" class="manage-column column-name" style="width: 190px;"><?php _e( 'Component', 'buddypress' ); ?></th>
+				<th scope="col" class="manage-column column-description"><?php _e( 'Description', 'buddypress' ); ?></th>
+			</tr>
+		</tfoot>
+
+		<tbody id="the-list">
+
+			<?php if ( !empty( $current_components ) ) : ?>
+
+				<?php foreach ( $current_components as $name => $labels ) : ?>
+
+					<?php if ( !in_array( $name, array( 'core', 'members' ) ) ) :
+						$class = isset( $active_components[esc_attr( $name )] ) ? 'active' : 'inactive';
+					else :
+						$class = 'active';
+					endif; ?>
+
+					<tr id="<?php echo esc_attr( $name ); ?>" class="<?php echo esc_attr( $name ) . ' ' . esc_attr( $class ); ?>">
+						<th scope="row">
+
+							<?php if ( !in_array( $name, array( 'core', 'members' ) ) ) : ?>
+
+								<input type="checkbox" id="bp_components[<?php echo esc_attr( $name ); ?>]" name="bp_components[<?php echo esc_attr( $name ); ?>]" value="1"<?php checked( isset( $active_components[esc_attr( $name )] ) ); ?> />
+
+							<?php endif; ?>
+
+						</th>
+						<td class="plugin-title" style="width: 190px;">
+							<span></span>
+							<label for="bp_components[<?php echo esc_attr( $name ); ?>]">
+								<strong><?php echo esc_html( $labels['title'] ); ?></strong>
+							</label>
+
+							<div class="row-actions-visible">
+
+							</div>
+						</td>
+
+						<td class="column-description desc">
+							<div class="plugin-description">
+								<p><?php echo $labels['description']; ?></p>
+							</div>
+							<div class="active second plugin-version-author-uri">
+
+							</div>
+						</td>
+					</tr>
+
+				<?php endforeach ?>
+
+			<?php else : ?>
+
+				<tr class="no-items">
+					<td class="colspanchange" colspan="3"><?php _e( 'No components found.', 'buddypress' ); ?></td>
+				</tr>
+
+			<?php endif; ?>
+
+		</tbody>
+	</table>
+
+	<input type="hidden" name="bp_components[members]" value="1" />
+
+	<?php
+}
+
+/**
+ * Handle saving the Component settings.
+ *
+ * @since 1.6.0
+ * @todo Use settings API when it supports saving network settings
+ */
+function bp_core_admin_components_settings_handler() {
+
+	// Bail if not saving settings
+	if ( ! isset( $_POST['bp-admin-component-submit'] ) )
+		return;
+
+	// Bail if nonce fails
+	if ( ! check_admin_referer( 'bp-admin-component-setup' ) )
+		return;
+
+	// Settings form submitted, now save the settings. First, set active components
+	if ( isset( $_POST['bp_components'] ) ) {
+
+		// Load up BuddyPress
+		$bp = buddypress();
+
+		// Save settings and upgrade schema
+		require_once( $bp->plugin_dir . '/bp-core/admin/bp-core-admin-schema.php' );
+
+		$submitted = stripslashes_deep( $_POST['bp_components'] );
+		$bp->active_components = bp_core_admin_get_active_components_from_submitted_settings( $submitted );
+
+		bp_core_install( $bp->active_components );
+		bp_core_add_page_mappings( $bp->active_components );
+		bp_update_option( 'bp-active-components', $bp->active_components );
+	}
+
+	// Where are we redirecting to?
+	$base_url = bp_get_admin_url( add_query_arg( array( 'page' => 'bp-components', 'updated' => 'true' ), 'admin.php' ) );
+
+	// Redirect
+	wp_redirect( $base_url );
+	die();
+}
+add_action( 'bp_admin_init', 'bp_core_admin_components_settings_handler' );
+
+/**
+ * Calculates the components that should be active after save, based on submitted settings.
+ *
+ * The way that active components must be set after saving your settings must
+ * be calculated differently depending on which of the Components subtabs you
+ * are coming from:
+ * - When coming from All or Active, the submitted checkboxes accurately
+ *   reflect the desired active components, so we simply pass them through
+ * - When coming from Inactive, components can only be activated - already
+ *   active components will not be passed in the $_POST global. Thus, we must
+ *   parse the newly activated components with the already active components
+ *   saved in the $bp global
+ * - When activating a Retired component, the situation is similar to Inactive.
+ * - When deactivating a Retired component, no value is passed in the $_POST
+ *   global (because the component settings are checkboxes). So, in order to
+ *   determine whether a retired component is being deactivated, we retrieve a
+ *   list of retired components, and check each one to ensure that its checkbox
+ *   is not present, before merging the submitted components with the active
+ *   ones.
+ *
+ * @since 1.7.0
+ *
+ * @param array $submitted This is the array of component settings coming from the POST
+ *                         global. You should stripslashes_deep() before passing to this function.
+ *
+ * @return array The calculated list of component settings
+ */
+function bp_core_admin_get_active_components_from_submitted_settings( $submitted ) {
+	$current_action = 'all';
+
+	if ( isset( $_GET['action'] ) && in_array( $_GET['action'], array( 'active', 'inactive', 'retired' ) ) ) {
+		$current_action = $_GET['action'];
+	}
+
+	$current_components = buddypress()->active_components;
+
+	switch ( $current_action ) {
+		case 'retired' :
+			$retired_components = bp_core_admin_get_components( 'retired' );
+			foreach ( array_keys( $retired_components ) as $retired_component ) {
+				if ( ! isset( $submitted[ $retired_component ] ) ) {
+					unset( $current_components[ $retired_component ] );
+				}
+			}
+			// fall through
+
+		case 'inactive' :
+			$components = array_merge( $submitted, $current_components );
+			break;
+
+		case 'all' :
+		case 'active' :
+		default :
+			$components = $submitted;
+			break;
+	}
+
+	return $components;
+}
+
+/**
+ * Return a list of component information, optionally filtered by type.
+ *
+ * We use this information both to build the markup for the admin screens, as
+ * well as to do some processing on settings data submitted from those screens.
+ *
+ * @since 1.7.0
+ *
+ * @param string $type 'all', 'optional', 'retired', 'required'.
+ *
+ * @return array An array of requested component data
+ */
+function bp_core_admin_get_components( $type = 'all' ) {
+
+	// Required components
+	$required_components = array(
+		'core' => array(
+			'title'       => __( 'BuddyPress Core', 'buddypress' ),
+			'description' => __( 'It&#8216;s what makes <del>time travel</del> BuddyPress possible!', 'buddypress' )
+		),
+		'members' => array(
+			'title'       => __( 'Community Members', 'buddypress' ),
+			'description' => __( 'Everything in a BuddyPress community revolves around its members.', 'buddypress' )
+		),
+	);
+
+	// Retired components
+	$retired_components = array(
+		'forums' => array(
+			'title'       => __( 'Group Forums', 'buddypress' ),
+			'description' => sprintf( __( 'BuddyPress Forums are retired. Use %s.', 'buddypress' ), '<a href="https://bbpress.org/">bbPress</a>' )
+		),
+	);
+
+	// Optional core components
+	$optional_components = array(
+		'xprofile' => array(
+			'title'       => __( 'Extended Profiles', 'buddypress' ),
+			'description' => __( 'Customize your community with fully editable profile fields that allow your users to describe themselves.', 'buddypress' )
+		),
+		'settings' => array(
+			'title'       => __( 'Account Settings', 'buddypress' ),
+			'description' => __( 'Allow your users to modify their account and notification settings directly from within their profiles.', 'buddypress' )
+		),
+		'friends'  => array(
+			'title'       => __( 'Friend Connections', 'buddypress' ),
+			'description' => __( 'Let your users make connections so they can track the activity of others and focus on the people they care about the most.', 'buddypress' )
+		),
+		'messages' => array(
+			'title'       => __( 'Private Messaging', 'buddypress' ),
+			'description' => __( 'Allow your users to talk to each other directly and in private. Not just limited to one-on-one discussions, messages can be sent between any number of members.', 'buddypress' )
+		),
+		'activity' => array(
+			'title'       => __( 'Activity Streams', 'buddypress' ),
+			'description' => __( 'Global, personal, and group activity streams with threaded commenting, direct posting, favoriting, and @mentions, all with full RSS feed and email notification support.', 'buddypress' )
+		),
+		'notifications' => array(
+			'title'       => __( 'Notifications', 'buddypress' ),
+			'description' => __( 'Notify members of relevant activity with a toolbar bubble and/or via email, and allow them to customize their notification settings.', 'buddypress' )
+		),
+		'groups'   => array(
+			'title'       => __( 'User Groups', 'buddypress' ),
+			'description' => __( 'Groups allow your users to organize themselves into specific public, private or hidden sections with separate activity streams and member listings.', 'buddypress' )
+		),
+		'forums'   => array(
+			'title'       => __( 'Group Forums (Legacy)', 'buddypress' ),
+			'description' => __( 'Group forums allow for focused, bulletin-board style conversations.', 'buddypress' )
+		),
+		'blogs'    => array(
+			'title'       => __( 'Site Tracking', 'buddypress' ),
+			'description' => __( 'Record activity for new posts and comments from your site.', 'buddypress' )
+		)
+	);
+
+
+	// Add blogs tracking if multisite
+	if ( is_multisite() ) {
+		$optional_components['blogs']['description'] = __( 'Record activity for new sites, posts, and comments across your network.', 'buddypress' );
+	}
+
+	switch ( $type ) {
+		case 'required' :
+			$components = $required_components;
+			break;
+		case 'optional' :
+			$components = $optional_components;
+			break;
+		case 'retired' :
+			$components = $retired_components;
+			break;
+		case 'all' :
+		default :
+			$components = array_merge( $required_components, $optional_components, $retired_components );
+			break;
+
+	}
+
+	/**
+	 * Filters the list of component information.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array  $components Array of component information.
+	 * @param string $type       Type of component list requested.
+	 *                           Possible values include 'all', 'optional',
+	 *							 'retired', 'required'.
+	 */
+	return apply_filters( 'bp_core_admin_get_components', $components, $type );
+}
Index: /tags/2.4.3/src/bp-core/admin/bp-core-admin-functions.php
===================================================================
--- /tags/2.4.3/src/bp-core/admin/bp-core-admin-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/admin/bp-core-admin-functions.php	(revision 10410)
@@ -0,0 +1,957 @@
+<?php
+/**
+ * BuddyPress Common Admin Functions.
+ *
+ * @package BuddyPress
+ * @subpackage CoreAdministration
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/** Menu **********************************************************************/
+
+/**
+ * Initializes the wp-admin area "BuddyPress" menus and sub menus.
+ *
+ * @package BuddyPress Core
+ * @uses bp_current_user_can() returns true if the current user is a site admin, false if not
+ */
+function bp_core_admin_menu_init() {
+	add_action( bp_core_admin_hook(), 'bp_core_add_admin_menu', 9 );
+}
+
+/**
+ * In BP 1.6, the top-level admin menu was removed. For backpat, this function
+ * keeps the top-level menu if a plugin has registered a menu into the old
+ * 'bp-general-settings' menu.
+ *
+ * The old "bp-general-settings" page was renamed "bp-components".
+ *
+ * @global array $_parent_pages
+ * @global array $_registered_pages
+ * @global array $submenu
+ * @since 1.6.0
+ */
+function bp_core_admin_backpat_menu() {
+	global $_parent_pages, $_registered_pages, $submenu;
+
+	// If there's no bp-general-settings menu (perhaps because the current
+	// user is not an Administrator), there's nothing to do here
+	if ( ! isset( $submenu['bp-general-settings'] ) ) {
+		return;
+	}
+
+	/**
+	 * By default, only the core "Help" submenu is added under the top-level BuddyPress menu.
+	 * This means that if no third-party plugins have registered their admin pages into the
+	 * 'bp-general-settings' menu, it will only contain one item. Kill it.
+	 */
+	if ( 1 != count( $submenu['bp-general-settings'] ) ) {
+		return;
+	}
+
+	// This removes the top-level menu
+	remove_submenu_page( 'bp-general-settings', 'bp-general-settings' );
+	remove_menu_page( 'bp-general-settings' );
+
+	// These stop people accessing the URL directly
+	unset( $_parent_pages['bp-general-settings'] );
+	unset( $_registered_pages['toplevel_page_bp-general-settings'] );
+}
+add_action( bp_core_admin_hook(), 'bp_core_admin_backpat_menu', 999 );
+
+/**
+ * This tells WP to highlight the Settings > BuddyPress menu item,
+ * regardless of which actual BuddyPress admin screen we are on.
+ *
+ * The conditional prevents the behaviour when the user is viewing the
+ * backpat "Help" page, the Activity page, or any third-party plugins.
+ *
+ * @global string $plugin_page
+ * @global array $submenu
+ * @since 1.6.0
+ */
+function bp_core_modify_admin_menu_highlight() {
+	global $plugin_page, $submenu_file;
+
+	// This tweaks the Settings subnav menu to show only one BuddyPress menu item
+	if ( ! in_array( $plugin_page, array( 'bp-activity', 'bp-general-settings', ) ) ) {
+		$submenu_file = 'bp-components';
+	}
+
+	// Network Admin > Tools
+	if ( in_array( $plugin_page, array( 'bp-tools', 'available-tools' ) ) ) {
+		$submenu_file = $plugin_page;
+	}
+}
+
+/**
+ * Generates markup for a fallback top-level BuddyPress menu page, if the site is running
+ * a legacy plugin which hasn't been updated. If the site is up to date, this page
+ * will never appear.
+ *
+ * @see bp_core_admin_backpat_menu()
+ * @since 1.6.0
+ * @todo Add convenience links into the markup once new positions are finalised.
+ */
+function bp_core_admin_backpat_page() {
+	$url          = bp_core_do_network_admin() ? network_admin_url( 'settings.php' ) : admin_url( 'options-general.php' );
+	$settings_url = add_query_arg( 'page', 'bp-components', $url ); ?>
+
+	<div class="wrap">
+		<h2><?php _e( 'Why have all my BuddyPress menus disappeared?', 'buddypress' ); ?></h2>
+
+		<p><?php _e( "Don't worry! We've moved the BuddyPress options into more convenient and easier to find locations. You're seeing this page because you are running a legacy BuddyPress plugin which has not been updated.", 'buddypress' ); ?></p>
+		<p><?php printf( __( 'Components, Pages, Settings, and Forums, have been moved to <a href="%s">Settings &gt; BuddyPress</a>. Profile Fields has been moved into the <a href="%s">Users</a> menu.', 'buddypress' ), esc_url( $settings_url ), bp_get_admin_url( 'users.php?page=bp-profile-setup' ) ); ?></p>
+	</div>
+
+	<?php
+}
+
+/** Notices *******************************************************************/
+
+/**
+ * Print admin messages to admin_notices or network_admin_notices.
+ *
+ * BuddyPress combines all its messages into a single notice, to avoid a preponderance of yellow
+ * boxes.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_current_user_can() to check current user permissions before showing the notices.
+ * @uses bp_is_root_blog()
+ */
+function bp_core_print_admin_notices() {
+
+	// Only the super admin should see messages
+	if ( ! bp_current_user_can( 'bp_moderate' ) ) {
+		return;
+	}
+
+	// On multisite installs, don't show on a non-root blog, unless
+	// 'do_network_admin' is overridden.
+	if ( is_multisite() && bp_core_do_network_admin() && ! bp_is_root_blog() ) {
+		return;
+	}
+
+	$notice_types = array();
+	foreach ( buddypress()->admin->notices as $notice ) {
+		$notice_types[] = $notice['type'];
+	}
+	$notice_types = array_unique( $notice_types );
+
+	foreach ( $notice_types as $type ) {
+		$notices = wp_list_filter( buddypress()->admin->notices, array( 'type' => $type ) );
+		printf( '<div id="message" class="fade %s">', sanitize_html_class( $type ) );
+
+		foreach ( $notices as $notice ) {
+			printf( '<p>%s</p>', $notice['message'] );
+		}
+
+		printf( '</div>' );
+	}
+}
+add_action( 'admin_notices',         'bp_core_print_admin_notices' );
+add_action( 'network_admin_notices', 'bp_core_print_admin_notices' );
+
+/**
+ * Add an admin notice to the BP queue.
+ *
+ * Messages added with this function are displayed in BuddyPress's general purpose admin notices
+ * box. It is recommended that you hook this function to admin_init, so that your messages are
+ * loaded in time.
+ *
+ * @since 1.5.0
+ *
+ * @param string $notice The notice you are adding to the queue.
+ * @param string $type   The notice type; optional. Usually either "updated" or "error".
+ */
+function bp_core_add_admin_notice( $notice = '', $type = 'updated' ) {
+
+	// Do not add if the notice is empty
+	if ( empty( $notice ) ) {
+		return;
+	}
+
+	// Double check the object before referencing it
+	if ( ! isset( buddypress()->admin->notices ) ) {
+		buddypress()->admin->notices = array();
+	}
+
+	// Add the notice
+	buddypress()->admin->notices[] = array(
+		'message' => $notice,
+		'type'    => $type,
+	);
+}
+
+/**
+ * Verify that some BP prerequisites are set up properly, and notify the admin if not.
+ *
+ * On every Dashboard page, this function checks the following:
+ *   - that pretty permalinks are enabled.
+ *   - that every BP component that needs a WP page for a directory has one.
+ *   - that no WP page has multiple BP components associated with it.
+ * The administrator will be shown a notice for each check that fails.
+ *
+ * @global WPDB $wpdb WordPress DB object
+ * @global WP_Rewrite $wp_rewrite
+ * @since 1.2.0
+ */
+function bp_core_activation_notice() {
+	global $wp_rewrite, $wpdb;
+
+	// Only the super admin gets warnings
+	if ( ! bp_current_user_can( 'bp_moderate' ) ) {
+		return;
+	}
+
+	// Bail in user admin
+	if ( is_user_admin() ) {
+		return;
+	}
+
+	// On multisite installs, don't load on a non-root blog, unless do_network_admin is overridden
+	if ( is_multisite() && bp_core_do_network_admin() && ! bp_is_root_blog() ) {
+		return;
+	}
+
+	// Bail if in network admin, and BuddyPress is not network activated
+	if ( is_network_admin() && ! bp_is_network_activated() ) {
+		return;
+	}
+
+	/**
+	 * Check to make sure that the blog setup routine has run. This can't
+	 * happen during the wizard because of the order which the components
+	 * are loaded.
+	 */
+	if ( bp_is_active( 'blogs' ) ) {
+		$bp    = buddypress();
+		$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$bp->blogs->table_name}" );
+
+		if ( empty( $count ) ) {
+			bp_blogs_record_existing_blogs();
+		}
+	}
+
+	// Add notice if no rewrite rules are enabled
+	if ( empty( $wp_rewrite->permalink_structure ) ) {
+		bp_core_add_admin_notice( sprintf( __( '<strong>BuddyPress is almost ready</strong>. You must <a href="%s">update your permalink structure</a> to something other than the default for it to work.', 'buddypress' ), admin_url( 'options-permalink.php' ) ), 'error' );
+	}
+
+	// Get BuddyPress instance
+	$bp = buddypress();
+
+	/**
+	 * Check for orphaned BP components (BP component is enabled, no WP page exists)
+	 */
+	$orphaned_components = array();
+	$wp_page_components  = array();
+
+	// Only components with 'has_directory' require a WP page to function
+	foreach( array_keys( $bp->loaded_components ) as $component_id ) {
+		if ( !empty( $bp->{$component_id}->has_directory ) ) {
+			$wp_page_components[] = array(
+				'id'   => $component_id,
+				'name' => isset( $bp->{$component_id}->name ) ? $bp->{$component_id}->name : ucwords( $bp->{$component_id}->id )
+			);
+		}
+	}
+
+	// Activate and Register are special cases. They are not components but they need WP pages.
+	// If user registration is disabled, we can skip this step.
+	if ( bp_get_signup_allowed() ) {
+		$wp_page_components[] = array(
+			'id'   => 'activate',
+			'name' => __( 'Activate', 'buddypress' )
+		);
+
+		$wp_page_components[] = array(
+			'id'   => 'register',
+			'name' => __( 'Register', 'buddypress' )
+		);
+	}
+
+	// On the first admin screen after a new installation, this isn't set, so grab it to suppress a misleading error message.
+	if ( empty( $bp->pages->members ) ) {
+		$bp->pages = bp_core_get_directory_pages();
+	}
+
+	foreach( $wp_page_components as $component ) {
+		if ( !isset( $bp->pages->{$component['id']} ) ) {
+			$orphaned_components[] = $component['name'];
+		}
+	}
+
+	// Special case: If the Forums component is orphaned, but the bbPress 1.x installation is
+	// not correctly set up, don't show a nag. (In these cases, it's probably the case that the
+	// user is using bbPress 2.x; see https://buddypress.trac.wordpress.org/ticket/4292
+	if ( isset( $bp->forums->name ) && in_array( $bp->forums->name, $orphaned_components ) && !bp_forums_is_installed_correctly() ) {
+		$forum_key = array_search( $bp->forums->name, $orphaned_components );
+		unset( $orphaned_components[$forum_key] );
+		$orphaned_components = array_values( $orphaned_components );
+	}
+
+	if ( !empty( $orphaned_components ) ) {
+		$admin_url = bp_get_admin_url( add_query_arg( array( 'page' => 'bp-page-settings' ), 'admin.php' ) );
+		$notice    = sprintf( __( 'The following active BuddyPress Components do not have associated WordPress Pages: %2$s. <a href="%1$s">Repair</a>', 'buddypress' ), esc_url( $admin_url ), '<strong>' . implode( '</strong>, <strong>', $orphaned_components ) . '</strong>' );
+
+		bp_core_add_admin_notice( $notice );
+	}
+
+	// BP components cannot share a single WP page. Check for duplicate assignments, and post a message if found.
+	$dupe_names = array();
+	$page_ids   = (array)bp_core_get_directory_page_ids();
+	$dupes      = array_diff_assoc( $page_ids, array_unique( $page_ids ) );
+
+	if ( !empty( $dupes ) ) {
+		foreach( array_keys( $dupes ) as $dupe_component ) {
+			$dupe_names[] = $bp->pages->{$dupe_component}->title;
+		}
+
+		// Make sure that there are no duplicate duplicates :)
+		$dupe_names = array_unique( $dupe_names );
+	}
+
+	// If there are duplicates, post a message about them
+	if ( !empty( $dupe_names ) ) {
+		$admin_url = bp_get_admin_url( add_query_arg( array( 'page' => 'bp-page-settings' ), 'admin.php' ) );
+		$notice    = sprintf( __( 'Each BuddyPress Component needs its own WordPress page. The following WordPress Pages have more than one component associated with them: %2$s. <a href="%1$s">Repair</a>', 'buddypress' ), esc_url( $admin_url ), '<strong>' . implode( '</strong>, <strong>', $dupe_names ) . '</strong>' );
+
+		bp_core_add_admin_notice( $notice );
+	}
+}
+
+/**
+ * Redirect user to BuddyPress's What's New page on activation.
+ *
+ * @since 1.7.0
+ *
+ * @internal Used internally to redirect BuddyPress to the about page on activation.
+ *
+ * @uses get_transient() To see if transient to redirect exists.
+ * @uses delete_transient() To delete the transient if it exists.
+ * @uses is_network_admin() To bail if being network activated.
+ * @uses wp_safe_redirect() To redirect.
+ * @uses add_query_arg() To help build the URL to redirect to.
+ * @uses admin_url() To get the admin URL to index.php.
+ */
+function bp_do_activation_redirect() {
+
+	// Bail if no activation redirect
+	if ( ! get_transient( '_bp_activation_redirect' ) ) {
+		return;
+	}
+
+	// Delete the redirect transient
+	delete_transient( '_bp_activation_redirect' );
+
+	// Bail if activating from network, or bulk
+	if ( isset( $_GET['activate-multi'] ) ) {
+		return;
+	}
+
+	$query_args = array( 'page' => 'bp-about' );
+	if ( get_transient( '_bp_is_new_install' ) ) {
+		$query_args['is_new_install'] = '1';
+		delete_transient( '_bp_is_new_install' );
+	}
+
+	// Redirect to BuddyPress about page
+	wp_safe_redirect( add_query_arg( $query_args, bp_get_admin_url( 'index.php' ) ) );
+}
+
+/** UI/Styling ****************************************************************/
+
+/**
+ * Output the tabs in the admin area.
+ *
+ * @since 1.5.0
+ *
+ * @param string $active_tab Name of the tab that is active. Optional.
+ */
+function bp_core_admin_tabs( $active_tab = '' ) {
+	$tabs_html    = '';
+	$idle_class   = 'nav-tab';
+	$active_class = 'nav-tab nav-tab-active';
+
+	/**
+	 * Filters the admin tabs to be displayed.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param array $value Array of tabs to output to the admin area.
+	 */
+	$tabs         = apply_filters( 'bp_core_admin_tabs', bp_core_get_admin_tabs( $active_tab ) );
+
+	// Loop through tabs and build navigation
+	foreach ( array_values( $tabs ) as $tab_data ) {
+		$is_current = (bool) ( $tab_data['name'] == $active_tab );
+		$tab_class  = $is_current ? $active_class : $idle_class;
+		$tabs_html .= '<a href="' . esc_url( $tab_data['href'] ) . '" class="' . esc_attr( $tab_class ) . '">' . esc_html( $tab_data['name'] ) . '</a>';
+	}
+
+	echo $tabs_html;
+
+	/**
+	 * Fires after the output of tabs for the admin area.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_admin_tabs' );
+}
+
+/**
+ * Get the data for the tabs in the admin area.
+ *
+ * @since 2.2.0
+ *
+ * @param string $active_tab Name of the tab that is active. Optional.
+ *
+ * @return string
+ */
+function bp_core_get_admin_tabs( $active_tab = '' ) {
+	$tabs = array(
+		'0' => array(
+			'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-components' ), 'admin.php' ) ),
+			'name' => __( 'Components', 'buddypress' )
+		),
+		'1' => array(
+			'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-page-settings' ), 'admin.php' ) ),
+			'name' => __( 'Pages', 'buddypress' )
+		),
+		'2' => array(
+			'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-settings' ), 'admin.php' ) ),
+			'name' => __( 'Settings', 'buddypress' )
+		),
+	);
+
+	// If forums component is active, add additional tab
+	if ( bp_is_active( 'forums' ) && class_exists( 'BP_Forums_Component' ) ) {
+
+		// enqueue thickbox
+		wp_enqueue_script( 'thickbox' );
+		wp_enqueue_style( 'thickbox' );
+
+		$tabs['3'] = array(
+			'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bb-forums-setup'  ), 'admin.php' ) ),
+			'name' => __( 'Forums', 'buddypress' )
+		);
+	}
+
+	/**
+	 * Filters the tab data used in our wp-admin screens.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $tabs Tab data.
+	 */
+	return apply_filters( 'bp_core_get_admin_tabs', $tabs );
+}
+
+/** Help **********************************************************************/
+
+/**
+ * adds contextual help to BuddyPress admin pages.
+ *
+ * @since 1.7.0
+ * @todo Make this part of the BP_Component class and split into each component
+ *
+ * @param string $screen
+ */
+function bp_core_add_contextual_help( $screen = '' ) {
+
+	$screen = get_current_screen();
+
+	switch ( $screen->id ) {
+
+		// Component page
+		case 'settings_page_bp-components' :
+
+			// help tabs
+			$screen->add_help_tab( array(
+				'id'      => 'bp-comp-overview',
+				'title'   => __( 'Overview', 'buddypress' ),
+				'content' => bp_core_add_contextual_help_content( 'bp-comp-overview' ),
+			) );
+
+			// help panel - sidebar links
+			$screen->set_help_sidebar(
+				'<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
+				'<p>' . __( '<a href="https://codex.buddypress.org/getting-started/configure-components/">Managing Components</a>', 'buddypress' ) . '</p>' .
+				'<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
+			);
+			break;
+
+		// Pages page
+		case 'settings_page_bp-page-settings' :
+
+			// Help tabs
+			$screen->add_help_tab( array(
+				'id' => 'bp-page-overview',
+				'title' => __( 'Overview', 'buddypress' ),
+				'content' => bp_core_add_contextual_help_content( 'bp-page-overview' ),
+			) );
+
+			// Help panel - sidebar links
+			$screen->set_help_sidebar(
+				'<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
+				'<p>' . __( '<a href="https://codex.buddypress.org/getting-started/configure-components/#settings-buddypress-pages">Managing Pages</a>', 'buddypress' ) . '</p>' .
+				'<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
+			);
+
+			break;
+
+		// Settings page
+		case 'settings_page_bp-settings' :
+
+			// Help tabs
+			$screen->add_help_tab( array(
+				'id'      => 'bp-settings-overview',
+				'title'   => __( 'Overview', 'buddypress' ),
+				'content' => bp_core_add_contextual_help_content( 'bp-settings-overview' ),
+			) );
+
+			// Help panel - sidebar links
+			$screen->set_help_sidebar(
+				'<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
+				'<p>' . __( '<a href="https://codex.buddypress.org/getting-started/configure-components/#settings-buddypress-settings">Managing Settings</a>', 'buddypress' ) . '</p>' .
+				'<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
+			);
+
+			break;
+
+		// Profile fields page
+		case 'users_page_bp-profile-setup' :
+
+			// Help tabs
+			$screen->add_help_tab( array(
+				'id'      => 'bp-profile-overview',
+				'title'   => __( 'Overview', 'buddypress' ),
+				'content' => bp_core_add_contextual_help_content( 'bp-profile-overview' ),
+			) );
+
+			// Help panel - sidebar links
+			$screen->set_help_sidebar(
+				'<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
+				'<p>' . __( '<a href="https://codex.buddypress.org/administrator-guide/extended-profiles/">Managing Profile Fields</a>', 'buddypress' ) . '</p>' .
+				'<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
+			);
+
+			break;
+	}
+}
+add_action( 'contextual_help', 'bp_core_add_contextual_help' );
+
+/**
+ * renders contextual help content to contextual help tabs.
+ *
+ * @since 1.7.0
+ *
+ * @param string $tab
+ *
+ * @return string
+ */
+function bp_core_add_contextual_help_content( $tab = '' ) {
+
+	switch ( $tab ) {
+		case 'bp-comp-overview' :
+			$retval = __( 'By default, all but four of the BuddyPress components are enabled. You can selectively enable or disable any of the components by using the form below. Your BuddyPress installation will continue to function. However, the features of the disabled components will no longer be accessible to anyone using the site.', 'buddypress' );
+			break;
+
+		case 'bp-page-overview' :
+			$retval = __( 'BuddyPress Components use WordPress Pages for their root directory/archive pages. You can change the page associations for each active component by using the form below.', 'buddypress' );
+			break;
+
+		case 'bp-settings-overview' :
+			$retval = __( 'Extra configuration settings are provided and activated. You can selectively enable or disable any setting by using the form on this screen.', 'buddypress' );
+			break;
+
+		case 'bp-profile-overview' :
+			$retval = __( 'Your users will distinguish themselves through their profile page. Create relevant profile fields that will show on each users profile.', 'buddypress' ) . '<br /><br />' . __( 'Note: Any fields in the first group will appear on the signup page.', 'buddypress' );
+			break;
+
+		default:
+			$retval = false;
+			break;
+	}
+
+	// Wrap text in a paragraph tag
+	if ( !empty( $retval ) ) {
+		$retval = '<p>' . $retval . '</p>';
+	}
+
+	return $retval;
+}
+
+/** Separator *****************************************************************/
+
+/**
+ * Add a separator to the WordPress admin menus.
+ *
+ * @since 1.7.0
+ *
+ * @uses bp_current_user_can() To check users capability on root blog.
+ */
+function bp_admin_separator() {
+
+	// Bail if BuddyPress is not network activated and viewing network admin
+	if ( is_network_admin() && ! bp_is_network_activated() ) {
+		return;
+	}
+
+	// Bail if BuddyPress is network activated and viewing site admin
+	if ( ! is_network_admin() && bp_is_network_activated() ) {
+		return;
+	}
+
+	// Prevent duplicate separators when no core menu items exist
+	if ( ! bp_current_user_can( 'bp_moderate' ) ) {
+		return;
+	}
+
+	// Bail if there are no components with admin UI's. Hardcoded for now, until
+	// there's a real API for determining this later.
+	if ( ! bp_is_active( 'activity' ) && ! bp_is_active( 'groups' ) ) {
+		return;
+	}
+
+	global $menu;
+
+	$menu[] = array( '', 'read', 'separator-buddypress', '', 'wp-menu-separator buddypress' );
+}
+
+/**
+ * Tell WordPress we have a custom menu order.
+ *
+ * @since 1.7.0
+ * @uses bp_current_user_can() To check users capability on root blog.
+ *
+ * @param bool $menu_order Menu order.
+ *
+ * @return bool Always true
+ */
+function bp_admin_custom_menu_order( $menu_order = false ) {
+
+	// Bail if user cannot see admin pages
+	if ( ! bp_current_user_can( 'bp_moderate' ) ) {
+		return $menu_order;
+	}
+
+	return true;
+}
+
+/**
+ * Move our custom separator above our custom post types.
+ *
+ * @since 1.7.0
+ * @uses bp_current_user_can() To check users capability on root blog.
+ *
+ * @param array $menu_order Menu Order.
+ *
+ * @return array Modified menu order
+ */
+function bp_admin_menu_order( $menu_order = array() ) {
+
+	// Bail if user cannot see admin pages
+	if ( empty( $menu_order ) || ! bp_current_user_can( 'bp_moderate' ) ) {
+		return $menu_order;
+	}
+
+	// Initialize our custom order array
+	$bp_menu_order = array();
+
+	// Menu values
+	$last_sep     = is_network_admin() ? 'separator1' : 'separator2';
+
+	/**
+	 * Filters the custom admin menus.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $value Empty array.
+	 */
+	$custom_menus = (array) apply_filters( 'bp_admin_menu_order', array() );
+
+	// Bail if no components have top level admin pages
+	if ( empty( $custom_menus ) ) {
+		return $menu_order;
+	}
+
+	// Add our separator to beginning of array
+	array_unshift( $custom_menus, 'separator-buddypress' );
+
+	// Loop through menu order and do some rearranging
+	foreach ( (array) $menu_order as $item ) {
+
+		// Position BuddyPress menus above appearance
+		if ( $last_sep == $item ) {
+
+			// Add our custom menus
+			foreach( (array) $custom_menus as $custom_menu ) {
+				if ( array_search( $custom_menu, $menu_order ) ) {
+					$bp_menu_order[] = $custom_menu;
+				}
+			}
+
+			// Add the appearance separator
+			$bp_menu_order[] = $last_sep;
+
+		// Skip our menu items
+		} elseif ( ! in_array( $item, $custom_menus ) ) {
+			$bp_menu_order[] = $item;
+		}
+	}
+
+	// Return our custom order
+	return $bp_menu_order;
+}
+
+/** Utility  *****************************************************************/
+
+/**
+ * When using a WP_List_Table, get the currently selected bulk action.
+ *
+ * WP_List_Tables have bulk actions at the top and at the bottom of the tables,
+ * and the inputs have different keys in the $_REQUEST array. This function
+ * reconciles the two values and returns a single action being performed.
+ *
+ * @since 1.7.0
+ *
+ * @return string
+ */
+function bp_admin_list_table_current_bulk_action() {
+
+	$action = ! empty( $_REQUEST['action'] ) ? $_REQUEST['action'] : '';
+
+	// If the bottom is set, let it override the action
+	if ( ! empty( $_REQUEST['action2'] ) && $_REQUEST['action2'] != "-1" ) {
+		$action = $_REQUEST['action2'];
+	}
+
+	return $action;
+}
+
+/** Menus *********************************************************************/
+
+/**
+ * Register meta box and associated JS for BuddyPress WP Nav Menu.
+ *
+ * @since 1.9.0
+ */
+function bp_admin_wp_nav_menu_meta_box() {
+	if ( ! bp_is_root_blog() ) {
+		return;
+	}
+
+	add_meta_box( 'add-buddypress-nav-menu', __( 'BuddyPress', 'buddypress' ), 'bp_admin_do_wp_nav_menu_meta_box', 'nav-menus', 'side', 'default' );
+
+	add_action( 'admin_print_footer_scripts', 'bp_admin_wp_nav_menu_restrict_items' );
+}
+
+/**
+ * Build and populate the BuddyPress accordion on Appearance > Menus.
+ *
+ * @since 1.9.0
+ *
+ * @global $nav_menu_selected_id
+ */
+function bp_admin_do_wp_nav_menu_meta_box() {
+	global $nav_menu_selected_id;
+
+	$walker = new BP_Walker_Nav_Menu_Checklist( false );
+	$args   = array( 'walker' => $walker );
+
+	$post_type_name = 'buddypress';
+
+	$tabs = array();
+
+	$tabs['loggedin']['label']  = __( 'Logged-In', 'buddypress' );
+	$tabs['loggedin']['pages']  = bp_nav_menu_get_loggedin_pages();
+
+	$tabs['loggedout']['label'] = __( 'Logged-Out', 'buddypress' );
+	$tabs['loggedout']['pages'] = bp_nav_menu_get_loggedout_pages();
+
+	?>
+
+	<div id="buddypress-menu" class="posttypediv">
+		<h4><?php _e( 'Logged-In', 'buddypress' ) ?></h4>
+		<p><?php _e( '<em>Logged-In</em> links are relative to the current user, and are not visible to visitors who are not logged in.', 'buddypress' ) ?></p>
+
+		<div id="tabs-panel-posttype-<?php echo $post_type_name; ?>-loggedin" class="tabs-panel tabs-panel-active">
+			<ul id="buddypress-menu-checklist-loggedin" class="categorychecklist form-no-clear">
+				<?php echo walk_nav_menu_tree( array_map( 'wp_setup_nav_menu_item', $tabs['loggedin']['pages'] ), 0, (object) $args );?>
+			</ul>
+		</div>
+
+		<h4><?php _e( 'Logged-Out', 'buddypress' ) ?></h4>
+		<p><?php _e( '<em>Logged-Out</em> links are not visible to users who are logged in.', 'buddypress' ) ?></p>
+
+		<div id="tabs-panel-posttype-<?php echo $post_type_name; ?>-loggedout" class="tabs-panel tabs-panel-active">
+			<ul id="buddypress-menu-checklist-loggedout" class="categorychecklist form-no-clear">
+				<?php echo walk_nav_menu_tree( array_map( 'wp_setup_nav_menu_item', $tabs['loggedout']['pages'] ), 0, (object) $args );?>
+			</ul>
+		</div>
+
+		<p class="button-controls">
+			<span class="add-to-menu">
+				<input type="submit"<?php if ( function_exists( 'wp_nav_menu_disabled_check' ) ) : wp_nav_menu_disabled_check( $nav_menu_selected_id ); endif; ?> class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu', 'buddypress' ); ?>" name="add-custom-menu-item" id="submit-buddypress-menu" />
+				<span class="spinner"></span>
+			</span>
+		</p>
+	</div><!-- /#buddypress-menu -->
+
+	<?php
+}
+
+/**
+ * Restrict various items from view if editing a BuddyPress menu.
+ *
+ * If a person is editing a BP menu item, that person should not be able to
+ * see or edit the following fields:
+ *
+ * - CSS Classes - We use the 'bp-menu' CSS class to determine if the
+ *   menu item belongs to BP, so we cannot allow manipulation of this field to
+ *   occur.
+ * - URL - This field is automatically generated by BP on output, so this
+ *   field is useless and can cause confusion.
+ *
+ * Note: These restrictions are only enforced if JavaScript is enabled.
+ *
+ * @since 1.9.0
+ */
+function bp_admin_wp_nav_menu_restrict_items() {
+?>
+	<script type="text/javascript">
+	jQuery( '#menu-to-edit').on( 'click', 'a.item-edit', function() {
+		var settings  = jQuery(this).closest( '.menu-item-bar' ).next( '.menu-item-settings' );
+		var css_class = settings.find( '.edit-menu-item-classes' );
+
+		if( css_class.val().indexOf( 'bp-menu' ) === 0 ) {
+			css_class.attr( 'readonly', 'readonly' );
+			settings.find( '.field-url' ).css( 'display', 'none' );
+		}
+	});
+	</script>
+<?php
+}
+
+/**
+ * Add "Mark as Spam/Ham" button to user row actions.
+ *
+ * @since 2.0.0
+ *
+ * @param array  $actions     User row action links.
+ * @param object $user_object Current user information.
+ *
+ * @return array $actions User row action links.
+ */
+function bp_core_admin_user_row_actions( $actions, $user_object ) {
+
+	// Setup the $user_id variable from the current user object
+	$user_id = 0;
+	if ( !empty( $user_object->ID ) ) {
+		$user_id = absint( $user_object->ID );
+	}
+
+	// Bail early if user cannot perform this action, or is looking at themselves
+	if ( current_user_can( 'edit_user', $user_id ) && ( bp_loggedin_user_id() !== $user_id ) ) {
+
+		// Admin URL could be single site or network
+		$url = bp_get_admin_url( 'users.php' );
+
+		// If spammed, create unspam link
+		if ( bp_is_user_spammer( $user_id ) ) {
+			$url             = add_query_arg( array( 'action' => 'ham', 'user' => $user_id ), $url );
+			$unspam_link     = wp_nonce_url( $url, 'bp-spam-user' );
+			$actions['ham']  = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $unspam_link ), esc_html__( 'Not Spam', 'buddypress' ) );
+
+		// If not already spammed, create spam link
+		} else {
+			$url             = add_query_arg( array( 'action' => 'spam', 'user' => $user_id ), $url );
+			$spam_link       = wp_nonce_url( $url, 'bp-spam-user' );
+			$actions['spam'] = sprintf( '<a class="submitdelete" href="%1$s">%2$s</a>', esc_url( $spam_link ), esc_html__( 'Spam', 'buddypress' ) );
+		}
+	}
+
+	// Create a "View" link
+	$url             = bp_core_get_user_domain( $user_id );
+	$actions['view'] = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $url ), esc_html__( 'View', 'buddypress' ) );
+
+	// Return new actions
+	return $actions;
+}
+
+/**
+ * Catch requests to mark individual users as spam/ham from users.php.
+ *
+ * @since 2.0.0
+ */
+function bp_core_admin_user_manage_spammers() {
+
+	// Print our inline scripts on non-Multisite
+	add_action( 'admin_footer', 'bp_core_admin_user_spammed_js' );
+
+	$action  = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : false;
+	$updated = isset( $_REQUEST['updated'] ) ? $_REQUEST['updated'] : false;
+	$mode    = isset( $_POST['mode'] ) ? $_POST['mode'] : false;
+
+	// if this is a multisite, bulk request, stop now!
+	if ( 'list' == $mode ) {
+		return;
+	}
+
+	// Process a spam/ham request
+	if ( ! empty( $action ) && in_array( $action, array( 'spam', 'ham' ) ) ) {
+
+		check_admin_referer( 'bp-spam-user' );
+
+		$user_id = ! empty( $_REQUEST['user'] ) ? intval( $_REQUEST['user'] ) : false;
+
+		if ( empty( $user_id ) ) {
+			return;
+		}
+
+		$redirect = wp_get_referer();
+
+		$status = ( $action == 'spam' ) ? 'spam' : 'ham';
+
+		// Process the user
+		bp_core_process_spammer_status( $user_id, $status );
+
+		$redirect = add_query_arg( array( 'updated' => 'marked-' . $status ), $redirect );
+
+		wp_redirect( $redirect );
+	}
+
+	// Display feedback
+	if ( ! empty( $updated ) && in_array( $updated, array( 'marked-spam', 'marked-ham' ) ) ) {
+
+		if ( 'marked-spam' === $updated ) {
+			$notice = __( 'User marked as spammer. Spam users are visible only to site admins.', 'buddypress' );
+		} else {
+			$notice = __( 'User removed from spam.', 'buddypress' );
+		}
+
+		bp_core_add_admin_notice( $notice );
+	}
+}
+
+/**
+ * Inline script that adds the 'site-spammed' class to spammed users.
+ *
+ * @since 2.0.0
+ */
+function bp_core_admin_user_spammed_js() {
+	?>
+	<script type="text/javascript">
+		jQuery( document ).ready( function($) {
+			$( '.row-actions .ham' ).each( function() {
+				$( this ).closest( 'tr' ).addClass( 'site-spammed' );
+			});
+		});
+	</script>
+	<?php
+}
Index: /tags/2.4.3/src/bp-core/admin/bp-core-admin-schema.php
===================================================================
--- /tags/2.4.3/src/bp-core/admin/bp-core-admin-schema.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/admin/bp-core-admin-schema.php	(revision 10410)
@@ -0,0 +1,526 @@
+<?php
+/**
+ * BuddyPress DB schema.
+ *
+ * @package BuddyPress
+ * @subpackage CoreAdministration
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Get the DB schema to use for BuddyPress components.
+ *
+ * @since 1.1.0
+ *
+ * @global $wpdb $wpdb
+ * @return string The default database character-set, if set.
+ */
+function bp_core_set_charset() {
+	global $wpdb;
+
+	require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
+
+	return !empty( $wpdb->charset ) ? "DEFAULT CHARACTER SET {$wpdb->charset}" : '';
+}
+
+/**
+ * Main installer.
+ *
+ * Can be passed an optional array of components to explicitly run installation
+ * routines on, typically the first time a component is activated in Settings.
+ *
+ * @since 1.0.0
+ *
+ * @param array|bool $active_components Components to install.
+ */
+function bp_core_install( $active_components = false ) {
+
+	bp_pre_schema_upgrade();
+
+	// If no components passed, get all the active components from the main site
+	if ( empty( $active_components ) ) {
+
+		/** This filter is documented in bp-core/admin/bp-core-admin-components.php */
+		$active_components = apply_filters( 'bp_active_components', bp_get_option( 'bp-active-components' ) );
+	}
+
+	// Install Activity Streams even when inactive (to store last_activity data)
+	bp_core_install_activity_streams();
+
+	// Install the signups table
+	bp_core_maybe_install_signups();
+
+	// Notifications
+	if ( !empty( $active_components['notifications'] ) ) {
+		bp_core_install_notifications();
+	}
+
+	// Friend Connections
+	if ( !empty( $active_components['friends'] ) ) {
+		bp_core_install_friends();
+	}
+
+	// Extensible Groups
+	if ( !empty( $active_components['groups'] ) ) {
+		bp_core_install_groups();
+	}
+
+	// Private Messaging
+	if ( !empty( $active_components['messages'] ) ) {
+		bp_core_install_private_messaging();
+	}
+
+	// Extended Profiles
+	if ( !empty( $active_components['xprofile'] ) ) {
+		bp_core_install_extended_profiles();
+	}
+
+	// Blog tracking
+	if ( !empty( $active_components['blogs'] ) ) {
+		bp_core_install_blog_tracking();
+	}
+}
+
+/**
+ * Install database tables for the Notifications component.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_core_set_charset()
+ * @uses bp_core_get_table_prefix()
+ * @uses dbDelta()
+ */
+function bp_core_install_notifications() {
+	$sql             = array();
+	$charset_collate = bp_core_set_charset();
+	$bp_prefix       = bp_core_get_table_prefix();
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_notifications (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				user_id bigint(20) NOT NULL,
+				item_id bigint(20) NOT NULL,
+				secondary_item_id bigint(20),
+				component_name varchar(75) NOT NULL,
+				component_action varchar(75) NOT NULL,
+				date_notified datetime NOT NULL,
+				is_new bool NOT NULL DEFAULT 0,
+				KEY item_id (item_id),
+				KEY secondary_item_id (secondary_item_id),
+				KEY user_id (user_id),
+				KEY is_new (is_new),
+				KEY component_name (component_name),
+				KEY component_action (component_action),
+				KEY useritem (user_id,is_new)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_notifications_meta (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				notification_id bigint(20) NOT NULL,
+				meta_key varchar(255) DEFAULT NULL,
+				meta_value longtext DEFAULT NULL,
+				KEY notification_id (notification_id),
+				KEY meta_key (meta_key(191))
+			) {$charset_collate};";
+
+	dbDelta( $sql );
+}
+
+/**
+ * Install database tables for the Activity component.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_core_set_charset()
+ * @uses bp_core_get_table_prefix()
+ * @uses dbDelta()
+ */
+function bp_core_install_activity_streams() {
+	$sql             = array();
+	$charset_collate = bp_core_set_charset();
+	$bp_prefix       = bp_core_get_table_prefix();
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_activity (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				user_id bigint(20) NOT NULL,
+				component varchar(75) NOT NULL,
+				type varchar(75) NOT NULL,
+				action text NOT NULL,
+				content longtext NOT NULL,
+				primary_link text NOT NULL,
+				item_id bigint(20) NOT NULL,
+				secondary_item_id bigint(20) DEFAULT NULL,
+				date_recorded datetime NOT NULL,
+				hide_sitewide bool DEFAULT 0,
+				mptt_left int(11) NOT NULL DEFAULT 0,
+				mptt_right int(11) NOT NULL DEFAULT 0,
+				is_spam tinyint(1) NOT NULL DEFAULT 0,
+				KEY date_recorded (date_recorded),
+				KEY user_id (user_id),
+				KEY item_id (item_id),
+				KEY secondary_item_id (secondary_item_id),
+				KEY component (component),
+				KEY type (type),
+				KEY mptt_left (mptt_left),
+				KEY mptt_right (mptt_right),
+				KEY hide_sitewide (hide_sitewide),
+				KEY is_spam (is_spam)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_activity_meta (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				activity_id bigint(20) NOT NULL,
+				meta_key varchar(255) DEFAULT NULL,
+				meta_value longtext DEFAULT NULL,
+				KEY activity_id (activity_id),
+				KEY meta_key (meta_key(191))
+			) {$charset_collate};";
+
+	dbDelta( $sql );
+}
+
+/**
+ * Install database tables for the Notifications component.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_core_set_charset()
+ * @uses bp_core_get_table_prefix()
+ * @uses dbDelta()
+ */
+function bp_core_install_friends() {
+	$sql             = array();
+	$charset_collate = bp_core_set_charset();
+	$bp_prefix       = bp_core_get_table_prefix();
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_friends (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				initiator_user_id bigint(20) NOT NULL,
+				friend_user_id bigint(20) NOT NULL,
+				is_confirmed bool DEFAULT 0,
+				is_limited bool DEFAULT 0,
+				date_created datetime NOT NULL,
+				KEY initiator_user_id (initiator_user_id),
+				KEY friend_user_id (friend_user_id)
+			) {$charset_collate};";
+
+	dbDelta( $sql );
+}
+
+/**
+ * Install database tables for the Groups component.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_core_set_charset()
+ * @uses bp_core_get_table_prefix()
+ * @uses dbDelta()
+ */
+function bp_core_install_groups() {
+	$sql             = array();
+	$charset_collate = bp_core_set_charset();
+	$bp_prefix       = bp_core_get_table_prefix();
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_groups (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				creator_id bigint(20) NOT NULL,
+				name varchar(100) NOT NULL,
+				slug varchar(200) NOT NULL,
+				description longtext NOT NULL,
+				status varchar(10) NOT NULL DEFAULT 'public',
+				enable_forum tinyint(1) NOT NULL DEFAULT '1',
+				date_created datetime NOT NULL,
+				KEY creator_id (creator_id),
+				KEY status (status)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_groups_members (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				group_id bigint(20) NOT NULL,
+				user_id bigint(20) NOT NULL,
+				inviter_id bigint(20) NOT NULL,
+				is_admin tinyint(1) NOT NULL DEFAULT '0',
+				is_mod tinyint(1) NOT NULL DEFAULT '0',
+				user_title varchar(100) NOT NULL,
+				date_modified datetime NOT NULL,
+				comments longtext NOT NULL,
+				is_confirmed tinyint(1) NOT NULL DEFAULT '0',
+				is_banned tinyint(1) NOT NULL DEFAULT '0',
+				invite_sent tinyint(1) NOT NULL DEFAULT '0',
+				KEY group_id (group_id),
+				KEY is_admin (is_admin),
+				KEY is_mod (is_mod),
+				KEY user_id (user_id),
+				KEY inviter_id (inviter_id),
+				KEY is_confirmed (is_confirmed)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_groups_groupmeta (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				group_id bigint(20) NOT NULL,
+				meta_key varchar(255) DEFAULT NULL,
+				meta_value longtext DEFAULT NULL,
+				KEY group_id (group_id),
+				KEY meta_key (meta_key(191))
+			) {$charset_collate};";
+
+	dbDelta( $sql );
+}
+
+/**
+ * Install database tables for the Messages component.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_core_set_charset()
+ * @uses bp_core_get_table_prefix()
+ * @uses dbDelta()
+ */
+function bp_core_install_private_messaging() {
+	$sql             = array();
+	$charset_collate = bp_core_set_charset();
+	$bp_prefix       = bp_core_get_table_prefix();
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_messages_messages (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				thread_id bigint(20) NOT NULL,
+				sender_id bigint(20) NOT NULL,
+				subject varchar(200) NOT NULL,
+				message longtext NOT NULL,
+				date_sent datetime NOT NULL,
+				KEY sender_id (sender_id),
+				KEY thread_id (thread_id)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_messages_recipients (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				user_id bigint(20) NOT NULL,
+				thread_id bigint(20) NOT NULL,
+				unread_count int(10) NOT NULL DEFAULT '0',
+				sender_only tinyint(1) NOT NULL DEFAULT '0',
+				is_deleted tinyint(1) NOT NULL DEFAULT '0',
+				KEY user_id (user_id),
+				KEY thread_id (thread_id),
+				KEY is_deleted (is_deleted),
+				KEY sender_only (sender_only),
+				KEY unread_count (unread_count)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_messages_notices (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				subject varchar(200) NOT NULL,
+				message longtext NOT NULL,
+				date_sent datetime NOT NULL,
+				is_active tinyint(1) NOT NULL DEFAULT '0',
+				KEY is_active (is_active)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_messages_meta (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				message_id bigint(20) NOT NULL,
+				meta_key varchar(255) DEFAULT NULL,
+				meta_value longtext DEFAULT NULL,
+				KEY message_id (message_id),
+				KEY meta_key (meta_key(191))
+			) {$charset_collate};";
+
+	dbDelta( $sql );
+}
+
+/**
+ * Install database tables for the Profiles component.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_core_set_charset()
+ * @uses bp_core_get_table_prefix()
+ * @uses dbDelta()
+ */
+function bp_core_install_extended_profiles() {
+	global $wpdb;
+
+	$sql             = array();
+	$charset_collate = bp_core_set_charset();
+	$bp_prefix       = bp_core_get_table_prefix();
+
+	// These values should only be updated if they are not already present
+	if ( ! bp_get_option( 'bp-xprofile-base-group-name' ) ) {
+		bp_update_option( 'bp-xprofile-base-group-name', _x( 'General', 'First field-group name', 'buddypress' ) );
+	}
+
+	if ( ! bp_get_option( 'bp-xprofile-fullname-field-name' ) ) {
+		bp_update_option( 'bp-xprofile-fullname-field-name', _x( 'Display Name', 'Display name field', 'buddypress' ) );
+	}
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_xprofile_groups (
+				id bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				name varchar(150) NOT NULL,
+				description mediumtext NOT NULL,
+				group_order bigint(20) NOT NULL DEFAULT '0',
+				can_delete tinyint(1) NOT NULL,
+				KEY can_delete (can_delete)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_xprofile_fields (
+				id bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				group_id bigint(20) unsigned NOT NULL,
+				parent_id bigint(20) unsigned NOT NULL,
+				type varchar(150) NOT NULL,
+				name varchar(150) NOT NULL,
+				description longtext NOT NULL,
+				is_required tinyint(1) NOT NULL DEFAULT '0',
+				is_default_option tinyint(1) NOT NULL DEFAULT '0',
+				field_order bigint(20) NOT NULL DEFAULT '0',
+				option_order bigint(20) NOT NULL DEFAULT '0',
+				order_by varchar(15) NOT NULL DEFAULT '',
+				can_delete tinyint(1) NOT NULL DEFAULT '1',
+				KEY group_id (group_id),
+				KEY parent_id (parent_id),
+				KEY field_order (field_order),
+				KEY can_delete (can_delete),
+				KEY is_required (is_required)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_xprofile_data (
+				id bigint(20) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				field_id bigint(20) unsigned NOT NULL,
+				user_id bigint(20) unsigned NOT NULL,
+				value longtext NOT NULL,
+				last_updated datetime NOT NULL,
+				KEY field_id (field_id),
+				KEY user_id (user_id)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_xprofile_meta (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				object_id bigint(20) NOT NULL,
+				object_type varchar(150) NOT NULL,
+				meta_key varchar(255) DEFAULT NULL,
+				meta_value longtext DEFAULT NULL,
+				KEY object_id (object_id),
+				KEY meta_key (meta_key(191))
+			) {$charset_collate};";
+
+	dbDelta( $sql );
+
+	// Insert the default group and fields
+	$insert_sql = array();
+
+	if ( ! $wpdb->get_var( "SELECT id FROM {$bp_prefix}bp_xprofile_groups WHERE id = 1" ) ) {
+		$insert_sql[] = "INSERT INTO {$bp_prefix}bp_xprofile_groups ( name, description, can_delete ) VALUES ( " . $wpdb->prepare( '%s', stripslashes( bp_get_option( 'bp-xprofile-base-group-name' ) ) ) . ", '', 0 );";
+	}
+
+	if ( ! $wpdb->get_var( "SELECT id FROM {$bp_prefix}bp_xprofile_fields WHERE id = 1" ) ) {
+		$insert_sql[] = "INSERT INTO {$bp_prefix}bp_xprofile_fields ( group_id, parent_id, type, name, description, is_required, can_delete ) VALUES ( 1, 0, 'textbox', " . $wpdb->prepare( '%s', stripslashes( bp_get_option( 'bp-xprofile-fullname-field-name' ) ) ) . ", '', 1, 0 );";
+	}
+
+	dbDelta( $insert_sql );
+}
+
+/**
+ * Install database tables for the Sites component.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_core_set_charset()
+ * @uses bp_core_get_table_prefix()
+ * @uses dbDelta()
+ */
+function bp_core_install_blog_tracking() {
+	$sql             = array();
+	$charset_collate = bp_core_set_charset();
+	$bp_prefix       = bp_core_get_table_prefix();
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_user_blogs (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				user_id bigint(20) NOT NULL,
+				blog_id bigint(20) NOT NULL,
+				KEY user_id (user_id),
+				KEY blog_id (blog_id)
+			) {$charset_collate};";
+
+	$sql[] = "CREATE TABLE {$bp_prefix}bp_user_blogs_blogmeta (
+				id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+				blog_id bigint(20) NOT NULL,
+				meta_key varchar(255) DEFAULT NULL,
+				meta_value longtext DEFAULT NULL,
+				KEY blog_id (blog_id),
+				KEY meta_key (meta_key(191))
+			) {$charset_collate};";
+
+	dbDelta( $sql );
+}
+
+/** Signups *******************************************************************/
+
+/**
+ * Install the signups table.
+ *
+ * @since 2.0.0
+ *
+ * @global $wpdb
+ * @uses wp_get_db_schema() to get WordPress ms_global schema
+ */
+function bp_core_install_signups() {
+	global $wpdb;
+
+	// Signups is not there and we need it so let's create it
+	require_once( buddypress()->plugin_dir . '/bp-core/admin/bp-core-admin-schema.php' );
+	require_once( ABSPATH                  . 'wp-admin/includes/upgrade.php'     );
+
+	// Never use bp_core_get_table_prefix() for any global users tables
+	$wpdb->signups = $wpdb->base_prefix . 'signups';
+
+	// Use WP's core CREATE TABLE query
+	$create_queries = wp_get_db_schema( 'ms_global' );
+	if ( ! is_array( $create_queries ) ) {
+		$create_queries = explode( ';', $create_queries );
+		$create_queries = array_filter( $create_queries );
+	}
+
+	// Filter out all the queries except wp_signups
+	foreach ( $create_queries as $key => $query ) {
+		if ( preg_match( "|CREATE TABLE ([^ ]*)|", $query, $matches ) ) {
+			if ( trim( $matches[1], '`' ) !== $wpdb->signups ) {
+				unset( $create_queries[ $key ] );
+			}
+		}
+	}
+
+	// Run WordPress's database upgrader
+	if ( ! empty( $create_queries ) ) {
+		dbDelta( $create_queries );
+	}
+}
+
+/**
+ * Update the signups table, adding `signup_id` column and drop `domain` index.
+ *
+ * This is necessary because WordPress's `pre_schema_upgrade()` function wraps
+ * table ALTER's in multisite checks, and other plugins may have installed their
+ * own sign-ups table; Eg: Gravity Forms User Registration Add On.
+ *
+ * @since 2.0.1
+ *
+ * @see pre_schema_upgrade()
+ * @link https://core.trac.wordpress.org/ticket/27855 WordPress Trac Ticket
+ * @link https://buddypress.trac.wordpress.org/ticket/5563 BuddyPress Trac Ticket
+ *
+ * @global WPDB $wpdb
+ */
+function bp_core_upgrade_signups() {
+	global $wpdb;
+
+	// Bail if global tables should not be upgraded
+	if ( defined( 'DO_NOT_UPGRADE_GLOBAL_TABLES' ) ) {
+		return;
+	}
+
+	// Never use bp_core_get_table_prefix() for any global users tables
+	$wpdb->signups = $wpdb->base_prefix . 'signups';
+
+	// Attempt to alter the signups table
+	$wpdb->query( "ALTER TABLE {$wpdb->signups} ADD signup_id BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST" );
+	$wpdb->query( "ALTER TABLE {$wpdb->signups} DROP INDEX domain" );
+}
Index: /tags/2.4.3/src/bp-core/admin/bp-core-admin-settings.php
===================================================================
--- /tags/2.4.3/src/bp-core/admin/bp-core-admin-settings.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/admin/bp-core-admin-settings.php	(revision 10410)
@@ -0,0 +1,407 @@
+<?php
+/**
+ * BuddyPress Admin Settings.
+ *
+ * @package BuddyPress
+ * @subpackage CoreAdministration
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Main settings section description for the settings page.
+ *
+ * @since 1.6.0
+ */
+function bp_admin_setting_callback_main_section() { }
+
+/**
+ * Admin bar for logged out users setting field.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_form_option() To output the option value.
+ */
+function bp_admin_setting_callback_admin_bar() {
+?>
+
+	<input id="hide-loggedout-adminbar" name="hide-loggedout-adminbar" type="checkbox" value="1" <?php checked( !bp_hide_loggedout_adminbar( false ) ); ?> />
+	<label for="hide-loggedout-adminbar"><?php _e( 'Show the Toolbar for logged out users', 'buddypress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Allow members to delete their accounts setting field.
+ *
+ * @since 1.6.0
+ *
+ * @uses checked() To display the checked attribute.
+ */
+function bp_admin_setting_callback_account_deletion() {
+?>
+
+	<input id="bp-disable-account-deletion" name="bp-disable-account-deletion" type="checkbox" value="1" <?php checked( !bp_disable_account_deletion( false ) ); ?> />
+	<label for="bp-disable-account-deletion"><?php _e( 'Allow registered members to delete their own accounts', 'buddypress' ); ?></label>
+
+<?php
+}
+
+/** Activity *******************************************************************/
+
+/**
+ * Groups settings section description for the settings page.
+ *
+ * @since 1.6.0
+ */
+function bp_admin_setting_callback_activity_section() { }
+
+/**
+ * Allow Akismet setting field.
+ *
+ * @since 1.6.0
+ *
+ * @uses checked() To display the checked attribute.
+ */
+function bp_admin_setting_callback_activity_akismet() {
+?>
+
+	<input id="_bp_enable_akismet" name="_bp_enable_akismet" type="checkbox" value="1" <?php checked( bp_is_akismet_active( true ) ); ?> />
+	<label for="_bp_enable_akismet"><?php _e( 'Allow Akismet to scan for activity stream spam', 'buddypress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Allow activity comments on blog posts and forum posts.
+ *
+ * @since 1.6.0
+ */
+function bp_admin_setting_callback_blogforum_comments() {
+?>
+
+	<input id="bp-disable-blogforum-comments" name="bp-disable-blogforum-comments" type="checkbox" value="1" <?php checked( !bp_disable_blogforum_comments( false ) ); ?> />
+	<label for="bp-disable-blogforum-comments"><?php _e( 'Allow activity stream commenting on blog and forum posts', 'buddypress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Allow Heartbeat to refresh activity stream.
+ *
+ * @since 2.0.0
+ */
+function bp_admin_setting_callback_heartbeat() {
+?>
+
+	<input id="_bp_enable_heartbeat_refresh" name="_bp_enable_heartbeat_refresh" type="checkbox" value="1" <?php checked( bp_is_activity_heartbeat_active( true ) ); ?> />
+	<label for="_bp_enable_heartbeat_refresh"><?php _e( 'Automatically check for new items while viewing the activity stream', 'buddypress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Sanitization for bp-disable-blogforum-comments setting.
+ *
+ * In the UI, a checkbox asks whether you'd like to *enable* blog/forum activity comments. For
+ * legacy reasons, the option that we store is 1 if these comments are *disabled*. So we use this
+ * function to flip the boolean before saving the intval.
+ *
+ * @since 1.6.0
+ *
+ * @param bool $value
+ *
+ * @return bool
+ */
+function bp_admin_sanitize_callback_blogforum_comments( $value = false ) {
+	return $value ? 0 : 1;
+}
+
+/** XProfile ******************************************************************/
+
+/**
+ * Profile settings section description for the settings page.
+ *
+ * @since 1.6.0
+ */
+function bp_admin_setting_callback_xprofile_section() { }
+
+/**
+ * Enable BP->WP profile syncing field.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_form_option() To output the option value.
+ */
+function bp_admin_setting_callback_profile_sync() {
+?>
+
+	<input id="bp-disable-profile-sync" name="bp-disable-profile-sync" type="checkbox" value="1" <?php checked( !bp_disable_profile_sync( false ) ); ?> />
+	<label for="bp-disable-profile-sync"><?php _e( 'Enable BuddyPress to WordPress profile syncing', 'buddypress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Allow members to upload avatars field.
+ *
+ * @since 1.6.0
+ *
+ * @uses checked() To display the checked attribute.
+ */
+function bp_admin_setting_callback_avatar_uploads() {
+?>
+
+	<input id="bp-disable-avatar-uploads" name="bp-disable-avatar-uploads" type="checkbox" value="1" <?php checked( !bp_disable_avatar_uploads( false ) ); ?> />
+	<label for="bp-disable-avatar-uploads"><?php _e( 'Allow registered members to upload avatars', 'buddypress' ); ?></label>
+
+<?php
+}
+
+/**
+ * Allow members to upload cover images field.
+ *
+ * @since 2.4.0
+ */
+function bp_admin_setting_callback_cover_image_uploads() {
+?>
+	<input id="bp-disable-cover-image-uploads" name="bp-disable-cover-image-uploads" type="checkbox" value="1" <?php checked( ! bp_disable_cover_image_uploads() ); ?> />
+	<label for="bp-disable-cover-image-uploads"><?php _e( 'Allow registered members to upload cover images', 'buddypress' ); ?></label>
+<?php
+}
+
+/** Groups Section ************************************************************/
+
+/**
+ * Groups settings section description for the settings page.
+ *
+ * @since 1.6.0
+ */
+function bp_admin_setting_callback_groups_section() { }
+
+/**
+ * Allow all users to create groups field.
+ *
+ * @since 1.6.0
+ *
+ * @uses checked() To display the checked attribute.
+ */
+function bp_admin_setting_callback_group_creation() {
+?>
+
+	<input id="bp_restrict_group_creation" name="bp_restrict_group_creation" type="checkbox"value="1" <?php checked( !bp_restrict_group_creation( false ) ); ?> />
+	<label for="bp_restrict_group_creation"><?php _e( 'Enable group creation for all users', 'buddypress' ); ?></label>
+	<p class="description"><?php _e( 'Administrators can always create groups, regardless of this setting.', 'buddypress' ); ?></p>
+
+<?php
+}
+
+/**
+ * 'Enable group avatars' field markup.
+ *
+ * @since 2.3.0
+ */
+function bp_admin_setting_callback_group_avatar_uploads() {
+?>
+	<input id="bp-disable-group-avatar-uploads" name="bp-disable-group-avatar-uploads" type="checkbox" value="1" <?php checked( ! bp_disable_group_avatar_uploads() ); ?> />
+	<label for="bp-disable-group-avatar-uploads"><?php _e( 'Allow customizable avatars for groups', 'buddypress' ); ?></label>
+<?php
+}
+
+/**
+ * 'Enable group cover images' field markup.
+ *
+ * @since 2.4.0
+ */
+function bp_admin_setting_callback_group_cover_image_uploads() {
+?>
+	<input id="bp-disable-group-cover-image-uploads" name="bp-disable-group-cover-image-uploads" type="checkbox" value="1" <?php checked( ! bp_disable_group_cover_image_uploads() ); ?> />
+	<label for="bp-disable-group-cover-image-uploads"><?php _e( 'Allow customizable cover images for groups', 'buddypress' ); ?></label>
+<?php
+}
+
+/** Forums Section ************************************************************/
+
+/**
+ * Forums settings section description for the settings page.
+ *
+ * @since 1.6.0
+ */
+function bp_admin_setting_callback_bbpress_section() { }
+
+/**
+ * bb-config.php location field.
+ *
+ * @since 1.6.0
+ * @uses checked() To display the checked attribute.
+ * @uses bp_get_option() To get the config location.
+ * @uses bp_form_option() To get the sanitized form option.
+ */
+function bp_admin_setting_callback_bbpress_configuration() {
+
+	$config_location = bp_get_option( 'bb-config-location' );
+	$file_exists     = (bool) ( file_exists( $config_location ) || is_file( $config_location ) ); ?>
+
+	<input name="bb-config-location" type="text" id="bb-config-location" value="<?php bp_form_option( 'bb-config-location', '' ); ?>" class="medium-text" style="width: 300px;" />
+
+	<?php if ( false === $file_exists ) : ?>
+
+		<a class="button" href="<?php bp_admin_url( 'admin.php?page=bb-forums-setup&repair=1' ); ?>" title="<?php esc_attr_e( 'Attempt to save a new config file.', 'buddypress' ); ?>"><?php _e( 'Repair', 'buddypress' ) ?></a>
+		<span class="attention"><?php _e( 'File does not exist', 'buddypress' ); ?></span>
+
+	<?php endif; ?>
+
+	<p class="description"><?php _e( 'Absolute path to your bbPress configuration file.', 'buddypress' ); ?></p>
+
+<?php
+}
+
+/** Settings Page *************************************************************/
+
+/**
+ * The main settings page
+ *
+ * @since 1.6.0
+ *
+ * @uses settings_fields() To output the hidden fields for the form.
+ * @uses do_settings_sections() To output the settings sections.
+ */
+function bp_core_admin_settings() {
+
+	// We're saving our own options, until the WP Settings API is updated to work with Multisite
+	$form_action = add_query_arg( 'page', 'bp-settings', bp_get_admin_url( 'admin.php' ) );
+
+	?>
+
+	<div class="wrap">
+
+		<h2 class="nav-tab-wrapper"><?php bp_core_admin_tabs( __( 'Settings', 'buddypress' ) ); ?></h2>
+
+		<form action="<?php echo esc_url( $form_action ) ?>" method="post">
+
+			<?php settings_fields( 'buddypress' ); ?>
+
+			<?php do_settings_sections( 'buddypress' ); ?>
+
+			<p class="submit">
+				<input type="submit" name="submit" class="button-primary" value="<?php esc_attr_e( 'Save Settings', 'buddypress' ); ?>" />
+			</p>
+		</form>
+	</div>
+
+<?php
+}
+
+/**
+ * Save our settings.
+ *
+ * @since 1.6.0
+ */
+function bp_core_admin_settings_save() {
+	global $wp_settings_fields;
+
+	if ( isset( $_GET['page'] ) && 'bp-settings' == $_GET['page'] && !empty( $_POST['submit'] ) ) {
+		check_admin_referer( 'buddypress-options' );
+
+		// Because many settings are saved with checkboxes, and thus will have no values
+		// in the $_POST array when unchecked, we loop through the registered settings
+		if ( isset( $wp_settings_fields['buddypress'] ) ) {
+			foreach( (array) $wp_settings_fields['buddypress'] as $section => $settings ) {
+				foreach( $settings as $setting_name => $setting ) {
+					$value = isset( $_POST[$setting_name] ) ? $_POST[$setting_name] : '';
+
+					bp_update_option( $setting_name, $value );
+				}
+			}
+		}
+
+		// Some legacy options are not registered with the Settings API, or are reversed in the UI.
+		$legacy_options = array(
+			'bp-disable-account-deletion',
+			'bp-disable-avatar-uploads',
+			'bp-disable-cover-image-uploads',
+			'bp-disable-group-avatar-uploads',
+			'bp-disable-group-cover-image-uploads',
+			'bp_disable_blogforum_comments',
+			'bp-disable-profile-sync',
+			'bp_restrict_group_creation',
+			'hide-loggedout-adminbar',
+		);
+
+		foreach( $legacy_options as $legacy_option ) {
+			// Note: Each of these options is represented by its opposite in the UI
+			// Ie, the Profile Syncing option reads "Enable Sync", so when it's checked,
+			// the corresponding option should be unset
+			$value = isset( $_POST[$legacy_option] ) ? '' : 1;
+			bp_update_option( $legacy_option, $value );
+		}
+
+		bp_core_redirect( add_query_arg( array( 'page' => 'bp-settings', 'updated' => 'true' ), bp_get_admin_url( 'admin.php' ) ) );
+	}
+}
+add_action( 'bp_admin_init', 'bp_core_admin_settings_save', 100 );
+
+/**
+ * Output settings API option.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_bp_form_option()
+ *
+ * @param string $option
+ * @param string $default
+ * @param bool   $slug
+ */
+function bp_form_option( $option, $default = '' , $slug = false ) {
+	echo bp_get_form_option( $option, $default, $slug );
+}
+	/**
+	 * Return settings API option
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses bp_get_option()
+	 * @uses esc_attr()
+	 * @uses apply_filters()
+	 *
+	 * @param string $option
+	 * @param string $default
+	 * @param bool   $slug
+	 *
+	 * @return string
+	 */
+	function bp_get_form_option( $option, $default = '', $slug = false ) {
+
+		// Get the option and sanitize it
+		$value = bp_get_option( $option, $default );
+
+		// Slug?
+		if ( true === $slug ) {
+
+			/**
+			 * Filters the slug value in the form field.
+			 *
+			 * @since 1.6.0
+			 *
+			 * @param string $value Value being returned for the requested option.
+			 */
+			$value = esc_attr( apply_filters( 'editable_slug', $value ) );
+		} else { // Not a slug
+			$value = esc_attr( $value );
+		}
+
+		// Fallback to default
+		if ( empty( $value ) )
+			$value = $default;
+
+		/**
+		 * Filters the settings API option.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $value  Value being returned for the requested option.
+		 * @param string $option Option whose value is being requested.
+		 */
+		return apply_filters( 'bp_get_form_option', $value, $option );
+	}
Index: /tags/2.4.3/src/bp-core/admin/bp-core-admin-slugs.php
===================================================================
--- /tags/2.4.3/src/bp-core/admin/bp-core-admin-slugs.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/admin/bp-core-admin-slugs.php	(revision 10410)
@@ -0,0 +1,267 @@
+<?php
+/**
+ * BuddyPress Admin Slug Functions.
+ *
+ * @package BuddyPress
+ * @subpackage CoreAdministration
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Renders the page mapping admin panel.
+ *
+ * @since 1.6.0
+ * @todo Use settings API
+ * @uses bp_core_admin_component_options()
+ */
+function bp_core_admin_slugs_settings() {
+?>
+
+	<div class="wrap">
+
+		<h2 class="nav-tab-wrapper"><?php bp_core_admin_tabs( __( 'Pages', 'buddypress' ) ); ?></h2>
+		<form action="" method="post" id="bp-admin-page-form">
+
+			<?php bp_core_admin_slugs_options(); ?>
+
+			<p class="submit clear">
+				<input class="button-primary" type="submit" name="bp-admin-pages-submit" id="bp-admin-pages-submit" value="<?php esc_attr_e( 'Save Settings', 'buddypress' ) ?>"/>
+			</p>
+
+			<?php wp_nonce_field( 'bp-admin-pages-setup' ); ?>
+
+		</form>
+	</div>
+
+<?php
+}
+
+/**
+ * Generate a list of directory pages, for use when building Components panel markup.
+ *
+ * @since 2.4.1
+ *
+ * @return array
+ */
+function bp_core_admin_get_directory_pages() {
+	$bp = buddypress();
+	$directory_pages = array();
+
+	// Loop through loaded components and collect directories
+	if ( is_array( $bp->loaded_components ) ) {
+		foreach( $bp->loaded_components as $component_slug => $component_id ) {
+
+			// Only components that need directories should be listed here
+			if ( isset( $bp->{$component_id} ) && !empty( $bp->{$component_id}->has_directory ) ) {
+
+				// component->name was introduced in BP 1.5, so we must provide a fallback
+				$directory_pages[$component_id] = !empty( $bp->{$component_id}->name ) ? $bp->{$component_id}->name : ucwords( $component_id );
+			}
+		}
+	}
+
+	/**
+	 * Filters the loaded components needing directory page association to a WordPress page.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $directory_pages Array of available components to set associations for.
+	 */
+	return apply_filters( 'bp_directory_pages', $directory_pages );
+}
+
+/**
+ * Generate a list of static pages, for use when building Components panel markup.
+ *
+ * By default, this list contains 'register' and 'activate'.
+ *
+ * @since 2.4.1
+ *
+ * @return array
+ */
+function bp_core_admin_get_static_pages() {
+	$static_pages = array(
+		'register' => __( 'Register', 'buddypress' ),
+		'activate' => __( 'Activate', 'buddypress' ),
+	);
+
+	/**
+	 * Filters the default static pages for BuddyPress setup.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $static_pages Array of static default static pages.
+	 */
+	return apply_filters( 'bp_static_pages', $static_pages );
+}
+
+/**
+ * Creates reusable markup for page setup on the Components and Pages dashboard panel.
+ *
+ * @package BuddyPress
+ * @since 1.6.0
+ * @todo Use settings API
+ */
+function bp_core_admin_slugs_options() {
+	$bp = buddypress();
+
+	// Get the existing WP pages
+	$existing_pages = bp_core_get_directory_page_ids();
+
+	// Set up an array of components (along with component names) that have directory pages.
+	$directory_pages = bp_core_admin_get_directory_pages();
+
+	/** Directory Display *****************************************************/
+
+	if ( !empty( $directory_pages ) ) : ?>
+
+		<h3><?php _e( 'Directories', 'buddypress' ); ?></h3>
+
+		<p><?php _e( 'Associate a WordPress Page with each BuddyPress component directory.', 'buddypress' ); ?></p>
+
+		<table class="form-table">
+			<tbody>
+
+				<?php foreach ( $directory_pages as $name => $label ) : ?>
+
+					<tr valign="top">
+						<th scope="row">
+							<label for="bp_pages[<?php echo esc_attr( $name ) ?>]"><?php echo esc_html( $label ) ?></label>
+						</th>
+
+						<td>
+
+							<?php if ( ! bp_is_root_blog() ) switch_to_blog( bp_get_root_blog_id() ); ?>
+
+							<?php echo wp_dropdown_pages( array(
+								'name'             => 'bp_pages[' . esc_attr( $name ) . ']',
+								'echo'             => false,
+								'show_option_none' => __( '- None -', 'buddypress' ),
+								'selected'         => !empty( $existing_pages[$name] ) ? $existing_pages[$name] : false
+							) ); ?>
+
+							<?php if ( !empty( $existing_pages[$name] ) ) : ?>
+
+								<a href="<?php echo get_permalink( $existing_pages[$name] ); ?>" class="button-secondary" target="_bp"><?php _e( 'View', 'buddypress' ); ?></a>
+
+							<?php endif; ?>
+
+							<?php if ( ! bp_is_root_blog() ) restore_current_blog(); ?>
+
+						</td>
+					</tr>
+
+
+				<?php endforeach ?>
+
+				<?php
+
+				/**
+				 * Fires after the display of default directories.
+				 *
+				 * Allows plugins to add their own directory associations.
+				 *
+				 * @since 1.5.0
+				 */
+				do_action( 'bp_active_external_directories' ); ?>
+
+			</tbody>
+		</table>
+
+	<?php
+
+	endif;
+
+	/** Static Display ********************************************************/
+
+	$static_pages = bp_core_admin_get_static_pages();
+
+	if ( !empty( $static_pages ) ) : ?>
+
+		<h3><?php _e( 'Registration', 'buddypress' ); ?></h3>
+
+		<p><?php _e( 'Associate WordPress Pages with the following BuddyPress Registration pages.', 'buddypress' ); ?></p>
+
+		<table class="form-table">
+			<tbody>
+
+				<?php foreach ( $static_pages as $name => $label ) : ?>
+
+					<tr valign="top">
+						<th scope="row">
+							<label for="bp_pages[<?php echo esc_attr( $name ) ?>]"><?php echo esc_html( $label ) ?></label>
+						</th>
+
+						<td>
+
+							<?php if ( ! bp_is_root_blog() ) switch_to_blog( bp_get_root_blog_id() ); ?>
+
+							<?php echo wp_dropdown_pages( array(
+								'name'             => 'bp_pages[' . esc_attr( $name ) . ']',
+								'echo'             => false,
+								'show_option_none' => __( '- None -', 'buddypress' ),
+								'selected'         => !empty( $existing_pages[$name] ) ? $existing_pages[$name] : false
+							) ) ?>
+
+							<?php if ( !empty( $existing_pages[$name] ) ) : ?>
+
+								<a href="<?php echo get_permalink( $existing_pages[$name] ); ?>" class="button-secondary" target="_bp"><?php _e( 'View', 'buddypress' ); ?></a>
+
+							<?php endif; ?>
+
+							<?php if ( ! bp_is_root_blog() ) restore_current_blog(); ?>
+
+						</td>
+					</tr>
+
+				<?php endforeach ?>
+
+				<?php
+
+				/**
+				 * Fires after the display of default static pages for BuddyPress setup.
+				 *
+				 * @since 1.5.0
+				 */
+				do_action( 'bp_active_external_pages' ); ?>
+
+			</tbody>
+		</table>
+
+		<?php
+	endif;
+}
+
+/**
+ * Handle saving of the BuddyPress slugs.
+ *
+ * @since 1.6.0
+ * @todo Use settings API
+ */
+function bp_core_admin_slugs_setup_handler() {
+
+	if ( isset( $_POST['bp-admin-pages-submit'] ) ) {
+		if ( !check_admin_referer( 'bp-admin-pages-setup' ) )
+			return false;
+
+		// Then, update the directory pages
+		if ( isset( $_POST['bp_pages'] ) ) {
+			$valid_pages = array_merge( bp_core_admin_get_directory_pages(), bp_core_admin_get_static_pages() );
+
+			$new_directory_pages = array();
+			foreach ( (array) $_POST['bp_pages'] as $key => $value ) {
+				if ( isset( $valid_pages[ $key ] ) ) {
+					$new_directory_pages[ $key ] = (int) $value;
+				}
+			}
+			bp_core_update_directory_page_ids( $new_directory_pages );
+		}
+
+		$base_url = bp_get_admin_url( add_query_arg( array( 'page' => 'bp-page-settings', 'updated' => 'true' ), 'admin.php' ) );
+
+		wp_redirect( $base_url );
+	}
+}
+add_action( 'bp_admin_init', 'bp_core_admin_slugs_setup_handler' );
Index: /tags/2.4.3/src/bp-core/admin/bp-core-admin-tools.php
===================================================================
--- /tags/2.4.3/src/bp-core/admin/bp-core-admin-tools.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/admin/bp-core-admin-tools.php	(revision 10410)
@@ -0,0 +1,414 @@
+<?php
+/**
+ * BuddyPress Tools panel.
+ *
+ * @since 2.0.0
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Render the BuddyPress Tools page.
+ *
+ * @since 2.0.0
+ */
+function bp_core_admin_tools() {
+	?>
+	<div class="wrap">
+
+		<h2><?php esc_html_e( 'BuddyPress Tools', 'buddypress' ) ?></h2>
+
+		<p>
+			<?php esc_html_e( 'BuddyPress keeps track of various relationships between users, groups, and activity items. Occasionally these relationships become out of sync, most often after an import, update, or migration.', 'buddypress' ); ?>
+			<?php esc_html_e( 'Use the tools below to manually recalculate these relationships.', 'buddypress' ); ?>
+		</p>
+		<p class="description"><?php esc_html_e( 'Some of these tools create substantial database overhead. Avoid running more than one repair job at a time.', 'buddypress' ); ?></p>
+
+		<form class="settings" method="post" action="">
+			<table class="form-table">
+				<tbody>
+					<tr valign="top">
+						<th scope="row"><?php esc_html_e( 'Data to Repair:', 'buddypress' ) ?></th>
+						<td>
+							<fieldset>
+								<legend class="screen-reader-text"><span><?php esc_html_e( 'Repair', 'buddypress' ) ?></span></legend>
+
+								<?php foreach ( bp_admin_repair_list() as $item ) : ?>
+
+									<label for="<?php echo esc_attr( str_replace( '_', '-', $item[0] ) ); ?>"><input type="checkbox" class="checkbox" name="<?php echo esc_attr( $item[0] ) . '" id="' . esc_attr( str_replace( '_', '-', $item[0] ) ); ?>" value="1" /> <?php echo esc_html( $item[1] ); ?></label><br />
+
+								<?php endforeach; ?>
+
+							</fieldset>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+
+			<fieldset class="submit">
+				<input class="button-primary" type="submit" name="bp-tools-submit" value="<?php esc_attr_e( 'Repair Items', 'buddypress' ); ?>" />
+				<?php wp_nonce_field( 'bp-do-counts' ); ?>
+			</fieldset>
+		</form>
+	</div>
+	<?php
+}
+
+/**
+ * Handle the processing and feedback of the admin tools page.
+ *
+ * @since 2.0.0
+ */
+function bp_admin_repair_handler() {
+	if ( ! bp_is_post_request() ) {
+		return;
+	}
+
+	if ( empty( $_POST['bp-tools-submit'] ) ) {
+		return;
+	}
+
+	check_admin_referer( 'bp-do-counts' );
+
+	// Stores messages
+	$messages = array();
+
+	wp_cache_flush();
+
+	foreach ( (array) bp_admin_repair_list() as $item ) {
+		if ( isset( $item[2] ) && isset( $_POST[$item[0]] ) && 1 === absint( $_POST[$item[0]] ) && is_callable( $item[2] ) ) {
+			$messages[] = call_user_func( $item[2] );
+		}
+	}
+
+	if ( count( $messages ) ) {
+		foreach ( $messages as $message ) {
+			bp_admin_tools_feedback( $message[1] );
+		}
+	}
+}
+add_action( bp_core_admin_hook(), 'bp_admin_repair_handler' );
+
+/**
+ * Get the array of the repair list.
+ *
+ * @return array
+ */
+function bp_admin_repair_list() {
+	$repair_list = array();
+
+	// Members:
+	// - member count
+	// - last_activity migration (2.0)
+	$repair_list[20] = array(
+		'bp-total-member-count',
+		__( 'Count total members', 'buddypress' ),
+		'bp_admin_repair_count_members',
+	);
+
+	$repair_list[25] = array(
+		'bp-last-activity',
+		__( 'Repair user "last activity" data', 'buddypress' ),
+		'bp_admin_repair_last_activity',
+	);
+
+	// Friends:
+	// - user friend count
+	if ( bp_is_active( 'friends' ) ) {
+		$repair_list[0] = array(
+			'bp-user-friends',
+			__( 'Count friends for each user', 'buddypress' ),
+			'bp_admin_repair_friend_count',
+		);
+	}
+
+	// Groups:
+	// - user group count
+	if ( bp_is_active( 'groups' ) ) {
+		$repair_list[10] = array(
+			'bp-group-count',
+			__( 'Count groups for each user', 'buddypress' ),
+			'bp_admin_repair_group_count',
+		);
+	}
+
+	// Blogs:
+	// - user blog count
+	if ( bp_is_active( 'blogs' ) ) {
+		$repair_list[90] = array(
+			'bp-blog-records',
+			__( 'Repopulate blogs records', 'buddypress' ),
+			'bp_admin_repair_blog_records',
+		);
+	}
+
+	ksort( $repair_list );
+
+	/**
+	 * Filters the array of the repair list.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $repair_list Array of values for the Repair list options.
+	 */
+	return (array) apply_filters( 'bp_repair_list', $repair_list );
+}
+
+/**
+ * Recalculate friend counts for each user.
+ *
+ * @since 2.0.0
+ *
+ * @return array
+ */
+function bp_admin_repair_friend_count() {
+	global $wpdb;
+
+	if ( ! bp_is_active( 'friends' ) ) {
+		return;
+	}
+
+	$statement = __( 'Counting the number of friends for each user&hellip; %s', 'buddypress' );
+	$result    = __( 'Failed!', 'buddypress' );
+
+	$sql_delete = "DELETE FROM {$wpdb->usermeta} WHERE meta_key IN ( 'total_friend_count' );";
+	if ( is_wp_error( $wpdb->query( $sql_delete ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	$bp = buddypress();
+
+	// Walk through all users on the site
+	$total_users = $wpdb->get_row( "SELECT count(ID) as c FROM {$wpdb->users}" )->c;
+
+	$updated = array();
+	if ( $total_users > 0 ) {
+		$per_query = 500;
+		$offset = 0;
+		while ( $offset < $total_users ) {
+			// Only bother updating counts for users who actually have friendships
+			$friendships = $wpdb->get_results( $wpdb->prepare( "SELECT initiator_user_id, friend_user_id FROM {$bp->friends->table_name} WHERE is_confirmed = 1 AND ( ( initiator_user_id > %d AND initiator_user_id <= %d ) OR ( friend_user_id > %d AND friend_user_id <= %d ) )", $offset, $offset + $per_query, $offset, $offset + $per_query ) );
+
+			// The previous query will turn up duplicates, so we
+			// filter them here
+			foreach ( $friendships as $friendship ) {
+				if ( ! isset( $updated[ $friendship->initiator_user_id ] ) ) {
+					BP_Friends_Friendship::total_friend_count( $friendship->initiator_user_id );
+					$updated[ $friendship->initiator_user_id ] = 1;
+				}
+
+				if ( ! isset( $updated[ $friendship->friend_user_id ] ) ) {
+					BP_Friends_Friendship::total_friend_count( $friendship->friend_user_id );
+					$updated[ $friendship->friend_user_id ] = 1;
+				}
+			}
+
+			$offset += $per_query;
+		}
+	} else {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	return array( 0, sprintf( $statement, __( 'Complete!', 'buddypress' ) ) );
+}
+
+/**
+ * Recalculate group counts for each user.
+ *
+ * @since 2.0.0
+ *
+ * @return array
+ */
+function bp_admin_repair_group_count() {
+	global $wpdb;
+
+	if ( ! bp_is_active( 'groups' ) ) {
+		return;
+	}
+
+	$statement = __( 'Counting the number of groups for each user&hellip; %s', 'buddypress' );
+	$result    = __( 'Failed!', 'buddypress' );
+
+	$sql_delete = "DELETE FROM {$wpdb->usermeta} WHERE meta_key IN ( 'total_group_count' );";
+	if ( is_wp_error( $wpdb->query( $sql_delete ) ) ) {
+		return array( 1, sprintf( $statement, $result ) );
+	}
+
+	$bp = buddypress();
+
+	// Walk through all users on the site
+	$total_users = $wpdb->get_row( "SELECT count(ID) as c FROM {$wpdb->users}" )->c;
+
+	if ( $total_users > 0 ) {
+		$per_query = 500;
+		$offset = 0;
+		while ( $offset < $total_users ) {
+			// But only bother to update counts for users that have groups
+			$users = $wpdb->get_col( $wpdb->prepare( "SELECT user_id FROM {$bp->groups->table_name_members} WHERE is_confirmed = 1 AND is_banned = 0 AND user_id > %d AND user_id <= %d", $offset, $offset + $per_query ) );
+
+			foreach ( $users as $user ) {
+				BP_Groups_Member::refresh_total_group_count_for_user( $user );
+			}
+
+			$offset += $per_query;
+		}
+	} else {
+		return array( 2, sprintf( $statement, $result ) );
+	}
+
+	return array( 0, sprintf( $statement, __( 'Complete!', 'buddypress' ) ) );
+}
+
+/**
+ * Recalculate user-to-blog relationships and useful blog meta data.
+ *
+ * @since 2.1.0
+ *
+ * @return array
+ */
+function bp_admin_repair_blog_records() {
+
+	// Description of this tool, displayed to the user
+	$statement = __( 'Repopulating Blogs records&hellip; %s', 'buddypress' );
+
+	// Default to failure text
+	$result    = __( 'Failed!',   'buddypress' );
+
+	// Default to unrepaired
+	$repair    = false;
+
+	// Run function if blogs component is active
+	if ( bp_is_active( 'blogs' ) ) {
+		$repair = bp_blogs_record_existing_blogs();
+	}
+
+	// Setup success/fail messaging
+	if ( true === $repair ) {
+		$result = __( 'Complete!', 'buddypress' );
+	}
+
+	// All done!
+	return array( 0, sprintf( $statement, $result ) );
+}
+
+/**
+ * Recalculate the total number of active site members.
+ *
+ * @since 2.0.0
+ */
+function bp_admin_repair_count_members() {
+	$statement = __( 'Counting the number of active members on the site&hellip; %s', 'buddypress' );
+	delete_transient( 'bp_active_member_count' );
+	bp_core_get_active_member_count();
+	return array( 0, sprintf( $statement, __( 'Complete!', 'buddypress' ) ) );
+}
+
+/**
+ * Repair user last_activity data.
+ *
+ * Re-runs the migration from usermeta introduced in BP 2.0.
+ *
+ * @since 2.0.0
+ */
+function bp_admin_repair_last_activity() {
+	$statement = __( 'Determining last activity dates for each user&hellip; %s', 'buddypress' );
+	bp_last_activity_migrate();
+	return array( 0, sprintf( $statement, __( 'Complete!', 'buddypress' ) ) );
+}
+
+/**
+ * Assemble admin notices relating success/failure of repair processes.
+ *
+ * @since 2.0.0
+ *
+ * @param string      $message Feedback message.
+ * @param string|bool $class   Unused.
+ *
+ * @return bool
+ */
+function bp_admin_tools_feedback( $message, $class = false ) {
+	if ( is_string( $message ) ) {
+		$message = '<p>' . $message . '</p>';
+		$class = $class ? $class : 'updated';
+	} elseif ( is_wp_error( $message ) ) {
+		$errors = $message->get_error_messages();
+
+		switch ( count( $errors ) ) {
+			case 0:
+				return false;
+
+			case 1:
+				$message = '<p>' . $errors[0] . '</p>';
+				break;
+
+			default:
+				$message = '<ul>' . "\n\t" . '<li>' . implode( '</li>' . "\n\t" . '<li>', $errors ) . '</li>' . "\n" . '</ul>';
+				break;
+		}
+
+		$class = $class ? $class : 'error';
+	} else {
+		return false;
+	}
+
+	$message = '<div id="message" class="' . esc_attr( $class ) . '">' . $message . '</div>';
+	$message = str_replace( "'", "\'", $message );
+	$lambda  = create_function( '', "echo '$message';" );
+
+	add_action( bp_core_do_network_admin() ? 'network_admin_notices' : 'admin_notices', $lambda );
+
+	return $lambda;
+}
+
+/**
+ * Render the Available Tools page.
+ *
+ * We register this page on Network Admin as a top-level home for our
+ * BuddyPress tools. This displays the default content.
+ *
+ * @since 2.0.0
+ */
+function bp_core_admin_available_tools_page() {
+	?>
+	<div class="wrap">
+		<h2><?php esc_attr_e( 'Tools', 'buddypress' ) ?></h2>
+
+		<?php
+
+		/**
+		 * Fires inside the markup used to display the Available Tools page.
+		 *
+		 * @since 2.0.0
+		 */
+		do_action( 'bp_network_tool_box' ); ?>
+
+	</div>
+	<?php
+}
+
+/**
+ * Render an introduction of BuddyPress tools on Available Tools page.
+ *
+ * @since 2.0.0
+ */
+function bp_core_admin_available_tools_intro() {
+	$query_arg = array(
+		'page' => 'bp-tools'
+	);
+
+	$page = bp_core_do_network_admin() ? 'admin.php' : 'tools.php' ;
+	$url  = add_query_arg( $query_arg, bp_get_admin_url( $page ) );
+	?>
+	<div class="card tool-box">
+		<h3><?php esc_html_e( 'BuddyPress Tools', 'buddypress' ) ?></h3>
+		<p>
+			<?php esc_html_e( 'BuddyPress keeps track of various relationships between users, groups, and activity items. Occasionally these relationships become out of sync, most often after an import, update, or migration.', 'buddypress' ); ?>
+			<?php printf( esc_html_x( 'Use the %s to repair these relationships.', 'buddypress tools intro', 'buddypress' ), '<a href="' . esc_url( $url ) . '">' . esc_html__( 'BuddyPress Tools', 'buddypress' ) . '</a>' ); ?>
+		</p>
+	</div>
+	<?php
+}
Index: /tags/2.4.3/src/bp-core/admin/css/common-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-core/admin/css/common-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-core/admin/css/common-rtl.css	(revision 10410)
@@ -0,0 +1,258 @@
+/**
+ * CSS that is always loaded when in wp-admin
+ *
+ * @since 1.6.0
+ */
+
+/* Welcome Screen
+------------------------------------------------------------------------------*/
+
+body.dashboard_page_bp-about span.dashicons,
+body.index_page_bp-about span.dashicons {
+	float: right;
+	clear: right;
+	margin: 15px 0 0 15px ;
+	height: 90px;
+	width: 90px;
+	background-color: #cccccc;
+	-webkit-border-radius: 50%;
+	border-radius: 50%;
+	border: 1px solid #c1c1c1;
+	font-size: 65px;
+	line-height: 90px;
+	color: #999;
+	text-align: center;
+}
+
+/* About Page - since 2.3.3 (WP 4.3) */
+
+.about-wrap .two-col > div {
+	position: relative;
+	width: 47.6%;
+	margin-left: 4.799999999%;
+	float: right;
+}
+
+.about-wrap [class$=col] .last-feature {
+	margin-left: 0;
+}
+
+.about-wrap .feature-list.finer-points h4,
+.about-wrap .feature-list.finer-points p {
+	margin-right: 115px;
+}
+
+@media screen and ( max-width: 782px ) {
+	.about-wrap .two-col > div {
+		width: 100%;
+		margin: 30px 0 0;
+		padding: 0 0 30px;
+		border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+	}
+	body.dashboard_page_bp-about span.dashicons,
+	body.index_page_bp-about span.dashicons {
+		font-size: 55px;
+		line-height: 70px;
+		height: 70px;
+		width: 70px;
+	}
+	.about-wrap .feature-list h2 {
+	  margin: 30px 0 0;
+	  text-align: center;
+	}
+	.about-wrap .feature-list.finer-points h4,
+	.about-wrap .feature-list.finer-points p {
+		margin-right: 90px;
+	}
+}
+
+/* About Page - since 2.4.0 (WP 4.4-trunk) */
+
+.about-wrap .headline-feature {
+	margin-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.about-wrap .headline-feature p {
+	font-size: 1.15em;
+	margin: 1.25em;
+	text-align: center;
+}
+
+.about-wrap .headline-feature h3 {
+	font-size: 1.75em;
+	font-weight: normal;
+	margin: 1.25em 0 0.6em;
+	text-align: center;
+}
+
+.about-wrap .feature-section {
+	margin-top: 40px;
+}
+
+.about-wrap  .changelog .feature-section {
+	margin-top: 0;
+}
+
+/* User's Lists
+------------------------------------------------------------------------------*/
+
+body.site-users-php th#role,
+body.users-php th#role,
+body.users_page_bp-signups th#count_sent {
+	width: 10%;
+}
+
+body.site-users-php th#name,
+body.site-users-php th#email,
+body.users-php th#name,
+body.users-php th#registered,
+body.users-php th#email,
+body.users_page_bp-signups th#name,
+body.users_page_bp-signups th#registered,
+body.users_page_bp-signups th#email,
+body.users_page_bp-signups th#date_sent {
+	width: 15%;
+}
+
+body.users-php th#blogs,
+body.users_page_bp-signups th#blogs {
+	width: 20%;
+}
+
+body.users_page_bp-signups th.column-count_sent,
+body.users_page_bp-signups td.count_sent {
+	text-align: center;
+}
+
+/* Tools */
+#adminmenu .toplevel_page_network-tools div.wp-menu-image:before {
+    content: "";
+}
+
+/* Components
+------------------------------------------------------------------------------*/
+
+/* Dashicons */
+#adminmenu #toplevel_page_bp-activity .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-activity_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-activity_network .wp-menu-image:before {
+	content: "\f452";
+}
+
+#adminmenu #toplevel_page_bp-groups .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-groups_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-groups_network .wp-menu-image:before {
+	content: "\f456";
+}
+
+#adminmenu #toplevel_page_bp-notifications .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-notifications_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-notifications_network .wp-menu-image:before {
+	content: "\f439";
+}
+
+#adminmenu #toplevel_page_bp-messages .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-messages_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-messages_network .wp-menu-image:before {
+	content: "\f457";
+}
+
+#adminmenu #toplevel_page_bp-friends .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-friends_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-friends_network .wp-menu-image:before{
+	content: "\f454";
+}
+
+#adminmenu #toplevel_page_bp-settings .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-settings_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-settings_network .wp-menu-image:before {
+	content: "\f108";
+}
+
+#adminmenu li.toplevel_page_bp-components .wp-menu-image,
+#adminmenu li.toplevel_page_bp-general-settings .wp-menu-image {
+	content: "\f448";
+}
+
+/* Settings - Dashicons (WP 3.8+) */
+.settings_page_bp-components td.plugin-title span {
+	float: right;
+	width: 18px;
+	height: 18px;
+	margin-left: 5px;
+}
+
+.settings_page_bp-components td.plugin-title span:before {
+	font-family: 'dashicons';
+	font-size: 18px;
+}
+
+.settings_page_bp-components tr.activity td.plugin-title span:before {
+	content: "\f452";
+}
+
+.settings_page_bp-components tr.notifications td.plugin-title span:before {
+	content: "\f339";
+}
+
+.settings_page_bp-components tr.xprofile td.plugin-title span:before {
+	content: "\f336";
+}
+
+.settings_page_bp-components tr.settings td.plugin-title span:before {
+	content: "\f108";
+}
+
+.settings_page_bp-components tr.groups td.plugin-title span:before {
+	content: "\f456";
+}
+
+.settings_page_bp-components tr.messages td.plugin-title span:before {
+	content: "\f457";
+}
+
+.settings_page_bp-components tr.forums td.plugin-title span:before {
+	content: "\f452";
+}
+
+.settings_page_bp-components tr.blogs td.plugin-title span:before {
+	content: "\f120";
+}
+
+.settings_page_bp-components tr.friends td.plugin-title span:before {
+	content: "\f454";
+}
+
+#bp-admin-component-form .widefat th {
+    display: table-cell;
+    vertical-align: top;
+}
+
+/* Version Badge */
+
+.bp-badge {
+	font: normal 150px/1 'dashicons' !important;
+	color: #D84800;
+	display: inline-block;
+}
+
+.bp-badge:before {
+	content: "\f448";
+}
+
+.about-wrap .bp-badge {
+	position: absolute;
+	top: 0;
+	left: 0;
+}
+
+/* About Page - since 2.3.3 (WP 4.3) */
+
+@media only screen and (max-width: 500px) {
+	.about-wrap .bp-badge {
+		position: relative;
+		margin: 10px auto;
+		top: auto;
+		left: auto;
+	}
+}
Index: /tags/2.4.3/src/bp-core/admin/css/common.css
===================================================================
--- /tags/2.4.3/src/bp-core/admin/css/common.css	(revision 10410)
+++ /tags/2.4.3/src/bp-core/admin/css/common.css	(revision 10410)
@@ -0,0 +1,258 @@
+/**
+ * CSS that is always loaded when in wp-admin
+ *
+ * @since 1.6.0
+ */
+
+/* Welcome Screen
+------------------------------------------------------------------------------*/
+
+body.dashboard_page_bp-about span.dashicons,
+body.index_page_bp-about span.dashicons {
+	float: left;
+	clear: left;
+	margin: 15px 15px 0 0 ;
+	height: 90px;
+	width: 90px;
+	background-color: #cccccc;
+	-webkit-border-radius: 50%;
+	border-radius: 50%;
+	border: 1px solid #c1c1c1;
+	font-size: 65px;
+	line-height: 90px;
+	color: #999;
+	text-align: center;
+}
+
+/* About Page - since 2.3.3 (WP 4.3) */
+
+.about-wrap .two-col > div {
+	position: relative;
+	width: 47.6%;
+	margin-right: 4.799999999%;
+	float: left;
+}
+
+.about-wrap [class$=col] .last-feature {
+	margin-right: 0;
+}
+
+.about-wrap .feature-list.finer-points h4,
+.about-wrap .feature-list.finer-points p {
+	margin-left: 115px;
+}
+
+@media screen and ( max-width: 782px ) {
+	.about-wrap .two-col > div {
+		width: 100%;
+		margin: 30px 0 0;
+		padding: 0 0 30px;
+		border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+	}
+	body.dashboard_page_bp-about span.dashicons,
+	body.index_page_bp-about span.dashicons {
+		font-size: 55px;
+		line-height: 70px;
+		height: 70px;
+		width: 70px;
+	}
+	.about-wrap .feature-list h2 {
+	  margin: 30px 0 0;
+	  text-align: center;
+	}
+	.about-wrap .feature-list.finer-points h4,
+	.about-wrap .feature-list.finer-points p {
+		margin-left: 90px;
+	}
+}
+
+/* About Page - since 2.4.0 (WP 4.4-trunk) */
+
+.about-wrap .headline-feature {
+	margin-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.about-wrap .headline-feature p {
+	font-size: 1.15em;
+	margin: 1.25em;
+	text-align: center;
+}
+
+.about-wrap .headline-feature h3 {
+	font-size: 1.75em;
+	font-weight: normal;
+	margin: 1.25em 0 0.6em;
+	text-align: center;
+}
+
+.about-wrap .feature-section {
+	margin-top: 40px;
+}
+
+.about-wrap  .changelog .feature-section {
+	margin-top: 0;
+}
+
+/* User's Lists
+------------------------------------------------------------------------------*/
+
+body.site-users-php th#role,
+body.users-php th#role,
+body.users_page_bp-signups th#count_sent {
+	width: 10%;
+}
+
+body.site-users-php th#name,
+body.site-users-php th#email,
+body.users-php th#name,
+body.users-php th#registered,
+body.users-php th#email,
+body.users_page_bp-signups th#name,
+body.users_page_bp-signups th#registered,
+body.users_page_bp-signups th#email,
+body.users_page_bp-signups th#date_sent {
+	width: 15%;
+}
+
+body.users-php th#blogs,
+body.users_page_bp-signups th#blogs {
+	width: 20%;
+}
+
+body.users_page_bp-signups th.column-count_sent,
+body.users_page_bp-signups td.count_sent {
+	text-align: center;
+}
+
+/* Tools */
+#adminmenu .toplevel_page_network-tools div.wp-menu-image:before {
+    content: "";
+}
+
+/* Components
+------------------------------------------------------------------------------*/
+
+/* Dashicons */
+#adminmenu #toplevel_page_bp-activity .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-activity_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-activity_network .wp-menu-image:before {
+	content: "\f452";
+}
+
+#adminmenu #toplevel_page_bp-groups .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-groups_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-groups_network .wp-menu-image:before {
+	content: "\f456";
+}
+
+#adminmenu #toplevel_page_bp-notifications .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-notifications_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-notifications_network .wp-menu-image:before {
+	content: "\f439";
+}
+
+#adminmenu #toplevel_page_bp-messages .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-messages_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-messages_network .wp-menu-image:before {
+	content: "\f457";
+}
+
+#adminmenu #toplevel_page_bp-friends .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-friends_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-friends_network .wp-menu-image:before{
+	content: "\f454";
+}
+
+#adminmenu #toplevel_page_bp-settings .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-settings_user .wp-menu-image:before,
+#adminmenu #toplevel_page_bp-settings_network .wp-menu-image:before {
+	content: "\f108";
+}
+
+#adminmenu li.toplevel_page_bp-components .wp-menu-image,
+#adminmenu li.toplevel_page_bp-general-settings .wp-menu-image {
+	content: "\f448";
+}
+
+/* Settings - Dashicons (WP 3.8+) */
+.settings_page_bp-components td.plugin-title span {
+	float: left;
+	width: 18px;
+	height: 18px;
+	margin-right: 5px;
+}
+
+.settings_page_bp-components td.plugin-title span:before {
+	font-family: 'dashicons';
+	font-size: 18px;
+}
+
+.settings_page_bp-components tr.activity td.plugin-title span:before {
+	content: "\f452";
+}
+
+.settings_page_bp-components tr.notifications td.plugin-title span:before {
+	content: "\f339";
+}
+
+.settings_page_bp-components tr.xprofile td.plugin-title span:before {
+	content: "\f336";
+}
+
+.settings_page_bp-components tr.settings td.plugin-title span:before {
+	content: "\f108";
+}
+
+.settings_page_bp-components tr.groups td.plugin-title span:before {
+	content: "\f456";
+}
+
+.settings_page_bp-components tr.messages td.plugin-title span:before {
+	content: "\f457";
+}
+
+.settings_page_bp-components tr.forums td.plugin-title span:before {
+	content: "\f452";
+}
+
+.settings_page_bp-components tr.blogs td.plugin-title span:before {
+	content: "\f120";
+}
+
+.settings_page_bp-components tr.friends td.plugin-title span:before {
+	content: "\f454";
+}
+
+#bp-admin-component-form .widefat th {
+    display: table-cell;
+    vertical-align: top;
+}
+
+/* Version Badge */
+
+.bp-badge {
+	font: normal 150px/1 'dashicons' !important;
+	color: #D84800;
+	display: inline-block;
+}
+
+.bp-badge:before {
+	content: "\f448";
+}
+
+.about-wrap .bp-badge {
+	position: absolute;
+	top: 0;
+	right: 0;
+}
+
+/* About Page - since 2.3.3 (WP 4.3) */
+
+@media only screen and (max-width: 500px) {
+	.about-wrap .bp-badge {
+		position: relative;
+		margin: 10px auto;
+		top: auto;
+		right: auto;
+	}
+}
Index: /tags/2.4.3/src/bp-core/bp-core-actions.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-actions.php	(revision 10410)
@@ -0,0 +1,107 @@
+<?php
+/**
+ * BuddyPress Filters & Actions.
+ *
+ * @package BuddyPress
+ * @subpackage Hooks
+ *
+ * This file contains the actions and filters that are used through-out BuddyPress.
+ * They are consolidated here to make searching for them easier, and to help
+ * developers understand at a glance the order in which things occur.
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Attach BuddyPress to WordPress.
+ *
+ * BuddyPress 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 BuddyPress 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--BuddyPress Sub-actions
+  */
+add_action( 'plugins_loaded',          'bp_loaded',                 10    );
+add_action( 'init',                    'bp_init',                   10    );
+add_action( 'parse_query',             'bp_parse_query',            2     ); // Early for overrides.
+add_action( 'wp',                      'bp_ready',                  10    );
+add_action( 'set_current_user',        'bp_setup_current_user',     10    );
+add_action( 'setup_theme',             'bp_setup_theme',            10    );
+add_action( 'after_setup_theme',       'bp_after_setup_theme',      100   ); // After WP themes.
+add_action( 'wp_enqueue_scripts',      'bp_enqueue_scripts',        10    );
+add_action( 'admin_bar_menu',          'bp_setup_admin_bar',        20    ); // After WP core.
+add_action( 'template_redirect',       'bp_template_redirect',      10    );
+add_action( 'widgets_init',            'bp_widgets_init',           10    );
+add_action( 'generate_rewrite_rules',  'bp_generate_rewrite_rules', 10    );
+
+/**
+ * bp_loaded - Attached to 'plugins_loaded' above.
+ *
+ * Attach various loader actions to the bp_loaded action.
+ * The load order helps to execute code at the correct time.
+ *                                                      v---Load order
+ */
+add_action( 'bp_loaded', 'bp_setup_components',         2  );
+add_action( 'bp_loaded', 'bp_include',                  4  );
+add_action( 'bp_loaded', 'bp_setup_cache_groups',       5  );
+add_action( 'bp_loaded', 'bp_setup_widgets',            6  );
+add_action( 'bp_loaded', 'bp_register_member_types',    8  );
+add_action( 'bp_loaded', 'bp_register_theme_packages',  12 );
+add_action( 'bp_loaded', 'bp_register_theme_directory', 14 );
+
+/**
+ * bp_init - Attached to 'init' above.
+ *
+ * Attach various initialization actions to the bp_init action.
+ * The load order helps to execute code at the correct time.
+ *                                                   v---Load order
+ */
+add_action( 'bp_init', 'bp_core_set_uri_globals',    2  );
+add_action( 'bp_init', 'bp_register_taxonomies',     3  );
+add_action( 'bp_init', 'bp_setup_globals',           4  );
+add_action( 'bp_init', 'bp_setup_canonical_stack',   5  );
+add_action( 'bp_init', 'bp_setup_nav',               6  );
+add_action( 'bp_init', 'bp_setup_title',             8  );
+add_action( 'bp_init', 'bp_core_load_admin_bar_css', 12 );
+add_action( 'bp_init', 'bp_add_rewrite_tags',        20 );
+add_action( 'bp_init', 'bp_add_rewrite_rules',       30 );
+add_action( 'bp_init', 'bp_add_permastructs',        40 );
+
+/**
+ * bp_template_redirect - Attached to 'template_redirect' above.
+ *
+ * Attach various template actions to the bp_template_redirect action.
+ * The load order helps to execute code at the correct time.
+ *
+ * Note that we currently use template_redirect versus template include because
+ * BuddyPress is a bully and overrides the existing themes output in many
+ * places. This won't always be this way, we promise.
+ *                                                           v---Load order
+ */
+add_action( 'bp_template_redirect', 'bp_redirect_canonical', 2  );
+add_action( 'bp_template_redirect', 'bp_actions',            4  );
+add_action( 'bp_template_redirect', 'bp_screens',            6  );
+add_action( 'bp_template_redirect', 'bp_post_request',       10 );
+add_action( 'bp_template_redirect', 'bp_get_request',        10 );
+
+/**
+ * Add the BuddyPress functions file and the Theme Compat Default features.
+ */
+add_action( 'bp_after_setup_theme', 'bp_load_theme_functions',                    1 );
+add_action( 'bp_after_setup_theme', 'bp_register_theme_compat_default_features', 10 );
+
+// Load the admin.
+if ( is_admin() ) {
+	add_action( 'bp_loaded', 'bp_admin' );
+}
+
+// Activation redirect.
+add_action( 'bp_activation', 'bp_add_activation_redirect' );
Index: /tags/2.4.3/src/bp-core/bp-core-admin.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-admin.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-admin.php	(revision 10410)
@@ -0,0 +1,930 @@
+<?php
+/**
+ * Main BuddyPress Admin Class.
+ *
+ * @package BuddyPress
+ * @subpackage CoreAdministration
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+if ( !class_exists( 'BP_Admin' ) ) :
+
+/**
+ * Load BuddyPress plugin admin area.
+ *
+ * @package BuddyPress
+ * @subpackage CoreAdministration
+ * @todo Break this apart into each applicable Component
+ *
+ * @since 1.6.0
+ */
+class BP_Admin {
+
+	/** Directory *************************************************************/
+
+	/**
+	 * Path to the BuddyPress admin directory.
+	 *
+	 * @var string $admin_dir
+	 */
+	public $admin_dir = '';
+
+	/** URLs ******************************************************************/
+
+	/**
+	 * URL to the BuddyPress admin directory.
+	 *
+	 * @var string $admin_url
+	 */
+	public $admin_url = '';
+
+	/**
+	 * URL to the BuddyPress images directory.
+	 *
+	 * @var string $images_url
+	 */
+	public $images_url = '';
+
+	/**
+	 * URL to the BuddyPress admin CSS directory.
+	 *
+	 * @var string $css_url
+	 */
+	public $css_url = '';
+
+	/**
+	 * URL to the BuddyPress admin JS directory.
+	 *
+	 * @var string
+	 */
+	public $js_url = '';
+
+	/** Other *****************************************************************/
+
+	/**
+	 * Notices used for user feedback, like saving settings.
+	 *
+	 * @var array()
+	 */
+	public $notices = array();
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * The main BuddyPress admin loader.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses BP_Admin::setup_globals() Setup the globals needed.
+	 * @uses BP_Admin::includes() Include the required files.
+	 * @uses BP_Admin::setup_actions() Setup the hooks and actions.
+	 */
+	public function __construct() {
+		$this->setup_globals();
+		$this->includes();
+		$this->setup_actions();
+	}
+
+	/**
+	 * Set admin-related globals.
+	 *
+	 * @since 1.6.0
+	 */
+	private function setup_globals() {
+		$bp = buddypress();
+
+		// Paths and URLs
+		$this->admin_dir  = trailingslashit( $bp->plugin_dir  . 'bp-core/admin' ); // Admin path
+		$this->admin_url  = trailingslashit( $bp->plugin_url  . 'bp-core/admin' ); // Admin url
+		$this->images_url = trailingslashit( $this->admin_url . 'images'        ); // Admin images URL
+		$this->css_url    = trailingslashit( $this->admin_url . 'css'           ); // Admin css URL
+		$this->js_url     = trailingslashit( $this->admin_url . 'js'            ); // Admin css URL
+
+		// Main settings page
+		$this->settings_page = bp_core_do_network_admin() ? 'settings.php' : 'options-general.php';
+
+		// Main capability
+		$this->capability = bp_core_do_network_admin() ? 'manage_network_options' : 'manage_options';
+	}
+
+	/**
+	 * Include required files.
+	 *
+	 * @since 1.6.0
+	 */
+	private function includes() {
+		require( $this->admin_dir . 'bp-core-admin-actions.php'    );
+		require( $this->admin_dir . 'bp-core-admin-settings.php'   );
+		require( $this->admin_dir . 'bp-core-admin-functions.php'  );
+		require( $this->admin_dir . 'bp-core-admin-components.php' );
+		require( $this->admin_dir . 'bp-core-admin-slugs.php'      );
+		require( $this->admin_dir . 'bp-core-admin-tools.php'      );
+	}
+
+	/**
+	 * Set up the admin hooks, actions, and filters.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses add_action() To add various actions.
+	 * @uses add_filter() To add various filters.
+	 */
+	private function setup_actions() {
+
+		/** General Actions ***************************************************/
+
+		// Add some page specific output to the <head>
+		add_action( 'bp_admin_head',            array( $this, 'admin_head'  ), 999 );
+
+		// Add menu item to settings menu
+		add_action( bp_core_admin_hook(),       array( $this, 'admin_menus' ), 5 );
+
+		// Enqueue all admin JS and CSS
+		add_action( 'bp_admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
+
+		/** BuddyPress Actions ************************************************/
+
+		// Load the BuddyPress metabox in the WP Nav Menu Admin UI
+		add_action( 'load-nav-menus.php', 'bp_admin_wp_nav_menu_meta_box' );
+
+		// Add settings
+		add_action( 'bp_register_admin_settings', array( $this, 'register_admin_settings' ) );
+
+		// Add a link to BuddyPress About page to the admin bar
+		add_action( 'admin_bar_menu', array( $this, 'admin_bar_about_link' ), 15 );
+
+		// Add a description of new BuddyPress tools in the available tools page
+		add_action( 'tool_box',            'bp_core_admin_available_tools_intro' );
+		add_action( 'bp_network_tool_box', 'bp_core_admin_available_tools_intro' );
+
+		// On non-multisite, catch
+		add_action( 'load-users.php', 'bp_core_admin_user_manage_spammers' );
+
+		/** Filters ***********************************************************/
+
+		// Add link to settings page
+		add_filter( 'plugin_action_links',               array( $this, 'modify_plugin_action_links' ), 10, 2 );
+		add_filter( 'network_admin_plugin_action_links', array( $this, 'modify_plugin_action_links' ), 10, 2 );
+
+		// Add "Mark as Spam" row actions on users.php
+		add_filter( 'ms_user_row_actions', 'bp_core_admin_user_row_actions', 10, 2 );
+		add_filter( 'user_row_actions',    'bp_core_admin_user_row_actions', 10, 2 );
+	}
+
+	/**
+	 * Add the navigational menu elements.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses add_management_page() To add the Recount page in Tools section.
+	 * @uses add_options_page() To add the Forums settings page in Settings
+	 *       section.
+	 */
+	public function admin_menus() {
+
+		// Bail if user cannot moderate
+		if ( ! bp_current_user_can( 'manage_options' ) ) {
+			return;
+		}
+
+		// About
+		add_dashboard_page(
+			__( 'Welcome to BuddyPress',  'buddypress' ),
+			__( 'Welcome to BuddyPress',  'buddypress' ),
+			'manage_options',
+			'bp-about',
+			array( $this, 'about_screen' )
+		);
+
+		// Credits
+		add_dashboard_page(
+			__( 'Welcome to BuddyPress',  'buddypress' ),
+			__( 'Welcome to BuddyPress',  'buddypress' ),
+			'manage_options',
+			'bp-credits',
+			array( $this, 'credits_screen' )
+		);
+
+		$hooks = array();
+
+		// Changed in BP 1.6 . See bp_core_admin_backpat_menu()
+		$hooks[] = add_menu_page(
+			__( 'BuddyPress', 'buddypress' ),
+			__( 'BuddyPress', 'buddypress' ),
+			$this->capability,
+			'bp-general-settings',
+			'bp_core_admin_backpat_menu',
+			'div'
+		);
+
+		$hooks[] = add_submenu_page(
+			'bp-general-settings',
+			__( 'BuddyPress Help', 'buddypress' ),
+			__( 'Help', 'buddypress' ),
+			$this->capability,
+			'bp-general-settings',
+			'bp_core_admin_backpat_page'
+		);
+
+		// Add the option pages
+		$hooks[] = add_submenu_page(
+			$this->settings_page,
+			__( 'BuddyPress Components', 'buddypress' ),
+			__( 'BuddyPress', 'buddypress' ),
+			$this->capability,
+			'bp-components',
+			'bp_core_admin_components_settings'
+		);
+
+		$hooks[] = add_submenu_page(
+			$this->settings_page,
+			__( 'BuddyPress Pages', 'buddypress' ),
+			__( 'BuddyPress Pages', 'buddypress' ),
+			$this->capability,
+			'bp-page-settings',
+			'bp_core_admin_slugs_settings'
+		);
+
+		$hooks[] = add_submenu_page(
+			$this->settings_page,
+			__( 'BuddyPress Settings', 'buddypress' ),
+			__( 'BuddyPress Settings', 'buddypress' ),
+			$this->capability,
+			'bp-settings',
+			'bp_core_admin_settings'
+		);
+
+		// For consistency with non-Multisite, we add a Tools menu in
+		// the Network Admin as a home for our Tools panel
+		if ( is_multisite() && bp_core_do_network_admin() ) {
+			$tools_parent = 'network-tools';
+
+			$hooks[] = add_menu_page(
+				__( 'Tools', 'buddypress' ),
+				__( 'Tools', 'buddypress' ),
+				$this->capability,
+				$tools_parent,
+				'bp_core_tools_top_level_item',
+				'',
+				24 // just above Settings
+			);
+
+			$hooks[] = add_submenu_page(
+				$tools_parent,
+				__( 'Available Tools', 'buddypress' ),
+				__( 'Available Tools', 'buddypress' ),
+				$this->capability,
+				'available-tools',
+				'bp_core_admin_available_tools_page'
+			);
+		} else {
+			$tools_parent = 'tools.php';
+		}
+
+		$hooks[] = add_submenu_page(
+			$tools_parent,
+			__( 'BuddyPress Tools', 'buddypress' ),
+			__( 'BuddyPress', 'buddypress' ),
+			$this->capability,
+			'bp-tools',
+			'bp_core_admin_tools'
+		);
+
+		// Fudge the highlighted subnav item when on a BuddyPress admin page
+		foreach( $hooks as $hook ) {
+			add_action( "admin_head-$hook", 'bp_core_modify_admin_menu_highlight' );
+		}
+	}
+
+	/**
+	 * Register the settings.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses add_settings_section() To add our own settings section.
+	 * @uses add_settings_field() To add various settings fields.
+	 * @uses register_setting() To register various settings.
+	 */
+	public function register_admin_settings() {
+
+		/** Main Section ******************************************************/
+
+		// Add the main section
+		add_settings_section( 'bp_main', __( 'Main Settings', 'buddypress' ), 'bp_admin_setting_callback_main_section', 'buddypress' );
+
+		// Hide toolbar for logged out users setting
+		add_settings_field( 'hide-loggedout-adminbar', __( 'Toolbar', 'buddypress' ), 'bp_admin_setting_callback_admin_bar', 'buddypress', 'bp_main' );
+		register_setting( 'buddypress', 'hide-loggedout-adminbar', 'intval' );
+
+		// Only show 'switch to Toolbar' option if the user chose to retain the BuddyBar during the 1.6 upgrade
+		if ( (bool) bp_get_option( '_bp_force_buddybar', false ) ) {
+			add_settings_field( '_bp_force_buddybar', __( 'Toolbar', 'buddypress' ), 'bp_admin_setting_callback_force_buddybar', 'buddypress', 'bp_main' );
+			register_setting( 'buddypress', '_bp_force_buddybar', 'bp_admin_sanitize_callback_force_buddybar' );
+		}
+
+		// Allow account deletion
+		add_settings_field( 'bp-disable-account-deletion', __( 'Account Deletion', 'buddypress' ), 'bp_admin_setting_callback_account_deletion', 'buddypress', 'bp_main' );
+		register_setting( 'buddypress', 'bp-disable-account-deletion', 'intval' );
+
+		/** XProfile Section **************************************************/
+
+		if ( bp_is_active( 'xprofile' ) ) {
+
+			// Add the main section
+			add_settings_section( 'bp_xprofile', _x( 'Profile Settings', 'BuddyPress setting tab', 'buddypress' ), 'bp_admin_setting_callback_xprofile_section', 'buddypress' );
+
+			// Avatars
+			add_settings_field( 'bp-disable-avatar-uploads', __( 'Profile Photo Uploads', 'buddypress' ), 'bp_admin_setting_callback_avatar_uploads', 'buddypress', 'bp_xprofile' );
+			register_setting( 'buddypress', 'bp-disable-avatar-uploads', 'intval' );
+
+			// Cover images
+			if ( bp_is_active( 'xprofile', 'cover_image' ) ) {
+				add_settings_field( 'bp-disable-cover-image-uploads', __( 'Cover Image Uploads', 'buddypress' ), 'bp_admin_setting_callback_cover_image_uploads', 'buddypress', 'bp_xprofile' );
+				register_setting( 'buddypress', 'bp-disable-cover-image-uploads', 'intval' );
+			}
+
+			// Profile sync setting
+			add_settings_field( 'bp-disable-profile-sync',   __( 'Profile Syncing',  'buddypress' ), 'bp_admin_setting_callback_profile_sync', 'buddypress', 'bp_xprofile' );
+			register_setting  ( 'buddypress', 'bp-disable-profile-sync', 'intval' );
+		}
+
+		/** Groups Section ****************************************************/
+
+		if ( bp_is_active( 'groups' ) ) {
+
+			// Add the main section
+			add_settings_section( 'bp_groups', __( 'Groups Settings',  'buddypress' ), 'bp_admin_setting_callback_groups_section', 'buddypress' );
+
+			// Allow subscriptions setting
+			add_settings_field( 'bp_restrict_group_creation', __( 'Group Creation', 'buddypress' ), 'bp_admin_setting_callback_group_creation',   'buddypress', 'bp_groups' );
+			register_setting( 'buddypress', 'bp_restrict_group_creation', 'intval' );
+
+			// Allow group avatars.
+			add_settings_field( 'bp-disable-group-avatar-uploads', __( 'Group Photo Uploads', 'buddypress' ), 'bp_admin_setting_callback_group_avatar_uploads', 'buddypress', 'bp_groups' );
+			register_setting( 'buddypress', 'bp-disable-group-avatar-uploads', 'intval' );
+
+			// Allow group cover images.
+			if ( bp_is_active( 'groups', 'cover_image' ) ) {
+				add_settings_field( 'bp-disable-group-cover-image-uploads', __( 'Group Cover Image Uploads', 'buddypress' ), 'bp_admin_setting_callback_group_cover_image_uploads', 'buddypress', 'bp_groups' );
+				register_setting( 'buddypress', 'bp-disable-group-cover-image-uploads', 'intval' );
+			}
+		}
+
+		/** Forums ************************************************************/
+
+		if ( bp_is_active( 'forums' ) ) {
+
+			// Add the main section
+			add_settings_section( 'bp_forums', __( 'Legacy Group Forums', 'buddypress' ), 'bp_admin_setting_callback_bbpress_section', 'buddypress' );
+
+			// Allow subscriptions setting
+			add_settings_field( 'bb-config-location', __( 'bbPress Configuration', 'buddypress' ), 'bp_admin_setting_callback_bbpress_configuration', 'buddypress', 'bp_forums' );
+			register_setting( 'buddypress', 'bb-config-location', '' );
+		}
+
+		/** Activity Section **************************************************/
+
+		if ( bp_is_active( 'activity' ) ) {
+
+			// Add the main section
+			add_settings_section( 'bp_activity', __( 'Activity Settings', 'buddypress' ), 'bp_admin_setting_callback_activity_section', 'buddypress' );
+
+			// Activity commenting on blog and forum posts
+			add_settings_field( 'bp-disable-blogforum-comments', __( 'Blog &amp; Forum Comments', 'buddypress' ), 'bp_admin_setting_callback_blogforum_comments', 'buddypress', 'bp_activity' );
+			register_setting( 'buddypress', 'bp-disable-blogforum-comments', 'bp_admin_sanitize_callback_blogforum_comments' );
+
+			// Activity Heartbeat refresh
+			add_settings_field( '_bp_enable_heartbeat_refresh', __( 'Activity auto-refresh', 'buddypress' ), 'bp_admin_setting_callback_heartbeat', 'buddypress', 'bp_activity' );
+			register_setting( 'buddypress', '_bp_enable_heartbeat_refresh', 'intval' );
+
+			// Allow activity akismet
+			if ( is_plugin_active( 'akismet/akismet.php' ) && defined( 'AKISMET_VERSION' ) ) {
+				add_settings_field( '_bp_enable_akismet', __( 'Akismet', 'buddypress' ), 'bp_admin_setting_callback_activity_akismet', 'buddypress', 'bp_activity' );
+				register_setting( 'buddypress', '_bp_enable_akismet', 'intval' );
+			}
+		}
+	}
+
+	/**
+	 * Add a link to BuddyPress About page to the admin bar.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param WP_Admin_Bar $wp_admin_bar As passed to 'admin_bar_menu'.
+	 */
+	public function admin_bar_about_link( $wp_admin_bar ) {
+		if ( is_user_logged_in() ) {
+			$wp_admin_bar->add_menu( array(
+				'parent' => 'wp-logo',
+				'id'     => 'bp-about',
+				'title'  => esc_html__( 'About BuddyPress', 'buddypress' ),
+				'href'   => add_query_arg( array( 'page' => 'bp-about' ), bp_get_admin_url( 'index.php' ) ),
+			) );
+		}
+	}
+
+	/**
+	 * Add Settings link to plugins area.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array  $links Links array in which we would prepend our link.
+	 * @param string $file  Current plugin basename.
+	 *
+	 * @return array Processed links.
+	 */
+	public function modify_plugin_action_links( $links, $file ) {
+
+		// Return normal links if not BuddyPress
+		if ( plugin_basename( buddypress()->basename ) != $file ) {
+			return $links;
+		}
+
+		// Add a few links to the existing links array
+		return array_merge( $links, array(
+			'settings' => '<a href="' . esc_url( add_query_arg( array( 'page' => 'bp-components' ), bp_get_admin_url( $this->settings_page ) ) ) . '">' . esc_html__( 'Settings', 'buddypress' ) . '</a>',
+			'about'    => '<a href="' . esc_url( add_query_arg( array( 'page' => 'bp-about'      ), bp_get_admin_url( 'index.php'          ) ) ) . '">' . esc_html__( 'About',    'buddypress' ) . '</a>'
+		) );
+	}
+
+	/**
+	 * Add some general styling to the admin area.
+	 *
+	 * @since 1.6.0
+	 */
+	public function admin_head() {
+
+		// Settings pages
+		remove_submenu_page( $this->settings_page, 'bp-page-settings' );
+		remove_submenu_page( $this->settings_page, 'bp-settings'      );
+
+		// Network Admin Tools
+		remove_submenu_page( 'network-tools', 'network-tools' );
+
+		// About and Credits pages
+		remove_submenu_page( 'index.php', 'bp-about'   );
+		remove_submenu_page( 'index.php', 'bp-credits' );
+	}
+
+	/**
+	 * Add some general styling to the admin area.
+	 *
+	 * @since 1.6.0
+	 */
+	public function enqueue_scripts() {
+		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+
+		$file = $this->css_url . "common{$min}.css";
+
+		/**
+		 * Filters the BuddyPress Core Admin CSS file path.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $file File path for the admin CSS.
+		 */
+		$file = apply_filters( 'bp_core_admin_common_css', $file );
+		wp_enqueue_style( 'bp-admin-common-css', $file, array(), bp_get_version() );
+
+		wp_style_add_data( 'bp-admin-common-css', 'rtl', true );
+		if ( $min ) {
+			wp_style_add_data( 'bp-admin-common-css', 'suffix', $min );
+		}
+	}
+
+	/** About *****************************************************************/
+
+	/**
+	 * Output the about screen.
+	 *
+	 * @since 1.7.0
+	 */
+	public function about_screen() {
+	?>
+
+		<div class="wrap about-wrap">
+
+			<?php self::welcome_text(); ?>
+
+			<?php self::tab_navigation( __METHOD__ ); ?>
+
+			<?php if ( self::is_new_install() ) : ?>
+
+				<div id="welcome-panel" class="welcome-panel">
+					<div class="welcome-panel-content">
+						<h3 style="margin:0"><?php _e( 'Getting Started with BuddyPress', 'buddypress' ); ?></h3>
+						<div class="welcome-panel-column-container">
+							<div class="welcome-panel-column">
+								<h4><?php _e( 'Configure BuddyPress', 'buddypress' ); ?></h4>
+								<ul>
+									<li><?php printf(
+									'<a href="%s" class="welcome-icon welcome-edit-page">' . __( 'Set Up Components', 'buddypress' ) . '</a>', esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-components' ), $this->settings_page ) ) )
+									); ?></li>
+									<li><?php printf(
+									'<a href="%s" class="welcome-icon welcome-edit-page">' . __( 'Assign Components to Pages', 'buddypress' ) . '</a>', esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-page-settings' ), $this->settings_page ) ) )
+									); ?></li>
+									<li><?php printf(
+									'<a href="%s" class="welcome-icon welcome-edit-page">' . __( 'Customize Settings', 'buddypress' ) . '</a>', esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-settings' ), $this->settings_page ) ) )
+									); ?></li>
+								</ul>
+								<a class="button button-primary button-hero" style="margin-bottom:20px;margin-top:0;" href="<?php echo esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-components' ), $this->settings_page ) ) ); ?>"><?php _e( 'Get Started', 'buddypress' ); ?></a>
+							</div>
+							<div class="welcome-panel-column">
+								<h4><?php _e( 'Administration Tools', 'buddypress' ); ?></h4>
+								<ul>
+									<?php if ( bp_is_active( 'members' ) ) : ?>
+										<li><?php printf( '<a href="%s" class="welcome-icon welcome-add-page">' . __( 'Add User Profile Fields', 'buddypress' ) . '</a>', esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-profile-setup' ), 'users.php' ) ) ) ); ?></li>
+									<?php endif; ?>
+									<li><?php printf( '<a href="%s" class="welcome-icon welcome-add-page">' . __( 'Manage User Signups', 'buddypress' ) . '</a>', esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-signups' ), 'users.php' ) ) ) ); ?></li>
+									<?php if ( bp_is_active( 'activity' ) ) : ?>
+										<li><?php printf( '<a href="%s" class="welcome-icon welcome-add-page">' . __( 'Moderate Activity Streams', 'buddypress' ) . '</a>', esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-activity' ), 'admin.php' ) ) ) ); ?></li>
+									<?php endif; ?>
+									<?php if ( bp_is_active( 'groups' ) ) : ?>
+										<li><?php printf( '<a href="%s" class="welcome-icon welcome-add-page">' . __( 'Manage Groups', 'buddypress' ) . '</a>', esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-groups' ), 'admin.php' ) ) ) ); ?></li>
+									<?php endif; ?>
+									<li><?php printf( '<a href="%s" class="welcome-icon welcome-add-page">' . __( 'Repair Data', 'buddypress' ) . '</a>', esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-tools' ), 'tools.php' ) ) ) ); ?>
+									</li>
+								</ul>
+							</div>
+							<div class="welcome-panel-column welcome-panel-last">
+								<h4><?php _e( 'Community and Support', 'buddypress'  ); ?></h4>
+								<p class="welcome-icon welcome-learn-more" style="margin-right:10px"><?php _e( 'Looking for help? The <a href="https://codex.buddypress.org/">BuddyPress Codex</a> has you covered.', 'buddypress' ) ?></p>
+								<p class="welcome-icon welcome-learn-more" style="margin-right:10px"><?php _e( 'Can&#8217;t find what you need? Stop by <a href="https://buddypress.org/support/">our support forums</a>, where active BuddyPress users and developers are waiting to share tips and more.', 'buddypress' ) ?></p>
+							</div>
+						</div>
+					</div>
+				</div>
+
+			<?php endif; ?>
+
+			<div class="headline-feature">
+				<h3><?php esc_html_e( 'Cover Images For Members And Groups', 'buddypress' ); ?></h3>
+
+				<div class="featured-image">
+					<img src="<?php echo esc_url( buddypress()->plugin_url . 'bp-core/admin/images/group-cover-image.png' ); ?>" alt="<?php esc_attr_e( 'Cover Images for Members and Groups', 'buddypress' ); ?>" width="843" height="377">
+				</div>
+				<div>
+					 <p><?php printf( __( 'Your community can now upload cover photos for their user profiles and groups. <a href="%s">Learn more &rarr;</a>', 'buddypress' ), esc_url( 'https://codex.buddypress.org/themes/buddypress-cover-images/' ) ); ?></p>
+				</div>
+				<div class="clear"></div>
+
+			</div>
+
+			<hr>
+
+			<div class="feature-section two-col">
+				<div>
+					<img src="<?php echo esc_url( buddypress()->plugin_url . 'bp-core/admin/images/member-type-field.png' ); ?>" alt="<?php esc_attr_e( 'Profile Field for Member Type', 'buddypress' ); ?>">
+					<h3><?php esc_html_e( 'Profile Fields For Specific Member Types', 'buddypress' ); ?></h3>
+					<p><?php printf( __( 'You can create profile fields which are available to one or more <a href="%s">custom member types</a>.', 'buddypress' ), esc_url( 'https://codex.buddypress.org/developer/member-types/' ) ); ?></p>
+				</div>
+				<div class="last-feature">
+					<img src="<?php echo esc_url( buddypress()->plugin_url . 'bp-core/admin/images/theme-stylesheets.png' ); ?>" alt="<?php esc_attr_e( 'BuddyPress Companion Stylesheets', 'buddypress' ); ?>">
+					<h3><?php esc_html_e( 'More Companion Stylesheets For Themes', 'buddypress' ); ?></h3>
+					<p><?php esc_html_e( 'BuddyPress looks and works better than ever with WordPress theme Twenty Thirteen, and the upcoming Twenty Sixteen.', 'buddypress' ); ?></p>
+				</div>
+			</div>
+
+			<div class="feature-section two-col">
+				<div>
+					<img src="<?php echo esc_url( buddypress()->plugin_url . 'bp-core/admin/images/group-home-page.png' ); ?>" alt="<?php esc_attr_e( 'Customize your group home page', 'buddypress' ); ?>">
+					<h3><?php esc_html_e( 'Customizable Group Home Page', 'buddypress' ); ?></h3>
+					<p><?php esc_html_e( 'You can customize each and every Group\'s home page by using the new front page template hierarchy.', 'buddypress' ); ?> <a href="https://bpdevel.wordpress.com/2015/10/03/get-ready-2-4-0-will-introduce-important-changes-in-groups-homes/"><?php esc_html_e( 'Learn how &rarr;', 'buddypress' ); ?></a></p>
+				</div>
+				<div class="last-feature">
+					<img src="<?php echo esc_url( buddypress()->plugin_url . 'bp-core/admin/images/accessibility.gif' ); ?>" alt="<?php esc_attr_e( 'Accessibility Upgrades', 'buddypress' ); ?>">
+					<h3><?php esc_html_e( 'Accessibility Upgrades', 'buddypress' ); ?></h3>
+					<p><?php _e( 'We\'re making BuddyPress more usable and accessible for everyone, from the frontend templates to the backend admin screens.', 'buddypress' ); ?></p>
+				</div>
+
+			</div>
+
+			<div class="changelog">
+				<h3><?php esc_html_e( 'Under The Hood', 'buddypress' ); ?></h3>
+
+				<div class="feature-section col two-col">
+					<div>
+						<h4><?php esc_html_e( 'User Experience', 'buddypress' ); ?></h4>
+						<p><?php esc_html_e( "Enhancements to the What's New form bring better user experience for your community, and make it easier for developers to add new features.", 'buddypress' ); ?></p>
+
+						<h4><?php esc_html_e( 'Cache Improvements', 'buddypress' ); ?></h4>
+						<p><?php esc_html_e( 'Continued object and query cache enhancements have been implemented across different components to improve performance.', 'buddypress' ); ?></p>
+					</div>
+					<div class="last-feature">
+						<h4><?php esc_html_e( 'Developer Reference', 'buddypress' ); ?></h4>
+						<p><?php esc_html_e( 'Regular updates to inline code documentation make it easier for developers to understand how BuddyPress works.', 'buddypress' ); ?></p>
+
+						<h4><?php esc_html_e( 'Constant Refinements', 'buddypress' ); ?></h4>
+						<p><?php esc_html_e( 'The BuddyPress codebase is continuously tested and updated with the latest WordPress enhancements and features.', 'buddypress' ); ?></p>
+					</div>
+				</div>
+			</div>
+
+			<p><?php _ex( 'Learn more:', 'About screen, website links', 'buddypress' ); ?> <a href="https://buddypress.org/blog/"><?php _ex( 'News', 'About screen, link to project blog', 'buddypress' ); ?></a> &bullet; <a href="https://buddypress.org/support/"><?php _ex( 'Support', 'About screen, link to support site', 'buddypress' ); ?></a> &bullet; <a href="https://codex.buddypress.org/"><?php _ex( 'Documentation', 'About screen, link to documentation', 'buddypress' ); ?></a> &bullet; <a href="https://bpdevel.wordpress.com/"><?php _ex( 'Development Blog', 'About screen, link to development blog', 'buddypress' ); ?></a></p>
+			<p><?php _ex( 'Twitter:', 'official Twitter accounts:', 'buddypress' ); ?> <a href="https://twitter.com/buddypress/"><?php _ex( 'BuddyPress', '@buddypress twitter account name', 'buddypress' ); ?></a> &bullet; <a href="https://twitter.com/bptrac/"><?php _ex( 'Trac', '@bptrac twitter account name', 'buddypress' ); ?></a> &bullet; <a href="https://twitter.com/buddypressdev/"><?php _ex( 'Development', '@buddypressdev twitter account name', 'buddypress' ); ?></a></p>
+
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Output the credits screen.
+	 *
+	 * Hardcoding this in here is pretty janky. It's fine for now, but we'll
+	 * want to leverage api.wordpress.org eventually.
+	 *
+	 * @since 1.7.0
+	 */
+	public function credits_screen() {
+	?>
+
+		<div class="wrap about-wrap">
+
+			<?php self::welcome_text(); ?>
+
+			<?php self::tab_navigation( __METHOD__ ); ?>
+
+			<p class="about-description"><?php _e( 'BuddyPress is created by a worldwide network of friendly folks like these.', 'buddypress' ); ?></p>
+
+			<h4 class="wp-people-group"><?php _e( 'Project Leaders', 'buddypress' ); ?></h4>
+			<ul class="wp-people-group " id="wp-people-group-project-leaders">
+				<li class="wp-person" id="wp-person-johnjamesjacoby">
+					<a href="https://profiles.wordpress.org/johnjamesjacoby"><img src="//www.gravatar.com/avatar/81ec16063d89b162d55efe72165c105f?s=60" class="gravatar" alt="John James Jacoby" /></a>
+					<a class="web" href="https://profiles.wordpress.org/johnjamesjacoby">John James Jacoby</a>
+					<span class="title"><?php _e( 'Project Lead', 'buddypress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-boonebgorges">
+					<a href="https://profiles.wordpress.org/boonebgorges"><img src="//www.gravatar.com/avatar/9cf7c4541a582729a5fc7ae484786c0c?s=60" class="gravatar" alt="Boone B. Gorges" /></a>
+					<a class="web" href="https://profiles.wordpress.org/boonebgorges">Boone B. Gorges</a>
+					<span class="title"><?php _e( 'Lead Developer', 'buddypress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-djpaul">
+					<a href="https://profiles.wordpress.org/djpaul"><img src="//www.gravatar.com/avatar/3bc9ab796299d67ce83dceb9554f75df?s=60" class="gravatar" alt="Paul Gibbs" /></a>
+					<a class="web" href="https://profiles.wordpress.org/djpaul">Paul Gibbs</a>
+					<span class="title"><?php _e( 'Lead Developer', 'buddypress' ); ?></span>
+				</li>
+			</ul>
+
+			<h4 class="wp-people-group"><?php _e( 'Core Team', 'buddypress' ); ?></h4>
+			<ul class="wp-people-group " id="wp-people-group-core-team">
+				<li class="wp-person" id="wp-person-r-a-y">
+					<a href="https://profiles.wordpress.org/r-a-y"><img src="//www.gravatar.com/avatar/3bfa556a62b5bfac1012b6ba5f42ebfa?s=60" class="gravatar" alt="Ray" /></a>
+					<a class="web" href="https://profiles.wordpress.org/r-a-y">Ray</a>
+					<span class="title"><?php _e( 'Core Developer', 'buddypress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-imath">
+					<a href="https://profiles.wordpress.org/imath"><img src="//www.gravatar.com/avatar/8b208ca408dad63888253ee1800d6a03?s=60" class="gravatar" alt="Mathieu Viet" /></a>
+					<a class="web" href="https://profiles.wordpress.org/imath">Mathieu Viet</a>
+					<span class="title"><?php _e( 'Core Developer', 'buddypress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-mercime">
+					<a href="https://profiles.wordpress.org/mercime"><img src="//www.gravatar.com/avatar/fae451be6708241627983570a1a1817a?s=60" class="gravatar" alt="Mercime" /></a>
+					<a class="web" href="https://profiles.wordpress.org/mercime">Mercime</a>
+					<span class="title"><?php _e( 'Navigator', 'buddypress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-dcavins">
+					<a href="https://profiles.wordpress.org/dcavins"><img src="//www.gravatar.com/avatar/a5fa7e83d59cb45ebb616235a176595a?s=60" class="gravatar" alt="David Cavins" /></a>
+					<a class="web" href="https://profiles.wordpress.org/dcavins">David Cavins</a>
+					<span class="title"><?php _e( 'Core Developer', 'buddypress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-tw2113">
+					<a href="https://profiles.wordpress.org/tw2113"><img src="//www.gravatar.com/avatar/a5d7c934621fa1c025b83ee79bc62366?s=60" class="gravatar" alt="Michael Beckwith" /></a>
+					<a class="web" href="https://profiles.wordpress.org/tw2113">Michael Beckwith</a>
+					<span class="title"><?php _e( 'Core Developer', 'buddypress' ); ?></span>
+				</li>
+				<li class="wp-person" id="wp-person-hnla">
+					<a href="https://profiles.wordpress.org/hnla"><img src="//www.gravatar.com/avatar/3860c955aa3f79f13b92826ae47d07fe?s=60" class="gravatar" alt="Hugo Ashmore" /></a>
+					<a class="web" href="https://profiles.wordpress.org/hnla">Hugo</a>
+					<span class="title"><?php _e( 'Core Developer', 'buddypress' ); ?></span>
+				</li>
+			</ul>
+
+			<h4 class="wp-people-group"><?php _e( 'Recent Rockstars', 'buddypress' ); ?></h4>
+			<ul class="wp-people-group " id="wp-people-group-rockstars">
+				<li class="wp-person" id="wp-person-henry-wright">
+					<a href="https://profiles.wordpress.org/henry.wright"><img src="//www.gravatar.com/avatar/0da2f1a9340d6af196b870f6c107a248?s=60" class="gravatar" alt="Henry Wright" /></a>
+					<a class="web" href="https://profiles.wordpress.org/henry.wright">Henry Wright</a>
+				</li>
+				<li class="wp-person" id="wp-person-danbp">
+					<a href="https://profiles.wordpress.org/danbp"><img src="//www.gravatar.com/avatar/0deae2e7003027fbf153500cd3fa5501?s=60" class="gravatar" alt="danbp" /></a>
+					<a class="web" href="https://profiles.wordpress.org/danbp">danbp</a>
+				</li>
+				<li class="wp-person" id="wp-person-shanebp">
+					<a href="https://profiles.wordpress.org/shanebp"><img src="//www.gravatar.com/avatar/ffd294ab5833ba14aaf175f9acc71cc4?s=60" class="gravatar" alt="shanebp" /></a>
+					<a class="web" href="https://profiles.wordpress.org/shanebp">shanebp</a>
+				</li>
+				<li class="wp-person" id="wp-person-netweb">
+					<a href="https://profiles.wordpress.org/netweb"><img src="//www.gravatar.com/avatar/97e1620b501da675315ba7cfb740e80f?s=60" class="gravatar" alt="Stephen Edgar" /></a>
+					<a class="web" href="https://profiles.wordpress.org/netweb">Stephen Edgar</a>
+				</li>
+				<li class="wp-person" id="wp-person-dimensionmedia">
+					<a href="https://profiles.wordpress.org/dimensionmedia"><img src="//www.gravatar.com/avatar/7735aada1ec39d0c1118bd92ed4551f1?s=60" class="gravatar" alt="David Bisset" /></a>
+					<a class="web" href="https://profiles.wordpress.org/dimensionmedia">David Bisset</a>
+				</li>
+
+			</ul>
+
+			<h4 class="wp-people-group"><?php printf( esc_html__( 'Contributors to BuddyPress %s', 'buddypress' ), self::display_version() ); ?></h4>
+			<p class="wp-credits-list">
+				<a href="https://profiles.wordpress.org/ankit-k-gupta/">Ankit K Gupta</a>,
+				<a href="https://profiles.wordpress.org/anthonyvalera/">anthonyvalera</a>,
+				<a href="https://profiles.wordpress.org/boonebgorges/">Boone B Gorges (boonebgorges)</a>,
+				<a href="https://profiles.wordpress.org/thebrandonallen/">Brandon Allen (thebrandonallen)</a>,
+				<a href="https://profiles.wordpress.org/BuddyBoss/">BuddyBoss</a>,
+				<a href="https://profiles.wordpress.org/needle/">Christian Wach (needle)</a>,
+				<a href="https://profiles.wordpress.org/damland/">damland</a>,
+				<a href="https://profiles.wordpress.org/danbp/">danbp</a>,
+				<a href="https://profiles.wordpress.org/daniluk4000/">daniluk4000</a>,
+				<a href="https://profiles.wordpress.org/davidtcarson/">David Carson (davidtcarson)</a>,
+				<a href="https://profiles.wordpress.org/dcavins/">David Cavins (dcavins)</a>,
+				<a href="https://profiles.wordpress.org/valendesigns/">Derek Herman (valendesigns)</a>,
+				<a href="https://profiles.wordpress.org/ganesh641/">ganesh641</a>,
+				<a href="https://profiles.wordpress.org/hnla/">Hugo (hnla)</a>,
+				<a href="https://profiles.wordpress.org/jdgrimes/">J.D. Grimes</a>,
+				<a href="https://profiles.wordpress.org/JeffMatson/">JeffMatson</a>,
+				<a href="https://profiles.wordpress.org/jmarx75/">jmarx75</a>,
+				<a href="https://profiles.wordpress.org/johnjamesjacoby/">John James Jacoby (johnjamesjacoby)</a>,
+				<a href="https://profiles.wordpress.org/kahless/">Jon Breitenbucher (kahless)</a>,
+				<a href="https://profiles.wordpress.org/jbrinley/">Jonathan Brinley (jbrinley)</a>,
+				<a href="https://profiles.wordpress.org/dunhakdis/">Joseph G. (dunhakdis)</a>,
+				<a href="https://profiles.wordpress.org/lakrisgubben/">lakrisgubben</a>,
+				<a href="https://profiles.wordpress.org/landwire/">landwire</a>,
+				<a href="https://profiles.wordpress.org/Offereins">Laurens Offereins (Offereins)</a>
+				<a href="https://profiles.wordpress.org/lenasterg/">lenasterg</a>,
+				<a href="https://profiles.wordpress.org/mrk-1/">m@rk</a>,
+				<a href="https://profiles.wordpress.org/mahype/">mahype</a>,
+				<a href="https://profiles.wordpress.org/imath/">Mathieu Viet (imath)</a>,
+				<a href="https://profiles.wordpress.org/mehulkaklotar/">mehulkaklotar</a>,
+				<a href="https://profiles.wordpress.org/mercime/">mercime</a>,
+				<a href="https://profiles.wordpress.org/tw2113/">Michael Beckwith (tw2113)</a>,
+				<a href="https://profiles.wordpress.org/modemlooper/">modemlooper</a>,
+				<a href="https://profiles.wordpress.org/pareshradadiya/">paresh.radadiya (pareshradadiya)</a>,
+				<a href="https://profiles.wordpress.org/DJPaul/">Paul Gibbs (DJPaul)</a>,
+				<a href="https://profiles.wordpress.org/r-a-y/">r-a-y</a>,
+				<a href="https://profiles.wordpress.org/ramiy/">Rami Yushuvaev (ramiy)</a>,
+				<a href="https://profiles.wordpress.org/ritteshpatel/">Ritesh Patel (ritteshpatel)</a>,
+				<a href="https://profiles.wordpress.org/SergeyBiryukov/">Sergey Biryukov (SergeyBiryukov)</a>,
+				<a href="https://profiles.wordpress.org/shanebp/">shanebp</a>,
+				<a href="https://profiles.wordpress.org/slaffik/">Slava UA (slaffik)</a>,
+				<a href="https://profiles.wordpress.org/netweb/">Stephen Edgar (netweb)</a>,
+				<a href="https://profiles.wordpress.org/svenl77/">svenl77</a>,
+				<a href="https://profiles.wordpress.org/tanner-m/">Tanner Moushey</a>,
+				<a href="https://profiles.wordpress.org/thomaslhotta/">thomaslhotta</a>,
+				<a href="https://profiles.wordpress.org/vnd/">vnd</a>,
+				<a href="https://profiles.wordpress.org/willgladstone/">willgladstone</a>.
+			</p>
+
+			<h4 class="wp-people-group"><?php _e( 'External Libraries', 'buddypress' ); ?></h4>
+			<p class="wp-credits-list">
+				<a href="https://github.com/ichord/At.js">At.js</a>,
+				<a href="https://github.com/ichord/Caret.js">Caret.js</a>,
+				<a href="https://github.com/carhartl/jquery-cookie">jquery.cookie</a>.
+			</p>
+
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Output welcome text and badge for What's New and Credits pages.
+	 *
+	 * @since 2.2.0
+	 */
+	public static function welcome_text() {
+
+		// Switch welcome text based on whether this is a new installation or not
+		$welcome_text = ( self::is_new_install() )
+			? __( 'Thank you for installing BuddyPress! BuddyPress helps you run any kind of social network on your WordPress, with member profiles, activity streams, user groups, messaging, and more.', 'buddypress' )
+			: __( 'BuddyPress %s comes with a bunch of great improvements we think you&#8217;re really going to like.', 'buddypress' );
+
+		?>
+
+		<h1><?php printf( esc_html__( 'Welcome to BuddyPress %s', 'buddypress' ), self::display_version() ); ?></h1>
+
+		<div class="about-text">
+			<?php
+			if ( self::is_new_install() ) {
+				echo $welcome_text;
+			} else {
+				printf( $welcome_text, self::display_version() );
+			}
+			?>
+		</div>
+
+		<div class="bp-badge"></div>
+
+		<?php
+	}
+
+	/**
+	 * Output tab navigation for `What's New` and `Credits` pages.
+	 *
+	 * @since 2.2.0
+	 * @param string $tab
+	 */
+	public static function tab_navigation( $tab = 'whats_new' ) {
+	?>
+
+		<h2 class="nav-tab-wrapper">
+			<a class="nav-tab <?php if ( 'BP_Admin::about_screen' === $tab ) : ?>nav-tab-active<?php endif; ?>" href="<?php echo esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-about' ), 'index.php' ) ) ); ?>">
+				<?php esc_html_e( 'What&#8217;s New', 'buddypress' ); ?>
+			</a><a class="nav-tab <?php if ( 'BP_Admin::credits_screen' === $tab ) : ?>nav-tab-active<?php endif; ?>" href="<?php echo esc_url( bp_get_admin_url( add_query_arg( array( 'page' => 'bp-credits' ), 'index.php' ) ) ); ?>">
+				<?php esc_html_e( 'Credits', 'buddypress' ); ?>
+			</a>
+		</h2>
+
+	<?php
+	}
+
+	/** Helpers ***************************************************************/
+
+	/**
+	 * Return true/false based on whether a query argument is set.
+	 *
+	 * @see bp_do_activation_redirect()
+	 *
+	 * @since 2.2.0
+	 *
+	 * @return bool
+	 */
+	public static function is_new_install() {
+		return (bool) isset( $_GET['is_new_install'] );
+	}
+
+	/**
+	 * Return a user-friendly version-number string, for use in translations.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @return string
+	 */
+	public static function display_version() {
+
+		// Use static variable to prevent recalculations
+		static $display = '';
+
+		// Only calculate on first run
+		if ( '' === $display ) {
+
+			// Get current version
+			$version = bp_get_version();
+
+			// Check for prerelease hyphen
+			$pre     = strpos( $version, '-' );
+
+			// Strip prerelease suffix
+			$display = ( false !== $pre )
+				? substr( $version, 0, $pre )
+				: $version;
+		}
+
+		// Done!
+		return $display;
+	}
+}
+endif; // class_exists check
+
+/**
+ * Setup BuddyPress Admin.
+ *
+ * @since 1.6.0
+ *
+ * @uses BP_Admin
+ */
+function bp_admin() {
+	buddypress()->admin = new BP_Admin();
+	return;
+
+
+	// These are strings we may use to describe maintenance/security releases, where we aim for no new strings.
+
+	_n_noop( 'Maintenance Release', 'Maintenance Releases', 'buddypress' );
+	_n_noop( 'Security Release', 'Security Releases', 'buddypress' );
+	_n_noop( 'Maintenance and Security Release', 'Maintenance and Security Releases', 'buddypress' );
+
+	/* translators: 1: WordPress version number. */
+	_n_noop( '<strong>Version %1$s</strong> addressed a security issue.',
+	         '<strong>Version %1$s</strong> addressed some security issues.',
+	         'buddypress' );
+
+	/* translators: 1: WordPress version number, 2: plural number of bugs. */
+	_n_noop( '<strong>Version %1$s</strong> addressed %2$s bug.',
+	         '<strong>Version %1$s</strong> addressed %2$s bugs.',
+	         'buddypress' );
+
+	/* translators: 1: WordPress version number, 2: plural number of bugs. Singular security issue. */
+	_n_noop( '<strong>Version %1$s</strong> addressed a security issue and fixed %2$s bug.',
+	         '<strong>Version %1$s</strong> addressed a security issue and fixed %2$s bugs.',
+	         'buddypress' );
+
+	/* translators: 1: WordPress version number, 2: plural number of bugs. More than one security issue. */
+	_n_noop( '<strong>Version %1$s</strong> addressed some security issues and fixed %2$s bug.',
+	         '<strong>Version %1$s</strong> addressed some security issues and fixed %2$s bugs.',
+	         'buddypress' );
+
+	__( 'For more information, see <a href="%s">the release notes</a>.', 'buddypress' );
+}
Index: /tags/2.4.3/src/bp-core/bp-core-adminbar.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-adminbar.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-adminbar.php	(revision 10410)
@@ -0,0 +1,129 @@
+<?php
+/**
+ * BuddyPress Core Toolbar.
+ *
+ * Handles the core functions related to the WordPress Toolbar.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Add the secondary BuddyPress area to the my-account menu.
+ *
+ * @since 1.6.0
+ *
+ * @global WP_Admin_Bar $wp_admin_bar
+ */
+function bp_admin_bar_my_account_root() {
+	global $wp_admin_bar;
+
+	// Bail if this is an ajax request
+	if ( !bp_use_wp_admin_bar() || defined( 'DOING_AJAX' ) )
+		return;
+
+	// Only add menu for logged in user
+	if ( is_user_logged_in() ) {
+
+		// Add secondary parent item for all BuddyPress components
+		$wp_admin_bar->add_menu( array(
+			'parent'    => 'my-account',
+			'id'        => 'my-account-buddypress',
+			'title'     => __( 'My Account', 'buddypress' ),
+			'group'     => true,
+			'meta'      => array(
+				'class' => 'ab-sub-secondary'
+			)
+		) );
+
+		// Remove 'Edit' post link as it's not applicable to BP
+		// Remove when https://core.trac.wordpress.org/ticket/29538 is addressed
+		if ( is_buddypress() ) {
+			$wp_admin_bar->remove_node( 'edit' );
+		}
+	}
+}
+add_action( 'admin_bar_menu', 'bp_admin_bar_my_account_root', 100 );
+
+/**
+ * Handle the Toolbar/BuddyBar business.
+ *
+ * @since 1.2.0
+ *
+ * @global string $wp_version
+ * @uses bp_get_option()
+ * @uses is_user_logged_in()
+ * @uses bp_use_wp_admin_bar()
+ * @uses show_admin_bar()
+ * @uses add_action() To hook 'bp_adminbar_logo' to 'bp_adminbar_logo'.
+ * @uses add_action() To hook 'bp_adminbar_login_menu' to 'bp_adminbar_menus'.
+ * @uses add_action() To hook 'bp_adminbar_account_menu' to 'bp_adminbar_menus'.
+ * @uses add_action() To hook 'bp_adminbar_thisblog_menu' to 'bp_adminbar_menus'.
+ * @uses add_action() To hook 'bp_adminbar_random_menu' to 'bp_adminbar_menus'.
+ * @uses add_action() To hook 'bp_core_admin_bar' to 'wp_footer'.
+ * @uses add_action() To hook 'bp_core_admin_bar' to 'admin_footer'.
+ */
+function bp_core_load_admin_bar() {
+
+	// Show the Toolbar for logged out users
+	if ( ! is_user_logged_in() && (int) bp_get_option( 'hide-loggedout-adminbar' ) != 1 ) {
+		show_admin_bar( true );
+	}
+
+	// Hide the WordPress Toolbar and show the BuddyBar
+	if ( ! bp_use_wp_admin_bar() ) {
+		_doing_it_wrong( __FUNCTION__, __( 'The BuddyBar is no longer supported. Please migrate to the WordPress toolbar as soon as possible.', 'buddypress' ), '2.1.0' );
+
+		// Keep the WP Toolbar from loading
+		show_admin_bar( false );
+
+		// Actions used to build the BP Toolbar
+		add_action( 'bp_adminbar_logo',  'bp_adminbar_logo'               );
+		add_action( 'bp_adminbar_menus', 'bp_adminbar_login_menu',    2   );
+		add_action( 'bp_adminbar_menus', 'bp_adminbar_account_menu',  4   );
+		add_action( 'bp_adminbar_menus', 'bp_adminbar_thisblog_menu', 6   );
+		add_action( 'bp_adminbar_menus', 'bp_adminbar_random_menu',   100 );
+
+		// Actions used to append BP Toolbar to footer
+		add_action( 'wp_footer',    'bp_core_admin_bar', 8 );
+		add_action( 'admin_footer', 'bp_core_admin_bar'    );
+	}
+}
+add_action( 'init', 'bp_core_load_admin_bar', 9 );
+
+/**
+ * Handle the enqueueing of toolbar CSS.
+ *
+ * This function exists mostly for backwards compatibility reasons, so anyone
+ * previously unhooking this function can continue to do so. It's hooked to
+ * the `bp_init` action in `bp-core-actions.php`.
+ *
+ * @since 1.5.0
+ */
+function bp_core_load_admin_bar_css() {
+	add_action( 'bp_enqueue_scripts',       'bp_core_enqueue_admin_bar_css', 1 );
+	add_action( 'bp_admin_enqueue_scripts', 'bp_core_enqueue_admin_bar_css', 1 );
+}
+
+/**
+ * Enqueue supplemental WordPress Toolbar styling.
+ *
+ * @since 2.1.0
+ *
+ * @see bp_core_register_common_styles()
+ * @see bp_core_load_admin_bar_css()
+ */
+function bp_core_enqueue_admin_bar_css() {
+
+	// Bail if not using WordPress's admin bar or it's not showing on this
+	// page request.
+	if ( ! bp_use_wp_admin_bar() || ! is_admin_bar_showing() ) {
+		return;
+	}
+
+	// Enqueue the additional adminbar css
+	wp_enqueue_style( 'bp-admin-bar' );
+}
Index: /tags/2.4.3/src/bp-core/bp-core-attachments.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-attachments.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-attachments.php	(revision 10410)
@@ -0,0 +1,1396 @@
+<?php
+/**
+ * BuddyPress Attachments functions.
+ *
+ * @package BuddyPress
+ * @subpackage Attachments
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Check if the current WordPress version is using Plupload 2.1.1
+ *
+ * Plupload 2.1.1 was introduced in WordPress 3.9. Our bp-plupload.js
+ * script requires it. So we need to make sure the current WordPress
+ * match with our needs.
+ *
+ * @since  2.3.0
+ *
+ * @return bool True if WordPress is 3.9+, false otherwise.
+ */
+function bp_attachments_is_wp_version_supported() {
+	return (bool) version_compare( bp_get_major_wp_version(), '3.9', '>=' );
+}
+
+/**
+ * Get the Attachments Uploads dir data
+ *
+ * @since  2.4.0
+ *
+ * @param  string        $data The data to get. Possible values are: 'dir', 'basedir' & 'baseurl'
+ *                       Leave empty to get all datas.
+ * @return string|array  The needed Upload dir data.
+ */
+function bp_attachments_uploads_dir_get( $data = '' ) {
+	$attachments_dir = 'buddypress';
+	$retval          = '';
+
+	if ( 'dir' === $data ) {
+		$retval = $attachments_dir;
+	} else {
+		$upload_data = bp_upload_dir();
+
+		// Return empty string, if Uploads data are not available
+		if ( ! $upload_data ) {
+			return $retval;
+		}
+
+		// Build the Upload data array for BuddyPress attachments
+		foreach ( $upload_data as $key => $value ) {
+			if ( 'basedir' === $key || 'baseurl' === $key ) {
+				$upload_data[ $key ] = trailingslashit( $value ) . $attachments_dir;
+			} else {
+				unset( $upload_data[ $key ] );
+			}
+		}
+
+		// Add the dir to the array
+		$upload_data['dir'] = $attachments_dir;
+
+		if ( empty( $data ) ) {
+			$retval = $upload_data;
+		} elseif ( isset( $upload_data[ $data ] ) ) {
+			$retval = $upload_data[ $data ];
+		}
+	}
+
+	/**
+	 * Filter here to edit the Attachments upload dir data.
+	 *
+	 * @since  2.4.0
+	 *
+	 * @param  string|array $retval      The needed Upload dir data or the full array of data
+	 * @param  string       $data        The data requested
+	 */
+	return apply_filters( 'bp_attachments_uploads_dir_get', $retval, $data );
+}
+
+/**
+ * Get the max upload file size for any attachment
+ *
+ * @since  2.4.0
+ *
+ * @param  string $type A string to inform about the type of attachment
+ *                      we wish to get the max upload file size for
+ * @return int    max upload file size for any attachment
+ */
+function bp_attachments_get_max_upload_file_size( $type = '' ) {
+	$fileupload_maxk = bp_core_get_root_option( 'fileupload_maxk' );
+
+	if ( '' === $fileupload_maxk ) {
+		$fileupload_maxk = 5120000; // 5mb;
+	} else {
+		$fileupload_maxk = $fileupload_maxk * 1024;
+	}
+
+	/**
+	 * Filter here to edit the max upload file size.
+	 *
+	 * @since  2.4.0
+	 *
+	 * @param  int    $fileupload_maxk Max upload file size for any attachment
+	 * @param  string $type            The attachment type (eg: 'avatar' or 'cover_image')
+	 */
+	return apply_filters( 'bp_attachments_get_max_upload_file_size', $fileupload_maxk, $type );
+}
+
+/**
+ * Get allowed types for any attachment
+ *
+ * @since  2.4.0
+ *
+ * @param  string $type  The extension types to get.
+ *                       Default: 'avatar'
+ * @return array         The list of allowed extensions for attachments
+ */
+function bp_attachments_get_allowed_types( $type = 'avatar' ) {
+	// Defaults to BuddyPress supported image extensions
+	$exts = array( 'jpeg', 'gif', 'png' );
+
+	/**
+	 * It's not a BuddyPress feature, get the allowed extensions
+	 * matching the $type requested
+	 */
+	if ( 'avatar' !== $type && 'cover_image' !== $type ) {
+		// Reset the default exts
+		$exts = array();
+
+		switch ( $type ) {
+			case 'video' :
+				$exts = wp_get_video_extensions();
+			break;
+
+			case 'audio' :
+				$exts = wp_get_video_extensions();
+			break;
+
+			default:
+				$allowed_mimes = get_allowed_mime_types();
+
+				/**
+				 * Search for allowed mimes matching the type
+				 *
+				 * eg: using 'application/vnd.oasis' as the $type
+				 * parameter will get all OpenOffice extensions supported
+				 * by WordPress and allowed for the current user.
+				 */
+				if ( '' !== $type ) {
+					$allowed_mimes = preg_grep( '/' . addcslashes( $type, '/.+-' ) . '/', $allowed_mimes );
+				}
+
+				$allowed_types = array_keys( $allowed_mimes );
+
+				// Loop to explode keys using '|'
+				foreach ( $allowed_types as $allowed_type ) {
+					$t = explode( '|', $allowed_type );
+					$exts = array_merge( $exts, (array) $t );
+				}
+			break;
+		}
+	}
+
+	/**
+	 * Filter here to edit the allowed extensions by attachment type.
+	 *
+	 * @since  2.4.0
+	 *
+	 * @param  array  $exts List of allowed extensions
+	 * @param  string $type The requested file type
+	 */
+	return apply_filters( 'bp_attachments_get_allowed_types', $exts, $type );
+}
+
+/**
+ * Get allowed attachment mime types.
+ *
+ * @since 2.4.0
+ *
+ * @param  string $type         The extension types to get (Optional).
+ * @param  array $allowed_types List of allowed extensions
+ * @return array                List of allowed mime types
+ */
+function bp_attachments_get_allowed_mimes( $type = '', $allowed_types = array() ) {
+	if ( empty( $allowed_types ) ) {
+		$allowed_types = bp_attachments_get_allowed_types( $type );
+	}
+
+	$validate_mimes = wp_match_mime_types( join( ',', $allowed_types ), wp_get_mime_types() );
+	$allowed_mimes  = array_map( 'implode', $validate_mimes );
+
+	/**
+	 * Include jpg type if jpeg is set
+	 */
+	if ( isset( $allowed_mimes['jpeg'] ) && ! isset( $allowed_mimes['jpg'] ) ) {
+		$allowed_mimes['jpg'] = $allowed_mimes['jpeg'];
+	}
+
+	return $allowed_mimes;
+}
+
+/**
+ * Check the uploaded attachment type is allowed
+ *
+ * @since  2.4.0
+ *
+ * @param  string $file          Full path to the file.
+ * @param  string $filename      The name of the file (may differ from $file due to $file being
+ *                               in a tmp directory).
+ * @param  array  $allowed_mimes The attachment allowed mimes (Required)
+ * @return bool                  True if the attachment type is allowed. False otherwise
+ */
+function bp_attachments_check_filetype( $file, $filename, $allowed_mimes ) {
+	$filetype = wp_check_filetype_and_ext( $file, $filename, $allowed_mimes );
+
+	if ( ! empty( $filetype['ext'] ) && ! empty( $filetype['type'] ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Use the absolute path to an image to set an attachment type for a given item.
+ *
+ * @since 2.4.0
+ *
+ * @param  string $type        The attachment type to create (avatar or cover_image). Default: avatar.
+ * @param  array  $args {
+ *     @type int    $item_id   The ID of the object (Required). Default: 0.
+ *     @type string $object    The object type (eg: group, user, blog) (Required). Default: 'user'.
+ *     @type string $component The component for the object (eg: groups, xprofile, blogs). Default: ''.
+ *     @type string $image     The absolute path to the image (Required). Default: ''.
+ *     @type int    $crop_w    Crop width. Default: 0.
+ *     @type int    $crop_h    Crop height. Default: 0.
+ *     @type int    $crop_x    The horizontal starting point of the crop. Default: 0.
+ *     @type int    $crop_y    The vertical starting point of the crop. Default: 0.
+ * }
+ * @return bool  True on success, false otherwise.
+ */
+function bp_attachments_create_item_type( $type = 'avatar', $args = array() ) {
+	if ( empty( $type ) || ( $type !== 'avatar' && $type !== 'cover_image' ) ) {
+		return false;
+	}
+
+	$r = bp_parse_args( $args, array(
+		'item_id'   => 0,
+		'object'    => 'user',
+		'component' => '',
+		'image'     => '',
+		'crop_w'    => 0,
+		'crop_h'    => 0,
+		'crop_x'    => 0,
+		'crop_y'    => 0
+	), 'create_item_' . $type );
+
+	if ( empty( $r['item_id'] ) || empty( $r['object'] ) || ! file_exists( $r['image'] ) || ! @getimagesize( $r['image'] ) ) {
+		return false;
+	}
+
+	// Make sure the file path is safe
+	if ( 0 !== validate_file( $r['image'] ) ) {
+		return false;
+	}
+
+	// Set the component if not already done
+	if ( empty( $r['component'] ) ) {
+		if ( 'user' === $r['object'] ) {
+			$r['component'] = 'xprofile';
+		} else {
+			$r['component'] = $r['object'] . 's';
+		}
+	}
+
+	// Get allowed mimes for the Attachment type and check the image one is.
+	$allowed_mimes = bp_attachments_get_allowed_mimes( $type );
+	$is_allowed    = wp_check_filetype( $r['image'], $allowed_mimes );
+
+	// It's not an image.
+	if ( ! $is_allowed['ext'] ) {
+		return false;
+	}
+
+	// Init the Attachment data
+	$attachment_data = array();
+
+	if ( 'avatar' === $type ) {
+		// Set crop width for the avatar if not given
+		if ( empty( $r['crop_w'] ) ) {
+			$r['crop_w'] = bp_core_avatar_full_width();
+		}
+
+		// Set crop height for the avatar if not given
+		if ( empty( $r['crop_h'] ) ) {
+			$r['crop_h'] = bp_core_avatar_full_height();
+		}
+
+		if ( is_callable( $r['component'] . '_avatar_upload_dir' ) ) {
+			$dir_args = array( $r['item_id'] );
+
+			// In case  of xprofile, we need an extra argument
+			if ( 'xprofile' === $r['component'] ) {
+				$dir_args = array( false, $r['item_id'] );
+			}
+
+			$attachment_data = call_user_func_array( $r['component'] . '_avatar_upload_dir', $dir_args );
+		}
+	} elseif ( 'cover_image' === $type ) {
+		$attachment_data = bp_attachments_uploads_dir_get();
+
+		// The BP Attachments Uploads Dir is not set, stop.
+		if ( ! $attachment_data ) {
+			return false;
+		}
+
+		// Default to members for xProfile
+		$object_subdir = 'members';
+
+		if ( 'xprofile' !== $r['component'] ) {
+			$object_subdir = sanitize_key( $r['component'] );
+		}
+
+		// Set Subdir
+		$attachment_data['subdir'] = $object_subdir . '/' . $r['item_id'] . '/cover-image';
+
+		// Set Path
+		$attachment_data['path'] = trailingslashit( $attachment_data['basedir'] ) . $attachment_data['subdir'];
+	}
+
+	if ( ! isset( $attachment_data['path'] ) || ! isset( $attachment_data['subdir'] ) ) {
+		return false;
+	}
+
+	// It's not a regular upload, we may need to create some folders
+	if ( ! is_dir( $attachment_data['path'] ) ) {
+		if ( ! wp_mkdir_p( $attachment_data['path'] ) ) {
+			return false;
+		}
+	}
+
+	// Set the image name and path
+	$image_file_name = wp_unique_filename( $attachment_data['path'], basename( $r['image'] ) );
+	$image_file_path = $attachment_data['path'] . '/' . $image_file_name;
+
+	// Copy the image file into the avatar dir
+	if ( ! copy( $r['image'], $image_file_path ) ) {
+		return false;
+	}
+
+	// Init the response
+	$created = false;
+
+	// It's an avatar, we need to crop it.
+	if ( 'avatar' === $type ) {
+		$created = bp_core_avatar_handle_crop( array(
+			'object'        => $r['object'],
+			'avatar_dir'    => trim( dirname( $attachment_data['subdir'] ), '/' ),
+			'item_id'       => (int) $r['item_id'],
+			'original_file' => trailingslashit( $attachment_data['subdir'] ) . $image_file_name,
+			'crop_w'        => $r['crop_w'],
+			'crop_h'        => $r['crop_h'],
+			'crop_x'        => $r['crop_x'],
+			'crop_y'        => $r['crop_y']
+		) );
+
+	// It's a cover image we need to fit it to feature's dimensions
+	} elseif ( 'cover_image' === $type ) {
+		$cover_image = bp_attachments_cover_image_generate_file( array(
+			'file'            => $image_file_path,
+			'component'       => $r['component'],
+			'cover_image_dir' => $attachment_data['path']
+		) );
+
+		$created = ! empty( $cover_image['cover_file'] );
+	}
+
+	// Remove copied file if it fails
+	if ( ! $created ) {
+		@unlink( $image_file_path );
+	}
+
+	// Return the response
+	return $created;
+}
+
+/**
+ * Get the url or the path for a type of attachment
+ *
+ * @since  2.4.0
+ *
+ * @param  string $data whether to get the url or the path
+ * @param  array  $args {
+ *     @type string $object_dir  The object dir (eg: members/groups). Defaults to members.
+ *     @type int    $item_id     The object id (eg: a user or a group id). Defaults to current user.
+ *     @type string $type        The type of the attachment which is also the subdir where files are saved.
+ *                               Defaults to 'cover-image'
+ *     @type string $file        The name of the file.
+ * }
+ * @return string|bool the url or the path to the attachment, false otherwise
+ */
+function bp_attachments_get_attachment( $data = 'url', $args = array() ) {
+	// Default value
+	$attachment_data = false;
+
+	$r = bp_parse_args( $args, array(
+		'object_dir' => 'members',
+		'item_id'    => bp_loggedin_user_id(),
+		'type'       => 'cover-image',
+		'file'       => '',
+	), 'attachments_get_attachment_src' );
+
+	// Get BuddyPress Attachments Uploads Dir datas
+	$bp_attachments_uploads_dir = bp_attachments_uploads_dir_get();
+
+	// The BP Attachments Uploads Dir is not set, stop.
+	if ( ! $bp_attachments_uploads_dir ) {
+		return $attachment_data;
+	}
+
+	$type_subdir = $r['object_dir'] . '/' . $r['item_id'] . '/' . $r['type'];
+	$type_dir    = trailingslashit( $bp_attachments_uploads_dir['basedir'] ) . $type_subdir;
+
+	if ( ! is_dir( $type_dir ) ) {
+		return $attachment_data;
+	}
+
+	if ( ! empty( $r['file'] ) ) {
+		if ( ! file_exists( trailingslashit( $type_dir ) . $r['file'] ) ) {
+			return $attachment_data;
+		}
+
+		if ( 'url' === $data ) {
+			$attachment_data = trailingslashit( $bp_attachments_uploads_dir['baseurl'] ) . $type_subdir . '/' . $r['file'];
+		} else {
+			$attachment_data = trailingslashit( $type_dir ) . $r['file'];
+		}
+
+	} else {
+		$file = false;
+
+		// Open the directory and get the first file
+		if ( $att_dir = opendir( $type_dir ) ) {
+
+			while ( false !== ( $attachment_file = readdir( $att_dir ) ) ) {
+				// Look for the first file having the type in its name
+				if ( false !== strpos( $attachment_file, $r['type'] ) && empty( $file ) ) {
+					$file = $attachment_file;
+					break;
+				}
+			}
+		}
+
+		if ( empty( $file ) ) {
+			return $attachment_data;
+		}
+
+		if ( 'url' === $data ) {
+			$attachment_data = trailingslashit( $bp_attachments_uploads_dir['baseurl'] ) . $type_subdir . '/' . $file;
+		} else {
+			$attachment_data = trailingslashit( $type_dir ) . $file;
+		}
+	}
+
+	return $attachment_data;
+}
+
+/**
+ * Delete an attachment for the given arguments
+ *
+ * @since  2.4.0
+ *
+ * @param  array $args
+ * @see    bp_attachments_get_attachment() For more information on accepted arguments.
+ * @return bool True if the attachment was deleted, false otherwise
+ */
+function bp_attachments_delete_file( $args = array() ) {
+	$attachment_path = bp_attachments_get_attachment( 'path', $args );
+
+	if ( empty( $attachment_path ) ) {
+		return false;
+	}
+
+	@unlink( $attachment_path );
+	return true;
+}
+
+/**
+ * Get the BuddyPress Plupload settings.
+ *
+ * @since  2.3.0
+ *
+ * @return array list of BuddyPress Plupload settings.
+ */
+function bp_attachments_get_plupload_default_settings() {
+
+	$max_upload_size = wp_max_upload_size();
+
+	if ( ! $max_upload_size ) {
+		$max_upload_size = 0;
+	}
+
+	$defaults = array(
+		'runtimes'            => 'html5,flash,silverlight,html4',
+		'file_data_name'      => 'file',
+		'multipart_params'    => array(
+			'action'          => 'bp_upload_attachment',
+			'_wpnonce'        => wp_create_nonce( 'bp-uploader' ),
+		),
+		'url'                 => admin_url( 'admin-ajax.php', 'relative' ),
+		'flash_swf_url'       => includes_url( 'js/plupload/plupload.flash.swf' ),
+		'silverlight_xap_url' => includes_url( 'js/plupload/plupload.silverlight.xap' ),
+		'filters' => array(
+			'max_file_size'   => $max_upload_size . 'b',
+		),
+		'multipart'           => true,
+		'urlstream_upload'    => true,
+	);
+
+	// WordPress is not allowing multi selection for iOs 7 device.. See #29602.
+	if ( wp_is_mobile() && strpos( $_SERVER['HTTP_USER_AGENT'], 'OS 7_' ) !== false &&
+		strpos( $_SERVER['HTTP_USER_AGENT'], 'like Mac OS X' ) !== false ) {
+
+		$defaults['multi_selection'] = false;
+	}
+
+	$settings = array(
+		'defaults' => $defaults,
+		'browser'  => array(
+			'mobile'    => wp_is_mobile(),
+			'supported' => _device_can_upload(),
+		),
+		'limitExceeded' => is_multisite() && ! is_upload_space_available(),
+	);
+
+	/**
+	 * Filter the BuddyPress Plupload default settings.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param array $params Default Plupload parameters array.
+	 */
+	return apply_filters( 'bp_attachments_get_plupload_default_settings', $settings );
+}
+
+/**
+ * Builds localization strings for the BuddyPress Uploader scripts.
+ *
+ * @since  2.3.0
+ *
+ * @return array Plupload default localization strings.
+ */
+function bp_attachments_get_plupload_l10n() {
+	// Localization strings
+	return apply_filters( 'bp_attachments_get_plupload_l10n', array(
+			'queue_limit_exceeded'      => __( 'You have attempted to queue too many files.', 'buddypress' ),
+			'file_exceeds_size_limit'   => __( '%s exceeds the maximum upload size for this site.', 'buddypress' ),
+			'zero_byte_file'            => __( 'This file is empty. Please try another.', 'buddypress' ),
+			'invalid_filetype'          => __( 'This file type is not allowed. Please try another.', 'buddypress' ),
+			'not_an_image'              => __( 'This file is not an image. Please try another.', 'buddypress' ),
+			'image_memory_exceeded'     => __( 'Memory exceeded. Please try another smaller file.', 'buddypress' ),
+			'image_dimensions_exceeded' => __( 'This is larger than the maximum size. Please try another.', 'buddypress' ),
+			'default_error'             => __( 'An error occurred. Please try again later.', 'buddypress' ),
+			'missing_upload_url'        => __( 'There was a configuration error. Please contact the server administrator.', 'buddypress' ),
+			'upload_limit_exceeded'     => __( 'You may only upload 1 file.', 'buddypress' ),
+			'http_error'                => __( 'HTTP error.', 'buddypress' ),
+			'upload_failed'             => __( 'Upload failed.', 'buddypress' ),
+			'big_upload_failed'         => __( 'Please try uploading this file with the %1$sbrowser uploader%2$s.', 'buddypress' ),
+			'big_upload_queued'         => __( '%s exceeds the maximum upload size for the multi-file uploader when used in your browser.', 'buddypress' ),
+			'io_error'                  => __( 'IO error.', 'buddypress' ),
+			'security_error'            => __( 'Security error.', 'buddypress' ),
+			'file_cancelled'            => __( 'File canceled.', 'buddypress' ),
+			'upload_stopped'            => __( 'Upload stopped.', 'buddypress' ),
+			'dismiss'                   => __( 'Dismiss', 'buddypress' ),
+			'crunching'                 => __( 'Crunching&hellip;', 'buddypress' ),
+			'unique_file_warning'       => __( 'Make sure to upload a unique file', 'buddypress' ),
+			'error_uploading'           => __( '&#8220;%s&#8221; has failed to upload.', 'buddypress' ),
+			'has_avatar_warning'        => __( 'If you&#39;d like to delete the existing profile photo but not upload a new one, please use the delete tab.', 'buddypress' )
+	) );
+}
+
+/**
+ * Enqueues the script needed for the Uploader UI.
+ *
+ * @see  BP_Attachment::script_data() && BP_Attachment_Avatar::script_data() for examples showing how
+ * to set specific script data.
+ *
+ * @since  2.3.0
+ *
+ * @param  string $class name of the class extending BP_Attachment (eg: BP_Attachment_Avatar).
+ *
+ * @return null|WP_Error
+ */
+function bp_attachments_enqueue_scripts( $class = '' ) {
+	// Enqueue me just once per page, please.
+	if ( did_action( 'bp_attachments_enqueue_scripts' ) ) {
+		return;
+	}
+
+	if ( ! $class || ! class_exists( $class ) ) {
+		return new WP_Error( 'missing_parameter' );
+	}
+
+	// Get an instance of the class and get the script data
+	$attachment = new $class;
+	$script_data  = $attachment->script_data();
+
+	$args = bp_parse_args( $script_data, array(
+		'action'            => '',
+		'file_data_name'    => '',
+		'max_file_size'     => 0,
+		'browse_button'     => 'bp-browse-button',
+		'container'         => 'bp-upload-ui',
+		'drop_element'      => 'drag-drop-area',
+		'bp_params'         => array(),
+		'extra_css'         => array(),
+		'extra_js'          => array(),
+		'feedback_messages' => array(),
+	), 'attachments_enqueue_scripts' );
+
+	if ( empty( $args['action'] ) || empty( $args['file_data_name'] ) ) {
+		return new WP_Error( 'missing_parameter' );
+	}
+
+	// Get the BuddyPress uploader strings
+	$strings = bp_attachments_get_plupload_l10n();
+
+	// Get the BuddyPress uploader settings
+	$settings = bp_attachments_get_plupload_default_settings();
+
+	// Set feedback messages
+	if ( ! empty( $args['feedback_messages'] ) ) {
+		$strings['feedback_messages'] = $args['feedback_messages'];
+	}
+
+	// Use a temporary var to ease manipulation
+	$defaults = $settings['defaults'];
+
+	// Set the upload action
+	$defaults['multipart_params']['action'] = $args['action'];
+
+	// Set BuddyPress upload parameters if provided
+	if ( ! empty( $args['bp_params'] ) ) {
+		$defaults['multipart_params']['bp_params'] = $args['bp_params'];
+	}
+
+	// Merge other arguments
+	$ui_args = array_intersect_key( $args, array(
+		'file_data_name' => true,
+		'browse_button'  => true,
+		'container'      => true,
+		'drop_element'   => true,
+	) );
+
+	$defaults = array_merge( $defaults, $ui_args );
+
+	if ( ! empty( $args['max_file_size'] ) ) {
+		$defaults['filters']['max_file_size'] = $args['max_file_size'] . 'b';
+	}
+
+	// Specific to BuddyPress Avatars
+	if ( 'bp_avatar_upload' === $defaults['multipart_params']['action'] ) {
+
+		// Include the cropping informations for avatars
+		$settings['crop'] = array(
+			'full_h'  => bp_core_avatar_full_height(),
+			'full_w'  => bp_core_avatar_full_width(),
+		);
+
+		// Avatar only need 1 file and 1 only!
+		$defaults['multi_selection'] = false;
+
+		// Does the object already has an avatar set
+		$has_avatar = $defaults['multipart_params']['bp_params']['has_avatar'];
+
+		// What is the object the avatar belongs to
+		$object = $defaults['multipart_params']['bp_params']['object'];
+
+		// Init the Avatar nav
+		$avatar_nav = array(
+			'upload' => array( 'id' => 'upload', 'caption' => __( 'Upload', 'buddypress' ), 'order' => 0  ),
+
+			// The delete view will only show if the object has an avatar
+			'delete' => array( 'id' => 'delete', 'caption' => __( 'Delete', 'buddypress' ), 'order' => 100, 'hide' => (int) ! $has_avatar ),
+		);
+
+		// Create the Camera Nav if the WebCam capture feature is enabled
+		if ( bp_avatar_use_webcam() && 'user' === $object ) {
+			$avatar_nav['camera'] = array( 'id' => 'camera', 'caption' => __( 'Take Photo', 'buddypress' ), 'order' => 10 );
+
+			// Set warning messages
+			$strings['camera_warnings'] = array(
+				'requesting'  => __( 'Please allow us to access to your camera.', 'buddypress'),
+				'loading'     => __( 'Please wait as we access your camera.', 'buddypress' ),
+				'loaded'      => __( 'Camera loaded. Click on the "Capture" button to take your photo.', 'buddypress' ),
+				'noaccess'    => __( 'It looks like you do not have a webcam or we were unable to get permission to use your webcam. Please upload a photo instead.', 'buddypress' ),
+				'errormsg'    => __( 'Your browser is not supported. Please upload a photo instead.', 'buddypress' ),
+				'videoerror'  => __( 'Video error. Please upload a photo instead.', 'buddypress' ),
+				'ready'       => __( 'Your profile photo is ready. Click on the "Save" button to use this photo.', 'buddypress' ),
+				'nocapture'   => __( 'No photo was captured. Click on the "Capture" button to take your photo.', 'buddypress' ),
+			);
+		}
+
+		/**
+		 * Use this filter to add a navigation to a custom tool to set the object's avatar.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array $avatar_nav An associative array of available nav items where each item is an array organized this way:
+		 * $avatar_nav[ $nav_item_id ] {
+		 *     @type string $nav_item_id The nav item id in lower case without special characters or space.
+		 *     @type string $caption     The name of the item nav that will be displayed in the nav.
+		 *     @type int    $order       An integer to specify the priority of the item nav, choose one.
+		 *                               between 1 and 99 to be after the uploader nav item and before the delete nav item.
+		 *     @type int    $hide        If set to 1 the item nav will be hidden
+		 *                               (only used for the delete nav item).
+		 * }
+		 * @param string $object the object the avatar belongs to (eg: user or group)
+		 */
+		$settings['nav'] = bp_sort_by_key( apply_filters( 'bp_attachments_avatar_nav', $avatar_nav, $object ), 'order', 'num' );
+
+	// Specific to BuddyPress cover images
+	} elseif ( 'bp_cover_image_upload' === $defaults['multipart_params']['action'] ) {
+
+		// Cover images only need 1 file and 1 only!
+		$defaults['multi_selection'] = false;
+
+		// Default cover component is xprofile
+		$cover_component = 'xprofile';
+
+		// Get the object we're editing the cover image of
+		$object = $defaults['multipart_params']['bp_params']['object'];
+
+		// Set the cover component according to the object
+		if ( 'group' === $object ) {
+			$cover_component = 'groups';
+		} elseif ( 'user' !== $object ) {
+			$cover_component = apply_filters( 'bp_attachments_cover_image_ui_component', $cover_component );
+		}
+		// Get cover image advised dimensions
+		$cover_dimensions = bp_attachments_get_cover_image_dimensions( $cover_component );
+
+		// Set warning messages
+		$strings['cover_image_warnings'] = apply_filters( 'bp_attachments_cover_image_ui_warnings', array(
+			'dimensions'  => sprintf(
+					__( 'For better results, make sure to upload an image that is larger than %1$spx wide, and %2$spx tall.', 'buddypress' ),
+					(int) $cover_dimensions['width'],
+					(int) $cover_dimensions['height']
+				),
+		) );
+	}
+
+	// Set Plupload settings
+	$settings['defaults'] = $defaults;
+
+	/**
+	 * Enqueue some extra styles if required
+	 *
+	 * Extra styles need to be registered.
+	 */
+	if ( ! empty( $args['extra_css'] ) ) {
+		foreach ( (array) $args['extra_css'] as $css ) {
+			if ( empty( $css ) ) {
+				continue;
+			}
+
+			wp_enqueue_style( $css );
+		}
+	}
+
+	wp_enqueue_script ( 'bp-plupload' );
+	wp_localize_script( 'bp-plupload', 'BP_Uploader', array( 'strings' => $strings, 'settings' => $settings ) );
+
+	/**
+	 * Enqueue some extra scripts if required
+	 *
+	 * Extra scripts need to be registered.
+	 */
+	if ( ! empty( $args['extra_js'] ) ) {
+		foreach ( (array) $args['extra_js'] as $js ) {
+			if ( empty( $js ) ) {
+				continue;
+			}
+
+			wp_enqueue_script( $js );
+		}
+	}
+
+	/**
+	 * Fires at the conclusion of bp_attachments_enqueue_scripts()
+	 * to avoid the scripts to be loaded more than once.
+	 *
+	 * @since 2.3.0
+	 */
+	do_action( 'bp_attachments_enqueue_scripts' );
+}
+
+/**
+ * Check the current user's capability to edit an avatar for a given object.
+ *
+ * @since  2.3.0
+ *
+ * @param  string $capability The capability to check.
+ * @param  array  $args       An array containing the item_id and the object to check.
+ *
+ * @return bool
+ */
+function bp_attachments_current_user_can( $capability, $args = array() ) {
+	$can = false;
+
+	if ( 'edit_avatar' === $capability || 'edit_cover_image' === $capability ) {
+		/**
+		 * Needed avatar arguments are set.
+		 */
+		if ( isset( $args['item_id'] ) && isset( $args['object'] ) ) {
+			// Group profile photo
+			if ( bp_is_active( 'groups' ) && 'group' === $args['object'] ) {
+				if ( bp_is_group_create() ) {
+					$can = (bool) groups_is_user_creator( bp_loggedin_user_id(), $args['item_id'] ) || bp_current_user_can( 'bp_moderate' );
+				} else {
+					$can = (bool) groups_is_user_admin( bp_loggedin_user_id(), $args['item_id'] ) || bp_current_user_can( 'bp_moderate' );
+				}
+			// User profile photo
+			} elseif ( bp_is_active( 'xprofile' ) && 'user' === $args['object'] ) {
+				$can = bp_loggedin_user_id() === (int) $args['item_id'] || bp_current_user_can( 'bp_moderate' );
+			}
+		/**
+		 * No avatar arguments, fallback to bp_user_can_create_groups()
+		 * or bp_is_item_admin()
+		 */
+		} else {
+			if ( bp_is_group_create() ) {
+				$can = bp_user_can_create_groups();
+			} else {
+				$can = bp_is_item_admin();
+			}
+		}
+	}
+
+	return apply_filters( 'bp_attachments_current_user_can', $can, $capability, $args );
+}
+
+/**
+ * Send a JSON response back to an Ajax upload request.
+ *
+ * @since  2.3.0
+ *
+ * @param  bool  $success  True for a success, false otherwise.
+ * @param  bool  $is_html4 True if the Plupload runtime used is html4, false otherwise.
+ * @param  mixed $data     Data to encode as JSON, then print and die.
+ */
+function bp_attachments_json_response( $success, $is_html4 = false, $data = null ) {
+	$response = array( 'success' => $success );
+
+	if ( isset( $data ) ) {
+		$response['data'] = $data;
+	}
+
+	// Send regular json response
+	if ( ! $is_html4 ) {
+		wp_send_json( $response );
+
+	/**
+	 * Send specific json response
+	 * the html4 Plupload handler requires a text/html content-type for older IE.
+	 * See https://core.trac.wordpress.org/ticket/31037
+	 */
+	} else {
+		echo wp_json_encode( $response );
+
+		wp_die();
+	}
+}
+
+/**
+ * Get an Attachment template part.
+ *
+ * @since 2.3.0
+ *
+ * @param string $slug Template part slug. eg 'uploader' for 'uploader.php'.
+ *
+ * @return bool
+ */
+function bp_attachments_get_template_part( $slug ) {
+	$attachment_template_part = 'assets/_attachments/' . $slug;
+
+	// Load the attachment template in WP Administration screens.
+	if ( is_admin() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) {
+		$attachment_admin_template_part = buddypress()->themes_dir . '/bp-legacy/buddypress/' . $attachment_template_part . '.php';
+
+		// Check whether the template part exists.
+		if ( ! file_exists( $attachment_admin_template_part ) ) {
+			return false;
+		}
+
+		// Load the template part.
+		require( $attachment_admin_template_part );
+
+	// Load the attachment template in WP_USE_THEMES env.
+	} else {
+		bp_get_template_part( $attachment_template_part );
+	}
+}
+
+/** Cover Image ***************************************************************/
+
+/**
+ * Get the cover image settings
+ *
+ * @since  2.4.0
+ *
+ * @param  string $component the component to get the settings for ("xprofile" for user or "groups")
+ * @return array            the cover image settings
+ */
+function bp_attachments_get_cover_image_settings( $component = 'xprofile' ) {
+	// Default parameters
+	$args = array();
+
+	// First look in BP Theme Compat
+	$cover_image = bp_get_theme_compat_feature( 'cover_image' );
+
+	if ( ! empty( $cover_image ) ) {
+		$args = (array) $cover_image;
+	}
+
+	/**
+	 * Then let people override/set the feature using this dynamic filter
+	 *
+	 * eg: for the user's profile cover image use :
+	 * add_filter( 'bp_before_xprofile_cover_image_settings_parse_args', 'your_filter', 10, 1 );
+	 *
+	 * @since  2.4.0
+	 *
+	 * @param array $settings the cover image settings
+	 */
+	$settings = bp_parse_args( $args, array(
+		'components'    => array(),
+		'width'         => 1300,
+		'height'        => 225,
+		'callback'      => '',
+		'theme_handle'  => '',
+		'default_cover' => '',
+	), $component . '_cover_image_settings' );
+
+	if ( empty( $settings['components'] ) || empty( $settings['callback'] ) || empty( $settings['theme_handle'] ) ) {
+		return false;
+	}
+
+	// Current component is not supported
+	if ( ! in_array( $component, $settings['components'] ) ) {
+		return false;
+	}
+
+	// Finally return the settings
+	return $settings;
+}
+
+/**
+ * Get cover image Width and Height
+ *
+ * @since  2.4.0
+ *
+ * @param  string $component the BuddyPress component concerned ("xprofile" for user or "groups")
+ * @return array             an associative array containing the advised width and height for the cover image
+ */
+function bp_attachments_get_cover_image_dimensions( $component = 'xprofile' ) {
+	// Let's prevent notices when setting the warning strings
+	$default = array( 'width' => 0, 'height' => 0 );
+
+	$settings = bp_attachments_get_cover_image_settings( $component );
+
+	if ( empty( $settings ) ) {
+		return false;
+	}
+
+	// Get width and height
+	$wh = array_intersect_key( $settings, $default );
+
+	/**
+	 * Filter here to edit the cover image dimensions if needed.
+	 *
+	 * @since  2.4.0
+	 *
+	 * @param  array  $wh        an associative array containing the width and height values
+	 * @param  array  $settings  an associative array containing all the feature settings
+	 * @param  string $compnent  the requested component
+	 */
+	return apply_filters( 'bp_attachments_get_cover_image_dimensions', $wh, $settings, $component );
+}
+
+/**
+ * Are we on a page to edit a cover image ?
+ *
+ * @since  2.4.0
+ *
+ * @return bool True if on a page to edit a cover image, false otherwise
+ */
+function bp_attachments_cover_image_is_edit() {
+	$retval = false;
+
+	$current_component = bp_current_component();
+	if ( 'profile' === $current_component ) {
+		$current_component = 'xprofile';
+	}
+
+	if ( ! bp_is_active( $current_component, 'cover_image' ) ) {
+		return $retval;
+	}
+
+	if ( bp_is_user_change_cover_image() ) {
+		$retval = ! bp_disable_cover_image_uploads();
+	}
+
+	if ( ( bp_is_group_admin_page() && 'group-cover-image' == bp_get_group_current_admin_tab() )
+		|| ( bp_is_group_create() && bp_is_group_creation_step( 'group-cover-image' ) ) ) {
+		$retval = ! bp_disable_group_cover_image_uploads();
+	}
+
+	return apply_filters( 'bp_attachments_cover_image_is_edit', $retval, $current_component );
+}
+
+/**
+ * Does the user has a cover image ?
+ *
+ * @since  2.4.0
+ *
+ * @param  int $user_id
+ * @return bool True if the user has a cover image, false otherwise
+ */
+function bp_attachments_get_user_has_cover_image( $user_id = 0 ) {
+	if ( empty( $user_id ) ) {
+		$user_id = bp_displayed_user_id();
+	}
+
+	$cover_src = bp_attachments_get_attachment( 'url', array(
+		'item_id'   => $user_id,
+	) );
+
+	return (bool) apply_filters( 'bp_attachments_get_user_has_cover_image', $cover_src, $user_id );
+}
+
+/**
+ * Does the group has a cover image ?
+ *
+ * @since  2.4.0
+ *
+ * @param  int $group_id
+ * @return bool True if the group has a cover image, false otherwise
+ */
+function bp_attachments_get_group_has_cover_image( $group_id = 0 ) {
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	$cover_src = bp_attachments_get_attachment( 'url', array(
+		'object_dir' => 'groups',
+		'item_id'    => $group_id,
+	) );
+
+	return (bool) apply_filters( 'bp_attachments_get_user_has_cover_image', $cover_src, $group_id );
+}
+
+/**
+ * Generate the cover image file.
+ *
+ * @since 2.4.0
+ *
+ * @param  array  $args {
+ *     @type string $file            The absolute path to the image. Required.
+ *     @type string $component       The component for the object (eg: groups, xprofile). Required.
+ *     @type string $cover_image_dir The Cover image dir to write the image into. Required.
+ * }
+ * @param  BP_Attachment_Cover_Image $cover_image_class The class to use to fit the cover image.
+ * @return bool|array          An array containing cover image data on success, false otherwise.
+ */
+function bp_attachments_cover_image_generate_file( $args = array(), $cover_image_class = null ) {
+	// Bail if an argument is missing
+	if ( empty( $args['file'] ) || empty( $args['component'] ) || empty( $args['cover_image_dir'] ) ) {
+		return false;
+	}
+
+	// Get advised dimensions for the cover image
+	$dimensions = bp_attachments_get_cover_image_dimensions( $args['component'] );
+
+	// No dimensions or the file does not match with the cover image dir, stop!
+	if ( false === $dimensions || $args['file'] !== $args['cover_image_dir'] . '/' . wp_basename( $args['file'] ) ) {
+		return false;
+	}
+
+	if ( ! is_a( $cover_image_class, 'BP_Attachment_Cover_Image' ) ) {
+		$cover_image_class = new BP_Attachment_Cover_Image();
+	}
+
+	// Make sure the file is inside the Cover Image Upload path.
+	if ( false === strpos( $args['file'], $cover_image_class->upload_path ) ) {
+		return false;
+	}
+
+	// Resize the image so that it fit with the cover image dimensions
+	$cover_image  = $cover_image_class->fit( $args['file'], $dimensions );
+	$is_too_small = false;
+
+	// Image is too small in width and height
+	if ( empty( $cover_image ) ) {
+		$cover_file = $cover_image_class->generate_filename( $args['file'] );
+		@rename( $args['file'], $cover_file );
+
+		// It's too small!
+		$is_too_small = true;
+	} elseif ( ! empty( $cover_image['path'] ) ) {
+		$cover_file = $cover_image['path'];
+
+		// Image is too small in width or height
+		if ( $cover_image['width'] < $dimensions['width'] || $cover_image['height'] < $dimensions['height'] ) {
+			$is_too_small = true;
+		}
+	}
+
+	// We were not able to generate the cover image file.
+	if ( empty( $cover_file ) ) {
+		return false;
+	}
+
+	// Do some clean up with old cover image, now a new one is set.
+	$cover_basename = wp_basename( $cover_file );
+
+	if ( $att_dir = opendir( $args['cover_image_dir'] ) ) {
+		while ( false !== ( $attachment_file = readdir( $att_dir ) ) ) {
+			// skip directories and the new cover image
+			if ( 2 < strlen( $attachment_file ) && 0 !== strpos( $attachment_file, '.' ) && $cover_basename !== $attachment_file ) {
+				@unlink( $args['cover_image_dir'] . '/' . $attachment_file );
+			}
+		}
+	}
+
+	// Finally return needed data.
+	return array(
+		'cover_file'     => $cover_file,
+		'cover_basename' => $cover_basename,
+		'is_too_small'   => $is_too_small
+	);
+}
+
+/**
+ * Ajax Upload and set a cover image
+ *
+ * @since  2.4.0
+ *
+ * @return  string|null A json object containing success data if the upload succeeded
+ *                      error message otherwise.
+ */
+function bp_attachments_cover_image_ajax_upload() {
+	// Bail if not a POST action
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
+		wp_die();
+	}
+
+	/**
+	 * Sending the json response will be different if
+	 * the current Plupload runtime is html4
+	 */
+	$is_html4 = false;
+	if ( ! empty( $_POST['html4' ] ) ) {
+		$is_html4 = true;
+	}
+
+	// Check the nonce
+	check_admin_referer( 'bp-uploader' );
+
+	// Init the BuddyPress parameters
+	$bp_params = array();
+
+	// We need it to carry on
+	if ( ! empty( $_POST['bp_params'] ) ) {
+		$bp_params = bp_parse_args( $_POST['bp_params'], array(
+			'object'  => 'user',
+			'item_id' => bp_loggedin_user_id(),
+		), 'attachments_cover_image_ajax_upload' );
+	} else {
+		bp_attachments_json_response( false, $is_html4 );
+	}
+
+	// We need the object to set the uploads dir filter
+	if ( empty( $bp_params['object'] ) ) {
+		bp_attachments_json_response( false, $is_html4 );
+	}
+
+	// Capability check
+	if ( ! bp_attachments_current_user_can( 'edit_cover_image', $bp_params ) ) {
+		bp_attachments_json_response( false, $is_html4 );
+	}
+
+	$bp          = buddypress();
+	$needs_reset = array();
+
+	// Member's cover image
+	if ( 'user' === $bp_params['object'] ) {
+		$object_data = array( 'dir' => 'members', 'component' => 'xprofile' );
+
+		if ( ! bp_displayed_user_id() && ! empty( $bp_params['item_id'] ) ) {
+			$needs_reset = array( 'key' => 'displayed_user', 'value' => $bp->displayed_user );
+			$bp->displayed_user->id = $bp_params['item_id'];
+		}
+
+	// Group's cover image
+	} elseif ( 'group' === $bp_params['object'] ) {
+		$object_data = array( 'dir' => 'groups', 'component' => 'groups' );
+
+		if ( ! bp_get_current_group_id() && ! empty( $bp_params['item_id'] ) ) {
+			$needs_reset = array( 'component' => 'groups', 'key' => 'current_group', 'value' => $bp->groups->current_group );
+			$bp->groups->current_group = groups_get_group( array(
+				'group_id'        => $bp_params['item_id'],
+				'populate_extras' => false,
+			) );
+		}
+
+	// Other object's cover image
+	} else {
+		$object_data = apply_filters( 'bp_attachments_cover_image_object_dir', array(), $bp_params['object'] );
+	}
+
+	// Stop here in case of a missing parameter for the object
+	if ( empty( $object_data['dir'] ) || empty( $object_data['component'] ) ) {
+		bp_attachments_json_response( false, $is_html4 );
+	}
+
+	$cover_image_attachment = new BP_Attachment_Cover_Image();
+	$uploaded = $cover_image_attachment->upload( $_FILES );
+
+	// Reset objects
+	if ( ! empty( $needs_reset ) ) {
+		if ( ! empty( $needs_reset['component'] ) ) {
+			$bp->{$needs_reset['component']}->{$needs_reset['key']} = $needs_reset['value'];
+		} else {
+			$bp->{$needs_reset['key']} = $needs_reset['value'];
+		}
+	}
+
+	if ( ! empty( $uploaded['error'] ) ) {
+		// Upload error response
+		bp_attachments_json_response( false, $is_html4, array(
+			'type'    => 'upload_error',
+			'message' => sprintf( __( 'Upload Failed! Error was: %s', 'buddypress' ), $uploaded['error'] ),
+		) );
+	}
+
+	// Default error message
+	$error_message = __( 'There was a problem uploading the cover image.', 'buddypress' );
+
+	// Get BuddyPress Attachments Uploads Dir datas
+	$bp_attachments_uploads_dir = bp_attachments_uploads_dir_get();
+
+	// The BP Attachments Uploads Dir is not set, stop.
+	if ( ! $bp_attachments_uploads_dir ) {
+		bp_attachments_json_response( false, $is_html4, array(
+			'type'    => 'upload_error',
+			'message' => $error_message,
+		) );
+	}
+
+	$cover_subdir = $object_data['dir'] . '/' . $bp_params['item_id'] . '/cover-image';
+	$cover_dir    = trailingslashit( $bp_attachments_uploads_dir['basedir'] ) . $cover_subdir;
+
+	if ( ! is_dir( $cover_dir ) ) {
+		// Upload error response
+		bp_attachments_json_response( false, $is_html4, array(
+			'type'    => 'upload_error',
+			'message' => $error_message,
+		) );
+	}
+
+	/**
+	 * Generate the cover image so that it fit to feature's dimensions
+	 *
+	 * Unlike the Avatar, Uploading and generating the cover image is happening during
+	 * the same Ajax request, as we already instantiated the BP_Attachment_Cover_Image
+	 * class, let's use it.
+	 */
+	$cover = bp_attachments_cover_image_generate_file( array(
+		'file'            => $uploaded['file'],
+		'component'       => $object_data['component'],
+		'cover_image_dir' => $cover_dir
+	), $cover_image_attachment );
+
+	if ( ! $cover ) {
+		// Upload error response
+		bp_attachments_json_response( false, $is_html4, array(
+			'type'    => 'upload_error',
+			'message' => $error_message,
+		) );
+	}
+
+	// Build the url to the file
+	$cover_url = trailingslashit( $bp_attachments_uploads_dir['baseurl'] ) . $cover_subdir . '/' . $cover['cover_basename'];
+
+	// Init Feedback code, 1 is success
+	$feedback_code = 1;
+
+	// 0 is the size warning
+	if ( $cover['is_too_small'] ) {
+		$feedback_code = 0;
+	}
+
+	// Set the name of the file
+	$name = $_FILES['file']['name'];
+	$name_parts = pathinfo( $name );
+	$name = trim( substr( $name, 0, - ( 1 + strlen( $name_parts['extension'] ) ) ) );
+
+	/**
+	 * Fires if the new cover image was successfully uploaded.
+	 *
+	 * The dynamic portion of the hook will be xprofile in case of a user's
+	 * cover image, groups in case of a group's cover image. For instance:
+	 * Use add_action( 'xprofile_cover_image_uploaded' ) to run your specific
+	 * code once the user has set his cover image.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param int $item_id Inform about the item id the cover image was set for.
+	 */
+	do_action( $object_data['component'] . '_cover_image_uploaded', (int) $bp_params['item_id'] );
+
+	// Finally return the cover image url to the UI
+	bp_attachments_json_response( true, $is_html4, array(
+		'name'          => $name,
+		'url'           => $cover_url,
+		'feedback_code' => $feedback_code,
+	) );
+}
+add_action( 'wp_ajax_bp_cover_image_upload', 'bp_attachments_cover_image_ajax_upload' );
+
+/**
+ * Ajax delete a cover image for a given object and item id.
+ *
+ * @since 2.4.0
+ *
+ * @return string|null A json object containing success data if the cover image was deleted
+ *                     error message otherwise.
+ */
+function bp_attachments_cover_image_ajax_delete() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
+		wp_send_json_error();
+	}
+
+	$cover_image_data = $_POST;
+
+	if ( empty( $cover_image_data['object'] ) || empty( $cover_image_data['item_id'] ) ) {
+		wp_send_json_error();
+	}
+
+	// Check the nonce
+	check_admin_referer( 'bp_delete_cover_image', 'nonce' );
+
+	// Capability check
+	if ( ! bp_attachments_current_user_can( 'edit_cover_image', $cover_image_data ) ) {
+		wp_send_json_error();
+	}
+
+	// Set object for the user's case
+	if ( 'user' === $cover_image_data['object'] ) {
+		$component = 'xprofile';
+		$dir       = 'members';
+
+	// Set it for any other cases
+	} else {
+		$component = $cover_image_data['object'] . 's';
+		$dir       = $component;
+	}
+
+	// Handle delete
+	if ( bp_attachments_delete_file( array( 'item_id' => $cover_image_data['item_id'], 'object_dir' => $dir, 'type' => 'cover-image' ) ) ) {
+
+		// Defaults no cover image
+		$response = array(
+			'reset_url'     => '',
+			'feedback_code' => 3 ,
+		);
+
+		// Get cover image settings in case there's a default header
+		$cover_params = bp_attachments_get_cover_image_settings( $component );
+
+		// Check if there's a default cover
+		if ( ! empty( $cover_params['default_cover'] ) ) {
+			$response['reset_url'] = $cover_params['default_cover'];
+		}
+
+		// Finally send the reset url
+		wp_send_json_success( $response );
+
+	} else {
+		wp_send_json_error( array(
+			'feedback_code' => 2,
+		) );
+	}
+}
+add_action( 'wp_ajax_bp_cover_image_delete', 'bp_attachments_cover_image_ajax_delete' );
Index: /tags/2.4.3/src/bp-core/bp-core-avatars.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-avatars.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-avatars.php	(revision 10410)
@@ -0,0 +1,2010 @@
+<?php
+/**
+ * BuddyPress Avatars.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Set up the constants we need for avatar support.
+ */
+function bp_core_set_avatar_constants() {
+
+	$bp = buddypress();
+
+	if ( !defined( 'BP_AVATAR_THUMB_WIDTH' ) )
+		define( 'BP_AVATAR_THUMB_WIDTH', 50 );
+
+	if ( !defined( 'BP_AVATAR_THUMB_HEIGHT' ) )
+		define( 'BP_AVATAR_THUMB_HEIGHT', 50 );
+
+	if ( !defined( 'BP_AVATAR_FULL_WIDTH' ) )
+		define( 'BP_AVATAR_FULL_WIDTH', 150 );
+
+	if ( !defined( 'BP_AVATAR_FULL_HEIGHT' ) )
+		define( 'BP_AVATAR_FULL_HEIGHT', 150 );
+
+	if ( !defined( 'BP_AVATAR_ORIGINAL_MAX_WIDTH' ) )
+		define( 'BP_AVATAR_ORIGINAL_MAX_WIDTH', 450 );
+
+	if ( !defined( 'BP_AVATAR_ORIGINAL_MAX_FILESIZE' ) ) {
+		define( 'BP_AVATAR_ORIGINAL_MAX_FILESIZE', bp_attachments_get_max_upload_file_size( 'avatar' ) );
+	}
+
+	if ( ! defined( 'BP_SHOW_AVATARS' ) ) {
+		define( 'BP_SHOW_AVATARS', bp_get_option( 'show_avatars' ) );
+	}
+}
+add_action( 'bp_init', 'bp_core_set_avatar_constants', 3 );
+
+/**
+ * Set up global variables related to avatars.
+ *
+ * @since 1.5.0
+ */
+function bp_core_set_avatar_globals() {
+	$bp = buddypress();
+
+	$bp->avatar        = new stdClass;
+	$bp->avatar->thumb = new stdClass;
+	$bp->avatar->full  = new stdClass;
+
+	// Dimensions
+	$bp->avatar->thumb->width  = BP_AVATAR_THUMB_WIDTH;
+	$bp->avatar->thumb->height = BP_AVATAR_THUMB_HEIGHT;
+	$bp->avatar->full->width   = BP_AVATAR_FULL_WIDTH;
+	$bp->avatar->full->height  = BP_AVATAR_FULL_HEIGHT;
+
+	// Upload maximums
+	$bp->avatar->original_max_width    = BP_AVATAR_ORIGINAL_MAX_WIDTH;
+	$bp->avatar->original_max_filesize = BP_AVATAR_ORIGINAL_MAX_FILESIZE;
+
+	// Defaults
+	$bp->avatar->thumb->default = bp_core_avatar_default_thumb();
+	$bp->avatar->full->default  = bp_core_avatar_default();
+
+	// These have to be set on page load in order to avoid infinite filter loops at runtime
+	$bp->avatar->upload_path = bp_core_avatar_upload_path();
+	$bp->avatar->url = bp_core_avatar_url();
+
+	// Cache the root blog's show_avatars setting, to avoid unnecessary
+	// calls to switch_to_blog()
+	$bp->avatar->show_avatars = (bool) BP_SHOW_AVATARS;
+
+	// Backpat for pre-1.5
+	if ( ! defined( 'BP_AVATAR_UPLOAD_PATH' ) )
+		define( 'BP_AVATAR_UPLOAD_PATH', $bp->avatar->upload_path );
+
+	// Backpat for pre-1.5
+	if ( ! defined( 'BP_AVATAR_URL' ) )
+		define( 'BP_AVATAR_URL', $bp->avatar->url );
+
+	/**
+	 * Fires at the end of the core avatar globals setup.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_core_set_avatar_globals' );
+}
+add_action( 'bp_setup_globals', 'bp_core_set_avatar_globals' );
+
+/**
+ * Get an avatar for a BuddyPress object.
+ *
+ * Supports avatars for users, groups, and blogs by default, but can be
+ * extended to support custom components as well.
+ *
+ * This function gives precedence to locally-uploaded avatars. When a local
+ * avatar is not found, Gravatar is queried. To disable Gravatar fallbacks
+ * locally:
+ *    add_filter( 'bp_core_fetch_avatar_no_grav', '__return_true' );
+ *
+ * @since 2.4.0 Added 'extra_attr', 'scheme', 'rating' and 'force_default' for $args.
+ *              These are inherited from WordPress 4.2.0. See {@link get_avatar()}.
+ *
+ * @param array|string $args {
+ *     An array of arguments. All arguments are technically optional; some
+ *     will, if not provided, be auto-detected by bp_core_fetch_avatar(). This
+ *     auto-detection is described more below, when discussing specific
+ *     arguments.
+ *
+ *     @type int|bool    $item_id    The numeric ID of the item for which you're requesting
+ *                                   an avatar (eg, a user ID). If no 'item_id' is present,
+ *                                   the function attempts to infer an ID from the 'object' + the
+ *                                   current context: if 'object' is 'user' and the current page is a
+ *                                   user page, 'item_id' will default to the displayed user ID; if
+ *                                   'group' and on a group page, to the current group ID; if 'blog',
+ *                                   to the current blog's ID. If no 'item_id' can be determined in
+ *                                   this way, the function returns false. Default: false.
+ *     @type string      $object     The kind of object for which you're getting an
+ *                                   avatar. BuddyPress natively supports three options: 'user',
+ *                                   'group', 'blog'; a plugin may register more.  Default: 'user'.
+ *     @type string      $type       When a new avatar is uploaded to BP, 'thumb' and
+ *                                   'full' versions are saved. This parameter specifies whether you'd
+ *                                   like the 'full' or smaller 'thumb' avatar. Default: 'thumb'.
+ *     @type string|bool $avatar_dir The name of the subdirectory where the
+ *                                   requested avatar should be found. If no value is passed,
+ *                                   'avatar_dir' is inferred from 'object': 'user' becomes 'avatars',
+ *                                   'group' becomes 'group-avatars', 'blog' becomes 'blog-avatars'.
+ *                                   Remember that this string denotes a subdirectory of BP's main
+ *                                   avatar directory (usually based on {@link wp_upload_dir()}); it's a
+ *                                   string like 'group-avatars' rather than the full directory path.
+ *                                   Generally, it'll only be necessary to override the default value if
+ *                                   storing avatars in a non-default location. Defaults to false
+ *                                   (auto-detected).
+ *     @type int|bool    $width      Requested avatar width. The unit is px. This value
+ *                                   is used to build the 'width' attribute for the <img> element. If
+ *                                   no value is passed, BP uses the global avatar width for this
+ *                                   avatar type. Default: false (auto-detected).
+ *     @type int|bool    $height     Requested avatar height. The unit is px. This
+ *                                   value is used to build the 'height' attribute for the <img>
+ *                                   element. If no value is passed, BP uses the global avatar height
+ *                                   for this avatar type. Default: false (auto-detected).
+ *     @type string      $class      The CSS class for the <img> element. Note that BP
+ *                                   uses the 'avatar' class fairly extensively in its default styling,
+ *                                   so if you plan to pass a custom value, consider appending it to
+ *                                   'avatar' (eg 'avatar foo') rather than replacing it altogether.
+ *                                   Default: 'avatar'.
+ *     @type string|bool $css_id     The CSS id for the <img> element.
+ *                                   Default: false.
+ *     @type string      $title      The title attribute for the <img> element.
+ *                                   Default: false.
+ *     @type string      $alt        The alt attribute for the <img> element. In BP, this
+ *                                   value is generally passed by the wrapper functions, where the data
+ *                                   necessary for concatenating the string is at hand; see
+ *                                   {@link bp_get_activity_avatar()} for an example. Default: ''.
+ *     @type string|bool $email      An email to use in Gravatar queries. Unless
+ *                                   otherwise configured, BP uses Gravatar as a fallback for avatars
+ *                                   that are not provided locally. Gravatar's API requires using a hash
+ *                                   of the user's email address; this argument provides it. If not
+ *                                   provided, the function will infer it: for users, by getting the
+ *                                   user's email from the database, for groups/blogs, by concatenating
+ *                                   "{$item_id}-{$object}@{bp_get_root_domain()}". The user query adds
+ *                                   overhead, so it's recommended that wrapper functions provide a
+ *                                   value for 'email' when querying user IDs. Default: false.
+ *     @type bool       $no_grav     Whether to disable the default Gravatar fallback.
+ *                                   By default, BP will fall back on Gravatar when it cannot find a
+ *                                   local avatar. In some cases, this may be undesirable, in which
+ *                                   case 'no_grav' should be set to true. To disable Gravatar
+ *                                   fallbacks globally, see the 'bp_core_fetch_avatar_no_grav' filter.
+ *                                   Default: false.
+ *     @type bool       $html        Whether to return an <img> HTML element, vs a raw URL
+ *                                   to an avatar. If false, <img>-specific arguments (like 'css_id')
+ *                                   will be ignored. Default: true.
+ *     @type string     $extra_attr  HTML attributes to insert in the IMG element. Not sanitized. Default: ''.
+ *     @type string     $scheme      URL scheme to use. See set_url_scheme() for accepted values.
+ *                                   Default null.
+ *     @type string     $rating      What rating to display Gravatars for. Accepts 'G', 'PG', 'R', 'X'.
+ *                                   Default is the value of the 'avatar_rating' option.
+ *     @type bool       $force_default Used when creating the Gravatar URL. Whether to force the default
+ *                                     image regardless if the Gravatar exists. Default: false.
+ * }
+ *
+ * @return string Formatted HTML <img> element, or raw avatar URL based on $html arg.
+ */
+function bp_core_fetch_avatar( $args = '' ) {
+	$bp = buddypress();
+
+	// If avatars are disabled for the root site, obey that request and bail
+	if ( ! $bp->avatar->show_avatars ) {
+		return;
+	}
+
+	global $current_blog;
+
+	// Set the default variables array and parse it against incoming $args array.
+	$params = wp_parse_args( $args, array(
+		'item_id'       => false,
+		'object'        => 'user',
+		'type'          => 'thumb',
+		'avatar_dir'    => false,
+		'width'         => false,
+		'height'        => false,
+		'class'         => 'avatar',
+		'css_id'        => false,
+		'alt'           => '',
+		'email'         => false,
+		'no_grav'       => false,
+		'html'          => true,
+		'title'         => '',
+		'extra_attr'    => '',
+		'scheme'        => null,
+		'rating'        => get_option( 'avatar_rating' ),
+		'force_default' => false,
+	) );
+
+	/** Set item_id ***********************************************************/
+
+	if ( empty( $params['item_id'] ) ) {
+
+		switch ( $params['object'] ) {
+
+			case 'blog'  :
+				$params['item_id'] = $current_blog->id;
+				break;
+
+			case 'group' :
+				if ( bp_is_active( 'groups' ) ) {
+					$params['item_id'] = $bp->groups->current_group->id;
+				} else {
+					$params['item_id'] = false;
+				}
+
+				break;
+
+			case 'user'  :
+			default      :
+				$params['item_id'] = bp_displayed_user_id();
+				break;
+		}
+
+		/**
+		 * Filters the ID of the item being requested.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value  ID of avatar item being requested.
+		 * @param string $value  Avatar type being requested.
+		 * @param array  $params Array of parameters for the request.
+		 */
+		$params['item_id'] = apply_filters( 'bp_core_avatar_item_id', $params['item_id'], $params['object'], $params );
+
+		if ( empty( $params['item_id'] ) ) {
+			return false;
+		}
+	}
+
+	/** Set avatar_dir ********************************************************/
+
+	if ( empty( $params['avatar_dir'] ) ) {
+
+		switch ( $params['object'] ) {
+
+			case 'blog'  :
+				$params['avatar_dir'] = 'blog-avatars';
+				break;
+
+			case 'group' :
+				if ( bp_is_active( 'groups' ) ) {
+					$params['avatar_dir'] = 'group-avatars';
+				} else {
+					$params['avatar_dir'] = false;
+				}
+
+				break;
+
+			case 'user'  :
+			default      :
+				$params['avatar_dir'] = 'avatars';
+				break;
+		}
+
+		/**
+		 * Filters the avatar directory to use.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value  Name of the subdirectory where the requested avatar should be found.
+		 * @param string $value  Avatar type being requested.
+		 * @param array  $params Array of parameters for the request.
+		 */
+		$params['avatar_dir'] = apply_filters( 'bp_core_avatar_dir', $params['avatar_dir'], $params['object'], $params );
+
+		if ( empty( $params['avatar_dir'] ) ) {
+			return false;
+		}
+	}
+
+	/** <img> alt *************************************************************/
+
+	if ( false !== strpos( $params['alt'], '%s' ) || false !== strpos( $params['alt'], '%1$s' ) ) {
+
+		switch ( $params['object'] ) {
+
+			case 'blog'  :
+				$item_name = get_blog_option( $params['item_id'], 'blogname' );
+				break;
+
+			case 'group' :
+				$item_name = bp_get_group_name( groups_get_group( array( 'group_id' => $params['item_id'] ) ) );
+				break;
+
+			case 'user'  :
+			default :
+				$item_name = bp_core_get_user_displayname( $params['item_id'] );
+				break;
+		}
+
+		/**
+		 * Filters the alt attribute value to be applied to avatar.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value  alt to be applied to avatar.
+		 * @param string $value  ID of avatar item being requested.
+		 * @param string $value  Avatar type being requested.
+		 * @param array  $params Array of parameters for the request.
+		 */
+		$item_name = apply_filters( 'bp_core_avatar_alt', $item_name, $params['item_id'], $params['object'], $params );
+		$params['alt'] = sprintf( $params['alt'], $item_name );
+	}
+
+	/** Sanity Checks *********************************************************/
+
+	// Get a fallback for the 'alt' parameter, create html output.
+	if ( empty( $params['alt'] ) ) {
+		$params['alt'] = __( 'Profile Photo', 'buddypress' );
+	}
+	$html_alt = ' alt="' . esc_attr( $params['alt'] ) . '"';
+
+	// Filter image title and create html string.
+	$html_title = '';
+
+	/**
+	 * Filters the title attribute value to be applied to avatar.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $value  Title to be applied to avatar.
+	 * @param string $value  ID of avatar item being requested.
+	 * @param string $value  Avatar type being requested.
+	 * @param array  $params Array of parameters for the request.
+	 */
+	$params['title'] = apply_filters( 'bp_core_avatar_title', $params['title'], $params['item_id'], $params['object'], $params );
+
+	if ( ! empty( $params['title'] ) ) {
+		$html_title = ' title="' . esc_attr( $params['title'] ) . '"';
+	}
+
+	// Extra attributes
+	$extra_attr = ! empty( $args['extra_attr'] ) ? ' ' . $args['extra_attr'] : '';
+
+	// Set CSS ID and create html string.
+	$html_css_id = '';
+
+	/**
+	 * Filters the ID attribute to be applied to avatar.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string $value  ID to be applied to avatar.
+	 * @param string $value  ID of avatar item being requested.
+	 * @param string $value  Avatar type being requested.
+	 * @param array  $params Array of parameters for the request.
+	 */
+	$params['css_id'] = apply_filters( 'bp_core_css_id', $params['css_id'], $params['item_id'], $params['object'], $params );
+
+	if ( ! empty( $params['css_id'] ) ) {
+		$html_css_id = ' id="' . esc_attr( $params['css_id'] ) . '"';
+	}
+
+	// Set image width
+	if ( false !== $params['width'] ) {
+		// Width has been specified. No modification necessary.
+	} elseif ( 'thumb' == $params['type'] ) {
+		$params['width'] = bp_core_avatar_thumb_width();
+	} else {
+		$params['width'] = bp_core_avatar_full_width();
+	}
+	$html_width = ' width="' . $params['width'] . '"';
+
+	// Set image height
+	if ( false !== $params['height'] ) {
+		// Height has been specified. No modification necessary.
+	} elseif ( 'thumb' == $params['type'] ) {
+		$params['height'] = bp_core_avatar_thumb_height();
+	} else {
+		$params['height'] = bp_core_avatar_full_height();
+	}
+	$html_height = ' height="' . $params['height'] . '"';
+
+	/**
+	 * Filters the classes to be applied to the avatar.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array|string $value  Class(es) to be applied to the avatar.
+	 * @param string       $value  ID of the avatar item being requested.
+	 * @param string       $value  Avatar type being requested.
+	 * @param array        $params Array of parameters for the request.
+	 */
+	$params['class'] = apply_filters( 'bp_core_avatar_class', $params['class'], $params['item_id'], $params['object'], $params );
+
+	// Use an alias to leave the param unchanged
+	$avatar_classes = $params['class'];
+	if ( ! is_array( $avatar_classes ) ) {
+		$avatar_classes = explode( ' ', $avatar_classes );
+	}
+
+	// merge classes
+	$avatar_classes = array_merge( $avatar_classes, array(
+		$params['object'] . '-' . $params['item_id'] . '-avatar',
+		'avatar-' . $params['width'],
+	) );
+
+	// Sanitize each class
+	$avatar_classes = array_map( 'sanitize_html_class', $avatar_classes );
+
+	// populate the class attribute
+	$html_class = ' class="' . join( ' ', $avatar_classes ) . ' photo"';
+
+	// Set img URL and DIR based on prepopulated constants
+	$avatar_loc        = new stdClass();
+	$avatar_loc->path  = trailingslashit( bp_core_avatar_upload_path() );
+	$avatar_loc->url   = trailingslashit( bp_core_avatar_url() );
+
+	$avatar_loc->dir   = trailingslashit( $params['avatar_dir'] );
+
+	/**
+	 * Filters the avatar folder directory URL.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string $value Path to the avatar folder URL.
+	 * @param int    $value ID of the avatar item being requested.
+	 * @param string $value Avatar type being requested.
+	 * @param string $value Subdirectory where the requested avatar should be found.
+	 */
+	$avatar_folder_url = apply_filters( 'bp_core_avatar_folder_url', ( $avatar_loc->url  . $avatar_loc->dir . $params['item_id'] ), $params['item_id'], $params['object'], $params['avatar_dir'] );
+
+	/**
+	 * Filters the avatar folder directory path.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string $value Path to the avatar folder directory.
+	 * @param int    $value ID of the avatar item being requested.
+	 * @param string $value Avatar type being requested.
+	 * @param string $value Subdirectory where the requested avatar should be found.
+	 */
+	$avatar_folder_dir = apply_filters( 'bp_core_avatar_folder_dir', ( $avatar_loc->path . $avatar_loc->dir . $params['item_id'] ), $params['item_id'], $params['object'], $params['avatar_dir'] );
+
+	/**
+	 * Look for uploaded avatar first. Use it if it exists.
+	 * Set the file names to search for, to select the full size
+	 * or thumbnail image.
+	 */
+	$avatar_size              = ( 'full' == $params['type'] ) ? '-bpfull' : '-bpthumb';
+	$legacy_user_avatar_name  = ( 'full' == $params['type'] ) ? '-avatar2' : '-avatar1';
+	$legacy_group_avatar_name = ( 'full' == $params['type'] ) ? '-groupavatar-full' : '-groupavatar-thumb';
+
+	// Check for directory
+	if ( file_exists( $avatar_folder_dir ) ) {
+
+		// Open directory
+		if ( $av_dir = opendir( $avatar_folder_dir ) ) {
+
+			// Stash files in an array once to check for one that matches
+			$avatar_files = array();
+			while ( false !== ( $avatar_file = readdir( $av_dir ) ) ) {
+				// Only add files to the array (skip directories)
+				if ( 2 < strlen( $avatar_file ) ) {
+					$avatar_files[] = $avatar_file;
+				}
+			}
+
+			// Check for array
+			if ( 0 < count( $avatar_files ) ) {
+
+				// Check for current avatar
+				foreach( $avatar_files as $key => $value ) {
+					if ( strpos ( $value, $avatar_size )!== false ) {
+						$avatar_url = $avatar_folder_url . '/' . $avatar_files[$key];
+					}
+				}
+
+				// Legacy avatar check
+				if ( !isset( $avatar_url ) ) {
+					foreach( $avatar_files as $key => $value ) {
+						if ( strpos ( $value, $legacy_user_avatar_name )!== false ) {
+							$avatar_url = $avatar_folder_url . '/' . $avatar_files[$key];
+						}
+					}
+
+					// Legacy group avatar check
+					if ( !isset( $avatar_url ) ) {
+						foreach( $avatar_files as $key => $value ) {
+							if ( strpos ( $value, $legacy_group_avatar_name )!== false ) {
+								$avatar_url = $avatar_folder_url . '/' . $avatar_files[$key];
+							}
+						}
+					}
+				}
+			}
+		}
+
+		// Close the avatar directory
+		closedir( $av_dir );
+
+		// If we found a locally uploaded avatar
+		if ( isset( $avatar_url ) ) {
+			// Support custom scheme
+			$avatar_url = set_url_scheme( $avatar_url, $params['scheme'] );
+
+			// Return it wrapped in an <img> element
+			if ( true === $params['html'] ) {
+
+				/**
+				 * Filters an avatar URL wrapped in an <img> element.
+				 *
+				 * @since 1.1.0
+				 *
+				 * @param string $value             Full <img> element for an avatar.
+				 * @param array  $params            Array of parameters for the request.
+				 * @param string $value             ID of the item requested.
+				 * @param string $value             Subdirectory where the requested avatar should be found.
+				 * @param string $html_css_id       ID attribute for avatar.
+				 * @param string $html_width        Width attribute for avatar.
+				 * @param string $html_height       Height attribtue for avatar.
+				 * @param string $avatar_folder_url Avatar URL path.
+				 * @param string $avatar_folder_dir Avatar dir path.
+				 */
+				return apply_filters( 'bp_core_fetch_avatar', '<img src="' . $avatar_url . '"' . $html_class . $html_css_id  . $html_width . $html_height . $html_alt . $html_title . $extra_attr . ' />', $params, $params['item_id'], $params['avatar_dir'], $html_css_id, $html_width, $html_height, $avatar_folder_url, $avatar_folder_dir );
+
+			// ...or only the URL
+			} else {
+
+				/**
+				 * Filters a locally uploaded avatar URL.
+				 *
+				 * @since 1.2.5
+				 *
+				 * @param string $avatar_url URL for a locally uploaded avatar.
+				 * @param array  $params     Array of parameters for the request.
+				 */
+				return apply_filters( 'bp_core_fetch_avatar_url', $avatar_url, $params );
+			}
+		}
+	}
+
+	/**
+	 * Filters whether or not to skip Gravatar check.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool  $value  Whether or not to skip Gravatar.
+	 * @param array $params Array of parameters for the avatar request.
+	 */
+	if ( ! apply_filters( 'bp_core_fetch_avatar_no_grav', $params['no_grav'], $params ) ) {
+
+		// Set gravatar type
+		if ( empty( $bp->grav_default->{$params['object']} ) ) {
+			$default_grav = 'wavatar';
+		} elseif ( 'mystery' == $bp->grav_default->{$params['object']} ) {
+
+			/**
+			 * Filters the Mystery person avatar src value.
+			 *
+			 * @since 1.2.0
+			 *
+			 * @param string $value Avatar value.
+			 * @param string $value Width to display avatar at.
+			 */
+			$default_grav = apply_filters( 'bp_core_mysteryman_src', 'mm', $params['width'] );
+		} else {
+			$default_grav = $bp->grav_default->{$params['object']};
+		}
+
+		// Set gravatar object
+		if ( empty( $params['email'] ) ) {
+			if ( 'user' == $params['object'] ) {
+				$params['email'] = bp_core_get_user_email( $params['item_id'] );
+			} elseif ( 'group' == $params['object'] || 'blog' == $params['object'] ) {
+				$params['email'] = $params['item_id'] . '-' . $params['object'] . '@' . bp_get_root_domain();
+			}
+		}
+
+		/**
+		 * Filters the Gravatar email to use.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Email to use in Gravatar request.
+		 * @param string $value ID of the item being requested.
+		 * @param string $value Object type being requested.
+		 */
+		$params['email'] = apply_filters( 'bp_core_gravatar_email', $params['email'], $params['item_id'], $params['object'] );
+
+		/**
+		 * Filters the Gravatar URL host.
+		 *
+		 * @since 1.0.2
+		 *
+		 * @param string $value Gravatar URL host.
+		 */
+		$gravatar = apply_filters( 'bp_gravatar_url', '//www.gravatar.com/avatar/' );
+
+		// Append email hash to Gravatar
+		$gravatar .=  md5( strtolower( $params['email'] ) );
+
+		// Main Gravatar URL args
+		$url_args = array(
+			's' => $params['width']
+		);
+
+		// Custom Gravatar URL args
+		if ( ! empty( $params['force_default'] ) ) {
+			$url_args['f'] = 'y';
+		}
+		if ( ! empty( $params['rating'] ) ) {
+			$url_args['r'] = strtolower( $params['rating'] );
+		}
+		// Only set default image if 'Gravatar Logo' is not requested
+		if ( 'gravatar_default' !== $default_grav ) {
+			$url_args['d'] = $default_grav;
+		}
+
+		// Set up the Gravatar URL
+		$gravatar = esc_url( add_query_arg(
+			rawurlencode_deep( array_filter( $url_args ) ),
+			$gravatar
+		) );
+
+	// No avatar was found, and we've been told not to use a gravatar.
+	} else {
+
+		/**
+		 * Filters the avatar default when Gravatar is not used.
+		 *
+		 * This is a variable filter dependent on the avatar type being requested.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value  Default avatar for non-gravatar requests.
+		 * @param array  $params Array of parameters for the avatar request.
+		 */
+		$gravatar = apply_filters( 'bp_core_default_avatar_' . $params['object'], bp_core_avatar_default( 'local' ), $params );
+	}
+
+	if ( true === $params['html'] ) {
+
+		/** This filter is documented in bp-core/bp-core-avatars.php */
+		return apply_filters( 'bp_core_fetch_avatar', '<img src="' . $gravatar . '"' . $html_css_id . $html_class . $html_width . $html_height . $html_alt . $html_title . $extra_attr . ' />', $params, $params['item_id'], $params['avatar_dir'], $html_css_id, $html_width, $html_height, $avatar_folder_url, $avatar_folder_dir );
+	} else {
+
+		/** This filter is documented in bp-core/bp-core-avatars.php */
+		return apply_filters( 'bp_core_fetch_avatar_url', $gravatar, $params );
+	}
+}
+
+/**
+ * Delete an existing avatar.
+ *
+ * @param array|string $args {
+ *     Array of function parameters.
+ *     @type bool|int    $item_id    ID of the item whose avatar you're deleting.
+ *                                   Defaults to the current item of type $object.
+ *     @type string      $object     Object type of the item whose avatar you're
+ *                                   deleting. 'user', 'group', 'blog', or custom.
+ *                                   Default: 'user'.
+ *     @type bool|string $avatar_dir Subdirectory where avatar is located.
+ *                                   Default: false, which falls back on the default location
+ *                                   corresponding to the $object.
+ * }
+ * @return bool True on success, false on failure.
+ */
+function bp_core_delete_existing_avatar( $args = '' ) {
+
+	$defaults = array(
+		'item_id'    => false,
+		'object'     => 'user', // user OR group OR blog OR custom type (if you use filters)
+		'avatar_dir' => false
+	);
+
+	$args = wp_parse_args( $args, $defaults );
+	extract( $args, EXTR_SKIP );
+
+	if ( empty( $item_id ) ) {
+		if ( 'user' == $object )
+			$item_id = bp_displayed_user_id();
+		elseif ( 'group' == $object )
+			$item_id = buddypress()->groups->current_group->id;
+		elseif ( 'blog' == $object )
+			$item_id = $current_blog->id;
+
+		/** This filter is documented in bp-core/bp-core-avatars.php */
+		$item_id = apply_filters( 'bp_core_avatar_item_id', $item_id, $object );
+
+		if ( !$item_id ) return false;
+	}
+
+	if ( empty( $avatar_dir ) ) {
+		if ( 'user' == $object )
+			$avatar_dir = 'avatars';
+		elseif ( 'group' == $object )
+			$avatar_dir = 'group-avatars';
+		elseif ( 'blog' == $object )
+			$avatar_dir = 'blog-avatars';
+
+		/** This filter is documented in bp-core/bp-core-avatars.php */
+		$avatar_dir = apply_filters( 'bp_core_avatar_dir', $avatar_dir, $object );
+
+		if ( !$avatar_dir ) return false;
+	}
+
+	/** This filter is documented in bp-core/bp-core-avatars.php */
+	$avatar_folder_dir = apply_filters( 'bp_core_avatar_folder_dir', bp_core_avatar_upload_path() . '/' . $avatar_dir . '/' . $item_id, $item_id, $object, $avatar_dir );
+
+	if ( !file_exists( $avatar_folder_dir ) )
+		return false;
+
+	if ( $av_dir = opendir( $avatar_folder_dir ) ) {
+		while ( false !== ( $avatar_file = readdir($av_dir) ) ) {
+			if ( ( preg_match( "/-bpfull/", $avatar_file ) || preg_match( "/-bpthumb/", $avatar_file ) ) && '.' != $avatar_file && '..' != $avatar_file )
+				@unlink( $avatar_folder_dir . '/' . $avatar_file );
+		}
+	}
+	closedir($av_dir);
+
+	@rmdir( $avatar_folder_dir );
+
+	/**
+	 * Fires after deleting an existing avatar.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param array $args Array of arguments used for avatar deletion.
+	 */
+	do_action( 'bp_core_delete_existing_avatar', $args );
+
+	return true;
+}
+
+/**
+ * Ajax delete an avatar for a given object and item id.
+ *
+ * @since 2.3.0
+ *
+ * @return string|null A json object containing success data if the avatar was deleted
+ *                     error message otherwise.
+ */
+function bp_avatar_ajax_delete() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
+		wp_send_json_error();
+	}
+
+	$avatar_data = $_POST;
+
+	if ( empty( $avatar_data['object'] ) || empty( $avatar_data['item_id'] ) ) {
+		wp_send_json_error();
+	}
+
+	$nonce = 'bp_delete_avatar_link';
+	if ( 'group' === $avatar_data['object'] ) {
+		$nonce = 'bp_group_avatar_delete';
+	}
+
+	// Check the nonce
+	check_admin_referer( $nonce, 'nonce' );
+
+	// Capability check
+	if ( ! bp_attachments_current_user_can( 'edit_avatar', $avatar_data ) ) {
+		wp_send_json_error();
+	}
+
+	// Handle delete
+	if ( bp_core_delete_existing_avatar( array( 'item_id' => $avatar_data['item_id'], 'object' => $avatar_data['object'] ) ) ) {
+		$return = array(
+			'avatar' => html_entity_decode( bp_core_fetch_avatar( array(
+				'object'  => $avatar_data['object'],
+				'item_id' => $avatar_data['item_id'],
+				'html'    => false,
+				'type'    => 'full',
+			) ) ),
+			'feedback_code' => 4,
+			'item_id'       => $avatar_data['item_id'],
+		);
+
+		wp_send_json_success( $return );
+	} else {
+		wp_send_json_error( array(
+			'feedback_code' => 3,
+		) );
+	}
+}
+add_action( 'wp_ajax_bp_avatar_delete', 'bp_avatar_ajax_delete' );
+
+/**
+ * Handle avatar uploading.
+ *
+ * The functions starts off by checking that the file has been uploaded
+ * properly using bp_core_check_avatar_upload(). It then checks that the file
+ * size is within limits, and that it has an accepted file extension (jpg, gif,
+ * png). If everything checks out, crop the image and move it to its real
+ * location.
+ *
+ * @see bp_core_check_avatar_upload()
+ * @see bp_core_check_avatar_type()
+ *
+ * @param array  $file              The appropriate entry the from $_FILES superglobal.
+ * @param string $upload_dir_filter A filter to be applied to 'upload_dir'.
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_core_avatar_handle_upload( $file, $upload_dir_filter ) {
+
+	/**
+	 * Filters whether or not to handle uploading.
+	 *
+	 * If you want to override this function, make sure you return false.
+	 *
+	 * @since 1.2.4
+	 *
+	 * @param bool   $value             Whether or not to crop.
+	 * @param array  $file              Appropriate entry from $_FILES superglobal.
+	 * @parma string $upload_dir_filter A filter to be applied to 'upload_dir'.
+	 */
+	if ( ! apply_filters( 'bp_core_pre_avatar_handle_upload', true, $file, $upload_dir_filter ) ) {
+		return true;
+	}
+
+	// Setup some variables.
+	$bp          = buddypress();
+	$upload_path = bp_core_avatar_upload_path();
+
+	// Upload the file.
+	$avatar_attachment = new BP_Attachment_Avatar();
+	$bp->avatar_admin->original = $avatar_attachment->upload( $file, $upload_dir_filter );
+
+	// In case of an error, stop the process and display a feedback to the user.
+	if ( ! empty( $bp->avatar_admin->original['error'] ) ) {
+		bp_core_add_message( sprintf( __( 'Upload Failed! Error was: %s', 'buddypress' ), $bp->avatar_admin->original['error'] ), 'error' );
+		return false;
+	}
+
+	// The Avatar UI available width
+	$ui_available_width = 0;
+
+	// Try to set the ui_available_width using the avatar_admin global
+	if ( isset( $bp->avatar_admin->ui_available_width ) ) {
+		$ui_available_width = $bp->avatar_admin->ui_available_width;
+	}
+
+	// Maybe resize.
+	$bp->avatar_admin->resized = $avatar_attachment->shrink( $bp->avatar_admin->original['file'], $ui_available_width );
+	$bp->avatar_admin->image   = new stdClass();
+
+	// We only want to handle one image after resize.
+	if ( empty( $bp->avatar_admin->resized ) ) {
+		$bp->avatar_admin->image->file = $bp->avatar_admin->original['file'];
+		$bp->avatar_admin->image->dir  = str_replace( $upload_path, '', $bp->avatar_admin->original['file'] );
+	} else {
+		$bp->avatar_admin->image->file = $bp->avatar_admin->resized['path'];
+		$bp->avatar_admin->image->dir  = str_replace( $upload_path, '', $bp->avatar_admin->resized['path'] );
+		@unlink( $bp->avatar_admin->original['file'] );
+	}
+
+	// Check for WP_Error on what should be an image.
+	if ( is_wp_error( $bp->avatar_admin->image->dir ) ) {
+		bp_core_add_message( sprintf( __( 'Upload failed! Error was: %s', 'buddypress' ), $bp->avatar_admin->image->dir->get_error_message() ), 'error' );
+		return false;
+	}
+
+	// If the uploaded image is smaller than the "full" dimensions, throw a warning.
+	if ( $avatar_attachment->is_too_small( $bp->avatar_admin->image->file ) ) {
+		bp_core_add_message( sprintf( __( 'You have selected an image that is smaller than recommended. For best results, upload a picture larger than %d x %d pixels.', 'buddypress' ), bp_core_avatar_full_width(), bp_core_avatar_full_height() ), 'error' );
+	}
+
+	// Set the url value for the image.
+	$bp->avatar_admin->image->url = bp_core_avatar_url() . $bp->avatar_admin->image->dir;
+
+	return true;
+}
+
+/**
+ * Ajax upload an avatar.
+ *
+ * @since 2.3.0
+ *
+ * @return  string|null A json object containing success data if the upload succeeded
+ *                      error message otherwise.
+ */
+function bp_avatar_ajax_upload() {
+	// Bail if not a POST action
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
+		wp_die();
+	}
+
+	/**
+	 * Sending the json response will be different if
+	 * the current Plupload runtime is html4
+	 */
+	$is_html4 = false;
+	if ( ! empty( $_POST['html4' ] ) ) {
+		$is_html4 = true;
+	}
+
+	// Check the nonce
+	check_admin_referer( 'bp-uploader' );
+
+	// Init the BuddyPress parameters
+	$bp_params = array();
+
+	// We need it to carry on
+	if ( ! empty( $_POST['bp_params' ] ) ) {
+		$bp_params = $_POST['bp_params' ];
+	} else {
+		bp_attachments_json_response( false, $is_html4 );
+	}
+
+	// We need the object to set the uploads dir filter
+	if ( empty( $bp_params['object'] ) ) {
+		bp_attachments_json_response( false, $is_html4 );
+	}
+
+	// Capability check
+	if ( ! bp_attachments_current_user_can( 'edit_avatar', $bp_params ) ) {
+		bp_attachments_json_response( false, $is_html4 );
+	}
+
+	$bp = buddypress();
+	$bp_params['upload_dir_filter'] = '';
+	$needs_reset = array();
+
+	if ( 'user' === $bp_params['object'] && bp_is_active( 'xprofile' ) ) {
+		$bp_params['upload_dir_filter'] = 'xprofile_avatar_upload_dir';
+
+		if ( ! bp_displayed_user_id() && ! empty( $bp_params['item_id'] ) ) {
+			$needs_reset = array( 'key' => 'displayed_user', 'value' => $bp->displayed_user );
+			$bp->displayed_user->id = $bp_params['item_id'];
+		}
+	} elseif ( 'group' === $bp_params['object'] && bp_is_active( 'groups' ) ) {
+		$bp_params['upload_dir_filter'] = 'groups_avatar_upload_dir';
+
+		if ( ! bp_get_current_group_id() && ! empty( $bp_params['item_id'] ) ) {
+			$needs_reset = array( 'component' => 'groups', 'key' => 'current_group', 'value' => $bp->groups->current_group );
+			$bp->groups->current_group = groups_get_group( array(
+				'group_id'        => $bp_params['item_id'],
+				'populate_extras' => false,
+			) );
+		}
+	} else {
+		/**
+		 * Filter here to deal with other components.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @var array $bp_params the BuddyPress Ajax parameters.
+		 */
+		$bp_params = apply_filters( 'bp_core_avatar_ajax_upload_params', $bp_params );
+	}
+
+	if ( ! isset( $bp->avatar_admin ) ) {
+		$bp->avatar_admin = new stdClass();
+	}
+
+	/**
+	 * The BuddyPress upload parameters is including the Avatar UI Available width,
+	 * add it to the avatar_admin global for a later use.
+	 */
+	if ( isset( $bp_params['ui_available_width'] ) ) {
+		$bp->avatar_admin->ui_available_width =  (int) $bp_params['ui_available_width'];
+	}
+
+	// Upload the avatar
+	$avatar = bp_core_avatar_handle_upload( $_FILES, $bp_params['upload_dir_filter'] );
+
+	// Reset objects
+	if ( ! empty( $needs_reset ) ) {
+		if ( ! empty( $needs_reset['component'] ) ) {
+			$bp->{$needs_reset['component']}->{$needs_reset['key']} = $needs_reset['value'];
+		} else {
+			$bp->{$needs_reset['key']} = $needs_reset['value'];
+		}
+	}
+
+	// Init the feedback message
+	$feedback_message = false;
+
+	if ( ! empty( $bp->template_message ) ) {
+		$feedback_message = $bp->template_message;
+
+		// Remove template message.
+		$bp->template_message      = false;
+		$bp->template_message_type = false;
+		@setcookie( 'bp-message', false, time() - 1000, COOKIEPATH );
+		@setcookie( 'bp-message-type', false, time() - 1000, COOKIEPATH );
+	}
+
+	if ( empty( $avatar ) ) {
+		// Default upload error
+		$message = __( 'Upload failed.', 'buddypress' );
+
+		// Use the template message if set
+		if ( ! empty( $feedback_message ) ) {
+			$message = $feedback_message;
+		}
+
+		// Upload error reply
+		bp_attachments_json_response( false, $is_html4, array(
+			'type'    => 'upload_error',
+			'message' => $message,
+		) );
+	}
+
+	if ( empty( $bp->avatar_admin->image->file ) ) {
+		bp_attachments_json_response( false, $is_html4 );
+	}
+
+	$uploaded_image = @getimagesize( $bp->avatar_admin->image->file );
+
+	// Set the name of the file
+	$name = $_FILES['file']['name'];
+	$name_parts = pathinfo( $name );
+	$name = trim( substr( $name, 0, - ( 1 + strlen( $name_parts['extension'] ) ) ) );
+
+	// Finally return the avatar to the editor
+	bp_attachments_json_response( true, $is_html4, array(
+		'name'      => $name,
+		'url'       => $bp->avatar_admin->image->url,
+		'width'     => $uploaded_image[0],
+		'height'    => $uploaded_image[1],
+		'feedback'  => $feedback_message,
+	) );
+}
+add_action( 'wp_ajax_bp_avatar_upload', 'bp_avatar_ajax_upload' );
+
+ /**
+  * Handle avatar webcam capture.
+  *
+  * @since 2.3.0
+  *
+  * @param string $data    Base64 encoded image.
+  * @param int    $item_id Item to associate.
+  *
+  * @return bool True on success, false on failure.
+  */
+function bp_avatar_handle_capture( $data = '', $item_id = 0 ) {
+	if ( empty( $data ) || empty( $item_id ) ) {
+		return false;
+	}
+
+	$avatar_dir = bp_core_avatar_upload_path() . '/avatars';
+
+	// It's not a regular upload, we may need to create this folder.
+	if ( ! file_exists( $avatar_dir ) ) {
+		if ( ! wp_mkdir_p( $avatar_dir ) ) {
+			return false;
+		}
+	}
+
+	/**
+	 * Filters the Avatar folder directory.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $avatar_dir Directory for storing avatars.
+	 * @param int    $item_id    ID of the item being acted on.
+	 * @param string $value      Avatar type.
+	 * @param string $value      Avatars word.
+	 */
+	$avatar_folder_dir = apply_filters( 'bp_core_avatar_folder_dir', $avatar_dir . '/' . $item_id, $item_id, 'user', 'avatars' );
+
+	// It's not a regular upload, we may need to create this folder.
+	if( ! is_dir( $avatar_folder_dir ) ) {
+		if ( ! wp_mkdir_p( $avatar_folder_dir ) ) {
+			return false;
+		}
+	}
+
+	$original_file = $avatar_folder_dir . '/webcam-capture-' . $item_id . '.png';
+
+	if ( file_put_contents( $original_file, $data ) ) {
+		$avatar_to_crop = str_replace( bp_core_avatar_upload_path(), '', $original_file );
+
+		// Crop to default values
+		$crop_args = array( 'item_id' => $item_id, 'original_file' => $avatar_to_crop, 'crop_x' => 0, 'crop_y' => 0 );
+
+		return bp_core_avatar_handle_crop( $crop_args );
+	} else {
+		return false;
+	}
+}
+
+/**
+ * Crop an uploaded avatar.
+ *
+ * $args has the following parameters:
+ *  object - What component the avatar is for, e.g. "user"
+ *  avatar_dir  The absolute path to the avatar
+ *  item_id - Item ID
+ *  original_file - The absolute path to the original avatar file
+ *  crop_w - Crop width
+ *  crop_h - Crop height
+ *  crop_x - The horizontal starting point of the crop
+ *  crop_y - The vertical starting point of the crop
+ *
+ * @param array|string $args {
+ *     Array of function parameters.
+ *
+ *     @type string      $object        Object type of the item whose avatar you're
+ *                                      handling. 'user', 'group', 'blog', or custom.
+ *                                      Default: 'user'.
+ *     @type string      $avatar_dir    Subdirectory where avatar should be stored.
+ *                                      Default: 'avatars'.
+ *     @type bool|int    $item_id       ID of the item that the avatar belongs to.
+ *     @type bool|string $original_file Absolute path to the original avatar file.
+ *     @type int         $crop_w        Crop width. Default: the global 'full' avatar width,
+ *                                      as retrieved by bp_core_avatar_full_width().
+ *     @type int         $crop_h        Crop height. Default: the global 'full' avatar height,
+ *                                      as retrieved by bp_core_avatar_full_height().
+ *     @type int         $crop_x        The horizontal starting point of the crop. Default: 0.
+ *     @type int         $crop_y        The vertical starting point of the crop. Default: 0.
+ * }
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_core_avatar_handle_crop( $args = '' ) {
+
+	$r = wp_parse_args( $args, array(
+		'object'        => 'user',
+		'avatar_dir'    => 'avatars',
+		'item_id'       => false,
+		'original_file' => false,
+		'crop_w'        => bp_core_avatar_full_width(),
+		'crop_h'        => bp_core_avatar_full_height(),
+		'crop_x'        => 0,
+		'crop_y'        => 0
+	) );
+
+	/**
+	 * Filters whether or not to handle cropping.
+	 *
+	 * If you want to override this function, make sure you return false.
+	 *
+	 * @since 1.2.4
+	 *
+	 * @param bool  $value Whether or not to crop.
+	 * @param array $r     Array of parsed arguments for function.
+	 */
+	if ( ! apply_filters( 'bp_core_pre_avatar_handle_crop', true, $r ) ) {
+		return true;
+	}
+
+	// Crop the file
+	$avatar_attachment = new BP_Attachment_Avatar();
+	$cropped           = $avatar_attachment->crop( $r );
+
+	// Check for errors
+	if ( empty( $cropped['full'] ) || empty( $cropped['thumb'] ) || is_wp_error( $cropped['full'] ) || is_wp_error( $cropped['thumb'] ) ) {
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Ajax set an avatar for a given object and item id.
+ *
+ * @since 2.3.0
+ *
+ * @return  string|null A json object containing success data if the crop/capture succeeded
+ *                      error message otherwise.
+ */
+function bp_avatar_ajax_set() {
+	// Bail if not a POST action
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
+		wp_send_json_error();
+	}
+
+	// Check the nonce
+	check_admin_referer( 'bp_avatar_cropstore', 'nonce' );
+
+	$avatar_data = wp_parse_args( $_POST, array(
+		'crop_w' => bp_core_avatar_full_width(),
+		'crop_h' => bp_core_avatar_full_height(),
+		'crop_x' => 0,
+		'crop_y' => 0
+	) );
+
+	if ( empty( $avatar_data['object'] ) || empty( $avatar_data['item_id'] ) || empty( $avatar_data['original_file'] ) ) {
+		wp_send_json_error();
+	}
+
+	// Capability check
+	if ( ! bp_attachments_current_user_can( 'edit_avatar', $avatar_data ) ) {
+		wp_send_json_error();
+	}
+
+	if ( ! empty( $avatar_data['type'] ) && 'camera' === $avatar_data['type'] && 'user' === $avatar_data['object'] ) {
+		$webcam_avatar = false;
+
+		if ( ! empty( $avatar_data['original_file'] ) ) {
+			$webcam_avatar = str_replace( array( 'data:image/png;base64,', ' ' ), array( '', '+' ), $avatar_data['original_file'] );
+			$webcam_avatar = base64_decode( $webcam_avatar );
+		}
+
+		if ( ! bp_avatar_handle_capture( $webcam_avatar, $avatar_data['item_id'] ) ) {
+			wp_send_json_error( array(
+				'feedback_code' => 1
+			) );
+
+		} else {
+			$return = array(
+				'avatar' => html_entity_decode( bp_core_fetch_avatar( array(
+					'object'  => $avatar_data['object'],
+					'item_id' => $avatar_data['item_id'],
+					'html'    => false,
+					'type'    => 'full',
+				) ) ),
+				'feedback_code' => 2,
+				'item_id'       => $avatar_data['item_id'],
+			);
+
+			/**
+			 * Fires if the new avatar was successfully captured.
+			 *
+			 * @since 1.1.0 Used to inform the avatar was successfully cropped
+			 * @since 2.3.4 Add two new parameters to inform about the user id and
+			 *              about the way the avatar was set (eg: 'crop' or 'camera')
+			 *              Move the action at the right place, once the avatar is set
+			 *
+			 * @param string $item_id Inform about the user id the avatar was set for
+			 * @param string $type    Inform about the way the avatar was set ('camera')
+			 */
+			do_action( 'xprofile_avatar_uploaded', (int) $avatar_data['item_id'], $avatar_data['type'] );
+
+			wp_send_json_success( $return );
+		}
+
+		return;
+	}
+
+	$original_file = str_replace( bp_core_avatar_url(), '', $avatar_data['original_file'] );
+
+	// Set avatars dir & feedback part
+	if ( 'user' === $avatar_data['object'] ) {
+		$avatar_dir = 'avatars';
+
+	// Defaults to object-avatars dir
+	} else {
+		$avatar_dir = sanitize_key( $avatar_data['object'] ) . '-avatars';
+	}
+
+	// Crop args
+	$r = array(
+		'item_id'       => $avatar_data['item_id'],
+		'object'        => $avatar_data['object'],
+		'avatar_dir'    => $avatar_dir,
+		'original_file' => $original_file,
+		'crop_w'        => $avatar_data['crop_w'],
+		'crop_h'        => $avatar_data['crop_h'],
+		'crop_x'        => $avatar_data['crop_x'],
+		'crop_y'        => $avatar_data['crop_y']
+	);
+
+	// Handle crop
+	if ( bp_core_avatar_handle_crop( $r ) ) {
+		$return = array(
+			'avatar' => html_entity_decode( bp_core_fetch_avatar( array(
+				'object'  => $avatar_data['object'],
+				'item_id' => $avatar_data['item_id'],
+				'html'    => false,
+				'type'    => 'full',
+			) ) ),
+			'feedback_code' => 2,
+			'item_id'       => $avatar_data['item_id'],
+		);
+
+		if ( 'user' === $avatar_data['object'] ) {
+			/**
+			 * Fires if the new avatar was successfully cropped.
+			 *
+			 * @since 1.1.0 Used to inform the avatar was successfully cropped
+			 * @since 2.3.4 Add two new parameters to inform about the user id and
+			 *              about the way the avatar was set (eg: 'crop' or 'camera')
+			 *              Move the action at the right place, once the avatar is set
+			 *
+			 * @param string $item_id Inform about the user id the avatar was set for
+			 * @param string $type Inform about the way the avatar was set ('crop')
+			 */
+			do_action( 'xprofile_avatar_uploaded', (int) $avatar_data['item_id'], $avatar_data['type'] );
+		}
+
+		wp_send_json_success( $return );
+	} else {
+		wp_send_json_error( array(
+			'feedback_code' => 1,
+		) );
+	}
+}
+add_action( 'wp_ajax_bp_avatar_set', 'bp_avatar_ajax_set' );
+
+/**
+ * Replace default WordPress avatars with BP avatars, if available.
+ *
+ * See 'get_avatar' filter description in wp-includes/pluggable.php.
+ *
+ * @since 2.4.0 Added $args parameter to coincide with WordPress 4.2.0.
+ *
+ * @param string            $avatar  The avatar path passed to 'get_avatar'.
+ * @param int|string|object $user    A user ID, email address, or comment object.
+ * @param int               $size    Size of the avatar image ('thumb' or 'full').
+ * @param string            $default URL to a default image to use if no avatar is available.
+ * @param string            $alt     Alternate text to use in image tag. Default: ''.
+ * @param array             $args    Arguments passed to get_avatar_data(), after processing.
+ *
+ * @return string BP avatar path, if found; else the original avatar path.
+ */
+function bp_core_fetch_avatar_filter( $avatar, $user, $size, $default, $alt = '', $args = array() ) {
+	global $pagenow;
+
+	// Do not filter if inside WordPress options page
+	if ( 'options-discussion.php' == $pagenow )
+		return $avatar;
+
+	// If passed an object, assume $user->user_id
+	if ( is_object( $user ) ) {
+		if ( isset( $user->user_id ) ) {
+			$id = $user->user_id;
+		} else {
+			$id = $user->ID;
+		}
+
+	// If passed a number, assume it was a $user_id
+	} elseif ( is_numeric( $user ) ) {
+		$id = $user;
+
+	// If passed a string and that string returns a user, get the $id
+	} elseif ( is_string( $user ) && ( $user_by_email = get_user_by( 'email', $user ) ) ) {
+		$id = $user_by_email->ID;
+	}
+
+	// If somehow $id hasn't been assigned, return the result of get_avatar
+	if ( empty( $id ) ) {
+		return !empty( $avatar ) ? $avatar : $default;
+	}
+
+	// Image alt tag
+	if ( empty( $alt ) ) {
+		$alt = sprintf( __( 'Profile photo of %s', 'buddypress' ), bp_core_get_user_displayname( $id ) );
+	}
+
+	// Use the 'thumb' type, unless the requested width is bigger than
+	// BP's thumb width.
+	$type = 'thumb';
+	if ( (int) $size > bp_core_avatar_thumb_width() ) {
+		$type = 'full';
+	}
+
+	$avatar_args = array(
+		'item_id' => $id,
+		'type'    => $type,
+		'width'   => $size,
+		'height'  => $size,
+		'alt'     => $alt,
+	);
+
+	// Support new arguments as of WordPress 4.2.0
+	if ( ! empty( $args['width'] ) ) {
+		$avatar_args['width'] = $args['width'];
+	}
+	if ( ! empty( $args['height'] ) ) {
+		$avatar_args['height'] = $args['height'];
+	}
+	if ( ! empty( $args['class'] ) ) {
+		$avatar_args['class'] = $args['class'];
+	}
+	if ( ! empty( $args['class'] ) ) {
+		$avatar_args['class'] = $args['class'];
+	}
+	if ( ! empty( $args['extra_attr'] ) ) {
+		$avatar_args['extra_attr'] = $args['extra_attr'];
+	}
+	if ( ! empty( $args['scheme'] ) ) {
+		$avatar_args['scheme'] = $args['scheme'];
+	}
+	if ( ! empty( $args['force_default'] ) ) {
+		$avatar_args['force_default'] = $args['force_default'];
+	}
+	if ( ! empty( $args['rating'] ) ) {
+		$avatar_args['rating'] = $args['rating'];
+	}
+
+	// Let BuddyPress handle the fetching of the avatar
+	$bp_avatar = bp_core_fetch_avatar( $avatar_args );
+
+	// If BuddyPress found an avatar, use it. If not, use the result of get_avatar
+	return ( !$bp_avatar ) ? $avatar : $bp_avatar;
+}
+add_filter( 'get_avatar', 'bp_core_fetch_avatar_filter', 10, 6 );
+
+/**
+ * Is the current avatar upload error-free?
+ *
+ * @param array $file The $_FILES array.
+ *
+ * @return bool True if no errors are found. False if there are errors.
+ */
+function bp_core_check_avatar_upload( $file ) {
+	if ( isset( $file['error'] ) && $file['error'] )
+		return false;
+
+	return true;
+}
+
+/**
+ * Is the file size of the current avatar upload permitted?
+ *
+ * @param array $file The $_FILES array.
+ *
+ * @return bool True if the avatar is under the size limit, otherwise false.
+ */
+function bp_core_check_avatar_size( $file ) {
+	if ( $file['file']['size'] > bp_core_avatar_original_max_filesize() )
+		return false;
+
+	return true;
+}
+
+/**
+ * Get allowed avatar types.
+ *
+ * @since 2.3.0
+ */
+function bp_core_get_allowed_avatar_types() {
+	$allowed_types = bp_attachments_get_allowed_types( 'avatar' );
+
+	/**
+ 	 * Filters the list of allowed image types.
+ 	 *
+ 	 * @since 2.3.0
+ 	 *
+ 	 * @param array $allowed_types List of image types.
+ 	 */
+ 	$avatar_types = (array) apply_filters( 'bp_core_get_allowed_avatar_types', $allowed_types );
+
+ 	if ( empty( $avatar_types ) ) {
+ 		$avatar_types = $allowed_types;
+ 	} else {
+ 		$avatar_types = array_intersect( $allowed_types, $avatar_types );
+ 	}
+
+ 	return array_values( $avatar_types );
+}
+
+/**
+ * Get allowed avatar mime types.
+ *
+ * @since 2.3.0
+ */
+function bp_core_get_allowed_avatar_mimes() {
+	$allowed_types  = bp_core_get_allowed_avatar_types();
+
+	return bp_attachments_get_allowed_mimes( 'avatar', $allowed_types );
+}
+
+/**
+ * Does the current avatar upload have an allowed file type?
+ *
+ * Permitted file types are JPG, GIF and PNG.
+ *
+ * @param array $file The $_FILES array.
+ *
+ * @return bool True if the file extension is permitted, otherwise false.
+ */
+function bp_core_check_avatar_type( $file ) {
+	return bp_attachments_check_filetype( $file['file']['tmp_name'], $file['file']['name'], bp_core_get_allowed_avatar_mimes() );
+}
+
+/**
+ * Fetch data from the BP root blog's upload directory.
+ *
+ * @since 1.8.0
+ *
+ * @param string $type The variable we want to return from the $bp->avatars object.
+ *                     Only 'upload_path' and 'url' are supported. Default: 'upload_path'.
+ *
+ * @return string The avatar upload directory path.
+ */
+function bp_core_get_upload_dir( $type = 'upload_path' ) {
+	$bp = buddypress();
+
+	switch ( $type ) {
+		case 'upload_path' :
+			$constant = 'BP_AVATAR_UPLOAD_PATH';
+			$key      = 'basedir';
+
+			break;
+
+		case 'url' :
+			$constant = 'BP_AVATAR_URL';
+			$key      = 'baseurl';
+
+			break;
+
+		default :
+			return false;
+
+			break;
+	}
+
+	// See if the value has already been calculated and stashed in the $bp global.
+	if ( isset( $bp->avatar->$type ) ) {
+		$retval = $bp->avatar->$type;
+	} else {
+		// If this value has been set in a constant, just use that.
+		if ( defined( $constant ) ) {
+			$retval = constant( $constant );
+		} else {
+
+			// Use cached upload dir data if available.
+			if ( ! empty( $bp->avatar->upload_dir ) ) {
+				$upload_dir = $bp->avatar->upload_dir;
+
+			// No cache, so query for it.
+			} else {
+
+				// Get upload directory information from current site.
+				$upload_dir = bp_upload_dir();
+
+				// Stash upload directory data for later use.
+				$bp->avatar->upload_dir = $upload_dir;
+			}
+
+			// Directory does not exist and cannot be created.
+			if ( ! empty( $upload_dir['error'] ) ) {
+				$retval = '';
+
+			} else {
+				$retval = $upload_dir[$key];
+
+				// If $key is 'baseurl', check to see if we're on SSL
+				// Workaround for WP13941, WP15928, WP19037.
+				if ( $key == 'baseurl' && is_ssl() ) {
+					$retval = str_replace( 'http://', 'https://', $retval );
+				}
+			}
+
+		}
+
+		// Stash in $bp for later use.
+		$bp->avatar->$type = $retval;
+	}
+
+	return $retval;
+}
+
+/**
+ * Get the absolute upload path for the WP installation.
+ *
+ * @uses bp_core_get_upload_dir() To get upload directory info.
+ *
+ * @return string Absolute path to WP upload directory.
+ */
+function bp_core_avatar_upload_path() {
+
+	/**
+	 * Filters the absolute upload path for the WP installation.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value Absolute upload path for the WP installation.
+	 */
+	return apply_filters( 'bp_core_avatar_upload_path', bp_core_get_upload_dir() );
+}
+
+/**
+ * Get the raw base URL for root site upload location.
+ *
+ * @uses bp_core_get_upload_dir() To get upload directory info.
+ *
+ * @return string Full URL to current upload location.
+ */
+function bp_core_avatar_url() {
+
+	/**
+	 * Filters the raw base URL for root site upload location.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value Raw base URL for the root site upload location.
+	 */
+	return apply_filters( 'bp_core_avatar_url', bp_core_get_upload_dir( 'url' ) );
+}
+
+/**
+ * Check if a given user ID has an uploaded avatar.
+ *
+ * @since 1.0.0
+ *
+ * @param int $user_id ID of the user whose avatar is being checked.
+ *
+ * @return bool True if the user has uploaded a local avatar. Otherwise false.
+ */
+function bp_get_user_has_avatar( $user_id = 0 ) {
+
+	if ( empty( $user_id ) )
+		$user_id = bp_displayed_user_id();
+
+	$retval = false;
+	if ( bp_core_fetch_avatar( array( 'item_id' => $user_id, 'no_grav' => true, 'html' => false ) ) != bp_core_avatar_default( 'local' ) )
+		$retval = true;
+
+	/**
+	 * Filters whether or not a user has an uploaded avatar.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $retval  Whether or not a user has an uploaded avatar.
+	 * @param int  $user_id ID of the user being checked.
+	 */
+	return (bool) apply_filters( 'bp_get_user_has_avatar', $retval, $user_id );
+}
+
+/**
+ * Utility function for fetching an avatar dimension setting.
+ *
+ * @since 1.5.0
+ *
+ * @param string $type   Dimension type you're fetching dimensions for. 'thumb'
+ *                       or 'full'. Default: 'thumb'.
+ * @param string $h_or_w Which dimension is being fetched. 'height' or 'width'.
+ *                       Default: 'height'.
+ *
+ * @return int|bool $dim The dimension.
+ */
+function bp_core_avatar_dimension( $type = 'thumb', $h_or_w = 'height' ) {
+	$bp  = buddypress();
+	$dim = isset( $bp->avatar->{$type}->{$h_or_w} ) ? (int) $bp->avatar->{$type}->{$h_or_w} : false;
+
+	/**
+	 * Filters the avatar dimension setting.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int|bool $dim    Dimension setting for the type.
+	 * @param string   $type   The type of avatar whose dimensions are requested. Default 'thumb'.
+	 * @param string   $h_or_w The dimension parameter being requested. Default 'height'.
+	 */
+	return apply_filters( 'bp_core_avatar_dimension', $dim, $type, $h_or_w );
+}
+
+/**
+ * Get the 'thumb' avatar width setting.
+ *
+ * @since 1.5.0
+ *
+ * @return int The 'thumb' width.
+ */
+function bp_core_avatar_thumb_width() {
+
+	/**
+	 * Filters the 'thumb' avatar width setting.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $value Value for the 'thumb' avatar width setting.
+	 */
+	return apply_filters( 'bp_core_avatar_thumb_width', bp_core_avatar_dimension( 'thumb', 'width' ) );
+}
+
+/**
+ * Get the 'thumb' avatar height setting.
+ *
+ * @since 1.5.0
+ *
+ * @return int The 'thumb' height.
+ */
+function bp_core_avatar_thumb_height() {
+
+	/**
+	 * Filters the 'thumb' avatar height setting.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $value Value for the 'thumb' avatar height setting.
+	 */
+	return apply_filters( 'bp_core_avatar_thumb_height', bp_core_avatar_dimension( 'thumb', 'height' ) );
+}
+
+/**
+ * Get the 'full' avatar width setting.
+ *
+ * @since 1.5.0
+ *
+ * @return int The 'full' width.
+ */
+function bp_core_avatar_full_width() {
+
+	/**
+	 * Filters the 'full' avatar width setting.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $value Value for the 'full' avatar width setting.
+	 */
+	return apply_filters( 'bp_core_avatar_full_width', bp_core_avatar_dimension( 'full', 'width' ) );
+}
+
+/**
+ * Get the 'full' avatar height setting.
+ *
+ * @since 1.5.0
+ *
+ * @return int The 'full' height.
+ */
+function bp_core_avatar_full_height() {
+
+	/**
+	 * Filters the 'full' avatar height setting.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $value Value for the 'full' avatar height setting.
+	 */
+	return apply_filters( 'bp_core_avatar_full_height', bp_core_avatar_dimension( 'full', 'height' ) );
+}
+
+/**
+ * Get the max width for original avatar uploads.
+ *
+ * @since 1.5.0
+ *
+ * @return int The max width for original avatar uploads.
+ */
+function bp_core_avatar_original_max_width() {
+
+	/**
+	 * Filters the max width for original avatar uploads.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $value Value for the max width.
+	 */
+	return apply_filters( 'bp_core_avatar_original_max_width', (int) buddypress()->avatar->original_max_width );
+}
+
+/**
+ * Get the max filesize for original avatar uploads.
+ *
+ * @since 1.5.0
+ *
+ * @return int The max filesize for original avatar uploads.
+ */
+function bp_core_avatar_original_max_filesize() {
+
+	/**
+	 * Filters the max filesize for original avatar uploads.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $value Value for the max filesize.
+	 */
+	return apply_filters( 'bp_core_avatar_original_max_filesize', (int) buddypress()->avatar->original_max_filesize );
+}
+
+/**
+ * Get the URL of the 'full' default avatar.
+ *
+ * @since 1.5.0
+ *
+ * @param string $type 'local' if the fallback should be the locally-hosted version
+ *                     of the mystery-person, 'gravatar' if the fallback should be
+ *                     Gravatar's version. Default: 'gravatar'.
+ *
+ * @return string The URL of the default avatar.
+ */
+function bp_core_avatar_default( $type = 'gravatar' ) {
+	// Local override
+	if ( defined( 'BP_AVATAR_DEFAULT' ) ) {
+		$avatar = BP_AVATAR_DEFAULT;
+
+	// Use the local default image
+	} elseif ( 'local' === $type ) {
+		$avatar = buddypress()->plugin_url . 'bp-core/images/mystery-man.jpg';
+
+	// Use Gravatar's mystery person as fallback
+	} else {
+		$avatar = '//www.gravatar.com/avatar/00000000000000000000000000000000?d=mm&amp;s=' . bp_core_avatar_full_width();
+	}
+
+	/**
+	 * Filters the URL of the 'full' default avatar.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $avatar URL of the default avatar.
+	 */
+	return apply_filters( 'bp_core_avatar_default', $avatar );
+}
+
+/**
+ * Get the URL of the 'thumb' default avatar.
+ *
+ * Uses Gravatar's mystery-person avatar, unless BP_AVATAR_DEFAULT_THUMB has been
+ * defined.
+ *
+ * @since 1.5.0
+ *
+ * @param string $type 'local' if the fallback should be the locally-hosted version
+ *                     of the mystery-person, 'gravatar' if the fallback should be
+ *                     Gravatar's version. Default: 'gravatar'.
+ *
+ * @return string The URL of the default avatar thumb.
+ */
+function bp_core_avatar_default_thumb( $type = 'gravatar' ) {
+	// Local override
+	if ( defined( 'BP_AVATAR_DEFAULT_THUMB' ) ) {
+		$avatar = BP_AVATAR_DEFAULT_THUMB;
+
+	// Use the local default image
+	} elseif ( 'local' === $type ) {
+		$avatar = buddypress()->plugin_url . 'bp-core/images/mystery-man-50.jpg';
+
+	// Use Gravatar's mystery person as fallback
+	} else {
+		$avatar = '//www.gravatar.com/avatar/00000000000000000000000000000000?d=mm&amp;s=' . bp_core_avatar_thumb_width();
+	}
+
+	/**
+	 * Filters the URL of the 'thumb' default avatar.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $avatar URL of the default avatar.
+	 */
+	return apply_filters( 'bp_core_avatar_thumb', $avatar );
+}
+
+/**
+ * Reset the week parameter of the WordPress main query if needed.
+ *
+ * When cropping an avatar, a $_POST['w'] var is sent, setting the 'week'
+ * parameter of the WordPress main query to this posted var. To avoid
+ * notices, we need to make sure this 'week' query var is reset to 0.
+ *
+ * @since  2.2.0
+ *
+ * @param  WP_Query|null $posts_query the main query object.
+ *
+ * @uses   bp_is_group_create()
+ * @uses   bp_is_group_admin_page()
+ * @uses   bp_is_group_admin_screen() to check for a group admin screen
+ * @uses   bp_action_variable() to check for the group's avatar creation step
+ * @uses   bp_is_user_change_avatar() to check for the user's change profile screen
+ */
+function bp_core_avatar_reset_query( $posts_query = null ) {
+	$reset_w = false;
+
+	// Group's avatar edit screen
+	if ( bp_is_group_admin_page() ) {
+		$reset_w = bp_is_group_admin_screen( 'group-avatar' );
+
+	// Group's avatar create screen
+	} elseif ( bp_is_group_create() ) {
+		/**
+		 * we can't use bp_get_groups_current_create_step()
+		 * as it's not set yet
+		 */
+		$reset_w = 'group-avatar' === bp_action_variable( 1 );
+
+	// User's change avatar screen
+	} else {
+		$reset_w = bp_is_user_change_avatar();
+	}
+
+	// A user or a group is cropping an avatar
+	if ( true === $reset_w && isset( $_POST['avatar-crop-submit'] ) ) {
+		$posts_query->set( 'w', 0 );
+	}
+}
+add_action( 'bp_parse_query', 'bp_core_avatar_reset_query', 10, 1 );
+
+/**
+ * Checks whether Avatar UI should be loaded.
+ *
+ * @since  2.3.0
+ *
+ * @return bool True if Avatar UI should load, false otherwise.
+ */
+function bp_avatar_is_front_edit() {
+	$retval = false;
+
+	// No need to carry on if the current WordPress version is not supported.
+	if ( ! bp_attachments_is_wp_version_supported() ) {
+		return $retval;
+	}
+
+	if ( bp_is_user_change_avatar() && 'crop-image' !== bp_get_avatar_admin_step() ) {
+		$retval = ! bp_core_get_root_option( 'bp-disable-avatar-uploads' );
+	}
+
+	if ( bp_is_active( 'groups' ) ) {
+		// Group creation
+		if ( bp_is_group_create() && bp_is_group_creation_step( 'group-avatar' ) && 'crop-image' !== bp_get_avatar_admin_step() ) {
+			$retval = ! bp_disable_group_avatar_uploads();
+
+		// Group Manage
+		} elseif ( bp_is_group_admin_page() && bp_is_group_admin_screen( 'group-avatar' ) && 'crop-image' !== bp_get_avatar_admin_step() ) {
+			$retval = ! bp_disable_group_avatar_uploads();
+		}
+	}
+
+	/**
+	 * Use this filter if you need to :
+	 * - Load the avatar UI for a component that is !groups or !user (return true regarding your conditions)
+	 * - Completely disable the avatar UI introduced in 2.3 (eg: __return_false())
+	 *
+	 * @since  2.3.0
+	 *
+	 * @param bool whether to load the Avatar UI.
+	 */
+	return apply_filters( 'bp_avatar_is_front_edit', $retval );
+}
+
+/**
+ * Checks whether the Webcam Avatar UI part should be loaded.
+ *
+ * @since  2.3.0
+ *
+ * @global $is_safari
+ * @global $is_IE
+ * @return bool True to load the Webcam Avatar UI part. False otherwise.
+ */
+function bp_avatar_use_webcam() {
+	global $is_safari, $is_IE;
+
+	/**
+	 * Do not use the webcam feature for mobile devices
+	 * to avoid possible confusions.
+	 */
+	if ( wp_is_mobile() ) {
+		return false;
+	}
+
+	/**
+	 * Bail when the browser does not support getUserMedia.
+	 *
+	 * @see  http://caniuse.com/#feat=stream
+	 */
+	if ( $is_safari || $is_IE ) {
+		return false;
+	}
+
+	/**
+	 * Use this filter if you need to disable the webcam capture feature
+	 * by returning false.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param bool whether to load Webcam Avatar UI part.
+	 */
+	return apply_filters( 'bp_avatar_use_webcam', true );
+}
+
+/**
+ * Template function to load the Avatar UI javascript templates.
+ *
+ * @since  2.3.0
+ */
+function bp_avatar_get_templates() {
+	if ( ! bp_avatar_is_front_edit() ) {
+		return;
+	}
+
+	bp_attachments_get_template_part( 'avatars/index' );
+}
+
+/**
+ * Trick to check if the theme's BuddyPress templates are up to date.
+ *
+ * If the "avatar templates" are not including the new template tag, this will
+ * help users to get the avatar UI.
+ *
+ * @since  2.3.0
+ */
+function bp_avatar_template_check() {
+	if ( ! bp_avatar_is_front_edit() ) {
+		return;
+	}
+
+	if ( ! did_action( 'bp_attachments_avatar_check_template' ) ) {
+		bp_attachments_get_template_part( 'avatars/index' );
+	}
+}
Index: /tags/2.4.3/src/bp-core/bp-core-buddybar.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-buddybar.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-buddybar.php	(revision 10410)
@@ -0,0 +1,834 @@
+<?php
+/**
+ * Core BuddyPress Navigational Functions.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @todo Deprecate BuddyBar functions.
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Add an item to the main BuddyPress navigation array.
+ *
+ * @param array|string $args {
+ *     Array describing the new nav item.
+ *     @type string      $name                    Display name for the nav item.
+ *     @type string      $slug                    Unique URL slug for the nav item.
+ *     @type bool|string $item_css_id             Optional. 'id' attribute for the nav item. Default: the value of `$slug`.
+ *     @type bool        $show_for_displayed_user Optional. Whether the nav item should be visible when viewing a
+ *                                                member profile other than your own. Default: true.
+ *     @type bool        $site_admin_only         Optional. Whether the nav item should be visible only to site admins
+ *                                                (those with the 'bp_moderate' cap). Default: false.
+ *     @type int         $position                Optional. Numerical index specifying where the item should appear in
+ *                                                the nav array. Default: 99.
+ *     @type callable    $screen_function         The callback function that will run when the nav item is clicked.
+ *     @type bool|string $default_subnav_slug     Optional. The slug of the default subnav item to select when the nav
+ *                                                item is clicked.
+ * }
+ * @return bool|null Returns false on failure.
+ */
+function bp_core_new_nav_item( $args = '' ) {
+
+	$defaults = array(
+		'name'                    => false, // Display name for the nav item
+		'slug'                    => false, // URL slug for the nav item
+		'item_css_id'             => false, // The CSS ID to apply to the HTML of the nav item
+		'show_for_displayed_user' => true,  // When viewing another user does this nav item show up?
+		'site_admin_only'         => false, // Can only site admins see this nav item?
+		'position'                => 99,    // Index of where this nav item should be positioned
+		'screen_function'         => false, // The name of the function to run when clicked
+		'default_subnav_slug'     => false  // The slug of the default subnav item to select when clicked
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+
+	// First, add the nav item link to the bp_nav array.
+	$created = bp_core_create_nav_link( $r );
+
+	// To mimic the existing behavior, if bp_core_create_nav_link()
+	// returns false, we make an early exit and don't attempt to register
+	// the screen function.
+	if ( false === $created ) {
+		return false;
+	}
+
+	// Then, hook the screen function for the added nav item.
+	$hooked = bp_core_register_nav_screen_function( $r );
+	if ( false === $hooked ){
+		return false;
+	}
+
+	/**
+	 * Fires after adding an item to the main BuddyPress navigation array.
+	 * Note that, when possible, the more specific action hooks
+	 * `bp_core_create_nav_link` or `bp_core_register_nav_screen_function`
+	 * should be used.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $r        Parsed arguments for the nav item.
+	 * @param array $args     Originally passed in arguments for the nav item.
+	 * @param array $defaults Default arguments for a nav item.
+	 */
+	do_action( 'bp_core_new_nav_item', $r, $args, $defaults );
+}
+
+/**
+ * Add a link to the main BuddyPress navigation array.
+ *
+ * @since 2.4.0
+ *
+ * @param array|string $args {
+ *     Array describing the new nav item.
+ *     @type string      $name                    Display name for the nav item.
+ *     @type string      $slug                    Unique URL slug for the nav item.
+ *     @type bool|string $item_css_id             Optional. 'id' attribute for the nav item. Default: the value of `$slug`.
+ *     @type bool        $show_for_displayed_user Optional. Whether the nav item should be visible when viewing a
+ *                                                member profile other than your own. Default: true.
+ *     @type bool        $site_admin_only         Optional. Whether the nav item should be visible only to site admins
+ *                                                (those with the 'bp_moderate' cap). Default: false.
+ *     @type int         $position                Optional. Numerical index specifying where the item should appear in
+ *                                                the nav array. Default: 99.
+ *     @type callable    $screen_function         The callback function that will run when the nav item is clicked.
+ *     @type bool|string $default_subnav_slug     Optional. The slug of the default subnav item to select when the nav
+ *                                                item is clicked.
+ * }
+ * @return bool|null Returns false on failure.
+ */
+function bp_core_create_nav_link( $args = '' ) {
+	$bp = buddypress();
+
+	$defaults = array(
+		'name'                    => false, // Display name for the nav item
+		'slug'                    => false, // URL slug for the nav item
+		'item_css_id'             => false, // The CSS ID to apply to the HTML of the nav item
+		'show_for_displayed_user' => true,  // When viewing another user does this nav item show up?
+		'site_admin_only'         => false, // Can only site admins see this nav item?
+		'position'                => 99,    // Index of where this nav item should be positioned
+		'screen_function'         => false, // The name of the function to run when clicked
+		'default_subnav_slug'     => false  // The slug of the default subnav item to select when clicked
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+
+	// If we don't have the required info we need, don't create this nav item.
+	if ( empty( $r['name'] ) || empty( $r['slug'] ) ) {
+		return false;
+	}
+
+	// If this is for site admins only and the user is not one, don't create the nav item.
+	if ( ! empty( $r['site_admin_only'] ) && ! bp_current_user_can( 'bp_moderate' ) ) {
+		return false;
+	}
+
+	if ( empty( $r['item_css_id'] ) ) {
+		$r['item_css_id'] = $r['slug'];
+	}
+
+	$bp->bp_nav[$r['slug']] = array(
+		'name'                    => $r['name'],
+		'slug'                    => $r['slug'],
+		'link'                    => trailingslashit( bp_loggedin_user_domain() . $r['slug'] ),
+		'css_id'                  => $r['item_css_id'],
+		'show_for_displayed_user' => $r['show_for_displayed_user'],
+		'position'                => $r['position'],
+		'screen_function'         => &$r['screen_function'],
+		'default_subnav_slug'	  => $r['default_subnav_slug']
+	);
+
+	/**
+	 * Fires after a link is added to the main BuddyPress navigation array.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param array $r        Parsed arguments for the nav item.
+	 * @param array $args     Originally passed in arguments for the nav item.
+	 * @param array $defaults Default arguments for a nav item.
+	 */
+	do_action( 'bp_core_create_nav_link', $r, $args, $defaults );
+}
+
+/**
+ * Register a screen function for an item in the main nav array.
+ *
+ * @since 2.4.0
+ *
+ * @param array|string $args {
+ *     Array describing the new nav item.
+ *     @type string      $name                    Display name for the nav item.
+ *     @type string      $slug                    Unique URL slug for the nav item.
+ *     @type bool|string $item_css_id             Optional. 'id' attribute for the nav item. Default: the value of `$slug`.
+ *     @type bool        $show_for_displayed_user Optional. Whether the nav item should be visible when viewing a
+ *                                                member profile other than your own. Default: true.
+ *     @type bool        $site_admin_only         Optional. Whether the nav item should be visible only to site admins
+ *                                                (those with the 'bp_moderate' cap). Default: false.
+ *     @type int         $position                Optional. Numerical index specifying where the item should appear in
+ *                                                the nav array. Default: 99.
+ *     @type callable    $screen_function         The callback function that will run when the nav item is clicked.
+ *     @type bool|string $default_subnav_slug     Optional. The slug of the default subnav item to select when the nav
+ *                                                item is clicked.
+ * }
+ * @return bool|null Returns false on failure.
+ */
+function bp_core_register_nav_screen_function( $args = '' ) {
+	$bp = buddypress();
+
+	$defaults = array(
+		'name'                    => false, // Display name for the nav item
+		'slug'                    => false, // URL slug for the nav item
+		'item_css_id'             => false, // The CSS ID to apply to the HTML of the nav item
+		'show_for_displayed_user' => true,  // When viewing another user does this nav item show up?
+		'site_admin_only'         => false, // Can only site admins see this nav item?
+		'position'                => 99,    // Index of where this nav item should be positioned
+		'screen_function'         => false, // The name of the function to run when clicked
+		'default_subnav_slug'     => false  // The slug of the default subnav item to select when clicked
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+
+	// If we don't have the required info we need, don't register this screen function.
+	if ( empty( $r['slug'] ) ) {
+		return false;
+	}
+
+	/**
+	* If this is for site admins only and the user is not one,
+	* don't register this screen function.
+	*/
+	if ( ! empty( $r['site_admin_only'] ) && ! bp_current_user_can( 'bp_moderate' ) ) {
+		return false;
+	}
+
+ 	/**
+	 * If this nav item is hidden for the displayed user, and
+	 * the logged in user is not the displayed user
+	 * looking at their own profile, don't don't register this screen function.
+	 */
+	if ( empty( $r['show_for_displayed_user'] ) && ! bp_user_has_access() ) {
+		return false;
+	}
+
+	/**
+ 	 * If the nav item is visible, we are not viewing a user, and this is a root
+	 * component, don't attach the default subnav function so we can display a
+	 * directory or something else.
+ 	 */
+	if ( ( -1 != $r['position'] ) && bp_is_root_component( $r['slug'] ) && ! bp_displayed_user_id() ) {
+		return;
+	}
+
+	// Look for current component
+	if ( bp_is_current_component( $r['slug'] ) || bp_is_current_item( $r['slug'] ) ) {
+
+		// The requested URL has explicitly included the default subnav
+		// (eg: http://example.com/members/membername/activity/just-me/)
+		// The canonical version will not contain this subnav slug.
+		if ( ! empty( $r['default_subnav_slug'] ) && bp_is_current_action( $r['default_subnav_slug'] ) && ! bp_action_variable( 0 ) ) {
+			unset( $bp->canonical_stack['action'] );
+		} elseif ( ! bp_current_action() ) {
+
+			// Add our screen hook if screen function is callable
+			if ( is_callable( $r['screen_function'] ) ) {
+				add_action( 'bp_screens', $r['screen_function'], 3 );
+			}
+
+			if ( ! empty( $r['default_subnav_slug'] ) ) {
+
+				/**
+				 * Filters the default component subnav item.
+				 *
+				 * @since 1.5.0
+				 *
+				 * @param string $value The slug of the default subnav item
+				 *                      to select when clicked.
+				 * @param array  $r     Parsed arguments for the nav item.
+				 */
+				$bp->current_action = apply_filters( 'bp_default_component_subnav', $r['default_subnav_slug'], $r );
+			}
+		}
+	}
+
+	/**
+	 * Fires after the screen function for an item in the BuddyPress main
+	 * navigation is registered.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param array $r        Parsed arguments for the nav item.
+	 * @param array $args     Originally passed in arguments for the nav item.
+	 * @param array $defaults Default arguments for a nav item.
+	 */
+	do_action( 'bp_core_register_nav_screen_function', $r, $args, $defaults );
+}
+
+/**
+ * Modify the default subnav item that loads when a top level nav item is clicked.
+ *
+ * @param array|string $args {
+ *     @type string   $parent_slug     The slug of the nav item whose default is being changed.
+ *     @type callable $screen_function The new default callback function that will run when the nav item is clicked.
+ *     @type string   $subnav_slug     The slug of the new default subnav item.
+ * }
+ */
+function bp_core_new_nav_default( $args = '' ) {
+	$bp = buddypress();
+
+	$defaults = array(
+		'parent_slug'     => false, // Slug of the parent
+		'screen_function' => false, // The name of the function to run when clicked
+		'subnav_slug'     => false  // The slug of the subnav item to select when clicked
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+
+	if ( $function = $bp->bp_nav[$r['parent_slug']]['screen_function'] ) {
+		// Remove our screen hook if screen function is callable
+		if ( is_callable( $function ) ) {
+			remove_action( 'bp_screens', $function, 3 );
+		}
+	}
+
+	$bp->bp_nav[$r['parent_slug']]['screen_function'] = &$r['screen_function'];
+
+	if ( bp_is_current_component( $r['parent_slug'] ) ) {
+
+		// The only way to tell whether to set the subnav is to peek at the unfiltered_uri
+		// Find the component
+		$component_uri_key = array_search( $r['parent_slug'], $bp->unfiltered_uri );
+
+		if ( false !== $component_uri_key ) {
+			if ( ! empty( $bp->unfiltered_uri[$component_uri_key + 1] ) ) {
+				$unfiltered_action = $bp->unfiltered_uri[$component_uri_key + 1];
+			}
+		}
+
+		// No subnav item has been requested in the URL, so set a new nav default
+		if ( empty( $unfiltered_action ) ) {
+			if ( ! bp_is_current_action( $r['subnav_slug'] ) ) {
+				if ( is_callable( $r['screen_function'] ) ) {
+					add_action( 'bp_screens', $r['screen_function'], 3 );
+				}
+
+				$bp->current_action = $r['subnav_slug'];
+				unset( $bp->canonical_stack['action'] );
+			}
+
+		// The URL is explicitly requesting the new subnav item, but should be
+		// directed to the canonical URL
+		} elseif ( $unfiltered_action == $r['subnav_slug'] ) {
+			unset( $bp->canonical_stack['action'] );
+
+		// In all other cases (including the case where the original subnav item
+		// is explicitly called in the URL), the canonical URL will contain the
+		// subnav slug
+		} else {
+			$bp->canonical_stack['action'] = bp_current_action();
+		}
+	}
+
+	return;
+}
+
+/**
+ * Sort the navigation menu items.
+ *
+ * The sorting is split into a separate function because it can only happen
+ * after all plugins have had a chance to register their navigation items.
+ *
+ * @return bool|null Returns false on failure.
+ */
+function bp_core_sort_nav_items() {
+	$bp = buddypress();
+
+	if ( empty( $bp->bp_nav ) || ! is_array( $bp->bp_nav ) ) {
+		return false;
+	}
+
+	$temp = array();
+
+	foreach ( (array) $bp->bp_nav as $slug => $nav_item ) {
+		if ( empty( $temp[$nav_item['position']] ) ) {
+			$temp[$nav_item['position']] = $nav_item;
+		} else {
+			// increase numbers here to fit new items in.
+			do {
+				$nav_item['position']++;
+			} while ( ! empty( $temp[$nav_item['position']] ) );
+
+			$temp[$nav_item['position']] = $nav_item;
+		}
+	}
+
+	ksort( $temp );
+	$bp->bp_nav = &$temp;
+}
+add_action( 'wp_head',    'bp_core_sort_nav_items' );
+add_action( 'admin_head', 'bp_core_sort_nav_items' );
+
+/**
+ * Add a subnav item to the BuddyPress navigation.
+ *
+ * @param array|string $args {
+ *     Array describing the new subnav item.
+ *     @type string      $name              Display name for the subnav item.
+ *     @type string      $slug              Unique URL slug for the subnav item.
+ *     @type string      $parent_slug       Slug of the top-level nav item under which the new subnav item should
+ *                                          be added.
+ *     @type string      $parent_url        URL of the parent nav item.
+ *     @type bool|string $item_css_id       Optional. 'id' attribute for the nav item. Default: the value of `$slug`.
+ *     @type bool        $user_has_access   Optional. True if the logged-in user has access to the subnav item,
+ *                                          otherwise false. Can be set dynamically when registering the subnav;
+ *                                          eg, use `bp_is_my_profile()` to restrict access to profile owners only.
+ *                                          Default: true.
+ *     @type bool        $site_admin_only   Optional. Whether the nav item should be visible only to site admins
+ *                                          (those with the 'bp_moderate' cap). Default: false.
+ *     @type int         $position          Optional. Numerical index specifying where the item should appear in the
+ *                                          subnav array. Default: 90.
+ *     @type callable    $screen_function   The callback function that will run when the nav item is clicked.
+ *     @type string      $link              Optional. The URL that the subnav item should point to. Defaults to a value
+ *                                          generated from the `$parent_url` + `$slug`.
+ *     @type bool        $show_in_admin_bar Optional. Whether the nav item should be added into the group's "Edit"
+ *                                          Admin Bar menu for group admins. Default: false.
+ * }
+ * @return bool|null Returns false on failure.
+ */
+function bp_core_new_subnav_item( $args = '' ) {
+
+	// First, add the subnav item link to the bp_options_nav array.
+	$created = bp_core_create_subnav_link( $args );
+
+	// To mimic the existing behavior, if bp_core_create_subnav_link()
+	// returns false, we make an early exit and don't attempt to register
+	// the screen function.
+	if ( false === $created ) {
+		return false;
+	}
+
+	// Then, hook the screen function for the added subnav item.
+	$hooked = bp_core_register_subnav_screen_function( $args );
+	if ( false === $hooked ) {
+		return false;
+	}
+}
+
+/**
+ * Add a subnav link to the BuddyPress navigation.
+ *
+ * @param array|string $args {
+ *     Array describing the new subnav item.
+ *     @type string      $name              Display name for the subnav item.
+ *     @type string      $slug              Unique URL slug for the subnav item.
+ *     @type string      $parent_slug       Slug of the top-level nav item under which the
+ *                                          new subnav item should be added.
+ *     @type string      $parent_url        URL of the parent nav item.
+ *     @type bool|string $item_css_id       Optional. 'id' attribute for the nav
+ *                                          item. Default: the value of $slug.
+ *     @type bool        $user_has_access   Optional. True if the logged-in user has access to the
+ *                                          subnav item, otherwise false. Can be set dynamically
+ *                                          when registering the subnav; eg, use bp_is_my_profile()
+ *                                          to restrict access to profile owners only. Default: true.
+ *     @type bool        $site_admin_only   Optional. Whether the nav item should be visible only
+ *                                          to site admins (those with the 'bp_moderate' cap).
+ *                                          Default: false.
+ *     @type int         $position          Optional. Numerical index specifying where the item
+ *                                          should appear in the subnav array. Default: 90.
+ *     @type callable    $screen_function   The callback function that will run
+ *                                          when the nav item is clicked.
+ *     @type string      $link              Optional. The URL that the subnav item should point
+ *                                          to. Defaults to a value generated from the $parent_url + $slug.
+ *     @type bool        $show_in_admin_bar Optional. Whether the nav item should be added into
+ *                                          the group's "Edit" Admin Bar menu for group admins.
+ *                                          Default: false.
+ * }
+ *
+ * @return bool|null Returns false on failure.
+ */
+function bp_core_create_subnav_link( $args = '' ) {
+	$bp = buddypress();
+
+	$r = wp_parse_args( $args, array(
+		'name'              => false, // Display name for the nav item
+		'slug'              => false, // URL slug for the nav item
+		'parent_slug'       => false, // URL slug of the parent nav item
+		'parent_url'        => false, // URL of the parent item
+		'item_css_id'       => false, // The CSS ID to apply to the HTML of the nav item
+		'user_has_access'   => true,  // Can the logged in user see this nav item?
+		'no_access_url'     => '',
+		'site_admin_only'   => false, // Can only site admins see this nav item?
+		'position'          => 90,    // Index of where this nav item should be positioned
+		'screen_function'   => false, // The name of the function to run when clicked
+		'link'              => '',    // The link for the subnav item; optional, not usually required.
+		'show_in_admin_bar' => false, // Show the Manage link in the current group's "Edit" Admin Bar menu
+	) );
+
+	// If we don't have the required info we need, don't create this subnav item
+	if ( empty( $r['name'] ) || empty( $r['slug'] ) || empty( $r['parent_slug'] ) || empty( $r['parent_url'] ) || empty( $r['screen_function'] ) )
+		return false;
+
+	// Link was not forced, so create one
+	if ( empty( $r['link'] ) ) {
+		$r['link'] = trailingslashit( $r['parent_url'] . $r['slug'] );
+
+		// If this sub item is the default for its parent, skip the slug
+		if ( ! empty( $bp->bp_nav[$r['parent_slug']]['default_subnav_slug'] ) && $r['slug'] == $bp->bp_nav[$r['parent_slug']]['default_subnav_slug'] ) {
+			$r['link'] = trailingslashit( $r['parent_url'] );
+		}
+	}
+
+	// If this is for site admins only and the user is not one, don't create the subnav item
+	if ( ! empty( $r['site_admin_only'] ) && ! bp_current_user_can( 'bp_moderate' ) ) {
+		return false;
+	}
+
+	if ( empty( $r['item_css_id'] ) ) {
+		$r['item_css_id'] = $r['slug'];
+	}
+
+	$subnav_item = array(
+		'name'              => $r['name'],
+		'link'              => $r['link'],
+		'slug'              => $r['slug'],
+		'css_id'            => $r['item_css_id'],
+		'position'          => $r['position'],
+		'user_has_access'   => $r['user_has_access'],
+		'no_access_url'     => $r['no_access_url'],
+		'screen_function'   => &$r['screen_function'],
+		'show_in_admin_bar' => (bool) $r['show_in_admin_bar'],
+	);
+
+	$bp->bp_options_nav[$r['parent_slug']][$r['slug']] = $subnav_item;
+}
+
+/**
+ * Register a screen function, whether or not a related subnav link exists.
+ *
+ * @param array|string $args {
+ *     Array describing the new subnav item.
+ *     @type string   $slug              Unique URL slug for the subnav item.
+ *     @type string   $parent_slug       Slug of the top-level nav item under which the
+ *                                       new subnav item should be added.
+ *     @type string   $parent_url        URL of the parent nav item.
+ *     @type bool     $user_has_access   Optional. True if the logged-in user has access to the
+ *                                       subnav item, otherwise false. Can be set dynamically
+ *                                       when registering the subnav; eg, use bp_is_my_profile()
+ *                                       to restrict access to profile owners only. Default: true.
+ *     @type bool     $site_admin_only   Optional. Whether the nav item should be visible
+ *                                       only to site admins (those with the 'bp_moderate' cap).
+ *                                       Default: false.
+ *     @type int      $position          Optional. Numerical index specifying where the item
+ *                                       should appear in the subnav array. Default: 90.
+ *     @type callable $screen_function   The callback function that will run
+ *                                       when the nav item is clicked.
+ *     @type string   $link              Optional. The URL that the subnav item should point to.
+ *                                       Defaults to a value generated from the $parent_url + $slug.
+ *     @type bool     $show_in_admin_bar Optional. Whether the nav item should be added into
+ *                                       the group's "Edit" Admin Bar menu for group admins.
+ *                                       Default: false.
+ * }
+ *
+ * @return bool|null Returns false on failure.
+ */
+function bp_core_register_subnav_screen_function( $args = '' ) {
+	$bp = buddypress();
+
+	$r = wp_parse_args( $args, array(
+		'slug'              => false, // URL slug for the screen
+		'parent_slug'       => false, // URL slug of the parent screen
+		'user_has_access'   => true,  // Can the user visit this screen?
+		'no_access_url'     => '',
+		'site_admin_only'   => false, // Can only site admins visit this screen?
+		'screen_function'   => false, // The name of the function to run when clicked
+	) );
+
+	/**
+	 * Hook the screen function for the added subnav item. But this only needs to
+	 * be done if this subnav item is the current view, and the user has access to the
+	 * subnav item. We figure out whether we're currently viewing this subnav by
+	 * checking the following two conditions:
+	 *   (1) Either:
+	 *	     (a) the parent slug matches the current_component, or
+	 *	     (b) the parent slug matches the current_item
+	 *   (2) And either:
+	 *	     (a) the current_action matches $slug, or
+	 *       (b) there is no current_action (ie, this is the default subnav for the parent nav)
+	 *	     and this subnav item is the default for the parent item (which we check by
+	 *	     comparing this subnav item's screen function with the screen function of the
+	 *	     parent nav item in $bp->bp_nav). This condition only arises when viewing a
+	 *	     user, since groups should always have an action set.
+	 */
+
+	// If we *don't* meet condition (1), return
+	if ( ! bp_is_current_component( $r['parent_slug'] ) && ! bp_is_current_item( $r['parent_slug'] ) ) {
+		return;
+	}
+
+	// If we *do* meet condition (2), then the added subnav item is currently being requested
+	if ( ( bp_current_action() && bp_is_current_action( $r['slug'] ) ) || ( bp_is_user() && ! bp_current_action() && ( $r['screen_function'] == $bp->bp_nav[$r['parent_slug']]['screen_function'] ) ) ) {
+
+		// If this is for site admins only and the user is not one, don't create the subnav item
+		if ( ! empty( $r['site_admin_only'] ) && ! bp_current_user_can( 'bp_moderate' ) ) {
+			return false;
+		}
+
+		$hooked = bp_core_maybe_hook_new_subnav_screen_function( $r );
+
+		// If redirect args have been returned, perform the redirect now
+		if ( ! empty( $hooked['status'] ) && 'failure' === $hooked['status'] && isset( $hooked['redirect_args'] ) ) {
+			bp_core_no_access( $hooked['redirect_args'] );
+		}
+	}
+}
+
+/**
+ * For a given subnav item, either hook the screen function or generate redirect arguments, as necessary.
+ *
+ * @since 2.1.0
+ *
+ * @param array $subnav_item The subnav array added to bp_options_nav in `bp_core_new_subnav_item()`.
+ *
+ * @return array
+ */
+function bp_core_maybe_hook_new_subnav_screen_function( $subnav_item ) {
+	$retval = array(
+		'status' => '',
+	);
+
+	// Is this accessible by site admins only?
+	$site_admin_restricted = false;
+	if ( ! empty( $subnav_item['site_admin_only'] ) && ! bp_current_user_can( 'bp_moderate' ) ) {
+		$site_admin_restricted = true;
+	}
+
+	// User has access, so let's try to hook the display callback
+	if ( ! empty( $subnav_item['user_has_access'] ) && ! $site_admin_restricted ) {
+
+		// Screen function is invalid
+		if ( ! is_callable( $subnav_item['screen_function'] ) ) {
+			$retval['status'] = 'failure';
+
+		// Success - hook to bp_screens
+		} else {
+			add_action( 'bp_screens', $subnav_item['screen_function'], 3 );
+			$retval['status'] = 'success';
+		}
+
+	// User doesn't have access. Determine redirect arguments based on
+	// user status
+	} else {
+		$retval['status'] = 'failure';
+
+		if ( is_user_logged_in() ) {
+
+			$bp = buddypress();
+
+			// If a redirect URL has been passed to the subnav
+			// item, respect it
+			if ( ! empty( $subnav_item['no_access_url'] ) ) {
+				$message     = __( 'You do not have access to this page.', 'buddypress' );
+				$redirect_to = trailingslashit( $subnav_item['no_access_url'] );
+
+			// In the case of a user page, we try to assume a
+			// redirect URL
+			} elseif ( bp_is_user() ) {
+
+				// Redirect to the displayed user's default
+				// component, as long as that component is
+				// publicly accessible.
+				if ( bp_is_my_profile() || ! empty( $bp->bp_nav[ $bp->default_component ]['show_for_displayed_user'] ) ) {
+					$message     = __( 'You do not have access to this page.', 'buddypress' );
+					$redirect_to = bp_displayed_user_domain();
+
+				// In some cases, the default tab is not accessible to
+				// the logged-in user. So we fall back on a tab that we
+				// know will be accessible.
+				} else {
+					// Try 'activity' first
+					if ( bp_is_active( 'activity' ) && isset( $bp->pages->activity ) ) {
+						$redirect_to = trailingslashit( bp_displayed_user_domain() . bp_get_activity_slug() );
+					// Then try 'profile'
+					} else {
+						$redirect_to = trailingslashit( bp_displayed_user_domain() . ( 'xprofile' == $bp->profile->id ? 'profile' : $bp->profile->id ) );
+					}
+
+					$message     = '';
+				}
+
+			// Fall back to the home page
+			} else {
+				$message     = __( 'You do not have access to this page.', 'buddypress' );
+				$redirect_to = bp_get_root_domain();
+			}
+
+			$retval['redirect_args'] = array(
+				'message'  => $message,
+				'root'     => $redirect_to,
+				'redirect' => false,
+			);
+
+		} else {
+			// When the user is logged out, pass an empty array
+			// This indicates that the default arguments should be
+			// used in bp_core_no_access()
+			$retval['redirect_args'] = array();
+		}
+	}
+
+	return $retval;
+}
+
+/**
+ * Sort all subnavigation arrays.
+ *
+ * @return bool|null Returns false on failure.
+ */
+function bp_core_sort_subnav_items() {
+	$bp = buddypress();
+
+	if ( empty( $bp->bp_options_nav ) || !is_array( $bp->bp_options_nav ) )
+		return false;
+
+	foreach ( (array) $bp->bp_options_nav as $parent_slug => $subnav_items ) {
+		if ( !is_array( $subnav_items ) )
+			continue;
+
+		foreach ( (array) $subnav_items as $subnav_item ) {
+			if ( empty( $temp[$subnav_item['position']]) )
+				$temp[$subnav_item['position']] = $subnav_item;
+			else {
+				// increase numbers here to fit new items in.
+				do {
+					$subnav_item['position']++;
+				} while ( !empty( $temp[$subnav_item['position']] ) );
+
+				$temp[$subnav_item['position']] = $subnav_item;
+			}
+		}
+		ksort( $temp );
+		$bp->bp_options_nav[$parent_slug] = &$temp;
+		unset( $temp );
+	}
+}
+add_action( 'wp_head',    'bp_core_sort_subnav_items' );
+add_action( 'admin_head', 'bp_core_sort_subnav_items' );
+
+/**
+ * Check whether a given nav item has subnav items.
+ *
+ * @since 1.5.0
+ *
+ * @param string $nav_item The slug of the top-level nav item whose subnav items you're checking.
+ *                         Default: the current component slug.
+ *
+ * @return bool $has_subnav True if the nav item is found and has subnav items; false otherwise.
+ */
+function bp_nav_item_has_subnav( $nav_item = '' ) {
+	$bp = buddypress();
+
+	if ( !$nav_item )
+		$nav_item = bp_current_component();
+
+	$has_subnav = isset( $bp->bp_options_nav[$nav_item] ) && count( $bp->bp_options_nav[$nav_item] ) > 0;
+
+	/**
+	 * Filters whether or not a given nav item has subnav items.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool   $has_subnav Whether or not there is any subnav items.
+	 * @param string $nav_item   The slug of the top-level nav item whose subnav items you're checking.
+	 */
+	return apply_filters( 'bp_nav_item_has_subnav', $has_subnav, $nav_item );
+}
+
+/**
+ * Remove a nav item from the navigation array.
+ *
+ * @param int $parent_id The slug of the parent navigation item.
+ *
+ * @return bool Returns false on failure, ie if the nav item can't be found.
+ */
+function bp_core_remove_nav_item( $parent_id ) {
+	$bp = buddypress();
+
+	// Unset subnav items for this nav item
+	if ( isset( $bp->bp_options_nav[$parent_id] ) && is_array( $bp->bp_options_nav[$parent_id] ) ) {
+		foreach( (array) $bp->bp_options_nav[$parent_id] as $subnav_item ) {
+			bp_core_remove_subnav_item( $parent_id, $subnav_item['slug'] );
+		}
+	}
+
+	if ( empty( $bp->bp_nav[ $parent_id ] ) )
+		return false;
+
+	if ( $function = $bp->bp_nav[$parent_id]['screen_function'] ) {
+		// Remove our screen hook if screen function is callable
+		if ( is_callable( $function ) ) {
+			remove_action( 'bp_screens', $function, 3 );
+		}
+	}
+
+	unset( $bp->bp_nav[$parent_id] );
+}
+
+/**
+ * Remove a subnav item from the navigation array.
+ *
+ * @param string $parent_id The slug of the parent navigation item.
+ * @param string $slug      The slug of the subnav item to be removed.
+ */
+function bp_core_remove_subnav_item( $parent_id, $slug ) {
+	$bp = buddypress();
+
+	$screen_function = isset( $bp->bp_options_nav[$parent_id][$slug]['screen_function'] )
+		? $bp->bp_options_nav[$parent_id][$slug]['screen_function']
+		: false;
+
+	if ( ! empty( $screen_function ) ) {
+		// Remove our screen hook if screen function is callable
+		if ( is_callable( $screen_function ) ) {
+			remove_action( 'bp_screens', $screen_function, 3 );
+		}
+	}
+
+	unset( $bp->bp_options_nav[$parent_id][$slug] );
+
+	if ( isset( $bp->bp_options_nav[$parent_id] ) && !count( $bp->bp_options_nav[$parent_id] ) )
+		unset($bp->bp_options_nav[$parent_id]);
+}
+
+/**
+ * Clear all subnav items from a specific nav item.
+ *
+ * @param string $parent_slug The slug of the parent navigation item.
+ */
+function bp_core_reset_subnav_items( $parent_slug ) {
+	$bp = buddypress();
+
+	unset( $bp->bp_options_nav[$parent_slug] );
+}
+
+
+/**
+ * Retrieve the Toolbar display preference of a user based on context.
+ *
+ * This is a direct copy of WP's private _get_admin_bar_pref()
+ *
+ * @since 1.5.0
+ *
+ * @uses get_user_option()
+ *
+ * @param string $context Context of this preference check. 'admin' or 'front'.
+ * @param int    $user    Optional. ID of the user to check. Default: 0 (which falls back to the logged-in user's ID).
+ *
+ * @return bool True if the toolbar should be showing for this user.
+ */
+function bp_get_admin_bar_pref( $context, $user = 0 ) {
+	$pref = get_user_option( "show_admin_bar_{$context}", $user );
+	if ( false === $pref )
+		return true;
+
+	return 'true' === $pref;
+}
Index: /tags/2.4.3/src/bp-core/bp-core-cache.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-cache.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-cache.php	(revision 10410)
@@ -0,0 +1,248 @@
+<?php
+/**
+ * BuddyPress Core Caching Functions.
+ *
+ * @package BuddyPress
+ *
+ * Caching functions handle the clearing of cached objects and pages on specific
+ * actions throughout BuddyPress.
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Prune the WP Super Cache.
+ *
+ * @see prune_super_cache()
+ *
+ * When wp-super-cache is installed this function will clear cached pages
+ * so that success/error messages are not cached, or time sensitive content.
+ *
+ * @return int
+ */
+function bp_core_clear_cache() {
+	global $cache_path;
+
+	if ( function_exists( 'prune_super_cache' ) ) {
+
+		/**
+		 * Fires before the pruning of WP Super Cache.
+		 *
+		 * @since 1.0.0
+		 */
+		do_action( 'bp_core_clear_cache' );
+		return prune_super_cache( $cache_path, true );
+	}
+}
+
+/**
+ * Clear all cached objects for a user, or those that a user is part of.
+ *
+ * @param string $user_id User ID to delete cache for.
+ */
+function bp_core_clear_user_object_cache( $user_id ) {
+	wp_cache_delete( 'bp_user_' . $user_id, 'bp' );
+}
+
+/**
+ * Clear member count caches and transients.
+ */
+function bp_core_clear_member_count_caches() {
+	wp_cache_delete( 'bp_total_member_count', 'bp' );
+	delete_transient( 'bp_active_member_count' );
+}
+add_action( 'bp_core_activated_user',         'bp_core_clear_member_count_caches' );
+add_action( 'bp_core_process_spammer_status', 'bp_core_clear_member_count_caches' );
+add_action( 'bp_core_deleted_account',        'bp_core_clear_member_count_caches' );
+add_action( 'bp_first_activity_for_member',   'bp_core_clear_member_count_caches' );
+add_action( 'deleted_user',                   'bp_core_clear_member_count_caches' );
+
+/**
+ * Clear the directory_pages cache when one of the pages is updated.
+ *
+ * @since 2.0.0
+ *
+ * @param int $post_id ID of the page that was saved.
+ */
+function bp_core_clear_directory_pages_cache_page_edit( $post_id ) {
+	if ( ! bp_is_root_blog() ) {
+		return;
+	}
+
+	// Bail if BP is not defined here.
+	if ( ! buddypress() ) {
+		return;
+	}
+
+	$page_ids = bp_core_get_directory_page_ids( 'all' );
+
+	if ( ! in_array( $post_id, (array) $page_ids ) ) {
+		return;
+	}
+
+	wp_cache_delete( 'directory_pages', 'bp' );
+}
+add_action( 'save_post', 'bp_core_clear_directory_pages_cache_page_edit' );
+
+/**
+ * Clear the directory_pages cache when the bp-pages option is updated.
+ *
+ * @since 2.0.0
+ *
+ * @param string $option Option name.
+ */
+function bp_core_clear_directory_pages_cache_settings_edit( $option ) {
+	if ( 'bp-pages' === $option ) {
+		wp_cache_delete( 'directory_pages', 'bp' );
+	}
+}
+add_action( 'update_option', 'bp_core_clear_directory_pages_cache_settings_edit' );
+
+/**
+ * Clear the root_blog_options cache when any of its options are updated.
+ *
+ * @since 2.0.0
+ *
+ * @param string $option Option name.
+ */
+function bp_core_clear_root_options_cache( $option ) {
+	$keys = array_keys( bp_get_default_options() );
+	$keys = array_merge( $keys, array(
+		'registration',
+		'avatar_default',
+		'tags_blog_id',
+		'sitewide_tags_blog',
+		'registration',
+		'fileupload_mask',
+	) );
+
+	if ( in_array( $option, $keys ) ) {
+		wp_cache_delete( 'root_blog_options', 'bp' );
+	}
+}
+add_action( 'update_option', 'bp_core_clear_root_options_cache' );
+add_action( 'update_site_option', 'bp_core_clear_root_options_cache' );
+add_action( 'add_option', 'bp_core_clear_root_options_cache' );
+add_action( 'add_site_option', 'bp_core_clear_root_options_cache' );
+
+/**
+ * Determine which items from a list do not have cached values.
+ *
+ * @since 2.0.0
+ *
+ * @param array  $item_ids    ID list.
+ * @param string $cache_group The cache group to check against.
+ *
+ * @return array
+ */
+function bp_get_non_cached_ids( $item_ids, $cache_group ) {
+	$uncached = array();
+
+	foreach ( $item_ids as $item_id ) {
+		$item_id = (int) $item_id;
+		if ( false === wp_cache_get( $item_id, $cache_group ) ) {
+			$uncached[] = $item_id;
+		}
+	}
+
+	return $uncached;
+}
+
+/**
+ * Update the metadata cache for the specified objects.
+ *
+ * Based on WordPress's {@link update_meta_cache()}, this function primes the
+ * cache with metadata related to a set of objects. This is typically done when
+ * querying for a loop of objects; pre-fetching metadata for each queried
+ * object can lead to dramatic performance improvements when using metadata
+ * in the context of template loops.
+ *
+ * @since 1.6.0
+ *
+ * @global object $wpdb WordPress database object for queries..
+ *
+ * @param array $args {
+ *     Array of arguments.
+ *     @type array|string $object_ids       List of object IDs to fetch metadata for.
+ *                                          Accepts an array or a comma-separated list of numeric IDs.
+ *     @type string       $object_type      The type of object, eg 'groups' or 'activity'.
+ *     @type string       $meta_table       The name of the metadata table being queried.
+ *     @type string       $object_column    Optional. The name of the database column where IDs
+ *                                          (those provided by $object_ids) are found. Eg, 'group_id'
+ *                                          for the groups metadata tables. Default: $object_type . '_id'.
+ *     @type string       $cache_key_prefix Optional. The prefix to use when creating
+ *                                          cache key names. Default: the value of $meta_table.
+ * }
+ *
+ * @return array|bool Metadata cache for the specified objects, or false on failure.
+ */
+function bp_update_meta_cache( $args = array() ) {
+	global $wpdb;
+
+	$defaults = array(
+		'object_ids' 	   => array(), // Comma-separated list or array of item ids
+		'object_type' 	   => '',      // Canonical component id: groups, members, etc
+		'cache_group'      => '',      // Cache group
+		'meta_table' 	   => '',      // Name of the table containing the metadata
+		'object_column'    => '',      // DB column for the object ids (group_id, etc)
+		'cache_key_prefix' => ''       // Prefix to use when creating cache key names. Eg 'bp_groups_groupmeta'
+	);
+	$r = wp_parse_args( $args, $defaults );
+	extract( $r );
+
+	if ( empty( $object_ids ) || empty( $object_type ) || empty( $meta_table ) || empty( $cache_group ) ) {
+		return false;
+	}
+
+	if ( empty( $cache_key_prefix ) ) {
+		$cache_key_prefix = $meta_table;
+	}
+
+	if ( empty( $object_column ) ) {
+		$object_column = $object_type . '_id';
+	}
+
+	if ( ! $cache_group ) {
+		return false;
+	}
+
+	$object_ids   = wp_parse_id_list( $object_ids );
+	$uncached_ids = bp_get_non_cached_ids( $object_ids, $cache_group );
+
+	$cache = array();
+
+	// Get meta info
+	if ( ! empty( $uncached_ids ) ) {
+		$id_list   = join( ',', wp_parse_id_list( $uncached_ids ) );
+		$meta_list = $wpdb->get_results( esc_sql( "SELECT {$object_column}, meta_key, meta_value FROM {$meta_table} WHERE {$object_column} IN ({$id_list})" ), ARRAY_A );
+
+		if ( ! empty( $meta_list ) ) {
+			foreach ( $meta_list as $metarow ) {
+				$mpid = intval( $metarow[$object_column] );
+				$mkey = $metarow['meta_key'];
+				$mval = $metarow['meta_value'];
+
+				// Force subkeys to be array type:
+				if ( !isset( $cache[$mpid] ) || !is_array( $cache[$mpid] ) )
+					$cache[$mpid] = array();
+				if ( !isset( $cache[$mpid][$mkey] ) || !is_array( $cache[$mpid][$mkey] ) )
+					$cache[$mpid][$mkey] = array();
+
+				// Add a value to the current pid/key:
+				$cache[$mpid][$mkey][] = $mval;
+			}
+		}
+
+		foreach ( $uncached_ids as $uncached_id ) {
+			// Cache empty values as well
+			if ( ! isset( $cache[ $uncached_id ] ) ) {
+				$cache[ $uncached_id ] = array();
+			}
+
+			wp_cache_set( $uncached_id, $cache[ $uncached_id ], $cache_group );
+		}
+	}
+
+	return $cache;
+}
Index: /tags/2.4.3/src/bp-core/bp-core-caps.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-caps.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-caps.php	(revision 10410)
@@ -0,0 +1,421 @@
+<?php
+/**
+ * BuddyPress Capabilities.
+ *
+ * @package BuddyPress
+ * @subpackage Capabilities
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Return an array of roles from the currently loaded blog.
+ *
+ * WordPress roles are dynamically flipped when calls to switch_to_blog() and
+ * restore_current_blog() are made, so we use and trust WordPress core to have
+ * loaded the correct results for us here. As enhancements are made to
+ * WordPress's RBAC, so should our capability functions here.
+ *
+ * @since 2.1.0
+ *
+ * @return array
+ */
+function bp_get_current_blog_roles() {
+	global $wp_roles;
+
+	// Sanity check on roles global variable
+	$roles = isset( $wp_roles->roles )
+		? $wp_roles->roles
+		: array();
+
+	/**
+	 * Filters the list of editable roles.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param array $roles List of roles.
+	 */
+	$roles = apply_filters( 'editable_roles', $roles );
+
+	/**
+	 * Filters the array of roles from the currently loaded blog.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param array    $roles    Available roles.
+	 * @param WP_Roles $wp_roles Object of WordPress roles.
+	 */
+	return apply_filters( 'bp_get_current_blog_roles', $roles, $wp_roles );
+}
+
+/**
+ * Add capabilities to WordPress user roles.
+ *
+ * This is called on plugin activation.
+ *
+ * @since 1.6.0
+ *
+ * @uses get_role() To get the administrator, default and moderator roles.
+ * @uses WP_Role::add_cap() To add various capabilities.
+ * @uses do_action() Calls 'bp_add_caps'.
+ */
+function bp_add_caps() {
+	global $wp_roles;
+
+	// Load roles if not set
+	if ( ! isset( $wp_roles ) ) {
+		$wp_roles = new WP_Roles();
+	}
+
+	// Loop through available roles and add them
+	foreach( $wp_roles->role_objects as $role ) {
+		foreach ( bp_get_caps_for_role( $role->name ) as $cap ) {
+			$role->add_cap( $cap );
+		}
+	}
+
+	/**
+	 * Fires after the addition of capabilities to WordPress user roles.
+	 *
+	 * This is called on plugin activation.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_add_caps' );
+}
+
+/**
+ * Remove capabilities from WordPress user roles.
+ *
+ * This is called on plugin deactivation.
+ *
+ * @since 1.6.0
+ *
+ * @uses get_role() To get the administrator and default roles.
+ * @uses WP_Role::remove_cap() To remove various capabilities.
+ * @uses do_action() Calls 'bp_remove_caps'.
+ */
+function bp_remove_caps() {
+	global $wp_roles;
+
+	// Load roles if not set
+	if ( ! isset( $wp_roles ) ) {
+		$wp_roles = new WP_Roles();
+	}
+
+	// Loop through available roles and remove them
+	foreach( $wp_roles->role_objects as $role ) {
+		foreach ( bp_get_caps_for_role( $role->name ) as $cap ) {
+			$role->remove_cap( $cap );
+		}
+	}
+
+	/**
+	 * Fires after the removal of capabilities from WordPress user roles.
+	 *
+	 * This is called on plugin deactivation.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_remove_caps' );
+}
+
+/**
+ * Map community caps to built in WordPress caps.
+ *
+ * @since 1.6.0
+ *
+ * @see WP_User::has_cap() for description of the arguments passed to the
+ *      'map_meta_cap' filter.
+ * @uses apply_filters() Calls 'bp_map_meta_caps' with caps, cap, user ID and
+ *       args.
+ *
+ * @param array  $caps    See {@link WP_User::has_cap()}.
+ * @param string $cap     See {@link WP_User::has_cap()}.
+ * @param int    $user_id See {@link WP_User::has_cap()}.
+ * @param mixed  $args    See {@link WP_User::has_cap()}.
+ *
+ * @return array Actual capabilities for meta capability. See {@link WP_User::has_cap()}.
+ */
+function bp_map_meta_caps( $caps, $cap, $user_id, $args ) {
+
+	/**
+	 * Filters the community caps mapping to be built in WordPress caps.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array  $caps    Returns the user's actual capabilities.
+	 * @param string $cap     Capability name.
+	 * @param int    $user_id The user ID.
+	 * @param array  $args    Adds the context to the cap. Typically the object ID.
+	 */
+	return apply_filters( 'bp_map_meta_caps', $caps, $cap, $user_id, $args );
+}
+
+/**
+ * Return community capabilities.
+ *
+ * @since 1.6.0
+ *
+ * @uses apply_filters() Calls 'bp_get_community_caps' with the capabilities.
+ *
+ * @return array Community capabilities.
+ */
+function bp_get_community_caps() {
+
+	// Forum meta caps
+	$caps = array();
+
+	/**
+	 * Filters community capabilities.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $caps Array of capabilities to add. Empty by default.
+	 */
+	return apply_filters( 'bp_get_community_caps', $caps );
+}
+
+/**
+ * Return an array of capabilities based on the role that is being requested.
+ *
+ * @since 1.6.0
+ *
+ * @uses apply_filters() Allow return value to be filtered.
+ *
+ * @param string $role The role for which you're loading caps.
+ *
+ * @return array Capabilities for $role.
+ */
+function bp_get_caps_for_role( $role = '' ) {
+
+	// Which role are we looking for?
+	switch ( $role ) {
+
+		// Administrator
+		case 'administrator' :
+			$caps = array(
+				// Misc
+				'bp_moderate',
+			);
+
+			break;
+
+		// All other default WordPress blog roles
+		case 'editor'      :
+		case 'author'      :
+		case 'contributor' :
+		case 'subscriber'  :
+		default            :
+			$caps = array();
+			break;
+	}
+
+	/**
+	 * Filters the array of capabilities based on the role that is being requested.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array  $caps Array of capabilities to return.
+	 * @param string $role The role currently being loaded.
+	 */
+	return apply_filters( 'bp_get_caps_for_role', $caps, $role );
+}
+
+/**
+ * Set a default role for the current user.
+ *
+ * Give a user the default role when creating content on a site they do not
+ * already have a role or capability on.
+ *
+ * @since 1.6.0
+ *
+ * @global BuddyPress $bp Global BuddyPress settings object.
+ *
+ * @uses is_multisite()
+ * @uses bp_allow_global_access()
+ * @uses bp_is_user_inactive()
+ * @uses is_user_logged_in()
+ * @uses current_user_can()
+ * @uses WP_User::set_role()
+ */
+function bp_set_current_user_default_role() {
+
+	// Bail if not multisite or not root blog
+	if ( ! is_multisite() || ! bp_is_root_blog() ) {
+		return;
+	}
+
+	// Bail if user is not logged in or already a member
+	if ( ! is_user_logged_in() || is_user_member_of_blog() ) {
+		return;
+	}
+
+	// Bail if user is not active
+	if ( bp_is_user_inactive() ) {
+		return;
+	}
+
+	// Set the current users default role
+	buddypress()->current_user->set_role( bp_get_option( 'default_role', 'subscriber' ) );
+}
+
+/**
+ * Check whether the current user has a given capability.
+ *
+ * @since 1.6.0
+ * @since 2.4.0 Second argument modified to accept an array, rather than `$blog_id`.
+ *
+ * @param string    $capability Capability or role name.
+ * @param array|int $args {
+ *     Array of extra arguments applicable to the capability check.
+ *     @type int   $blog_id Optional. Blog ID. Defaults to the BP root blog.
+ *     @type mixed $a,...   Optional. Extra arguments applicable to the capability check.
+ * }
+ *
+ * @return bool True if the user has the cap for the given parameters.
+ */
+function bp_current_user_can( $capability, $args = array() ) {
+	$blog_id = 0;
+
+	// Backward compatibility for older $blog_id parameter.
+	if ( is_int( $args ) ) {
+		$blog_id = $args;
+		$args = array();
+
+	// New format for second parameter.
+	} elseif ( is_array( $args ) && isset( $args['blog_id'] ) ) {
+		// Get the blog ID if set, but don't pass along to `current_user_can_for_blog()`.
+		$blog_id = (int) $args['blog_id'];
+		unset( $args['blog_id'] );
+	}
+
+	// Backward compatibility for older bp_current_user_can() checks
+	if ( empty( $args ) ) {
+		$args = null;
+	}
+
+	// Use root blog if no ID passed
+	if ( empty( $blog_id ) ) {
+		$blog_id = bp_get_root_blog_id();
+	}
+
+	$args   = array( $blog_id, $capability, $args );
+	$retval = call_user_func_array( 'current_user_can_for_blog', $args );
+
+	/**
+	 * Filters whether or not the current user has a given capability.
+	 *
+	 * @since 1.6.0
+	 * @since 2.4.0 Pass `$args` variable.
+	 *
+	 * @param bool   $retval     Whether or not the current user has the capability.
+	 * @param string $capability The capability being checked for.
+	 * @param int    $blog_id    Blog ID. Defaults to the BP root blog.
+	 * @param array  $args       Array of extra arguments passed.
+	 */
+	return (bool) apply_filters( 'bp_current_user_can', $retval, $capability, $blog_id, $args );
+}
+
+/**
+ * Temporary implementation of 'bp_moderate' cap.
+ *
+ * In BuddyPress 1.6, the 'bp_moderate' cap was introduced. In order to
+ * enforce that bp_current_user_can( 'bp_moderate' ) always returns true for
+ * Administrators, we must manually add the 'bp_moderate' cap to the list of
+ * user caps for Admins.
+ *
+ * Note that this level of enforcement is only necessary in the case of
+ * non-Multisite. This is because WordPress automatically assigns every
+ * capability - and thus 'bp_moderate' - to Super Admins on a Multisite
+ * installation. See {@link WP_User::has_cap()}.
+ *
+ * This implementation of 'bp_moderate' is temporary, until BuddyPress properly
+ * matches caps to roles and stores them in the database.
+ *
+ * Plugin authors: Please do not use this function; thank you. :)
+ *
+ * @access private
+ * @since 1.6.0
+ *
+ * @see WP_User::has_cap()
+ *
+ * @param array  $caps    The caps that WP associates with the given role.
+ * @param string $cap     The caps being tested for in WP_User::has_cap().
+ * @param int    $user_id ID of the user being checked against.
+ * @param array  $args    Miscellaneous arguments passed to the user_has_cap filter.
+ *
+ * @return array $allcaps The user's cap list, with 'bp_moderate' appended, if relevant.
+ */
+function _bp_enforce_bp_moderate_cap_for_admins( $caps = array(), $cap = '', $user_id = 0, $args = array() ) {
+
+	// Bail if not checking the 'bp_moderate' cap
+	if ( 'bp_moderate' !== $cap ) {
+		return $caps;
+	}
+
+	// Bail if BuddyPress is not network activated
+	if ( bp_is_network_activated() ) {
+		return $caps;
+	}
+
+	// Never trust inactive users
+	if ( bp_is_user_inactive( $user_id ) ) {
+		return $caps;
+	}
+
+	// Only users that can 'manage_options' on this site can 'bp_moderate'
+	return array( 'manage_options' );
+}
+add_filter( 'map_meta_cap', '_bp_enforce_bp_moderate_cap_for_admins', 10, 4 );
+
+/** Deprecated ****************************************************************/
+
+/**
+ * Adds BuddyPress-specific user roles.
+ *
+ * This is called on plugin activation.
+ *
+ * @since 1.6.0
+ * @deprecated 1.7.0
+ */
+function bp_add_roles() {
+	_doing_it_wrong( 'bp_add_roles', __( 'Special community roles no longer exist. Use mapped capabilities instead', 'buddypress' ), '1.7' );
+}
+
+/**
+ * Removes BuddyPress-specific user roles.
+ *
+ * This is called on plugin deactivation.
+ *
+ * @since 1.6.0
+ * @deprecated 1.7.0
+ */
+function bp_remove_roles() {
+	_doing_it_wrong( 'bp_remove_roles', __( 'Special community roles no longer exist. Use mapped capabilities instead', 'buddypress' ), '1.7' );
+}
+
+
+/**
+ * The participant role for registered users without roles.
+ *
+ * This is primarily for multisite compatibility when users without roles on
+ * sites that have global communities enabled.
+ *
+ * @since 1.6.0
+ * @deprecated 1.7.0
+ */
+function bp_get_participant_role() {
+	_doing_it_wrong( 'bp_get_participant_role', __( 'Special community roles no longer exist. Use mapped capabilities instead', 'buddypress' ), '1.7' );
+}
+
+/**
+ * The moderator role for BuddyPress users.
+ *
+ * @since 1.6.0
+ * @deprecated 1.7.0
+ */
+function bp_get_moderator_role() {
+	_doing_it_wrong( 'bp_get_moderator_role', __( 'Special community roles no longer exist. Use mapped capabilities instead', 'buddypress' ), '1.7' );
+}
Index: /tags/2.4.3/src/bp-core/bp-core-catchuri.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-catchuri.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-catchuri.php	(revision 10410)
@@ -0,0 +1,929 @@
+<?php
+/**
+ * BuddyPress URI catcher.
+ *
+ * Functions for parsing the URI and determining which BuddyPress template file
+ * to use on-screen.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Analyze the URI and break it down into BuddyPress-usable chunks.
+ *
+ * BuddyPress can use complete custom friendly URIs without the user having to
+ * add new rewrite rules. Custom components are able to use their own custom
+ * URI structures with very little work.
+ *
+ * The URIs are broken down as follows:
+ *   - http:// example.com / members / andy / [current_component] / [current_action] / [action_variables] / [action_variables] / ...
+ *   - OUTSIDE ROOT: http:// example.com / sites / buddypress / members / andy / [current_component] / [current_action] / [action_variables] / [action_variables] / ...
+ *
+ *	Example:
+ *    - http://example.com/members/andy/profile/edit/group/5/
+ *    - $bp->current_component: string 'xprofile'
+ *    - $bp->current_action: string 'edit'
+ *    - $bp->action_variables: array ['group', 5]
+ *
+ * @since 1.0.0
+ */
+function bp_core_set_uri_globals() {
+	global $current_blog, $wp_rewrite;
+
+	// Don't catch URIs on non-root blogs unless multiblog mode is on
+	if ( !bp_is_root_blog() && !bp_is_multiblog_mode() )
+		return false;
+
+	$bp = buddypress();
+
+	// Define local variables
+	$root_profile = $match   = false;
+	$key_slugs    = $matches = $uri_chunks = array();
+
+	// Fetch all the WP page names for each component
+	if ( empty( $bp->pages ) )
+		$bp->pages = bp_core_get_directory_pages();
+
+	// Ajax or not?
+	if ( defined( 'DOING_AJAX' ) && DOING_AJAX || strpos( $_SERVER['REQUEST_URI'], 'wp-load.php' ) )
+		$path = bp_get_referer_path();
+	else
+		$path = esc_url( $_SERVER['REQUEST_URI'] );
+
+	/**
+	 * Filters the BuddyPress global URI path.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $path Path to set.
+	 */
+	$path = apply_filters( 'bp_uri', $path );
+
+	// Take GET variables off the URL to avoid problems
+	$path = strtok( $path, '?' );
+
+	// Fetch current URI and explode each part separated by '/' into an array
+	$bp_uri = explode( '/', $path );
+
+	// Loop and remove empties
+	foreach ( (array) $bp_uri as $key => $uri_chunk ) {
+		if ( empty( $bp_uri[$key] ) ) {
+			unset( $bp_uri[$key] );
+		}
+	}
+
+	// If running off blog other than root, any subdirectory names must be
+	// removed from $bp_uri. This includes two cases:
+	//
+	//    1. when WP is installed in a subdirectory,
+	//    2. when BP is running on secondary blog of a subdirectory
+	//       multisite installation. Phew!
+	if ( is_multisite() && !is_subdomain_install() && ( bp_is_multiblog_mode() || 1 != bp_get_root_blog_id() ) ) {
+
+		// Blow chunks
+		$chunks = explode( '/', $current_blog->path );
+
+		// If chunks exist...
+		if ( !empty( $chunks ) ) {
+
+			// ...loop through them...
+			foreach( $chunks as $key => $chunk ) {
+				$bkey = array_search( $chunk, $bp_uri );
+
+				// ...and unset offending keys
+				if ( false !== $bkey ) {
+					unset( $bp_uri[$bkey] );
+				}
+
+				$bp_uri = array_values( $bp_uri );
+			}
+		}
+	}
+
+	// Get site path items
+	$paths = explode( '/', bp_core_get_site_path() );
+
+	// Take empties off the end of path
+	if ( empty( $paths[count( $paths ) - 1] ) )
+		array_pop( $paths );
+
+	// Take empties off the start of path
+	if ( empty( $paths[0] ) )
+		array_shift( $paths );
+
+	// Reset indexes
+	$bp_uri = array_values( $bp_uri );
+	$paths  = array_values( $paths );
+
+	// Unset URI indices if they intersect with the paths
+	foreach ( (array) $bp_uri as $key => $uri_chunk ) {
+		if ( isset( $paths[$key] ) && $uri_chunk == $paths[$key] ) {
+			unset( $bp_uri[$key] );
+		}
+	}
+
+	// Reset the keys by merging with an empty array
+	$bp_uri = array_merge( array(), $bp_uri );
+
+	// If a component is set to the front page, force its name into $bp_uri
+	// so that $current_component is populated (unless a specific WP post is being requested
+	// via a URL parameter, usually signifying Preview mode)
+	if ( 'page' == get_option( 'show_on_front' ) && get_option( 'page_on_front' ) && empty( $bp_uri ) && empty( $_GET['p'] ) && empty( $_GET['page_id'] ) ) {
+		$post = get_post( get_option( 'page_on_front' ) );
+		if ( !empty( $post ) ) {
+			$bp_uri[0] = $post->post_name;
+		}
+	}
+
+	// Keep the unfiltered URI safe
+	$bp->unfiltered_uri = $bp_uri;
+
+	// Don't use $bp_unfiltered_uri, this is only for backpat with old plugins. Use $bp->unfiltered_uri.
+	$GLOBALS['bp_unfiltered_uri'] = &$bp->unfiltered_uri;
+
+	// Get slugs of pages into array
+	foreach ( (array) $bp->pages as $page_key => $bp_page )
+		$key_slugs[$page_key] = trailingslashit( '/' . $bp_page->slug );
+
+	// Bail if keyslugs are empty, as BP is not setup correct
+	if ( empty( $key_slugs ) )
+		return;
+
+	// Loop through page slugs and look for exact match to path
+	foreach ( $key_slugs as $key => $slug ) {
+		if ( $slug == $path ) {
+			$match      = $bp->pages->{$key};
+			$match->key = $key;
+			$matches[]  = 1;
+			break;
+		}
+	}
+
+	// No exact match, so look for partials
+	if ( empty( $match ) ) {
+
+		// Loop through each page in the $bp->pages global
+		foreach ( (array) $bp->pages as $page_key => $bp_page ) {
+
+			// Look for a match (check members first)
+			if ( in_array( $bp_page->name, (array) $bp_uri ) ) {
+
+				// Match found, now match the slug to make sure.
+				$uri_chunks = explode( '/', $bp_page->slug );
+
+				// Loop through uri_chunks
+				foreach ( (array) $uri_chunks as $key => $uri_chunk ) {
+
+					// Make sure chunk is in the correct position
+					if ( !empty( $bp_uri[$key] ) && ( $bp_uri[$key] == $uri_chunk ) ) {
+						$matches[] = 1;
+
+					// No match
+					} else {
+						$matches[] = 0;
+					}
+				}
+
+				// Have a match
+				if ( !in_array( 0, (array) $matches ) ) {
+					$match      = $bp_page;
+					$match->key = $page_key;
+					break;
+				};
+
+				// Unset matches
+				unset( $matches );
+			}
+
+			// Unset uri chunks
+			unset( $uri_chunks );
+		}
+	}
+
+	// URLs with BP_ENABLE_ROOT_PROFILES enabled won't be caught above
+	if ( empty( $matches ) && bp_core_enable_root_profiles() ) {
+
+		// Switch field based on compat
+		$field = bp_is_username_compatibility_mode() ? 'login' : 'slug';
+
+		// Make sure there's a user corresponding to $bp_uri[0]
+		if ( !empty( $bp->pages->members ) && !empty( $bp_uri[0] ) && $root_profile = get_user_by( $field, $bp_uri[0] ) ) {
+
+			// Force BP to recognize that this is a members page
+			$matches[]  = 1;
+			$match      = $bp->pages->members;
+			$match->key = 'members';
+		}
+	}
+
+	// Search doesn't have an associated page, so we check for it separately
+	if ( !empty( $bp_uri[0] ) && ( bp_get_search_slug() == $bp_uri[0] ) ) {
+		$matches[]   = 1;
+		$match       = new stdClass;
+		$match->key  = 'search';
+		$match->slug = bp_get_search_slug();
+	}
+
+	// This is not a BuddyPress page, so just return.
+	if ( empty( $matches ) )
+		return false;
+
+	$wp_rewrite->use_verbose_page_rules = false;
+
+	// Find the offset. With $root_profile set, we fudge the offset down so later parsing works
+	$slug       = !empty ( $match ) ? explode( '/', $match->slug ) : '';
+	$uri_offset = empty( $root_profile ) ? 0 : -1;
+
+	// Rejig the offset
+	if ( !empty( $slug ) && ( 1 < count( $slug ) ) ) {
+		// Only offset if not on a root profile. Fixes issue when Members page is nested.
+		if ( false === $root_profile ) {
+			array_pop( $slug );
+			$uri_offset = count( $slug );
+		}
+	}
+
+	// Global the unfiltered offset to use in bp_core_load_template().
+	// To avoid PHP warnings in bp_core_load_template(), it must always be >= 0
+	$bp->unfiltered_uri_offset = $uri_offset >= 0 ? $uri_offset : 0;
+
+	// We have an exact match
+	if ( isset( $match->key ) ) {
+
+		// Set current component to matched key
+		$bp->current_component = $match->key;
+
+		// If members component, do more work to find the actual component
+		if ( 'members' == $match->key ) {
+
+			$after_member_slug = false;
+			if ( ! empty( $bp_uri[ $uri_offset + 1 ] ) ) {
+				$after_member_slug = $bp_uri[ $uri_offset + 1 ];
+			}
+
+			// Are we viewing a specific user?
+			if ( $after_member_slug ) {
+				// If root profile, we've already queried for the user
+				if ( $root_profile instanceof WP_User ) {
+					$bp->displayed_user->id = $root_profile->ID;
+
+				// Switch the displayed_user based on compatibility mode
+				} elseif ( bp_is_username_compatibility_mode() ) {
+					$bp->displayed_user->id = (int) bp_core_get_userid( urldecode( $after_member_slug ) );
+
+				} else {
+					$bp->displayed_user->id = (int) bp_core_get_userid_from_nicename( $after_member_slug );
+				}
+			}
+
+			// Is this a member type directory?
+			if ( ! bp_displayed_user_id() && $after_member_slug === apply_filters( 'bp_members_member_type_base', _x( 'type', 'member type URL base', 'buddypress' ) ) && ! empty( $bp_uri[ $uri_offset + 2 ] ) ) {
+				$matched_types = bp_get_member_types( array(
+					'has_directory'  => true,
+					'directory_slug' => $bp_uri[ $uri_offset + 2 ],
+				) );
+
+				if ( ! empty( $matched_types ) ) {
+					$bp->current_member_type = reset( $matched_types );
+					unset( $bp_uri[ $uri_offset + 1 ] );
+				}
+			}
+
+			// If the slug matches neither a member type nor a specific member, 404.
+			if ( ! bp_displayed_user_id() && ! bp_get_current_member_type() && $after_member_slug ) {
+				// Prevent components from loading their templates.
+				$bp->current_component = '';
+				bp_do_404();
+				return;
+			}
+
+			// If the displayed user is marked as a spammer, 404 (unless logged-in user is a super admin)
+			if ( bp_displayed_user_id() && bp_is_user_spammer( bp_displayed_user_id() ) ) {
+				if ( bp_current_user_can( 'bp_moderate' ) ) {
+					bp_core_add_message( __( 'This user has been marked as a spammer. Only site admins can view this profile.', 'buddypress' ), 'warning' );
+				} else {
+					bp_do_404();
+					return;
+				}
+			}
+
+			// Bump the offset.
+			if ( bp_displayed_user_id() ) {
+				if ( isset( $bp_uri[$uri_offset + 2] ) ) {
+					$bp_uri                = array_merge( array(), array_slice( $bp_uri, $uri_offset + 2 ) );
+					$bp->current_component = $bp_uri[0];
+
+				// No component, so default will be picked later
+				} else {
+					$bp_uri                = array_merge( array(), array_slice( $bp_uri, $uri_offset + 2 ) );
+					$bp->current_component = '';
+				}
+
+				// Reset the offset
+				$uri_offset = 0;
+			}
+		}
+	}
+
+	// Determine the current action.
+	$current_action = isset( $bp_uri[ $uri_offset + 1 ] ) ? $bp_uri[ $uri_offset + 1 ] : '';
+
+	/*
+	 * If a BuddyPress directory is set to the WP front page, URLs like example.com/members/?s=foo
+	 * shouldn't interfere with blog searches.
+	 */
+	if ( empty( $current_action) && ! empty( $_GET['s'] ) && 'page' == get_option( 'show_on_front' ) && ! empty( $match->id ) ) {
+		$page_on_front = (int) get_option( 'page_on_front' );
+		if ( (int) $match->id === $page_on_front ) {
+			$bp->current_component = '';
+			return false;
+		}
+	}
+
+	$bp->current_action = $current_action;
+
+	// Slice the rest of the $bp_uri array and reset offset
+	$bp_uri      = array_slice( $bp_uri, $uri_offset + 2 );
+	$uri_offset  = 0;
+
+	// Set the entire URI as the action variables, we will unset the current_component and action in a second
+	$bp->action_variables = $bp_uri;
+
+	// Reset the keys by merging with an empty array
+	$bp->action_variables = array_merge( array(), $bp->action_variables );
+}
+
+/**
+ * Are root profiles enabled and allowed?
+ *
+ * @since 1.6.0
+ *
+ * @return bool True if yes, false if no.
+ */
+function bp_core_enable_root_profiles() {
+
+	$retval = false;
+
+	if ( defined( 'BP_ENABLE_ROOT_PROFILES' ) && ( true == BP_ENABLE_ROOT_PROFILES ) )
+		$retval = true;
+
+	/**
+	 * Filters whether or not root profiles are enabled and allowed.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $retval Whether or not root profiles are available.
+	 */
+	return apply_filters( 'bp_core_enable_root_profiles', $retval );
+}
+
+/**
+ * Load a specific template file with fallback support.
+ *
+ * Example:
+ *   bp_core_load_template( 'members/index' );
+ * Loads:
+ *   wp-content/themes/[activated_theme]/members/index.php
+ *
+ * @param array $templates Array of templates to attempt to load.
+ *
+ * @return bool|null Returns false on failure.
+ */
+function bp_core_load_template( $templates ) {
+	global $wp_query;
+
+	// Reset the post
+	bp_theme_compat_reset_post( array(
+		'ID'          => 0,
+		'is_404'      => true,
+		'post_status' => 'publish',
+	) );
+
+	// Set theme compat to false since the reset post function automatically sets
+	// theme compat to true
+	bp_set_theme_compat_active( false );
+
+	// Fetch each template and add the php suffix
+	$filtered_templates = array();
+	foreach ( (array) $templates as $template ) {
+		$filtered_templates[] = $template . '.php';
+	}
+
+	// Only perform template lookup for bp-default themes
+	if ( ! bp_use_theme_compat_with_current_theme() ) {
+		$template = locate_template( (array) $filtered_templates, false );
+
+	// Theme compat doesn't require a template lookup
+	} else {
+		$template = '';
+	}
+
+	/**
+	 * Filters the template locations.
+	 *
+	 * Allows plugins to alter where the template files are located.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string $template           Located template path.
+	 * @param array  $filtered_templates Array of templates to attempt to load.
+	 */
+	$located_template = apply_filters( 'bp_located_template', $template, $filtered_templates );
+	if ( !empty( $located_template ) ) {
+		// Template was located, lets set this as a valid page and not a 404.
+		status_header( 200 );
+		$wp_query->is_page     = true;
+		$wp_query->is_singular = true;
+		$wp_query->is_404      = false;
+
+		/**
+		 * Fires before the loading of a located template file.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $located_template Template found to be loaded.
+		 */
+		do_action( 'bp_core_pre_load_template', $located_template );
+
+		/**
+		 * Filters the selected template right before loading.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $located_template Template found to be loaded.
+		 */
+		load_template( apply_filters( 'bp_load_template', $located_template ) );
+
+		/**
+		 * Fires after the loading of a located template file.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $located_template Template found that was loaded.
+		 */
+		do_action( 'bp_core_post_load_template', $located_template );
+
+		// Kill any other output after this.
+		exit();
+
+	// No template found, so setup theme compatibility
+	// @todo Some other 404 handling if theme compat doesn't kick in
+	} else {
+
+		// We know where we are, so reset important $wp_query bits here early.
+		// The rest will be done by bp_theme_compat_reset_post() later.
+		if ( is_buddypress() ) {
+			status_header( 200 );
+			$wp_query->is_page     = true;
+			$wp_query->is_singular = true;
+			$wp_query->is_404      = false;
+		}
+
+		/**
+		 * Fires if there are no found templates to load and theme compat is needed.
+		 *
+		 * @since 1.7.0
+		 */
+		do_action( 'bp_setup_theme_compat' );
+	}
+}
+
+/**
+ * Redirect away from /profile URIs if XProfile is not enabled.
+ */
+function bp_core_catch_profile_uri() {
+	if ( !bp_is_active( 'xprofile' ) ) {
+
+		/**
+		 * Filters the path to redirect users to if XProfile is not enabled.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Path to redirect users to.
+		 */
+		bp_core_load_template( apply_filters( 'bp_core_template_display_profile', 'members/single/home' ) );
+	}
+}
+
+/**
+ * Catch unauthorized access to certain BuddyPress pages and redirect accordingly.
+ *
+ * @since 1.5.0
+ */
+function bp_core_catch_no_access() {
+	global $wp_query;
+
+	$bp = buddypress();
+
+	// If coming from bp_core_redirect() and $bp_no_status_set is true,
+	// we are redirecting to an accessible page so skip this check.
+	if ( !empty( $bp->no_status_set ) )
+		return false;
+
+	if ( !isset( $wp_query->queried_object ) && !bp_is_blog_page() ) {
+		bp_do_404();
+	}
+}
+add_action( 'bp_template_redirect', 'bp_core_catch_no_access', 1 );
+
+/**
+ * Redirect a user to log in for BP pages that require access control.
+ *
+ * Add an error message (if one is provided).
+ *
+ * If authenticated, redirects user back to requested content by default.
+ *
+ * @since 1.5.0
+ *
+ * @param array|string $args {
+ *     @type int    $mode     Specifies the destination of the redirect. 1 will
+ *                            direct to the root domain (home page), which assumes you have a
+ *                            log-in form there; 2 directs to wp-login.php. Default: 2.
+ *     @type string $redirect The URL the user will be redirected to after successfully
+ *                            logging in. Default: the URL originally requested.
+ *     @type string $root     The root URL of the site, used in case of error or mode 1 redirects.
+ *                            Default: the value of {@link bp_get_root_domain()}.
+ *     @type string $message  An error message to display to the user on the log-in page.
+ *                            Default: "You must log in to access the page you requested."
+ * }
+ */
+function bp_core_no_access( $args = '' ) {
+
+ 	// Build the redirect URL
+ 	$redirect_url  = is_ssl() ? 'https://' : 'http://';
+ 	$redirect_url .= $_SERVER['HTTP_HOST'];
+ 	$redirect_url .= $_SERVER['REQUEST_URI'];
+
+	$defaults = array(
+		'mode'     => 2,                    // 1 = $root, 2 = wp-login.php
+		'redirect' => $redirect_url,        // the URL you get redirected to when a user successfully logs in
+		'root'     => bp_get_root_domain(),	// the landing page you get redirected to when a user doesn't have access
+		'message'  => __( 'You must log in to access the page you requested.', 'buddypress' )
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+
+	/**
+	 * Filters the arguments used for user redirecting when visiting access controlled areas.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $r Array of parsed arguments for redirect determination.
+	 */
+	$r = apply_filters( 'bp_core_no_access', $r );
+	extract( $r, EXTR_SKIP );
+
+	/**
+	 * @ignore Ignore these filters and use 'bp_core_no_access' above
+	 */
+	$mode     = apply_filters( 'bp_no_access_mode',     $mode,     $root,     $redirect, $message );
+	$redirect = apply_filters( 'bp_no_access_redirect', $redirect, $root,     $message,  $mode    );
+	$root     = apply_filters( 'bp_no_access_root',     $root,     $redirect, $message,  $mode    );
+	$message  = apply_filters( 'bp_no_access_message',  $message,  $root,     $redirect, $mode    );
+	$root     = trailingslashit( $root );
+
+	switch ( $mode ) {
+
+		// Option to redirect to wp-login.php
+		// Error message is displayed with bp_core_no_access_wp_login_error()
+		case 2 :
+			if ( !empty( $redirect ) ) {
+				bp_core_redirect( add_query_arg( array( 'action' => 'bpnoaccess' ), wp_login_url( $redirect ) ) );
+			} else {
+				bp_core_redirect( $root );
+			}
+
+			break;
+
+		// Redirect to root with "redirect_to" parameter
+		// Error message is displayed with bp_core_add_message()
+		case 1 :
+		default :
+
+			$url = $root;
+			if ( !empty( $redirect ) ) {
+				$url = add_query_arg( 'redirect_to', urlencode( $redirect ), $root );
+			}
+
+			if ( !empty( $message ) ) {
+				bp_core_add_message( $message, 'error' );
+			}
+
+			bp_core_redirect( $url );
+
+			break;
+	}
+}
+
+/**
+ * Add an error message to wp-login.php.
+ *
+ * Hooks into the "bpnoaccess" action defined in bp_core_no_access().
+ *
+ * @since 1.5.0
+ *
+ * @global string $error Error message to pass to wp-login.php.
+ */
+function bp_core_no_access_wp_login_error() {
+	global $error;
+
+	/**
+	 * Filters the error message for wp-login.php when needing to log in before accessing.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $value Error message to display.
+	 * @param string $value URL to redirect user to after successful login.
+	 */
+	$error = apply_filters( 'bp_wp_login_error', __( 'You must log in to access the page you requested.', 'buddypress' ), $_REQUEST['redirect_to'] );
+
+	// shake shake shake!
+	add_action( 'login_head', 'wp_shake_js', 12 );
+}
+add_action( 'login_form_bpnoaccess', 'bp_core_no_access_wp_login_error' );
+
+/**
+ * Canonicalize BuddyPress URLs.
+ *
+ * This function ensures that requests for BuddyPress content are always
+ * redirected to their canonical versions. Canonical versions are always
+ * trailingslashed, and are typically the most general possible versions of the
+ * URL - eg, example.com/groups/mygroup/ instead of
+ * example.com/groups/mygroup/home/.
+ *
+ * @since 1.6.0
+ *
+ * @see BP_Members_Component::setup_globals() where
+ *      $bp->canonical_stack['base_url'] and ['component'] may be set.
+ * @see bp_core_new_nav_item() where $bp->canonical_stack['action'] may be set.
+ * @uses bp_get_canonical_url()
+ * @uses bp_get_requested_url()
+ */
+function bp_redirect_canonical() {
+
+	/**
+	 * Filters whether or not to do canonical redirects on BuddyPress URLs.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Whether or not to do canonical redirects. Default true.
+	 */
+	if ( !bp_is_blog_page() && apply_filters( 'bp_do_redirect_canonical', true ) ) {
+		// If this is a POST request, don't do a canonical redirect.
+		// This is for backward compatibility with plugins that submit form requests to
+		// non-canonical URLs. Plugin authors should do their best to use canonical URLs in
+		// their form actions.
+		if ( !empty( $_POST ) ) {
+			return;
+		}
+
+		// build the URL in the address bar
+		$requested_url  = bp_get_requested_url();
+
+		// Stash query args
+		$url_stack      = explode( '?', $requested_url );
+		$req_url_clean  = $url_stack[0];
+		$query_args     = isset( $url_stack[1] ) ? $url_stack[1] : '';
+
+		$canonical_url  = bp_get_canonical_url();
+
+		// Only redirect if we've assembled a URL different from the request
+		if ( $canonical_url !== $req_url_clean ) {
+
+			$bp = buddypress();
+
+			// Template messages have been deleted from the cookie by this point, so
+			// they must be readded before redirecting
+			if ( isset( $bp->template_message ) ) {
+				$message      = stripslashes( $bp->template_message );
+				$message_type = isset( $bp->template_message_type ) ? $bp->template_message_type : 'success';
+
+				bp_core_add_message( $message, $message_type );
+			}
+
+			if ( !empty( $query_args ) ) {
+				$canonical_url .= '?' . $query_args;
+			}
+
+			bp_core_redirect( $canonical_url, 301 );
+		}
+	}
+}
+
+/**
+ * Output rel=canonical header tag for BuddyPress content.
+ *
+ * @since 1.6.0
+ */
+function bp_rel_canonical() {
+	$canonical_url = bp_get_canonical_url();
+
+	// Output rel=canonical tag
+	echo "<link rel='canonical' href='" . esc_attr( $canonical_url ) . "' />\n";
+}
+
+/**
+ * Get the canonical URL of the current page.
+ *
+ * @since 1.6.0
+ *
+ * @uses apply_filters() Filter bp_get_canonical_url to modify return value.
+ *
+ * @param array $args {
+ *     Optional array of arguments.
+ *     @type bool $include_query_args Whether to include current URL arguments
+ *                                    in the canonical URL returned from the function.
+ * }
+ *
+ * @return string Canonical URL for the current page.
+ */
+function bp_get_canonical_url( $args = array() ) {
+
+	// For non-BP content, return the requested url, and let WP do the work
+	if ( bp_is_blog_page() ) {
+		return bp_get_requested_url();
+	}
+
+	$bp = buddypress();
+
+	$defaults = array(
+		'include_query_args' => false // Include URL arguments, eg ?foo=bar&foo2=bar2
+	);
+	$r = wp_parse_args( $args, $defaults );
+	extract( $r );
+
+	// Special case: when a BuddyPress directory (eg example.com/members)
+	// is set to be the front page, ensure that the current canonical URL
+	// is the home page URL.
+	if ( 'page' == get_option( 'show_on_front' ) && $page_on_front = (int) get_option( 'page_on_front' ) ) {
+		$front_page_component = array_search( $page_on_front, bp_core_get_directory_page_ids() );
+
+		// If requesting the front page component directory, canonical
+		// URL is the front page. We detect whether we're detecting a
+		// component *directory* by checking that bp_current_action()
+		// is empty - ie, this not a single item or a feed
+		if ( false !== $front_page_component && bp_is_current_component( $front_page_component ) && ! bp_current_action() ) {
+			$bp->canonical_stack['canonical_url'] = trailingslashit( bp_get_root_domain() );
+
+		// Except when the front page is set to the registration page
+		// and the current user is logged in. In this case we send to
+		// the members directory to avoid redirect loops
+		} elseif ( bp_is_register_page() && 'register' == $front_page_component && is_user_logged_in() ) {
+
+			/**
+			 * Filters the logged in register page redirect URL.
+			 *
+			 * @since 1.5.1
+			 *
+			 * @param string $value URL to redirect logged in members to.
+			 */
+			$bp->canonical_stack['canonical_url'] = apply_filters( 'bp_loggedin_register_page_redirect_to', bp_get_members_directory_permalink() );
+		}
+	}
+
+	if ( empty( $bp->canonical_stack['canonical_url'] ) ) {
+		// Build the URL in the address bar
+		$requested_url  = bp_get_requested_url();
+
+		// Stash query args
+		$url_stack      = explode( '?', $requested_url );
+
+		// Build the canonical URL out of the redirect stack
+		if ( isset( $bp->canonical_stack['base_url'] ) )
+			$url_stack[0] = $bp->canonical_stack['base_url'];
+
+		if ( isset( $bp->canonical_stack['component'] ) )
+			$url_stack[0] = trailingslashit( $url_stack[0] . $bp->canonical_stack['component'] );
+
+		if ( isset( $bp->canonical_stack['action'] ) )
+			$url_stack[0] = trailingslashit( $url_stack[0] . $bp->canonical_stack['action'] );
+
+		if ( !empty( $bp->canonical_stack['action_variables'] ) ) {
+			foreach( (array) $bp->canonical_stack['action_variables'] as $av ) {
+				$url_stack[0] = trailingslashit( $url_stack[0] . $av );
+			}
+		}
+
+		// Add trailing slash
+		$url_stack[0] = trailingslashit( $url_stack[0] );
+
+		// Stash in the $bp global
+		$bp->canonical_stack['canonical_url'] = implode( '?', $url_stack );
+	}
+
+	$canonical_url = $bp->canonical_stack['canonical_url'];
+
+	if ( !$include_query_args ) {
+		$canonical_url = array_reverse( explode( '?', $canonical_url ) );
+		$canonical_url = array_pop( $canonical_url );
+	}
+
+	/**
+	 * Filters the canonical url of the current page.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $canonical_url Canonical URL of the current page.
+	 * @param array  $args          Array of arguments to help determine canonical URL.
+	 */
+	return apply_filters( 'bp_get_canonical_url', $canonical_url, $args );
+}
+
+/**
+ * Return the URL as requested on the current page load by the user agent.
+ *
+ * @since 1.6.0
+ *
+ * @return string Requested URL string.
+ */
+function bp_get_requested_url() {
+	$bp = buddypress();
+
+	if ( empty( $bp->canonical_stack['requested_url'] ) ) {
+		$bp->canonical_stack['requested_url']  = is_ssl() ? 'https://' : 'http://';
+		$bp->canonical_stack['requested_url'] .= $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+	}
+
+	/**
+	 * Filters the URL as requested on the current page load by the user agent.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $value Requested URL string.
+	 */
+	return apply_filters( 'bp_get_requested_url', $bp->canonical_stack['requested_url'] );
+}
+
+/**
+ * Remove WP's canonical redirect when we are trying to load BP-specific content.
+ *
+ * Avoids issues with WordPress thinking that a BuddyPress URL might actually
+ * be a blog post or page.
+ *
+ * This function should be considered temporary, and may be removed without
+ * notice in future versions of BuddyPress.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_is_blog_page()
+ */
+function _bp_maybe_remove_redirect_canonical() {
+	if ( ! bp_is_blog_page() )
+		remove_action( 'template_redirect', 'redirect_canonical' );
+}
+add_action( 'bp_init', '_bp_maybe_remove_redirect_canonical' );
+
+/**
+ * Rehook maybe_redirect_404() to run later than the default.
+ *
+ * WordPress's maybe_redirect_404() allows admins on a multisite installation
+ * to define 'NOBLOGREDIRECT', a URL to which 404 requests will be redirected.
+ * maybe_redirect_404() is hooked to template_redirect at priority 10, which
+ * creates a race condition with bp_template_redirect(), our piggyback hook.
+ * Due to a legacy bug in BuddyPress, internal BP content (such as members and
+ * groups) is marked 404 in $wp_query until bp_core_load_template(), when BP
+ * manually overrides the automatic 404. However, the race condition with
+ * maybe_redirect_404() means that this manual un-404-ing doesn't happen in
+ * time, with the results that maybe_redirect_404() thinks that the page is
+ * a legitimate 404, and redirects incorrectly to NOBLOGREDIRECT.
+ *
+ * By switching maybe_redirect_404() to catch at a higher priority, we avoid
+ * the race condition. If bp_core_load_template() runs, it dies before reaching
+ * maybe_redirect_404(). If bp_core_load_template() does not run, it means that
+ * the 404 is legitimate, and maybe_redirect_404() can proceed as expected.
+ *
+ * This function will be removed in a later version of BuddyPress. Plugins
+ * (and plugin authors!) should ignore it.
+ *
+ * @since 1.6.1
+ *
+ * @link https://buddypress.trac.wordpress.org/ticket/4329
+ * @link https://buddypress.trac.wordpress.org/ticket/4415
+ */
+function _bp_rehook_maybe_redirect_404() {
+	if ( defined( 'NOBLOGREDIRECT' ) ) {
+		remove_action( 'template_redirect', 'maybe_redirect_404' );
+		add_action( 'template_redirect', 'maybe_redirect_404', 100 );
+	}
+}
+add_action( 'template_redirect', '_bp_rehook_maybe_redirect_404', 1 );
+
+/**
+ * Remove WP's rel=canonical HTML tag if we are trying to load BP-specific content.
+ *
+ * This function should be considered temporary, and may be removed without
+ * notice in future versions of BuddyPress.
+ *
+ * @since 1.6.0
+ */
+function _bp_maybe_remove_rel_canonical() {
+	if ( ! bp_is_blog_page() && ! is_404() ) {
+		remove_action( 'wp_head', 'rel_canonical' );
+		add_action( 'bp_head', 'bp_rel_canonical' );
+	}
+}
+add_action( 'wp_head', '_bp_maybe_remove_rel_canonical', 8 );
Index: /tags/2.4.3/src/bp-core/bp-core-classes.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-classes.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-classes.php	(revision 10410)
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+require dirname( __FILE__ ) . '/classes/class-bp-user-query.php';
+require dirname( __FILE__ ) . '/classes/class-bp-core-user.php';
+require dirname( __FILE__ ) . '/classes/class-bp-date-query.php';
+require dirname( __FILE__ ) . '/classes/class-bp-core-notification.php';
+require dirname( __FILE__ ) . '/classes/class-bp-button.php';
+require dirname( __FILE__ ) . '/classes/class-bp-embed.php';
+require dirname( __FILE__ ) . '/classes/class-bp-walker-nav-menu.php';
+require dirname( __FILE__ ) . '/classes/class-bp-walker-nav-menu-checklist.php';
+require dirname( __FILE__ ) . '/classes/class-bp-suggestions.php';
+require dirname( __FILE__ ) . '/classes/class-bp-members-suggestions.php';
+require dirname( __FILE__ ) . '/classes/class-bp-recursive-query.php';
+require dirname( __FILE__ ) . '/classes/class-bp-media-extractor.php';
+require dirname( __FILE__ ) . '/classes/class-bp-attachment.php';
+require dirname( __FILE__ ) . '/classes/class-bp-attachment-avatar.php';
+require dirname( __FILE__ ) . '/classes/class-bp-attachment-cover-image.php';
Index: /tags/2.4.3/src/bp-core/bp-core-component.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-component.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-component.php	(revision 10410)
@@ -0,0 +1,824 @@
+<?php
+/**
+ * Component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+if ( !class_exists( 'BP_Component' ) ) :
+
+/**
+ * BuddyPress Component Class.
+ *
+ * The BuddyPress component class is responsible for simplifying the creation
+ * of components that share similar behaviors and routines. It is used
+ * internally by BuddyPress to create the bundled components, but can be
+ * extended to create other really neat things.
+ *
+ * @package BuddyPress
+ * @subpackage Component
+ *
+ * @since 1.5.0
+ */
+class BP_Component {
+
+	/** Variables *************************************************************/
+
+	/**
+	 * Translatable name for the component.
+	 *
+	 * @internal
+	 * @var string $name
+	 */
+	public $name = '';
+
+	/**
+	 * Unique ID for the component.
+	 *
+	 * @var string $id
+	 */
+	public $id = '';
+
+	/**
+	 * Unique slug for the component, for use in query strings and URLs.
+	 *
+	 * @var string $slug
+	 */
+	public $slug = '';
+
+	/**
+	 * Does the component need a top-level directory?
+	 *
+	 * @var bool $has_directory
+	 */
+	public $has_directory = false;
+
+	/**
+	 * The path to the component's files.
+	 *
+	 * @var string $path
+	 */
+	public $path = '';
+
+	/**
+	 * The WP_Query loop for this component.
+	 *
+	 * @var WP_Query $query
+	 */
+	public $query = false;
+
+	/**
+	 * The current ID of the queried object.
+	 *
+	 * @var string $current_id
+	 */
+	public $current_id = '';
+
+	/**
+	 * Callback for formatting notifications.
+	 *
+	 * @var callable $notification_callback
+	 */
+	public $notification_callback = '';
+
+	/**
+	 * WordPress Toolbar links.
+	 *
+	 * @var array $admin_menu
+	 */
+	public $admin_menu = '';
+
+	/**
+	 * Placeholder text for component directory search box.
+	 *
+	 * @since 1.5.0
+	 * @var string $search_string
+	 */
+	public $search_string = '';
+
+	/**
+	 * Root slug for the component.
+	 *
+	 * @since 1.5.0
+	 * @var string $root_slug
+	 */
+	public $root_slug = '';
+
+	/**
+	 * Metadata tables for the component (if applicable).
+	 *
+	 * @since 2.0.0
+	 *
+	 * @var array
+	 */
+	public $meta_tables = array();
+
+	/**
+	 * Global tables for the component (if applicable).
+	 *
+	 * @since 2.0.0
+	 *
+	 * @var array
+	 */
+	public $global_tables = array();
+
+	/**
+	 * Query argument for component search URLs.
+	 *
+	 * @since 2.4.0
+	 * @var string
+	 */
+	public $search_query_arg = 's';
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * Component loader.
+	 *
+	 * @since 1.5.0
+	 * @since 1.9.0 Added $params as a parameter.
+	 * @since 2.3.0 Added $params['features'] as a configurable value.
+	 * @since 2.4.0 Added $params['search_query_arg'] as a configurable value.
+	 *
+	 * @param string $id   Unique ID. Letters, numbers, and underscores only.
+	 * @param string $name Unique name. This should be a translatable name, eg.
+	 *                     __( 'Groups', 'buddypress' ).
+	 * @param string $path The file path for the component's files. Used by {@link BP_Component::includes()}.
+	 * @param array  $params {
+	 *     Additional parameters used by the component.
+	 *     @type int    $adminbar_myaccount_order Set the position for our menu under the WP Toolbar's "My Account menu".
+	 *     @type array  $features                 An array of feature names. This is used to load additional files from your
+	 *                                            component directory and for feature active checks. eg. array( 'awesome' )
+	 *                                            would look for a file called "bp-{$this->id}-awesome.php" and you could use
+	 *                                            bp_is_active( $this->id, 'awesome' ) to determine if the feature is active.
+	 *     @type string $search_query_arg         String to be used as the query argument in component search URLs.
+	 * }
+	 */
+	public function start( $id = '', $name = '', $path = '', $params = array() ) {
+
+		// Internal identifier of component
+		$this->id   = $id;
+
+		// Internal component name
+		$this->name = $name;
+
+		// Path for includes
+		$this->path = $path;
+
+		// Miscellaneous component parameters that need to be set early on
+		if ( ! empty( $params ) ) {
+			// Sets the position for our menu under the WP Toolbar's "My Account" menu
+			if ( ! empty( $params['adminbar_myaccount_order'] ) ) {
+				$this->adminbar_myaccount_order = (int) $params['adminbar_myaccount_order'];
+			}
+
+			// Register features
+			if ( ! empty( $params['features'] ) ) {
+				$this->features = array_map( 'sanitize_title', (array) $params['features'] );
+			}
+
+			if ( ! empty( $params['search_query_arg'] ) ) {
+				$this->search_query_arg = sanitize_title( $params['search_query_arg'] );
+			}
+
+		// Set defaults if not passed
+		} else {
+			// new component menus are added before the settings menu if not set
+			$this->adminbar_myaccount_order = 90;
+		}
+
+		// Move on to the next step
+		$this->setup_actions();
+	}
+
+	/**
+	 * Set up component global variables.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses apply_filters() Calls 'bp_{@link bp_Component::name}_id'.
+	 * @uses apply_filters() Calls 'bp_{@link bp_Component::name}_slug'.
+	 *
+	 * @param array $args {
+	 *     All values are optional.
+	 *     @type string   $slug                  The component slug. Used to construct certain URLs, such as 'friends' in
+	 *                                           http://example.com/members/joe/friends/. Default: the value of $this->id.
+	 *     @type string   $root_slug             The component root slug. Note that this value is generally unused if the
+	 *                                           component has a root directory (the slug will be overridden by the
+	 *                                           post_name of the directory page). Default: the slug of the directory page
+	 *                                           if one is found, otherwise an empty string.
+	 *     @type bool     $has_directory         Set to true if the component requires an associated WordPress page.
+	 *     @type callable $notification_callback Optional. The callable function that formats the component's notifications.
+	 *     @type string   $search_term           Optional. The placeholder text in the component directory search box. Eg,
+	 *                                           'Search Groups...'.
+	 *     @type array    $global_tables         Optional. An array of database table names.
+	 *     @type array    $meta_tables           Optional. An array of metadata table names.
+	 * }
+	 */
+	public function setup_globals( $args = array() ) {
+
+		/** Slugs *************************************************************/
+
+		// If a WP directory page exists for the component, it should
+		// be the default value of 'root_slug'.
+		$default_root_slug = isset( buddypress()->pages->{$this->id}->slug ) ? buddypress()->pages->{$this->id}->slug : '';
+
+		$r = wp_parse_args( $args, array(
+			'slug'                  => $this->id,
+			'root_slug'             => $default_root_slug,
+			'has_directory'         => false,
+			'directory_title'       => '',
+			'notification_callback' => '',
+			'search_string'         => '',
+			'global_tables'         => '',
+			'meta_tables'           => '',
+		) );
+
+		/**
+		 * Filters the slug to be used for the permalink URI chunk after root.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Slug to use in permalink URI chunk.
+		 */
+		$this->slug                  = apply_filters( 'bp_' . $this->id . '_slug',                  $r['slug']                  );
+
+		/**
+		 * Filters the slug used for root directory.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Root directory slug.
+		 */
+		$this->root_slug             = apply_filters( 'bp_' . $this->id . '_root_slug',             $r['root_slug']             );
+
+		/**
+		 * Filters the component's top-level directory if available.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param bool $value Whether or not there is a top-level directory.
+		 */
+		$this->has_directory         = apply_filters( 'bp_' . $this->id . '_has_directory',         $r['has_directory']         );
+
+		/**
+		 * Filters the component's directory title.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $value Title to use for the directory.
+		 */
+		$this->directory_title       = apply_filters( 'bp_' . $this->id . '_directory_title',       $r['directory_title']         );
+
+		/**
+		 * Filters the placeholder text for search inputs for component.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Name to use in search input placeholders.
+		 */
+		$this->search_string         = apply_filters( 'bp_' . $this->id . '_search_string',         $r['search_string']         );
+
+		/**
+		 * Filters the callable function that formats the component's notifications.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Function callback.
+		 */
+		$this->notification_callback = apply_filters( 'bp_' . $this->id . '_notification_callback', $r['notification_callback'] );
+
+		// Set the global table names, if applicable
+		if ( ! empty( $r['global_tables'] ) ) {
+			$this->register_global_tables( $r['global_tables'] );
+		}
+
+		// Set the metadata table, if applicable
+		if ( ! empty( $r['meta_tables'] ) ) {
+			$this->register_meta_tables( $r['meta_tables'] );
+		}
+
+		/** BuddyPress ********************************************************/
+
+		// Register this component in the loaded components array
+		buddypress()->loaded_components[$this->slug] = $this->id;
+
+		/**
+		 * Fires at the end of the setup_globals method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_' . $this->id . '_setup_globals' );
+	}
+
+	/**
+	 * Include required files.
+	 *
+	 * Please note that, by default, this method is fired on the bp_include
+	 * hook, with priority 8. This is necessary so that core components are
+	 * loaded in time to be available to third-party plugins. However, this
+	 * load order means that third-party plugins whose main files are
+	 * loaded at bp_include with priority 10 (as recommended), will not be
+	 * loaded in time for their includes() method to fire automatically.
+	 *
+	 * For this reason, it is recommended that your plugin has its own
+	 * method or function for requiring necessary files. If you must use
+	 * this method, you will have to call it manually in your constructor
+	 * class, ie
+	 *   $this->includes();
+	 *
+	 * Note that when you pass an array value like 'actions' to includes,
+	 * it looks for the following three files (assuming your component is
+	 * called 'my_component'):
+	 *   - ./actions
+	 *   - ./bp-my_component/actions
+	 *   - ./bp-my_component/bp-my_component-actions.php
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses do_action() Calls 'bp_{@link bp_Component::name}includes'.
+	 *
+	 * @param array $includes An array of file names, or file name chunks,
+	 *                        to be parsed and then included.
+	 */
+	public function includes( $includes = array() ) {
+
+		// Bail if no files to include
+		if ( ! empty( $includes ) ) {
+			$slashed_path = trailingslashit( $this->path );
+
+			// Loop through files to be included
+			foreach ( (array) $includes as $file ) {
+
+				$paths = array(
+
+					// Passed with no extension
+					'bp-' . $this->id . '/bp-' . $this->id . '-' . $file  . '.php',
+					'bp-' . $this->id . '-' . $file . '.php',
+					'bp-' . $this->id . '/' . $file . '.php',
+
+					// Passed with extension
+					$file,
+					'bp-' . $this->id . '-' . $file,
+					'bp-' . $this->id . '/' . $file,
+				);
+
+				foreach ( $paths as $path ) {
+					if ( @is_file( $slashed_path . $path ) ) {
+						require( $slashed_path . $path );
+						break;
+					}
+				}
+			}
+		}
+
+		/**
+		 * Fires at the end of the includes method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_' . $this->id . '_includes' );
+	}
+
+	/**
+	 * Set up the actions.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses add_action() To add various actions.
+	 * @uses do_action() Calls 'bp_{@link BP_Component::name}setup_actions'.
+	 */
+	public function setup_actions() {
+
+		// Setup globals
+		add_action( 'bp_setup_globals',          array( $this, 'setup_globals'          ), 10 );
+
+		// Set up canonical stack
+		add_action( 'bp_setup_canonical_stack',  array( $this, 'setup_canonical_stack'  ), 10 );
+
+		// Include required files. Called early to ensure that BP core
+		// components are loaded before plugins that hook their loader functions
+		// to bp_include with the default priority of 10. This is for backwards
+		// compatibility; henceforth, plugins should register themselves by
+		// extending this base class.
+		add_action( 'bp_include',                array( $this, 'includes'               ), 8 );
+
+		// Setup navigation
+		add_action( 'bp_setup_nav',              array( $this, 'setup_nav'              ), 10 );
+
+		// Setup WP Toolbar menus
+		add_action( 'bp_setup_admin_bar',        array( $this, 'setup_admin_bar'        ), $this->adminbar_myaccount_order );
+
+		// Setup component title
+		add_action( 'bp_setup_title',            array( $this, 'setup_title'            ), 10 );
+
+		// Setup cache groups
+		add_action( 'bp_setup_cache_groups',     array( $this, 'setup_cache_groups'     ), 10 );
+
+		// Register post types
+		add_action( 'bp_register_post_types',    array( $this, 'register_post_types'    ), 10 );
+
+		// Register taxonomies
+		add_action( 'bp_register_taxonomies',    array( $this, 'register_taxonomies'    ), 10 );
+
+		// Add the rewrite tags
+		add_action( 'bp_add_rewrite_tags',       array( $this, 'add_rewrite_tags'       ), 10 );
+
+		// Add the rewrite rules
+		add_action( 'bp_add_rewrite_rules',      array( $this, 'add_rewrite_rules'      ), 10 );
+
+		// Add the permalink structure
+		add_action( 'bp_add_permastructs',       array( $this, 'add_permastructs'       ), 10 );
+
+		// Allow components to parse the main query
+		add_action( 'bp_parse_query',            array( $this, 'parse_query'            ), 10 );
+
+		// Generate rewrite rules
+		add_action( 'bp_generate_rewrite_rules', array( $this, 'generate_rewrite_rules' ), 10 );
+
+		/**
+		 * Fires at the end of the setup_actions method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_' . $this->id . '_setup_actions' );
+	}
+
+	/**
+	 * Set up the canonical URL stack for this component.
+	 *
+	 * @since 2.1.0
+	 */
+	public function setup_canonical_stack() {}
+
+	/**
+	 * Set up component navigation.
+	 *
+	 * @see bp_core_new_nav_item() For a description of the $main_nav
+	 *      parameter formatting.
+	 * @see bp_core_new_subnav_item() For a description of how each item
+	 *      in the $sub_nav parameter array should be formatted.
+	 *
+	 * @param array $main_nav Optional. Passed directly to bp_core_new_nav_item().
+	 *                        See that function for a description.
+	 * @param array $sub_nav  Optional. Multidimensional array, each item in
+	 *                        which is passed to bp_core_new_subnav_item(). See that
+	 *                        function for a description.
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		// No sub nav items without a main nav item
+		if ( !empty( $main_nav ) ) {
+			bp_core_new_nav_item( $main_nav );
+
+			// Sub nav items are not required
+			if ( !empty( $sub_nav ) ) {
+				foreach( (array) $sub_nav as $nav ) {
+					bp_core_new_subnav_item( $nav );
+				}
+			}
+		}
+
+		/**
+		 * Fires at the end of the setup_nav method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_' . $this->id . '_setup_nav' );
+	}
+
+	/**
+	 * Set up the component entries in the WordPress Admin Bar.
+	 *
+	 * @see WP_Admin_Bar::add_menu() for a description of the syntax
+	 *      required by each item in the $wp_admin_nav parameter array.
+	 * @global object $wp_admin_bar
+	 *
+	 * @param array $wp_admin_nav An array of nav item arguments. Each item in this parameter
+	 *                            array is passed to {@link WP_Admin_Bar::add_menu()}.
+	 *                            See that method for a description of the required syntax for
+	 *                            each item.
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		// Bail if this is an ajax request
+		if ( defined( 'DOING_AJAX' ) ) {
+			return;
+		}
+
+		// Do not proceed if BP_USE_WP_ADMIN_BAR constant is not set or is false
+		if ( ! bp_use_wp_admin_bar() ) {
+			return;
+		}
+
+		/**
+		 * Filters the admin navigation passed into setup_admin_bar.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param array $wp_admin_nav Array of navigation items to add.
+		 */
+		$wp_admin_nav = apply_filters( 'bp_' . $this->id . '_admin_nav', $wp_admin_nav );
+
+		// Do we have Toolbar menus to add?
+		if ( !empty( $wp_admin_nav ) ) {
+
+			// Set this objects menus
+			$this->admin_menu = $wp_admin_nav;
+
+			// Define the WordPress global
+			global $wp_admin_bar;
+
+			// Add each admin menu
+			foreach( $this->admin_menu as $admin_menu ) {
+				$wp_admin_bar->add_menu( $admin_menu );
+			}
+		}
+
+		/**
+		 * Fires at the end of the setup_admin_bar method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_' . $this->id . '_setup_admin_bar' );
+	}
+
+	/**
+	 * Set up the component title.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses do_action() Calls 'bp_{@link bp_Component::name}_setup_title'.
+	 */
+	public function setup_title() {
+
+		/**
+		 * Fires in the setup_title method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action(  'bp_' . $this->id . '_setup_title' );
+	}
+
+	/**
+	 * Setup component-specific cache groups.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @uses do_action() Calls 'bp_setup_{@link bp_Component::name}_cache_groups'.
+	 */
+	public function setup_cache_groups() {
+
+		/**
+		 * Fires in the setup_cache_groups method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 2.2.0
+		 */
+		do_action( 'bp_' . $this->id . '_setup_cache_groups' );
+	}
+
+	/**
+	 * Register global tables for the component, so that it may use WordPress's database API.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $tables Table names to register.
+	 */
+	public function register_global_tables( $tables = array() ) {
+
+		/**
+		 * Filters the global tables for the component, so that it may use WordPress' database API.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 * It allows for component-specific filtering of table names. To filter
+		 * *all* tables, use the 'bp_core_get_table_prefix' filter instead.
+		 *
+		 * @since 1.6.0
+		 */
+		$tables = apply_filters( 'bp_' . $this->id . '_global_tables', $tables );
+
+		// Add to the BuddyPress global object
+		if ( !empty( $tables ) && is_array( $tables ) ) {
+			foreach ( $tables as $global_name => $table_name ) {
+				$this->$global_name = $table_name;
+			}
+
+			// Keep a record of the metadata tables in the component
+			$this->global_tables = $tables;
+		}
+
+		/**
+		 * Fires at the end of the register_global_tables method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 2.0.0
+		 */
+		do_action( 'bp_' . $this->id . '_register_global_tables' );
+	}
+
+	/**
+	 * Register component metadata tables.
+	 *
+	 * Metadata tables are registered in the $wpdb global, for
+	 * compatibility with the WordPress metadata API.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $tables Table names to register.
+	 */
+	public function register_meta_tables( $tables = array() ) {
+		global $wpdb;
+
+		/**
+		 * Filters the global meta_tables for the component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 * It allows for component-specific filtering of table names. To filter
+		 * *all* tables, use the 'bp_core_get_table_prefix' filter instead.
+		 *
+		 * @since 2.0.0
+		 */
+		$tables = apply_filters( 'bp_' . $this->id . '_meta_tables', $tables );
+
+		/**
+		 * Add the name of each metadata table to WPDB to allow BuddyPress
+		 * components to play nicely with the WordPress metadata API.
+		 */
+		if ( !empty( $tables ) && is_array( $tables ) ) {
+			foreach( $tables as $meta_prefix => $table_name ) {
+				$wpdb->{$meta_prefix . 'meta'} = $table_name;
+			}
+
+			// Keep a record of the metadata tables in the component
+			$this->meta_tables = $tables;
+		}
+
+		/**
+		 * Fires at the end of the register_meta_tables method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 2.0.0
+		 */
+		do_action( 'bp_' . $this->id . '_register_meta_tables' );
+	}
+
+	/**
+	 * Set up the component post types.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses do_action() Calls 'bp_{@link bp_Component::name}_register_post_types'.
+	 */
+	public function register_post_types() {
+
+		/**
+		 * Fires in the register_post_types method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_' . $this->id . '_register_post_types' );
+	}
+
+	/**
+	 * Register component-specific taxonomies.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses do_action() Calls 'bp_{@link bp_Component::name}_register_taxonomies'.
+	 */
+	public function register_taxonomies() {
+
+		/**
+		 * Fires in the register_taxonomies method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_' . $this->id . '_register_taxonomies' );
+	}
+
+	/**
+	 * Add any additional rewrite tags.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses do_action() Calls 'bp_{@link bp_Component::name}_add_rewrite_tags'.
+	 */
+	public function add_rewrite_tags() {
+
+		/**
+		 * Fires in the add_rewrite_tags method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_' . $this->id . '_add_rewrite_tags' );
+	}
+
+	/**
+	 * Add any additional rewrite rules.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @uses do_action() Calls 'bp_{@link bp_Component::name}_add_rewrite_rules'.
+	 */
+	public function add_rewrite_rules() {
+
+		/**
+		 * Fires in the add_rewrite_rules method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.9.0
+		 */
+		do_action( 'bp_' . $this->id . '_add_rewrite_rules' );
+	}
+
+	/**
+	 * Add any permalink structures.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @uses do_action() Calls 'bp_{@link bp_Component::name}_add_permastruct'.
+	 */
+	public function add_permastructs() {
+
+		/**
+		 * Fires in the add_permastructs method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.9.0
+		 */
+		do_action( 'bp_' . $this->id . '_add_permastructs' );
+	}
+
+	/**
+	 * Allow components to parse the main query.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @uses do_action() Calls 'bp_{@link bp_Component::name}_parse_query'.
+	 *
+	 * @param object $query The main WP_Query.
+	 */
+	public function parse_query( $query ) {
+
+		/**
+		 * Fires in the parse_query method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param object $query Main WP_Query object. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_' . $this->id . '_parse_query', array( &$query ) );
+	}
+
+	/**
+	 * Generate any additional rewrite rules.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses do_action() Calls 'bp_{@link bp_Component::name}_generate_rewrite_rules'.
+	 */
+	public function generate_rewrite_rules() {
+
+		/**
+		 * Fires in the generate_rewrite_rules method inside BP_Component.
+		 *
+		 * This is a dynamic hook that is based on the component string ID.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_' . $this->id . '_generate_rewrite_rules' );
+	}
+}
+endif; // BP_Component.
Index: /tags/2.4.3/src/bp-core/bp-core-cssjs.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-cssjs.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-cssjs.php	(revision 10410)
@@ -0,0 +1,458 @@
+<?php
+/**
+ * Core component CSS & JS.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register scripts commonly used by BuddyPress.
+ *
+ * @since 2.1.0
+ */
+function bp_core_register_common_scripts() {
+	$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+	$url = buddypress()->plugin_url . 'bp-core/js/';
+
+	/**
+	 * Filters the BuddyPress Core javascript files to register.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param array $value Array of javascript file information to register.
+	 */
+	$scripts = apply_filters( 'bp_core_register_common_scripts', array(
+
+		// Legacy
+		'bp-confirm'        => array( 'file' => "{$url}confirm{$min}.js",        'dependencies' => array( 'jquery' ), 'footer' => false ),
+		'bp-widget-members' => array( 'file' => "{$url}widget-members{$min}.js", 'dependencies' => array( 'jquery' ), 'footer' => false ),
+		'bp-jquery-query'   => array( 'file' => "{$url}jquery-query{$min}.js",   'dependencies' => array( 'jquery' ), 'footer' => false ),
+		'bp-jquery-cookie'  => array( 'file' => "{$url}jquery-cookie{$min}.js",  'dependencies' => array( 'jquery' ), 'footer' => false ),
+		'bp-jquery-scroll-to' => array( 'file' => "{$url}jquery-scroll-to{$min}.js", 'dependencies' => array( 'jquery' ), 'footer' => false ),
+
+		// 2.1
+		'jquery-caret' => array( 'file' => "{$url}jquery.caret{$min}.js", 'dependencies' => array( 'jquery' ), 'footer' => true ),
+		'jquery-atwho' => array( 'file' => "{$url}jquery.atwho{$min}.js", 'dependencies' => array( 'jquery', 'jquery-caret' ), 'footer' => true ),
+
+		// 2.3
+		'bp-plupload' => array( 'file' => "{$url}bp-plupload{$min}.js", 'dependencies' => array( 'plupload', 'jquery', 'json2', 'wp-backbone' ), 'footer' => true ),
+		'bp-avatar'   => array( 'file' => "{$url}avatar{$min}.js", 'dependencies' => array( 'jcrop' ), 'footer' => true ),
+		'bp-webcam'   => array( 'file' => "{$url}webcam{$min}.js", 'dependencies' => array( 'bp-avatar' ), 'footer' => true ),
+
+		// 2.4
+		'bp-cover-image' => array( 'file' => "{$url}cover-image{$min}.js", 'dependencies' => array(), 'footer' => true ),
+
+	) );
+
+	$version = bp_get_version();
+	foreach ( $scripts as $id => $script ) {
+		wp_register_script( $id, $script['file'], $script['dependencies'], $version, $script['footer'] );
+	}
+}
+add_action( 'bp_enqueue_scripts',       'bp_core_register_common_scripts', 1 );
+add_action( 'bp_admin_enqueue_scripts', 'bp_core_register_common_scripts', 1 );
+
+/**
+ * Register styles commonly used by BuddyPress.
+ *
+ * @since 2.1.0
+ */
+function bp_core_register_common_styles() {
+	$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+	$url = buddypress()->plugin_url . 'bp-core/css/';
+
+	/**
+	 * Filters the URL for the Admin Bar stylesheet.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string $value URL for the Admin Bar stylesheet.
+	 */
+	$admin_bar_file = apply_filters( 'bp_core_admin_bar_css', "{$url}admin-bar{$min}.css" );
+
+	/**
+	 * Filters the BuddyPress Core stylesheet files to register.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param array $value Array of stylesheet file information to register.
+	 */
+	$styles = apply_filters( 'bp_core_register_common_styles', array(
+		'bp-admin-bar' => array(
+			'file'         => $admin_bar_file,
+			'dependencies' => array( 'admin-bar' )
+		),
+		'bp-avatar' => array(
+			'file'         => "{$url}avatar{$min}.css",
+			'dependencies' => array( 'jcrop' )
+		),
+	) );
+
+	foreach ( $styles as $id => $style ) {
+		wp_register_style( $id, $style['file'], $style['dependencies'], bp_get_version() );
+
+		wp_style_add_data( $id, 'rtl', true );
+		if ( $min ) {
+			wp_style_add_data( $id, 'suffix', $min );
+		}
+	}
+}
+add_action( 'bp_enqueue_scripts',       'bp_core_register_common_styles', 1 );
+add_action( 'bp_admin_enqueue_scripts', 'bp_core_register_common_styles', 1 );
+
+/**
+ * Load the JS for "Are you sure?" .confirm links.
+ */
+function bp_core_confirmation_js() {
+	if ( is_multisite() && ! bp_is_root_blog() ) {
+		return false;
+	}
+
+	wp_enqueue_script( 'bp-confirm' );
+
+	wp_localize_script( 'bp-confirm', 'BP_Confirm', array(
+		'are_you_sure' => __( 'Are you sure?', 'buddypress' ),
+	) );
+
+}
+add_action( 'bp_enqueue_scripts',       'bp_core_confirmation_js' );
+add_action( 'bp_admin_enqueue_scripts', 'bp_core_confirmation_js' );
+
+/**
+ * Enqueues the css and js required by the Avatar UI.
+ *
+ * @since  2.3.0
+ */
+function bp_core_avatar_scripts() {
+	if ( ! bp_avatar_is_front_edit() ) {
+		return false;
+	}
+
+	// Enqueue the Attachments scripts for the Avatar UI
+	bp_attachments_enqueue_scripts( 'BP_Attachment_Avatar' );
+
+	// Add Some actions for Theme backcompat
+	add_action( 'bp_after_profile_avatar_upload_content', 'bp_avatar_template_check' );
+	add_action( 'bp_after_group_admin_content',           'bp_avatar_template_check' );
+	add_action( 'bp_after_group_avatar_creation_step',    'bp_avatar_template_check' );
+}
+add_action( 'bp_enqueue_scripts', 'bp_core_avatar_scripts' );
+
+/**
+ * Enqueues the css and js required by the Cover Image UI.
+ *
+ * @since  2.4.0
+ */
+function bp_core_cover_image_scripts() {
+	if ( ! bp_attachments_cover_image_is_edit() ) {
+		return false;
+	}
+
+	// Enqueue the Attachments scripts for the Cover Image UI
+	bp_attachments_enqueue_scripts( 'BP_Attachment_Cover_Image' );
+}
+add_action( 'bp_enqueue_scripts', 'bp_core_cover_image_scripts' );
+
+/**
+ * Enqueues jCrop library and hooks BP's custom cropper JS.
+ */
+function bp_core_add_jquery_cropper() {
+	wp_enqueue_style( 'jcrop' );
+	wp_enqueue_script( 'jcrop', array( 'jquery' ) );
+	add_action( 'wp_head', 'bp_core_add_cropper_inline_js' );
+	add_action( 'wp_head', 'bp_core_add_cropper_inline_css' );
+}
+
+/**
+ * Output the inline JS needed for the cropper to work on a per-page basis.
+ */
+function bp_core_add_cropper_inline_js() {
+
+	/**
+	 * Filters the return value of getimagesize to determine if an image was uploaded.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param array $value Array of data found by getimagesize.
+	 */
+	$image = apply_filters( 'bp_inline_cropper_image', getimagesize( bp_core_avatar_upload_path() . buddypress()->avatar_admin->image->dir ) );
+	if ( empty( $image ) ) {
+		return;
+	}
+
+	// Get avatar full width and height
+	$full_height = bp_core_avatar_full_height();
+	$full_width  = bp_core_avatar_full_width();
+
+	// Calculate Aspect Ratio
+	if ( !empty( $full_height ) && ( $full_width != $full_height ) ) {
+		$aspect_ratio = $full_width / $full_height;
+	} else {
+		$aspect_ratio = 1;
+	}
+
+	// Default cropper coordinates
+
+	// Smaller than full-width: cropper defaults to entire image
+	if ( $image[0] < $full_width ) {
+		$crop_left  = 0;
+		$crop_right = $image[0];
+
+	// Less than 2x full-width: cropper defaults to full-width
+	} elseif ( $image[0] < ( $full_width * 2 ) ) {
+		$padding_w  = round( ( $image[0] - $full_width ) / 2 );
+		$crop_left  = $padding_w;
+		$crop_right = $image[0] - $padding_w;
+
+	// Larger than 2x full-width: cropper defaults to 1/2 image width
+	} else {
+		$crop_left  = round( $image[0] / 4 );
+		$crop_right = $image[0] - $crop_left;
+	}
+
+	// Smaller than full-height: cropper defaults to entire image
+	if ( $image[1] < $full_height ) {
+		$crop_top    = 0;
+		$crop_bottom = $image[1];
+
+	// Less than double full-height: cropper defaults to full-height
+	} elseif ( $image[1] < ( $full_height * 2 ) ) {
+		$padding_h   = round( ( $image[1] - $full_height ) / 2 );
+		$crop_top    = $padding_h;
+		$crop_bottom = $image[1] - $padding_h;
+
+	// Larger than 2x full-height: cropper defaults to 1/2 image height
+	} else {
+		$crop_top    = round( $image[1] / 4 );
+		$crop_bottom = $image[1] - $crop_top;
+	}
+
+	?>
+
+	<script type="text/javascript">
+		jQuery(window).load( function(){
+			jQuery('#avatar-to-crop').Jcrop({
+				onChange: showPreview,
+				onSelect: updateCoords,
+				aspectRatio: <?php echo (int) $aspect_ratio; ?>,
+				setSelect: [ <?php echo (int) $crop_left; ?>, <?php echo (int) $crop_top; ?>, <?php echo (int) $crop_right; ?>, <?php echo (int) $crop_bottom; ?> ]
+			});
+		});
+
+		function updateCoords(c) {
+			jQuery('#x').val(c.x);
+			jQuery('#y').val(c.y);
+			jQuery('#w').val(c.w);
+			jQuery('#h').val(c.h);
+		}
+
+		function showPreview(coords) {
+			if ( parseInt(coords.w) > 0 ) {
+				var fw = <?php echo (int) $full_width; ?>;
+				var fh = <?php echo (int) $full_height; ?>;
+				var rx = fw / coords.w;
+				var ry = fh / coords.h;
+
+				jQuery( '#avatar-crop-preview' ).css({
+					width: Math.round(rx * <?php echo (int) $image[0]; ?>) + 'px',
+					height: Math.round(ry * <?php echo (int) $image[1]; ?>) + 'px',
+					marginLeft: '-' + Math.round(rx * coords.x) + 'px',
+					marginTop: '-' + Math.round(ry * coords.y) + 'px'
+				});
+			}
+		}
+	</script>
+
+<?php
+}
+
+/**
+ * Output the inline CSS for the BP image cropper.
+ *
+ * @package BuddyPress Core
+ */
+function bp_core_add_cropper_inline_css() {
+?>
+
+	<style type="text/css">
+		.jcrop-holder { float: left; margin: 0 20px 20px 0; text-align: left; }
+		#avatar-crop-pane { width: <?php echo bp_core_avatar_full_width() ?>px; height: <?php echo bp_core_avatar_full_height() ?>px; overflow: hidden; }
+		#avatar-crop-submit { margin: 20px 0; }
+		.jcrop-holder img,
+		#avatar-crop-pane img,
+		#avatar-upload-form img,
+		#create-group-form img,
+		#group-settings-form img { border: none !important; max-width: none !important; }
+	</style>
+
+<?php
+}
+
+/**
+ * Define the 'ajaxurl' JS variable, used by themes as an AJAX endpoint.
+ *
+ * @since 1.1.0
+ */
+function bp_core_add_ajax_url_js() {
+?>
+
+	<script type="text/javascript">var ajaxurl = '<?php echo bp_core_ajax_url(); ?>';</script>
+
+<?php
+}
+add_action( 'wp_head', 'bp_core_add_ajax_url_js' );
+
+/**
+ * Get the proper value for BP's ajaxurl.
+ *
+ * Designed to be sensitive to FORCE_SSL_ADMIN and non-standard multisite
+ * configurations.
+ *
+ * @since 1.7.0
+ *
+ * @return string AJAX endpoint URL.
+ */
+function bp_core_ajax_url() {
+
+	/**
+	 * Filters the proper value for BuddyPress' ajaxurl.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $value Proper ajaxurl value for BuddyPress.
+	 */
+	return apply_filters( 'bp_core_ajax_url', admin_url( 'admin-ajax.php', is_ssl() ? 'admin' : 'http' ) );
+}
+
+/**
+ * Get the JavaScript dependencies for buddypress.js.
+ *
+ * @since 2.0.0
+ *
+ * @uses apply_filters() to allow other component to load extra dependencies.
+ *
+ * @return array The JavaScript dependencies.
+ */
+function bp_core_get_js_dependencies() {
+
+	/**
+	 * Filters the javascript dependencies for buddypress.js.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $value Array of javascript dependencies for buddypress.js.
+	 */
+	return apply_filters( 'bp_core_get_js_dependencies', array(
+		'jquery',
+		'bp-confirm',
+		'bp-widget-members',
+		'bp-jquery-query',
+		'bp-jquery-cookie',
+		'bp-jquery-scroll-to'
+	) );
+}
+
+/**
+ * Add inline css to display the component's single item cover image
+ *
+ * @since 2.4.0
+ *
+ * @param  bool $return true to get the inline css
+ * @return string|array the inline css or an associative array containing
+ *                      the css rules and the style handle
+ */
+function bp_add_cover_image_inline_css( $return = false ) {
+	$bp = buddypress();
+
+	// Find the component of the current item
+	if ( bp_is_user() ) {
+
+		// User is not allowed to upload cover images
+		// no need to carry on
+		if ( bp_disable_cover_image_uploads() ) {
+			return;
+		}
+
+		$cover_image_object = array(
+			'component' => 'xprofile',
+			'object' => $bp->displayed_user
+		);
+	} elseif ( bp_is_group() ) {
+
+		// Users are not allowed to upload cover images for their groups
+		// no need to carry on
+		if ( bp_disable_group_cover_image_uploads() ) {
+			return;
+		}
+
+		$cover_image_object = array(
+			'component' =>'groups',
+			'object' => $bp->groups->current_group
+		);
+	} else {
+		$cover_image_object = apply_filters( 'bp_current_cover_image_object_inline_css', array() );
+	}
+
+	// Bail if no component were found.
+	if ( empty( $cover_image_object['component'] ) || empty( $cover_image_object['object'] ) || ! bp_is_active( $cover_image_object['component'], 'cover_image' ) ) {
+		return;
+	}
+
+	// Get the settings of the cover image feature for the current component
+	$params = bp_attachments_get_cover_image_settings( $cover_image_object['component'] );
+
+	// Bail if no params.
+	if ( empty( $params ) ) {
+		return;
+	}
+
+	// Try to call the callback
+	if ( is_callable( $params['callback'] ) ) {
+
+		$object_dir = $cover_image_object['component'];
+
+		if ( 'xprofile' === $object_dir ) {
+			$object_dir = 'members';
+		}
+
+		$cover_image = bp_attachments_get_attachment( 'url', array(
+			'object_dir' => $object_dir,
+			'item_id'    => $cover_image_object['object']->id,
+		) );
+
+		if ( empty( $cover_image ) ) {
+			if ( ! empty( $params['default_cover'] ) ) {
+				$cover_image = $params['default_cover'];
+			}
+		}
+
+		$inline_css = call_user_func_array( $params['callback'], array( array(
+			'cover_image' => esc_url( $cover_image ),
+			'component'   => sanitize_key( $cover_image_object['component'] ),
+			'object_id'   => (int) $cover_image_object['object']->id,
+			'width'       => (int) $params['width'],
+			'height'      => (int) $params['height'],
+		) ) );
+
+		// Finally add the inline css to the handle
+		if ( ! empty( $inline_css ) ) {
+
+			// Used to get the css when Ajax setting the cover image
+			if ( true === $return ) {
+				return array(
+					'css_rules' => '<style type="text/css">' . "\n" . $inline_css . "\n" . '</style>',
+					'handle'    => $params['theme_handle'],
+				);
+			}
+
+			wp_add_inline_style( $params['theme_handle'], $inline_css );
+		} else {
+			return false;
+		}
+	}
+}
+add_action( 'bp_enqueue_scripts', 'bp_add_cover_image_inline_css', 11 );
Index: /tags/2.4.3/src/bp-core/bp-core-dependency.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-dependency.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-dependency.php	(revision 10410)
@@ -0,0 +1,690 @@
+<?php
+/**
+ * Plugin Dependency Action Hooks.
+ *
+ * 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 BuddyPress by mirroring existing WordPress hooks in many places
+ * allowing dependant plugins to hook into the BuddyPress specific ones, thus
+ * guaranteeing proper code execution only when BuddyPress 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.
+ *
+ * @todo use anonymous functions when PHP minimum requirement allows (5.3)
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+/**
+ * Fire the 'bp_include' action, where plugins should include files.
+ */
+function bp_include() {
+
+	/**
+	 * Fires inside the 'bp_include' function, where plugins should include files.
+	 *
+	 * @since 1.2.5
+	 */
+	do_action( 'bp_include' );
+}
+
+/**
+ * Fire the 'bp_setup_components' action, where plugins should initialize components.
+ */
+function bp_setup_components() {
+
+	/**
+	 * Fires inside the 'bp_setup_components' function, where plugins should initialize components.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_setup_components' );
+}
+
+/**
+ * Fire the 'bp_setup_canonical_stack' action, where plugins should set up their canonical URL.
+ */
+function bp_setup_canonical_stack() {
+
+	/**
+	 * Fires inside the 'bp_setup_canonical_stack' function, where plugins should set up their canonical URL.
+	 *
+	 * @since 2.1.0
+	 */
+	do_action( 'bp_setup_canonical_stack' );
+}
+
+/**
+ * Fire the 'bp_register_taxonomies' action, where plugins should register taxonomies.
+ *
+ * @since 2.2.0
+ */
+function bp_register_taxonomies() {
+
+	/**
+	 * Fires inside the 'bp_register_taxonomies' function, where plugins should register taxonomies.
+	 *
+	 * @since 2.2.0
+	 */
+	do_action( 'bp_register_taxonomies' );
+}
+
+/**
+ * Fire the 'bp_setup_globals' action, where plugins should initialize global settings.
+ */
+function bp_setup_globals() {
+
+	/**
+	 * Fires inside the 'bp_setup_globals' function, where plugins should initialize global settings.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_setup_globals' );
+}
+
+/**
+ * Fire the 'bp_setup_nav' action, where plugins should register their navigation items.
+ */
+function bp_setup_nav() {
+
+	/**
+	 * Fires inside the 'bp_setup_nav' function, where plugins should register their navigation items.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_setup_nav' );
+}
+
+/**
+ * Fire the 'bp_setup_admin_bar' action, where plugins should add items to the WP admin bar.
+ */
+function bp_setup_admin_bar() {
+	if ( bp_use_wp_admin_bar() ) {
+
+		/**
+		 * Fires inside the 'bp_setup_admin_bar' function, where plugins should add items to the WP admin bar.
+		 *
+		 * This hook will only fire if bp_use_wp_admin_bar() returns true.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_setup_admin_bar' );
+	}
+}
+
+/**
+ * Fire the 'bp_setup_title' action, where plugins should modify the page title.
+ */
+function bp_setup_title() {
+
+	/**
+	 * Fires inside the 'bp_setup_title' function, where plugins should modify the page title.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_setup_title' );
+}
+
+/**
+ * Fire the 'bp_register_widgets' action, where plugins should register widgets.
+ */
+function bp_setup_widgets() {
+
+	/**
+	 * Fires inside the 'bp_register_widgets' function, where plugins should register widgets.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_register_widgets' );
+}
+
+/**
+ * Fire the 'bp_register_member_types' action, where plugins should register member types.
+ *
+ * @since 2.3.0
+ */
+function bp_register_member_types() {
+
+	/**
+	 * Fires inside bp_register_member_types(), so plugins can register member types.
+	 *
+	 * @since 2.3.0
+	 */
+	do_action( 'bp_register_member_types' );
+}
+
+/**
+ * Fire the 'bp_setup_cache_groups' action, where cache groups are registered.
+ *
+ * @since 2.2.0
+ */
+function bp_setup_cache_groups() {
+
+	/**
+	 * Fires inside the 'bp_setup_cache_groups' function, where cache groups are registered.
+	 *
+	 * @since 2.2.0
+	 */
+	do_action( 'bp_setup_cache_groups' );
+}
+
+/**
+ * Set up the currently logged-in user.
+ *
+ * We white-list the WordPress customizer which purposely loads the user early.
+ *
+ * @link https://buddypress.trac.wordpress.org/ticket/6046
+ * @link https://core.trac.wordpress.org/ticket/24169
+ *
+ * @uses did_action() To make sure the user isn't loaded out of order.
+ * @uses do_action() Calls 'bp_setup_current_user'.
+ */
+function bp_setup_current_user() {
+
+	// If the current user is being setup before the "init" action has fired,
+	// strange (and difficult to debug) role/capability issues will occur.
+	if ( ! isset( $GLOBALS['wp_customize'] ) && ! did_action( 'after_setup_theme' ) ) {
+		_doing_it_wrong( __FUNCTION__, __( 'The current user is being initialized without using $wp->init().', 'buddypress' ), '1.7' );
+	}
+
+	/**
+	 * Fires to set up the current user setup process.
+	 *
+	 * @since 1.7.0
+	 */
+	do_action( 'bp_setup_current_user' );
+}
+
+/**
+ * Fire the 'bp_init' action, BuddyPress's main initialization hook.
+ */
+function bp_init() {
+
+	/**
+	 * Fires inside the 'bp_init' function, BuddyPress' main initialization hook.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_init' );
+}
+
+/**
+ * Fire the 'bp_loaded' action, which fires after BP's core plugin files have been loaded.
+ *
+ * Attached to 'plugins_loaded'.
+ */
+function bp_loaded() {
+
+	/**
+	 * Fires inside the 'bp_loaded' function, which fires after BP's core plugin files have been loaded.
+	 *
+	 * @since 1.2.5
+	 */
+	do_action( 'bp_loaded' );
+}
+
+/**
+ * Fire the 'bp_ready' action, which runs after BP is set up and the page is about to render.
+ *
+ * Attached to 'wp'.
+ */
+function bp_ready() {
+
+	/**
+	 * Fires inside the 'bp_ready' function, which runs after BP is set up and the page is about to render.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_ready' );
+}
+
+/**
+ * Fire the 'bp_actions' action, which runs just before rendering.
+ *
+ * Attach potential template actions, such as catching form requests or routing
+ * custom URLs.
+ */
+function bp_actions() {
+
+	/**
+	 * Fires inside the 'bp_actions' function, which runs just before rendering.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_actions' );
+}
+
+/**
+ * Fire the 'bp_screens' action, which runs just before rendering.
+ *
+ * Runs just after 'bp_actions'. Use this hook to attach your template
+ * loaders.
+ */
+function bp_screens() {
+
+	/**
+	 * Fires inside the 'bp_screens' function, which runs just before rendering.
+	 *
+	 * Runs just after 'bp_actions'. Use this hook to attach your template loaders.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_screens' );
+}
+
+/**
+ * Fire 'bp_widgets_init', which runs after widgets have been set up.
+ *
+ * Hooked to 'widgets_init'.
+ */
+function bp_widgets_init() {
+
+	/**
+	 * Fires inside the 'bp_widgets_init' function, which runs after widgets have been set up.
+	 *
+	 * Hooked to 'widgets_init'.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action ( 'bp_widgets_init' );
+}
+
+/**
+ * Fire 'bp_head', which is used to hook scripts and styles in the <head>.
+ *
+ * Hooked to 'wp_head'.
+ */
+function bp_head() {
+	do_action ( 'bp_head' );
+}
+
+/** Theme Permissions *********************************************************/
+
+/**
+ * Fire the 'bp_template_redirect' action.
+ *
+ * Run at 'template_redirect', just before WordPress selects and loads a theme
+ * template. The main purpose of this hook in BuddyPress is to redirect users
+ * who do not have the proper permission to access certain content.
+ *
+ * @since 1.6.0
+ *
+ * @uses do_action()
+ */
+function bp_template_redirect() {
+
+	/**
+	 * Fires inside the 'bp_template_redirect' function.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_template_redirect' );
+}
+
+/** Theme Helpers *************************************************************/
+
+/**
+ * Fire the 'bp_register_theme_directory' action.
+ *
+ * The main action used registering theme directories.
+ *
+ * @since 1.5.0
+ *
+ * @uses do_action()
+ */
+function bp_register_theme_directory() {
+
+	/**
+	 * Fires inside the 'bp_register_theme_directory' function.
+	 *
+	 * The main action used registering theme directories.
+	 *
+	 * @since 1.7.0
+	 */
+	do_action( 'bp_register_theme_directory' );
+}
+
+/**
+ * Fire the 'bp_register_theme_packages' action.
+ *
+ * The main action used registering theme packages.
+ *
+ * @since 1.7.0
+ *
+ * @uses do_action()
+ */
+function bp_register_theme_packages() {
+
+	/**
+	 * Fires inside the 'bp_register_theme_packages' function.
+	 *
+	 * @since 1.7.0
+	 */
+	do_action( 'bp_register_theme_packages' );
+}
+
+/**
+ * Fire the 'bp_enqueue_scripts' action, where BP enqueues its CSS and JS.
+ *
+ * @since 1.6.0
+ *
+ * @uses do_action() Calls 'bp_enqueue_scripts'.
+ */
+function bp_enqueue_scripts() {
+
+	/**
+	 * Fires inside the 'bp_enqueue_scripts' function, where BP enqueues its CSS and JS.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action ( 'bp_enqueue_scripts' );
+}
+
+/**
+ * Fire the 'bp_add_rewrite_tag' action, where BP adds its custom rewrite tags.
+ *
+ * @since 1.8.0
+ *
+ * @uses do_action() Calls 'bp_add_rewrite_tags'.
+ */
+function bp_add_rewrite_tags() {
+
+	/**
+	 * Fires inside the 'bp_add_rewrite_tags' function, where BP adds its custom rewrite tags.
+	 *
+	 * @since 1.8.0
+	 */
+	do_action( 'bp_add_rewrite_tags' );
+}
+
+/**
+ * Fire the 'bp_add_rewrite_rules' action, where BP adds its custom rewrite rules.
+ *
+ * @since 1.9.0
+ *
+ * @uses do_action() Calls 'bp_add_rewrite_rules'.
+ */
+function bp_add_rewrite_rules() {
+
+	/**
+	 * Fires inside the 'bp_add_rewrite_rules' function, where BP adds its custom rewrite rules.
+	 *
+	 * @since 1.9.0
+	 */
+	do_action( 'bp_add_rewrite_rules' );
+}
+
+/**
+ * Fire the 'bp_add_permastructs' action, where BP adds its BP-specific permalink structure.
+ *
+ * @since 1.9.0
+ *
+ * @uses do_action() Calls 'bp_add_permastructs'.
+ */
+function bp_add_permastructs() {
+
+	/**
+	 * Fires inside the 'bp_add_permastructs' function, where BP adds its BP-specific permalink structure.
+	 *
+	 * @since 1.9.0
+	 */
+	do_action( 'bp_add_permastructs' );
+}
+
+/**
+ * Fire the 'bp_setup_theme' action.
+ *
+ * The main purpose of 'bp_setup_theme' is give themes a place to load their
+ * BuddyPress-specific functionality.
+ *
+ * @since 1.6.0
+ *
+ * @uses do_action() Calls 'bp_setup_theme'.
+ */
+function bp_setup_theme() {
+
+	/**
+	 * Fires inside the 'bp_setup_theme' function.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action ( 'bp_setup_theme' );
+}
+
+/**
+ * Fire the 'bp_after_setup_theme' action.
+ *
+ * Piggy-back action for BuddyPress-specific theme actions once the theme has
+ * been set up and the theme's functions.php has loaded.
+ *
+ * Hooked to 'after_setup_theme' with a priority of 100. This allows plenty of
+ * time for other themes to load their features, such as BuddyPress support,
+ * before our theme compatibility layer kicks in.
+ *
+ * @since 1.6.0
+ *
+ * @uses do_action() Calls 'bp_after_setup_theme'.
+ */
+function bp_after_setup_theme() {
+
+	/**
+	 * Fires inside the 'bp_after_setup_theme' function.
+	 *
+	 * @since 1.7.0
+	 */
+	do_action ( 'bp_after_setup_theme' );
+}
+
+/** Theme Compatibility Filter ************************************************/
+
+/**
+ * Fire the 'bp_request' filter, a piggy-back of WP's 'request'.
+ *
+ * @since 1.7.0
+ *
+ * @see WP::parse_request() for a description of parameters.
+ *
+ * @param array $query_vars See {@link WP::parse_request()}.
+ *
+ * @return array $query_vars See {@link WP::parse_request()}.
+ */
+function bp_request( $query_vars = array() ) {
+
+	/**
+	 * Filters the query_vars for the current request.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $query_vars Array of query variables.
+	 */
+	return apply_filters( 'bp_request', $query_vars );
+}
+
+/**
+ * Fire the 'bp_login_redirect' filter, a piggy-back of WP's 'login_redirect'.
+ *
+ * @since 1.7.0
+ *
+ * @param string $redirect_to     See 'login_redirect'.
+ * @param string $redirect_to_raw See 'login_redirect'.
+ *
+ * @param bool   $user See 'login_redirect'.
+ *
+ * @return string
+ */
+function bp_login_redirect( $redirect_to = '', $redirect_to_raw = '', $user = false ) {
+
+	/**
+	 * Filters the URL to redirect to after login.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string           $redirect_to     The redirect destination URL.
+	 * @param string           $redirect_to_raw The requested redirect destination URL passed as a parameter.
+	 * @param WP_User|WP_Error $user            WP_User object if login was successful, WP_Error object otherwise.
+	 */
+	return apply_filters( 'bp_login_redirect', $redirect_to, $redirect_to_raw, $user );
+}
+
+/**
+ * Fire 'bp_template_include', main filter used for theme compatibility and displaying custom BP theme files.
+ *
+ * Hooked to 'template_include'.
+ *
+ * @since 1.6.0
+ *
+ * @uses apply_filters()
+ *
+ * @param string $template See 'template_include'.
+ *
+ * @return string Template file to use.
+ */
+function bp_template_include( $template = '' ) {
+
+	/**
+	 * Filters the template to use with template_include.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $template The path of the template to include.
+	 */
+	return apply_filters( 'bp_template_include', $template );
+}
+
+/**
+ * Fire the 'bp_generate_rewrite_rules' action, where BP generates its rewrite rules.
+ *
+ * @since 1.7.0
+ *
+ * @uses do_action() Calls 'bp_generate_rewrite_rules' with {@link WP_Rewrite}.
+ *
+ * @param WP_Rewrite $wp_rewrite See 'generate_rewrite_rules'.
+ */
+function bp_generate_rewrite_rules( $wp_rewrite ) {
+
+	/**
+	 * Fires inside the 'bp_generate_rewrite_rules' function.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param WP_Rewrite $wp_rewrite WP_Rewrite object. Passed by reference.
+	 */
+	do_action_ref_array( 'bp_generate_rewrite_rules', array( &$wp_rewrite ) );
+}
+
+/**
+ * Fire the 'bp_allowed_themes' filter.
+ *
+ * Filter the allowed themes list for BuddyPress-specific themes.
+ *
+ * @since 1.7.0
+ *
+ * @uses apply_filters() Calls 'bp_allowed_themes' with the allowed themes list.
+ *
+ * @param array $themes
+ *
+ * @return array
+ */
+function bp_allowed_themes( $themes ) {
+
+	/**
+	 * Filters the allowed themes list for BuddyPress-specific themes.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $template The path of the template to include.
+	 */
+	return apply_filters( 'bp_allowed_themes', $themes );
+}
+
+/** Requests ******************************************************************/
+
+/**
+ * The main action used for handling theme-side POST requests.
+ *
+ * @since 1.9.0
+ * @uses do_action()
+ */
+function bp_post_request() {
+
+	// Bail if not a POST action
+	if ( ! bp_is_post_request() ) {
+		return;
+	}
+
+	// Bail if no action
+	if ( empty( $_POST['action'] ) ) {
+		return;
+	}
+
+	// Sanitize the POST action
+	$action = sanitize_key( $_POST['action'] );
+
+	/**
+	 * Fires at the end of the bp_post_request function.
+	 *
+	 * 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.
+	 *
+	 * @since 1.9.0
+	 */
+	do_action( 'bp_post_request_' . $action );
+
+	/**
+	 * Fires at the end of the bp_post_request function.
+	 *
+	 * Use this static action if you don't mind checking the 'action' yourself.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $action The action being run.
+	 */
+	do_action( 'bp_post_request',   $action );
+}
+
+/**
+ * The main action used for handling theme-side GET requests.
+ *
+ * @since 1.9.0
+ * @uses do_action()
+ */
+function bp_get_request() {
+
+	// Bail if not a POST action
+	if ( ! bp_is_get_request() ) {
+		return;
+	}
+
+	// Bail if no action
+	if ( empty( $_GET['action'] ) ) {
+		return;
+	}
+
+	// Sanitize the GET action
+	$action = sanitize_key( $_GET['action'] );
+
+	/**
+	 * Fires at the end of the bp_get_request function.
+	 *
+	 * 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.
+	 *
+	 * @since 1.9.0
+	 */
+	do_action( 'bp_get_request_' . $action );
+
+	/**
+	 * Fires at the end of the bp_get_request function.
+	 *
+	 * Use this static action if you don't mind checking the 'action' yourself.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $action The action being run.
+	 */
+	do_action( 'bp_get_request',   $action );
+}
Index: /tags/2.4.3/src/bp-core/bp-core-filters.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-filters.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-filters.php	(revision 10410)
@@ -0,0 +1,1053 @@
+<?php
+/**
+ * BuddyPress Filters.
+ *
+ * This file contains the filters that are used throughout BuddyPress. 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.
+ *
+ *  - BuddyPress: In {@link BuddyPress::setup_actions()} in buddypress.php
+ *  - Component: In {@link BP_Component::setup_actions()} in
+ *                bp-core/bp-core-component.php
+ *  - Admin: More in {@link BP_Admin::setup_actions()} in
+ *            bp-core/bp-core-admin.php
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @see bp-core-actions.php
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Attach BuddyPress to WordPress.
+ *
+ * BuddyPress 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 BuddyPress 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--BuddyPress Sub-actions
+ */
+add_filter( 'request',                 'bp_request',             10    );
+add_filter( 'template_include',        'bp_template_include',    10    );
+add_filter( 'login_redirect',          'bp_login_redirect',      10, 3 );
+add_filter( 'map_meta_cap',            'bp_map_meta_caps',       10, 4 );
+
+// Add some filters to feedback messages
+add_filter( 'bp_core_render_message_content', 'wptexturize'       );
+add_filter( 'bp_core_render_message_content', 'convert_smilies'   );
+add_filter( 'bp_core_render_message_content', 'convert_chars'     );
+add_filter( 'bp_core_render_message_content', 'wpautop'           );
+add_filter( 'bp_core_render_message_content', 'shortcode_unautop' );
+add_filter( 'bp_core_render_message_content', 'wp_kses_data', 5   );
+
+/**
+ * Template Compatibility.
+ *
+ * If you want to completely bypass this and manage your own custom BuddyPress
+ * template hierarchy, start here by removing this filter, then look at how
+ * bp_template_include() works and do something similar. :)
+ */
+add_filter( 'bp_template_include',   'bp_template_include_theme_supports', 2, 1 );
+add_filter( 'bp_template_include',   'bp_template_include_theme_compat',   4, 2 );
+
+// Filter BuddyPress template locations
+add_filter( 'bp_get_template_stack', 'bp_add_template_stack_locations' );
+
+// Turn comments off for BuddyPress pages
+add_filter( 'comments_open', 'bp_comments_open', 10, 2 );
+
+/**
+ * Prevent specific pages (eg 'Activate') from showing on page listings.
+ *
+ * @uses bp_is_active() checks if a BuddyPress component is active.
+ *
+ * @param array $pages List of excluded page IDs, as passed to the
+ *                     'wp_list_pages_excludes' filter.
+ *
+ * @return array The exclude list, with BP's pages added.
+ */
+function bp_core_exclude_pages( $pages = array() ) {
+
+	// Bail if not the root blog
+	if ( ! bp_is_root_blog() )
+		return $pages;
+
+	$bp = buddypress();
+
+	if ( !empty( $bp->pages->activate ) )
+		$pages[] = $bp->pages->activate->id;
+
+	if ( !empty( $bp->pages->register ) )
+		$pages[] = $bp->pages->register->id;
+
+	if ( !empty( $bp->pages->forums ) && ( !bp_is_active( 'forums' ) || ( bp_is_active( 'forums' ) && bp_forums_has_directory() && !bp_forums_is_installed_correctly() ) ) )
+		$pages[] = $bp->pages->forums->id;
+
+	/**
+	 * Filters specific pages that shouldn't show up on page listings.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $pages Array of pages to exclude.
+	 */
+	return apply_filters( 'bp_core_exclude_pages', $pages );
+}
+add_filter( 'wp_list_pages_excludes', 'bp_core_exclude_pages' );
+
+/**
+ * Prevent specific pages (eg 'Activate') from showing in the Pages meta box of the Menu Administration screen.
+ *
+ * @since 2.0.0
+ *
+ * @uses bp_is_root_blog() checks if current blog is root blog.
+ * @uses buddypress() gets BuddyPress main instance
+ *
+ * @param object|null $object The post type object used in the meta box.
+ *
+ * @return object|null The $object, with a query argument to remove register and activate pages id.
+ */
+function bp_core_exclude_pages_from_nav_menu_admin( $object = null ) {
+
+	// Bail if not the root blog
+	if ( ! bp_is_root_blog() ) {
+		return $object;
+	}
+
+	if ( 'page' != $object->name ) {
+		return $object;
+	}
+
+	$bp = buddypress();
+	$pages = array();
+
+	if ( ! empty( $bp->pages->activate ) ) {
+		$pages[] = $bp->pages->activate->id;
+	}
+
+	if ( ! empty( $bp->pages->register ) ) {
+		$pages[] = $bp->pages->register->id;
+	}
+
+	if ( ! empty( $pages ) ) {
+		$object->_default_query['post__not_in'] = $pages;
+	}
+
+	return $object;
+}
+add_filter( 'nav_menu_meta_box_object', 'bp_core_exclude_pages_from_nav_menu_admin', 11, 1 );
+
+/**
+ * Adds current page CSS classes to the parent BP page in a WP Page Menu.
+ *
+ * Because BuddyPress primarily uses virtual pages, we need a way to highlight
+ * the BP parent page during WP menu generation.  This function checks the
+ * current BP component against the current page in the WP menu to see if we
+ * should highlight the WP page.
+ *
+ * @since 2.2.0
+ *
+ * @param array   $retval CSS classes for the current menu page in the menu.
+ * @param WP_Post $page   The page properties for the current menu item.
+ *
+ * @return array
+ */
+function bp_core_menu_highlight_parent_page( $retval, $page ) {
+	if ( ! is_buddypress() ) {
+		return $retval;
+	}
+
+	$page_id = false;
+
+	// loop against all BP component pages
+	foreach ( (array) buddypress()->pages as $component => $bp_page ) {
+		// handles the majority of components
+		if ( bp_is_current_component( $component ) ) {
+	                $page_id = (int) $bp_page->id;
+		}
+
+		// stop if not on a user page
+		if ( ! bp_is_user() && ! empty( $page_id ) ) {
+			break;
+		}
+
+		// members component requires an explicit check due to overlapping components
+		if ( bp_is_user() && 'members' === $component ) {
+			$page_id = (int) $bp_page->id;
+			break;
+		}
+	}
+
+	// duplicate some logic from Walker_Page::start_el() to highlight menu items
+	if ( ! empty( $page_id ) ) {
+		$_bp_page = get_post( $page_id );
+		if ( in_array( $page->ID, $_bp_page->ancestors, true ) ) {
+			$retval[] = 'current_page_ancestor';
+		}
+		if ( $page->ID === $page_id ) {
+			$retval[] = 'current_page_item';
+		} elseif ( $_bp_page && $page->ID === $_bp_page->post_parent ) {
+			$retval[] = 'current_page_parent';
+		}
+	}
+
+	$retval = array_unique( $retval );
+
+	return $retval;
+}
+add_filter( 'page_css_class', 'bp_core_menu_highlight_parent_page', 10, 2 );
+
+/**
+ * Adds current page CSS classes to the parent BP page in a WP Nav Menu.
+ *
+ * When {@link wp_nav_menu()} is used, this function helps to highlight the
+ * current BP parent page during nav menu generation.
+ *
+ * @since 2.2.0
+ *
+ * @param array   $retval CSS classes for the current nav menu item in the menu.
+ * @param WP_Post $item   The properties for the current nav menu item.
+ *
+ * @return array
+ */
+function bp_core_menu_highlight_nav_menu_item( $retval, $item ) {
+	// If we're not on a BP page or if the current nav item is not a page, stop!
+	if ( ! is_buddypress() || 'page' !== $item->object ) {
+		return $retval;
+	}
+
+	// get the WP page
+	$page   = get_post( $item->object_id );
+
+	// see if we should add our highlight CSS classes for the page
+	$retval = bp_core_menu_highlight_parent_page( $retval, $page );
+
+	return $retval;
+}
+add_filter( 'nav_menu_css_class', 'bp_core_menu_highlight_nav_menu_item', 10, 2 );
+
+/**
+ * Set "From" name in outgoing email to the site name.
+ *
+ * @uses bp_get_option() fetches the value for a meta_key in the wp_X_options table.
+ *
+ * @return string The blog name for the root blog.
+ */
+function bp_core_email_from_name_filter() {
+
+	/**
+	 * Filters the "From" name in outgoing email to the site name.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value Value to set the "From" name to.
+	 */
+ 	return apply_filters( 'bp_core_email_from_name_filter', bp_get_option( 'blogname', 'WordPress' ) );
+}
+add_filter( 'wp_mail_from_name', 'bp_core_email_from_name_filter' );
+
+/**
+ * Filter the blog post comments array and insert BuddyPress URLs for users.
+ *
+ * @param array $comments The array of comments supplied to the comments template.
+ * @param int   $post_id  The post ID.
+ *
+ * @return array $comments The modified comment array.
+ */
+function bp_core_filter_comments( $comments, $post_id ) {
+	global $wpdb;
+
+	foreach( (array) $comments as $comment ) {
+		if ( $comment->user_id )
+			$user_ids[] = $comment->user_id;
+	}
+
+	if ( empty( $user_ids ) )
+		return $comments;
+
+	$user_ids = implode( ',', wp_parse_id_list( $user_ids ) );
+
+	if ( !$userdata = $wpdb->get_results( "SELECT ID as user_id, user_login, user_nicename FROM {$wpdb->users} WHERE ID IN ({$user_ids})" ) )
+		return $comments;
+
+	foreach( (array) $userdata as $user )
+		$users[$user->user_id] = bp_core_get_user_domain( $user->user_id, $user->user_nicename, $user->user_login );
+
+	foreach( (array) $comments as $i => $comment ) {
+		if ( !empty( $comment->user_id ) ) {
+			if ( !empty( $users[$comment->user_id] ) )
+				$comments[$i]->comment_author_url = $users[$comment->user_id];
+		}
+	}
+
+	return $comments;
+}
+add_filter( 'comments_array', 'bp_core_filter_comments', 10, 2 );
+
+/**
+ * When a user logs in, redirect him in a logical way.
+ *
+ * @uses apply_filters() Filter 'bp_core_login_redirect' to modify where users
+ *       are redirected to on login.
+ *
+ * @param string  $redirect_to     The URL to be redirected to, sanitized in wp-login.php.
+ * @param string  $redirect_to_raw The unsanitized redirect_to URL ($_REQUEST['redirect_to']).
+ * @param WP_User $user            The WP_User object corresponding to a successfully
+ *                                 logged-in user. Otherwise a WP_Error object.
+ *
+ * @return string The redirect URL.
+ */
+function bp_core_login_redirect( $redirect_to, $redirect_to_raw, $user ) {
+
+	// Only modify the redirect if we're on the main BP blog
+	if ( !bp_is_root_blog() ) {
+		return $redirect_to;
+	}
+
+	// Only modify the redirect once the user is logged in
+	if ( !is_a( $user, 'WP_User' ) ) {
+		return $redirect_to;
+	}
+
+	/**
+	 * Filters whether or not to redirect.
+	 *
+	 * Allows plugins to have finer grained control of redirect upon login.
+	 *
+	 * @since 1.6.0
+	 *
+  * @param bool    $value           Whether or not to redirect.
+	 * @param string  $redirect_to     Sanitized URL to be redirected to.
+	 * @param string  $redirect_to_raw Unsanitized URL to be redirected to.
+	 * @param WP_User $user            The WP_User object corresponding to a
+	 *                                 successfully logged in user.
+	 */
+	$maybe_redirect = apply_filters( 'bp_core_login_redirect', false, $redirect_to, $redirect_to_raw, $user );
+	if ( false !== $maybe_redirect ) {
+		return $maybe_redirect;
+	}
+
+	// If a 'redirect_to' parameter has been passed that contains 'wp-admin', verify that the
+	// logged-in user has any business to conduct in the Dashboard before allowing the
+	// redirect to go through
+	if ( !empty( $redirect_to ) && ( false === strpos( $redirect_to, 'wp-admin' ) || user_can( $user, 'edit_posts' ) ) ) {
+		return $redirect_to;
+	}
+
+	if ( false === strpos( wp_get_referer(), 'wp-login.php' ) && false === strpos( wp_get_referer(), 'activate' ) && empty( $_REQUEST['nr'] ) ) {
+		return wp_get_referer();
+	}
+
+	/**
+	 * Filters the URL to redirect users to upon successful login.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $value URL to redirect to.
+	 */
+	return apply_filters( 'bp_core_login_redirect_to', bp_get_root_domain() );
+}
+add_filter( 'bp_login_redirect', 'bp_core_login_redirect', 10, 3 );
+
+/**
+ * Replace the generated password in the welcome email with '[User Set]'.
+ *
+ * On a standard BP installation, users who register themselves also set their
+ * own passwords. Therefore there is no need for the insecure practice of
+ * emailing the plaintext password to the user in the welcome email.
+ *
+ * This filter will not fire when a user is registered by the site admin.
+ *
+ * @param string $welcome_email Complete email passed through WordPress.
+ *
+ * @return string Filtered $welcome_email with the password replaced
+ *                by '[User Set]'.
+ */
+function bp_core_filter_user_welcome_email( $welcome_email ) {
+
+	// Don't touch the email when a user is registered by the site admin
+	if ( ( is_admin() || is_network_admin() ) && buddypress()->members->admin->signups_page != get_current_screen()->id ) {
+		return $welcome_email;
+	}
+
+	if ( strpos( bp_get_requested_url(), 'wp-activate.php' ) !== false ) {
+		return $welcome_email;
+	}
+
+	// Don't touch the email if we don't have a custom registration template
+	if ( ! bp_has_custom_signup_page() ) {
+		return $welcome_email;
+	}
+
+	// [User Set] Replaces 'PASSWORD' in welcome email; Represents value set by user
+	return str_replace( 'PASSWORD', __( '[User Set]', 'buddypress' ), $welcome_email );
+}
+add_filter( 'update_welcome_user_email', 'bp_core_filter_user_welcome_email' );
+
+/**
+ * Replace the generated password in the welcome email with '[User Set]'.
+ *
+ * On a standard BP installation, users who register themselves also set their
+ * own passwords. Therefore there is no need for the insecure practice of
+ * emailing the plaintext password to the user in the welcome email.
+ *
+ * This filter will not fire when a user is registered by the site admin.
+ *
+ * @param string $welcome_email Complete email passed through WordPress.
+ * @param int    $blog_id       ID of the blog user is joining.
+ * @param int    $user_id       ID of the user joining.
+ * @param string $password      Password of user.
+ *
+ * @return string Filtered $welcome_email with $password replaced by '[User Set]'.
+ */
+function bp_core_filter_blog_welcome_email( $welcome_email, $blog_id, $user_id, $password ) {
+
+	// Don't touch the email when a user is registered by the site admin
+	if ( ( is_admin() || is_network_admin() ) && buddypress()->members->admin->signups_page != get_current_screen()->id ) {
+		return $welcome_email;
+	}
+
+	// Don't touch the email if we don't have a custom registration template
+	if ( ! bp_has_custom_signup_page() )
+		return $welcome_email;
+
+	// [User Set] Replaces $password in welcome email; Represents value set by user
+	return str_replace( $password, __( '[User Set]', 'buddypress' ), $welcome_email );
+}
+add_filter( 'update_welcome_email', 'bp_core_filter_blog_welcome_email', 10, 4 );
+
+/**
+ * Notify new users of a successful registration (with blog).
+ *
+ * This function filter's WP's 'wpmu_signup_blog_notification', and replaces
+ * WP's default welcome email with a BuddyPress-specific message.
+ *
+ * @see wpmu_signup_blog_notification() for a description of parameters.
+ *
+ * @param string $domain     The new blog domain.
+ * @param string $path       The new blog path.
+ * @param string $title      The site title.
+ * @param string $user       The user's login name.
+ * @param string $user_email The user's email address.
+ * @param string $key        The activation key created in wpmu_signup_blog()
+ * @param array  $meta       By default, contains the requested privacy setting and
+ *                           lang_id.
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_core_activation_signup_blog_notification( $domain, $path, $title, $user, $user_email, $key, $meta ) {
+
+	// Set up activation link
+	$activate_url = bp_get_activation_page() ."?key=$key";
+	$activate_url = esc_url( $activate_url );
+
+	// Email contents
+	$message = sprintf( __( "%1\$s,\n\n\n\nThanks for registering! To complete the activation of your account and blog, please click the following link:\n\n%2\$s\n\n\n\nAfter you activate, you can visit your blog here:\n\n%3\$s", 'buddypress' ), $user, $activate_url, esc_url( "http://{$domain}{$path}" ) );
+	$subject = bp_get_email_subject( array( 'text' => sprintf( __( 'Activate %s', 'buddypress' ), 'http://' . $domain . $path ) ) );
+
+	/**
+	 * Filters the email that the notification is going to upon successful registration with blog.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $user_email The user's email address.
+	 * @param string $domain     The new blog domain.
+	 * @param string $path       The new blog path.
+	 * @param string $title      The site title.
+	 * @param string $user       The user's login name.
+	 * @param string $user_email The user's email address.
+	 * @param string $key        The activation key created in wpmu_signup_blog().
+	 * @param array  $meta       Array of meta values for the created site.
+	 */
+	$to      = apply_filters( 'bp_core_activation_signup_blog_notification_to',   $user_email, $domain, $path, $title, $user, $user_email, $key, $meta );
+
+	/**
+	 * Filters the subject that the notification uses upon successful registration with blog.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $subject    The subject to use.
+	 * @param string $domain     The new blog domain.
+	 * @param string $path       The new blog path.
+	 * @param string $title      The site title.
+	 * @param string $user       The user's login name.
+	 * @param string $user_email The user's email address.
+	 * @param string $key        The activation key created in wpmu_signup_blog().
+	 * @param array  $meta       Array of meta values for the created site.
+	 */
+	$subject = apply_filters( 'bp_core_activation_signup_blog_notification_subject', $subject, $domain, $path, $title, $user, $user_email, $key, $meta );
+
+	/**
+	 * Filters the message that the notification uses upon successful registration with blog.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $message    The message to use.
+	 * @param string $domain     The new blog domain.
+	 * @param string $path       The new blog path.
+	 * @param string $title      The site title.
+	 * @param string $user       The user's login name.
+	 * @param string $user_email The user's email address.
+	 * @param string $key        The activation key created in wpmu_signup_blog().
+	 * @param array  $meta       Array of meta values for the created site.
+	 */
+	$message = apply_filters( 'bp_core_activation_signup_blog_notification_message', $message, $domain, $path, $title, $user, $user_email, $key, $meta );
+
+	// Send the email
+	wp_mail( $to, $subject, $message );
+
+	// Set up the $admin_email to pass to the filter
+	$admin_email = bp_get_option( 'admin_email' );
+
+	/**
+	 * Fires after the sending of the notification to new users for successful registration with blog.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $admin_email Admin Email address for the site.
+	 * @param string $subject     Subject used in the notification email.
+	 * @param string $message     Message used in the notification email.
+	 * @param string $domain      The new blog domain.
+	 * @param string $path        The new blog path.
+	 * @param string $title       The site title.
+	 * @param string $user        The user's login name.
+	 * @param string $user_email  The user's email address.
+	 * @param string $key         The activation key created in wpmu_signup_blog().
+	 * @param array  $meta        Array of meta values for the created site.
+	 */
+	do_action( 'bp_core_sent_blog_signup_email', $admin_email, $subject, $message, $domain, $path, $title, $user, $user_email, $key, $meta );
+
+	// Return false to stop the original WPMU function from continuing
+	return false;
+}
+add_filter( 'wpmu_signup_blog_notification', 'bp_core_activation_signup_blog_notification', 1, 7 );
+
+/**
+ * Notify new users of a successful registration (without blog).
+ *
+ * @see wpmu_signup_user_notification() for a full description of params.
+ *
+ * @param string $user       The user's login name.
+ * @param string $user_email The user's email address.
+ * @param string $key        The activation key created in wpmu_signup_user()
+ * @param array  $meta       By default, an empty array.
+ *
+ * @return bool|string True on success, false on failure.
+ */
+function bp_core_activation_signup_user_notification( $user, $user_email, $key, $meta ) {
+
+	if ( is_admin() ) {
+		// If the user is created from the WordPress Add User screen, don't send BuddyPress signup notifications
+		if( in_array( get_current_screen()->id, array( 'user', 'user-network' ) ) ) {
+			// If the Super Admin want to skip confirmation email
+			if ( isset( $_POST[ 'noconfirmation' ] ) && is_super_admin() ) {
+				return false;
+
+			// WordPress will manage the signup process
+			} else {
+				return $user;
+			}
+
+		/**
+		 * There can be a case where the user was created without the skip confirmation
+		 * And the super admin goes in pending accounts to resend it. In this case, as the
+		 * meta['password'] is not set, the activation url must be WordPress one
+		 */
+		} elseif ( buddypress()->members->admin->signups_page == get_current_screen()->id ) {
+			$is_hashpass_in_meta = maybe_unserialize( $meta );
+
+			if ( empty( $is_hashpass_in_meta['password'] ) ) {
+				return $user;
+			}
+		}
+	}
+
+	// Set up activation link
+	$activate_url = bp_get_activation_page() . "?key=$key";
+	$activate_url = esc_url( $activate_url );
+
+	// Email contents
+	$message = sprintf( __( "Thanks for registering! To complete the activation of your account please click the following link:\n\n%1\$s\n\n", 'buddypress' ), $activate_url );
+	$subject = bp_get_email_subject( array( 'text' => __( 'Activate Your Account', 'buddypress' ) ) );
+
+	/**
+	 * Filters the email that the notification is going to upon successful registration without blog.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $user_email The user's email address.
+	 * @param string $user       The user's login name.
+	 * @param string $user_email The user's email address.
+	 * @param string $key        The activation key created in wpmu_signup_blog().
+	 * @param array  $meta       Array of meta values for the created site.
+	 */
+	$to      = apply_filters( 'bp_core_activation_signup_user_notification_to',   $user_email, $user, $user_email, $key, $meta );
+
+	/**
+	 * Filters the subject that the notification uses upon successful registration without blog.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $subject    The subject to use.
+	 * @param string $user       The user's login name.
+	 * @param string $user_email The user's email address.
+	 * @param string $key        The activation key created in wpmu_signup_blog().
+	 * @param array  $meta       Array of meta values for the created site.
+	 */
+	$subject = apply_filters( 'bp_core_activation_signup_user_notification_subject', $subject, $user, $user_email, $key, $meta );
+
+	/**
+	 * Filters the message that the notification uses upon successful registration without blog.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $message    The message to use.
+	 * @param string $user       The user's login name.
+	 * @param string $user_email The user's email address.
+	 * @param string $key        The activation key created in wpmu_signup_blog().
+	 * @param array  $meta       Array of meta values for the created site.
+	 */
+	$message = apply_filters( 'bp_core_activation_signup_user_notification_message', $message, $user, $user_email, $key, $meta );
+
+	// Send the email
+	wp_mail( $to, $subject, $message );
+
+	// Set up the $admin_email to pass to the filter
+	$admin_email = bp_get_option( 'admin_email' );
+
+	/**
+	 * Fires after the sending of the notification to new users for successful registration without blog.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $admin_email Admin Email address for the site.
+	 * @param string $subject     Subject used in the notification email.
+	 * @param string $message     Message used in the notification email.
+	 * @param string $user        The user's login name.
+	 * @param string $user_email  The user's email address.
+	 * @param string $key         The activation key created in wpmu_signup_blog().
+	 * @param array  $meta        Array of meta values for the created site. Default empty array.
+	 */
+	do_action( 'bp_core_sent_user_signup_email', $admin_email, $subject, $message, $user, $user_email, $key, $meta );
+
+	// Return false to stop the original WPMU function from continuing
+	return false;
+}
+add_filter( 'wpmu_signup_user_notification', 'bp_core_activation_signup_user_notification', 1, 4 );
+
+/**
+ * Filter the page title for BuddyPress pages.
+ *
+ * @since 1.5.0
+ *
+ * @see wp_title()
+ * @global object $bp BuddyPress global settings.
+ *
+ * @param  string $title       Original page title.
+ * @param  string $sep         How to separate the various items within the page title.
+ * @param  string $seplocation Direction to display title.
+ *
+ * @return string              New page title.
+ */
+function bp_modify_page_title( $title = '', $sep = '&raquo;', $seplocation = 'right' ) {
+	global $paged, $page, $_wp_theme_features;
+
+	// Get the BuddyPress title parts
+	$bp_title_parts = bp_get_title_parts( $seplocation );
+
+	// If not set, simply return the original title
+	if ( ! $bp_title_parts ) {
+		return $title;
+	}
+
+	// Get the blog name, so we can check if the original $title included it
+	$blogname = get_bloginfo( 'name', 'display' );
+
+	/**
+	 * Are we going to fake 'title-tag' theme functionality?
+	 *
+	 * @link https://buddypress.trac.wordpress.org/ticket/6107
+	 * @see wp_title()
+	 */
+	$title_tag_compatibility = (bool) ( ! empty( $_wp_theme_features['title-tag'] ) || strstr( $title, $blogname ) );
+
+	// Append the site title to title parts if theme supports title tag
+	if ( true === $title_tag_compatibility ) {
+		$bp_title_parts['site'] = $blogname;
+
+		if ( ( $paged >= 2 || $page >= 2 ) && ! is_404() && ! bp_is_single_activity() ) {
+			$bp_title_parts['page'] = sprintf( __( 'Page %s', 'buddypress' ), max( $paged, $page ) );
+		}
+	}
+
+	// Pad the separator with 1 space on each side
+	$prefix = str_pad( $sep, strlen( $sep ) + 2, ' ', STR_PAD_BOTH );
+
+	// Join the parts together
+	$new_title = join( $prefix, array_filter( $bp_title_parts ) );
+
+	// Append the prefix for pre `title-tag` compatibility
+	if ( false === $title_tag_compatibility ) {
+		$new_title = $new_title . $prefix;
+	}
+
+	/**
+	 * Filters the older 'wp_title' page title for BuddyPress pages.
+	 *
+	 * @since  1.5.0
+	 *
+	 * @param  string $new_title   The BuddyPress page title.
+	 * @param  string $title       The original WordPress page title.
+	 * @param  string $sep         The title parts separator.
+	 * @param  string $seplocation Location of the separator (left or right).
+	 */
+	return apply_filters( 'bp_modify_page_title', $new_title, $title, $sep, $seplocation );
+}
+add_filter( 'wp_title',             'bp_modify_page_title', 20, 3 );
+add_filter( 'bp_modify_page_title', 'wptexturize'                 );
+add_filter( 'bp_modify_page_title', 'convert_chars'               );
+add_filter( 'bp_modify_page_title', 'esc_html'                    );
+
+/**
+ * Filter the document title for BuddyPress pages.
+ *
+ * @since 2.4.3
+ *
+ * @param array $title The WordPress document title parts
+ * @return array the unchanged title parts or the BuddyPress ones
+ */
+function bp_modify_document_title_parts( $title = array() ) {
+	// Get the BuddyPress title parts
+	$bp_title_parts = bp_get_title_parts();
+
+	// If not set, simply return the original title
+	if ( ! $bp_title_parts ) {
+		return $title;
+	}
+
+	// Get the separator used by wp_get_document_title()
+	$sep = apply_filters( 'document_title_separator', '-' );
+
+	// Build the BuddyPress portion of the title.
+	// We don't need to sanitize this as WordPress will take care of it.
+	$bp_title = array(
+		'title' => join( " $sep ", $bp_title_parts )
+	);
+
+	// Add the pagination number if needed (not sure if this is necessary).
+	if ( isset( $title['page'] ) && ! bp_is_single_activity() ) {
+		$bp_title['page'] = $title['page'];
+	}
+
+	// Add the sitename if needed.
+	if ( isset( $title['site'] ) ) {
+		$bp_title['site'] = $title['site'];
+	}
+
+	/**
+	 * Filters BuddyPress title parts that will be used into the document title.
+	 *
+	 * @since  2.4.3
+	 *
+	 * @param  array $bp_title   The BuddyPress page title parts.
+	 * @param  array $title      The original WordPress title parts.
+	 */
+	return apply_filters( 'bp_modify_document_title_parts', $bp_title, $title );
+}
+add_filter( 'document_title_parts', 'bp_modify_document_title_parts', 20, 1 );
+
+/**
+ * Add BuddyPress-specific items to the wp_nav_menu.
+ *
+ * @since 1.9.0
+ *
+ * @param WP_Post $menu_item The menu item.
+ *
+ * @return WP_Post The modified WP_Post object.
+ */
+function bp_setup_nav_menu_item( $menu_item ) {
+	if ( is_admin() ) {
+		return $menu_item;
+	}
+
+	// Prevent a notice error when using the customizer
+	$menu_classes = $menu_item->classes;
+
+	if ( is_array( $menu_classes ) ) {
+		$menu_classes = implode( ' ', $menu_item->classes);
+	}
+
+	// We use information stored in the CSS class to determine what kind of
+	// menu item this is, and how it should be treated
+	preg_match( '/\sbp-(.*)-nav/', $menu_classes, $matches );
+
+	// If this isn't a BP menu item, we can stop here
+	if ( empty( $matches[1] ) ) {
+		return $menu_item;
+	}
+
+	switch ( $matches[1] ) {
+		case 'login' :
+			if ( is_user_logged_in() ) {
+				$menu_item->_invalid = true;
+			} else {
+				$menu_item->url = wp_login_url( bp_get_requested_url() );
+			}
+
+			break;
+
+		case 'logout' :
+			if ( ! is_user_logged_in() ) {
+				$menu_item->_invalid = true;
+			} else {
+				$menu_item->url = wp_logout_url( bp_get_requested_url() );
+			}
+
+			break;
+
+		// Don't show the Register link to logged-in users
+		case 'register' :
+			if ( is_user_logged_in() ) {
+				$menu_item->_invalid = true;
+			}
+
+			break;
+
+		// All other BP nav items are specific to the logged-in user,
+		// and so are not relevant to logged-out users
+		default:
+			if ( is_user_logged_in() ) {
+				$menu_item->url = bp_nav_menu_get_item_url( $matches[1] );
+			} else {
+				$menu_item->_invalid = true;
+			}
+
+			break;
+	}
+
+	// If component is deactivated, make sure menu item doesn't render
+	if ( empty( $menu_item->url ) ) {
+		$menu_item->_invalid = true;
+
+	// Highlight the current page
+	} else {
+		$current = bp_get_requested_url();
+		if ( strpos( $current, $menu_item->url ) !== false ) {
+			if ( is_array( $menu_item->classes ) ) {
+				$menu_item->classes[] = 'current_page_item';
+				$menu_item->classes[] = 'current-menu-item';
+			} else {
+				$menu_item->classes = array( 'current_page_item', 'current-menu-item' );
+			}
+		}
+	}
+
+	return $menu_item;
+}
+add_filter( 'wp_setup_nav_menu_item', 'bp_setup_nav_menu_item', 10, 1 );
+
+/**
+ * Populate BuddyPress user nav items for the customizer
+ *
+ * @since  2.3.3
+ *
+ * @param  array   $items  The array of menu items
+ * @param  string  $type   The requested type
+ * @param  string  $object The requested object name
+ * @param  integer $page   The page num being requested
+ * @return array           The paginated BuddyPress user nav items.
+ */
+function bp_customizer_nav_menus_get_items( $items = array(), $type = '', $object = '', $page = 0 ) {
+	if ( 'bp_loggedin_nav' === $object ) {
+		$bp_items = bp_nav_menu_get_loggedin_pages();
+	} elseif ( 'bp_loggedout_nav' === $object ) {
+		$bp_items = bp_nav_menu_get_loggedout_pages();
+	} else {
+		return $items;
+	}
+
+	foreach ( $bp_items as $bp_item ) {
+		$items[] = array(
+			'id'         => "bp-{$bp_item->post_excerpt}",
+			'title'      => html_entity_decode( $bp_item->post_title, ENT_QUOTES, get_bloginfo( 'charset' ) ),
+			'type'       => $type,
+			'url'        => esc_url_raw( $bp_item->guid ),
+			'classes'    => "bp-menu bp-{$bp_item->post_excerpt}-nav",
+			'type_label' => _x( 'Custom Link', 'customizer menu type label', 'buddypress' ),
+			'object'     => $object,
+			'object_id'  => -1,
+		);
+	}
+
+	return array_slice( $items, 10 * $page, 10 );
+}
+add_filter( 'customize_nav_menu_available_items', 'bp_customizer_nav_menus_get_items', 10, 4 );
+
+/**
+ * Set BuddyPress item navs for the customizer
+ *
+ * @since  2.3.3
+ *
+ * @param  array $item_types An associative array structured for the customizer.
+ * @return array $item_types An associative array structured for the customizer.
+ */
+function bp_customizer_nav_menus_set_item_types( $item_types = array() ) {
+	$item_types = array_merge( $item_types, array(
+		'bp_loggedin_nav' => array(
+			'title'  => _x( 'BuddyPress (logged-in)', 'customizer menu section title', 'buddypress' ),
+			'type'   => 'bp_nav',
+			'object' => 'bp_loggedin_nav',
+		),
+		'bp_loggedout_nav' => array(
+			'title'  => _x( 'BuddyPress (logged-out)', 'customizer menu section title', 'buddypress' ),
+			'type'   => 'bp_nav',
+			'object' => 'bp_loggedout_nav',
+		),
+	) );
+
+	return $item_types;
+}
+add_filter( 'customize_nav_menu_available_item_types', 'bp_customizer_nav_menus_set_item_types', 10, 1 );
+
+/**
+ * Filter SQL query strings to swap out the 'meta_id' column.
+ *
+ * WordPress uses the meta_id column for commentmeta and postmeta, and so
+ * hardcodes the column name into its *_metadata() functions. BuddyPress, on
+ * the other hand, uses 'id' for the primary column. To make WP's functions
+ * usable for BuddyPress, we use this just-in-time filter on 'query' to swap
+ * 'meta_id' with 'id.
+ *
+ * @since 2.0.0
+ *
+ * @access private Do not use.
+ *
+ * @param string $q SQL query.
+ *
+ * @return string
+ */
+function bp_filter_metaid_column_name( $q ) {
+	/*
+	 * Replace quoted content with __QUOTE__ to avoid false positives.
+	 * This regular expression will match nested quotes.
+	 */
+	$quoted_regex = "/'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'/s";
+	preg_match_all( $quoted_regex, $q, $quoted_matches );
+	$q = preg_replace( $quoted_regex, '__QUOTE__', $q );
+
+	$q = str_replace( 'meta_id', 'id', $q );
+
+	// Put quoted content back into the string.
+	if ( ! empty( $quoted_matches[0] ) ) {
+		for ( $i = 0; $i < count( $quoted_matches[0] ); $i++ ) {
+			$quote_pos = strpos( $q, '__QUOTE__' );
+			$q = substr_replace( $q, $quoted_matches[0][ $i ], $quote_pos, 9 );
+		}
+	}
+
+	return $q;
+}
+
+/**
+ * Filter the edit post link to avoid its display in BuddyPress pages.
+ *
+ * @since 2.1.0
+ *
+ * @param  string $edit_link The edit link.
+ * @param  int    $post_id   Post ID.
+ *
+ * @return bool|string Will be a boolean (false) if $post_id is 0. Will be a string (the unchanged edit link)
+ *                     otherwise
+ */
+function bp_core_filter_edit_post_link( $edit_link = '', $post_id = 0 ) {
+	if ( 0 === $post_id ) {
+		$edit_link = false;
+	}
+
+	return $edit_link;
+}
+
+/**
+ * Should BuddyPress load the mentions scripts and related assets, including results to prime the
+ * mentions suggestions?
+ *
+ * @since 2.2.0
+ *
+ * @param bool $load_mentions    True to load mentions assets, false otherwise.
+ * @param bool $mentions_enabled True if mentions are enabled.
+ *
+ * @return bool True if mentions scripts should be loaded.
+ */
+function bp_maybe_load_mentions_scripts_for_blog_content( $load_mentions, $mentions_enabled ) {
+	if ( ! $mentions_enabled ) {
+		return $load_mentions;
+	}
+
+	if ( $load_mentions || ( bp_is_blog_page() && is_singular() && comments_open() ) ) {
+		return true;
+	}
+
+	return $load_mentions;
+}
+add_filter( 'bp_activity_maybe_load_mentions_scripts', 'bp_maybe_load_mentions_scripts_for_blog_content', 10, 2 );
+
+/**
+ * Injects specific BuddyPress CSS classes into a widget sidebar.
+ *
+ * Helps to standardize styling of BuddyPress widgets within a theme that
+ * does not use dynamic CSS classes in their widget sidebar's 'before_widget'
+ * call.
+ *
+ * @since 2.4.0
+ * @access private
+ *
+ * @global array $wp_registered_widgets Current registered widgets.
+ * @param  array $params                Current sidebar params.
+ * @return array
+ */
+function _bp_core_inject_bp_widget_css_class( $params ) {
+	global $wp_registered_widgets;
+
+	$widget_id = $params[0]['widget_id'];
+
+	// If the current widget isn't a BuddyPress one, stop!
+	// We determine if a widget is a BuddyPress widget, if the widget class
+	// begins with 'bp_'.
+	if ( 0 !== strpos( $wp_registered_widgets[ $widget_id ]['callback'][0]->id_base, 'bp_' ) ) {
+		return $params;
+	}
+
+	// Dynamically add our widget CSS classes for BP widgets if not already there.
+	$classes = array();
+
+	// Try to find 'widget' CSS class.
+	if ( false === strpos( $params[0]['before_widget'], 'widget ' ) ) {
+		$classes[] = 'widget';
+	}
+
+	// Try to find 'buddypress' CSS class.
+	if ( false === strpos( $params[0]['before_widget'], ' buddypress' ) ) {
+		$classes[] = 'buddypress';
+	}
+
+	// Stop if widget already has our CSS classes.
+	if ( empty( $classes ) ) {
+		return $params;
+	}
+
+	// CSS injection time!
+	$params[0]['before_widget'] = str_replace( 'class="', 'class="' . implode( ' ', $classes ) . ' ', $params[0]['before_widget'] );
+
+	return $params;
+}
+add_filter( 'dynamic_sidebar_params', '_bp_core_inject_bp_widget_css_class' );
Index: /tags/2.4.3/src/bp-core/bp-core-functions.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-functions.php	(revision 10410)
@@ -0,0 +1,2606 @@
+<?php
+/**
+ * BuddyPress Common Functions.
+ *
+ * @package BuddyPress
+ * @subpackage Functions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/** Versions ******************************************************************/
+
+/**
+ * Output the BuddyPress version.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_version() To get the BuddyPress version.
+ */
+function bp_version() {
+	echo bp_get_version();
+}
+	/**
+	 * Return the BuddyPress version.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @return string The BuddyPress version.
+	 */
+	function bp_get_version() {
+		return buddypress()->version;
+	}
+
+/**
+ * Output the BuddyPress database version.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_db_version() To get the BuddyPress database version.
+ */
+function bp_db_version() {
+	echo bp_get_db_version();
+}
+	/**
+	 * Return the BuddyPress database version.
+	 *
+	 * @since 1.6.0
+	 * @return string The BuddyPress database version.
+	 */
+	function bp_get_db_version() {
+		return buddypress()->db_version;
+	}
+
+/**
+ * Output the BuddyPress database version.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_db_version_raw() To get the current database BuddyPress version.
+ */
+function bp_db_version_raw() {
+	echo bp_get_db_version_raw();
+}
+	/**
+	 * Return the BuddyPress database version.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @return string The BuddyPress version direct from the database.
+	 */
+	function bp_get_db_version_raw() {
+		$bp = buddypress();
+		return !empty( $bp->db_version_raw ) ? $bp->db_version_raw : 0;
+	}
+
+/** Functions *****************************************************************/
+
+/**
+ * Get the $wpdb base prefix, run through the 'bp_core_get_table_prefix' filter.
+ *
+ * The filter is intended primarily for use in multinetwork installations.
+ *
+ * @global object $wpdb WordPress database object.
+ *
+ * @return string Filtered database prefix.
+ */
+function bp_core_get_table_prefix() {
+	global $wpdb;
+
+	/**
+	 * Filters the $wpdb base prefix.
+	 *
+	 * Intended primarily for use in multinetwork installations.
+	 *
+	 * @since 1.2.6
+	 *
+	 * @param string $base_prefix Base prefix to use.
+	 */
+	return apply_filters( 'bp_core_get_table_prefix', $wpdb->base_prefix );
+}
+
+/**
+ * Sort an array of objects or arrays by a specific key/property.
+ *
+ * The main purpose for this function is so that you can avoid having to create
+ * your own awkward callback function for usort().
+ *
+ * @since 2.2.0
+ *
+ * @param  array      $items The items to be sorted. Its constituent items can be either associative arrays or objects.
+ * @param  string|int $key   The array index or property name to sort by.
+ * @param  string     $type  Sort type. 'alpha' for alphabetical, 'num' for numeric. Default: 'alpha'.
+ * @return array      $items The sorted array.
+ */
+function bp_sort_by_key( $items, $key, $type = 'alpha' ) {
+	usort( $items, create_function( '$a, $b', '
+		$values = array( 0 => false, 1 => false, );
+		$func_args = func_get_args();
+		foreach ( $func_args as $indexi => $index ) {
+			if ( isset( $index->' . $key . ' ) ) {
+				$values[ $indexi ] = $index->' . $key . ';
+			} elseif ( isset( $index["' . $key . '"] ) ) {
+				$values[ $indexi ] = $index["' . $key . '"];
+			}
+		}
+
+		if ( isset( $values[0], $values[1] ) ) {
+			if ( "num" === "' . $type . '" ) {
+				$cmp = $values[0] - $values[1];
+			} else {
+				$cmp = strcmp( $values[0], $values[1] );
+			}
+
+			if ( 0 > $cmp ) {
+				$retval = -1;
+			} elseif ( 0 < $cmp ) {
+				$retval = 1;
+			} else {
+				$retval = 0;
+			}
+			return $retval;
+		} else {
+			return 0;
+		}
+	') );
+
+	return $items;
+}
+
+/**
+ * Sort an array of objects or arrays by alphabetically sorting by a specific key/property.
+ *
+ * For instance, if you have an array of WordPress post objects, you can sort
+ * them by post_name as follows:
+ *     $sorted_posts = bp_alpha_sort_by_key( $posts, 'post_name' );
+ *
+ * @since 1.9.0
+ *
+ * @param  array      $items The items to be sorted. Its constituent items can be either associative arrays or objects.
+ * @param  string|int $key   The array index or property name to sort by.
+ * @return array      $items The sorted array.
+ */
+function bp_alpha_sort_by_key( $items, $key ) {
+	return bp_sort_by_key( $items, $key, 'alpha' );
+}
+
+/**
+ * Format numbers the BuddyPress way.
+ *
+ * @param int  $number   The number to be formatted.
+ * @param bool $decimals Whether to use decimals. See {@link number_format_i18n()}.
+ *
+ * @return string The formatted number.
+ */
+function bp_core_number_format( $number = 0, $decimals = false ) {
+
+	// Force number to 0 if needed
+	if ( ! is_numeric( $number ) ) {
+		$number = 0;
+	}
+
+	/**
+	 * Filters the BuddyPress formatted number.
+	 *
+	 * @since 1.2.4
+	 *
+	 * @param string $value    BuddyPress formatted value.
+	 * @param int    $number   The number to be formatted.
+	 * @param bool   $decimals Whether or not to use decimals.
+	 */
+	return apply_filters( 'bp_core_number_format', number_format_i18n( $number, $decimals ), $number, $decimals );
+}
+
+/**
+ * A utility for parsing individual function arguments into an array.
+ *
+ * The purpose of this function is to help with backward compatibility in cases where
+ *
+ *   function foo( $bar = 1, $baz = false, $barry = array(), $blip = false ) { // ...
+ *
+ * is deprecated in favor of
+ *
+ *   function foo( $args = array() ) {
+ *       $defaults = array(
+ *           'bar'  => 1,
+ *           'arg2' => false,
+ *           'arg3' => array(),
+ *           'arg4' => false,
+ *       );
+ *       $r = wp_parse_args( $args, $defaults ); // ...
+ *
+ * The first argument, $old_args_keys, is an array that matches the parameter positions (keys) to
+ * the new $args keys (values):
+ *
+ *   $old_args_keys = array(
+ *       0 => 'bar', // because $bar was the 0th parameter for foo()
+ *       1 => 'baz', // because $baz was the 1st parameter for foo()
+ *       2 => 'barry', // etc
+ *       3 => 'blip'
+ *   );
+ *
+ * For the second argument, $func_args, you should just pass the value of func_get_args().
+ *
+ * @since 1.6.0
+ *
+ * @param array $old_args_keys Old argument indexs, keyed to their positions.
+ * @param array $func_args     The parameters passed to the originating function.
+ *
+ * @return array $new_args The parsed arguments.
+ */
+function bp_core_parse_args_array( $old_args_keys, $func_args ) {
+	$new_args = array();
+
+	foreach( $old_args_keys as $arg_num => $arg_key ) {
+		if ( isset( $func_args[$arg_num] ) ) {
+			$new_args[$arg_key] = $func_args[$arg_num];
+		}
+	}
+
+	return $new_args;
+}
+
+/**
+ * Merge user defined arguments into defaults array.
+ *
+ * This function is used throughout BuddyPress 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. If no $filter_key is passed, no filters
+ * are applied.
+ *
+ * @since r7704
+ *
+ * @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 bp_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 ) ) {
+
+		/**
+		 * Filters the arguments key before parsing if filter key provided.
+		 *
+		 * This is a dynamic filter dependent on the specified key.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $r Array of arguments to use.
+		 */
+		$r = apply_filters( 'bp_before_' . $filter_key . '_parse_args', $r );
+	}
+
+	// Parse
+	if ( is_array( $defaults ) && !empty( $defaults ) ) {
+		$r = array_merge( $defaults, $r );
+	}
+
+	// Aggressively filter the args after the parse
+	if ( !empty( $filter_key ) ) {
+
+		/**
+		 * Filters the arguments key after parsing if filter key provided.
+		 *
+		 * This is a dynamic filter dependent on the specified key.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $r Array of parsed arguments.
+		 */
+		$r = apply_filters( 'bp_after_' . $filter_key . '_parse_args', $r );
+	}
+
+	// Return the parsed results
+	return $r;
+}
+
+/**
+ * Sanitizes a pagination argument based on both the request override and the
+ * original value submitted via a query argument, likely to a template class
+ * responsible for limiting the resultset of a template loop.
+ *
+ * @since 2.2.0
+ *
+ * @param  string $page_arg The $_REQUEST argument to look for.
+ * @param  int    $page     The original page value to fall back to.
+ *
+ * @return int              A sanitized integer value, good for pagination.
+ */
+function bp_sanitize_pagination_arg( $page_arg = '', $page = 1 ) {
+
+	// Check if request overrides exist
+	if ( isset( $_REQUEST[ $page_arg ] ) ) {
+
+		// Get the absolute integer value of the override
+		$int = absint( $_REQUEST[ $page_arg ] );
+
+		// If override is 0, do not use it. This prevents unlimited result sets.
+		// @see https://buddypress.trac.wordpress.org/ticket/5796
+		if ( $int ) {
+			$page = $int;
+		}
+	}
+
+	return intval( $page );
+}
+
+/**
+ * Sanitize an 'order' parameter for use in building SQL queries.
+ *
+ * Strings like 'DESC', 'desc', ' desc' will be interpreted into 'DESC'.
+ * Everything else becomes 'ASC'.
+ *
+ * @since 1.8.0
+ *
+ * @param string $order The 'order' string, as passed to the SQL constructor.
+ *
+ * @return string The sanitized value 'DESC' or 'ASC'.
+ */
+function bp_esc_sql_order( $order = '' ) {
+	$order = strtoupper( trim( $order ) );
+	return 'DESC' === $order ? 'DESC' : 'ASC';
+}
+
+/**
+ * Escape special characters in a SQL LIKE clause.
+ *
+ * In WordPress 4.0, like_escape() was deprecated, due to incorrect
+ * documentation and improper sanitization leading to a history of misuse. To
+ * maintain compatibility with versions of WP before 4.0, we duplicate the
+ * logic of the replacement, wpdb::esc_like().
+ *
+ * @since 2.1.0
+ *
+ * @see wpdb::esc_like() for more details on proper use.
+ *
+ * @param string $text The raw text to be escaped.
+ *
+ * @return string Text in the form of a LIKE phrase. Not SQL safe. Run through
+ *                wpdb::prepare() before use.
+ */
+function bp_esc_like( $text ) {
+	global $wpdb;
+
+	if ( method_exists( $wpdb, 'esc_like' ) ) {
+		return $wpdb->esc_like( $text );
+	} else {
+		return addcslashes( $text, '_%\\' );
+	}
+}
+
+/**
+ * Are we running username compatibility mode?
+ *
+ * @since 1.5.0
+ *
+ * @uses apply_filters() Filter 'bp_is_username_compatibility_mode' to alter.
+ * @todo Move to members component?
+ *
+ * @return bool False when compatibility mode is disabled, true when enabled.
+ *              Default: false.
+ */
+function bp_is_username_compatibility_mode() {
+
+	/**
+	 * Filters whether or not to use username compatibility mode.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $value Whether or not username compatibility mode should be used.
+	 */
+	return apply_filters( 'bp_is_username_compatibility_mode', defined( 'BP_ENABLE_USERNAME_COMPATIBILITY_MODE' ) && BP_ENABLE_USERNAME_COMPATIBILITY_MODE );
+}
+
+/**
+ * Should we use the WP Toolbar?
+ *
+ * The WP Toolbar, introduced in WP 3.1, is fully supported in BuddyPress as
+ * of BP 1.5. For BP 1.6, the WP Toolbar is the default.
+ *
+ * @since 1.5.0
+ *
+ * @uses apply_filters() Filter 'bp_use_wp_admin_bar' to alter.
+ *
+ * @return bool Default: true. False when WP Toolbar support is disabled.
+ */
+function bp_use_wp_admin_bar() {
+
+	// Default to true (to avoid loading deprecated BuddyBar code)
+	$use_admin_bar = true;
+
+	// Has the WP Toolbar constant been explicitly opted into?
+	if ( defined( 'BP_USE_WP_ADMIN_BAR' ) ) {
+		$use_admin_bar = (bool) BP_USE_WP_ADMIN_BAR;
+
+	// ...or is the old BuddyBar being forced back into use?
+	} elseif ( bp_force_buddybar( false ) ) {
+		$use_admin_bar = false;
+	}
+
+	/**
+	 * Filters whether or not to use the admin bar.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $use_admin_bar Whether or not to use the admin bar.
+	 */
+	return (bool) apply_filters( 'bp_use_wp_admin_bar', $use_admin_bar );
+}
+
+/** Directory *****************************************************************/
+
+/**
+ * Returns an array of core component IDs.
+ *
+ * @since 2.1.0
+ *
+ * @return array
+ */
+function bp_core_get_packaged_component_ids() {
+	$components = array(
+		'activity',
+		'members',
+		'groups',
+		'blogs',
+		'xprofile',
+		'friends',
+		'messages',
+		'settings',
+		'notifications',
+	);
+
+	// only add legacy forums if it is enabled
+	// prevents conflicts with bbPress, which also uses the same 'forums' id
+	if ( class_exists( 'BP_Forums_Component' ) ) {
+		$components[] = 'forums';
+	}
+
+	return $components;
+}
+
+/**
+ * Fetch a list of BP directory pages from the appropriate meta table.
+ *
+ * @since 1.5.0
+ *
+ * @param string $status 'active' to return only pages associated with active components, 'all' to return all saved
+ *                       pages. When running save routines, use 'all' to avoid removing data related to inactive
+ *                       components. Default: 'active'.
+ *
+ * @return array|string An array of page IDs, keyed by component names, or an
+ *                      empty string if the list is not found.
+ */
+function bp_core_get_directory_page_ids( $status = 'active' ) {
+	$page_ids = bp_get_option( 'bp-pages' );
+
+	// Ensure that empty indexes are unset. Should only matter in edge cases
+	if ( !empty( $page_ids ) && is_array( $page_ids ) ) {
+		foreach( (array) $page_ids as $component_name => $page_id ) {
+			if ( empty( $component_name ) || empty( $page_id ) ) {
+				unset( $page_ids[ $component_name ] );
+			}
+
+			// 'register' and 'activate' do not have components, but should be whitelisted.
+			if ( 'register' === $component_name || 'activate' === $component_name ) {
+				continue;
+			}
+
+			// Trashed pages should not appear in results.
+			if ( 'trash' == get_post_status( $page_id ) ) {
+				unset( $page_ids[ $component_name ] );
+
+			}
+
+			// Remove inactive component pages, if required.
+			if ( 'active' === $status && ! bp_is_active( $component_name ) ) {
+				unset( $page_ids[ $component_name ] );
+			}
+		}
+	}
+
+	/**
+	 * Filters the list of BP directory pages from the appropriate meta table.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $page_ids Array of directory pages.
+	 */
+	return apply_filters( 'bp_core_get_directory_page_ids', $page_ids );
+}
+
+/**
+ * Store the list of BP directory pages in the appropriate meta table.
+ *
+ * bp-pages data is stored in site_options (falls back to options on non-MS),
+ * in an array keyed by blog_id. This allows you to change your
+ * bp_get_root_blog_id() and go through the setup process again.
+ *
+ * @since 1.5.0
+ *
+ * @param array $blog_page_ids The IDs of the WP pages corresponding to BP
+ *                             component directories.
+ */
+function bp_core_update_directory_page_ids( $blog_page_ids ) {
+	bp_update_option( 'bp-pages', $blog_page_ids );
+}
+
+/**
+ * Get names and slugs for BuddyPress component directory pages.
+ *
+ * @since 1.5.0
+ *
+ * @return object Page names, IDs, and slugs.
+ */
+function bp_core_get_directory_pages() {
+	global $wpdb;
+
+	// Look in cache first
+	$pages = wp_cache_get( 'directory_pages', 'bp' );
+
+	if ( false === $pages ) {
+
+		// Set pages as standard class
+		$pages = new stdClass;
+
+		// Get pages and IDs
+		$page_ids = bp_core_get_directory_page_ids();
+		if ( !empty( $page_ids ) ) {
+
+			// Always get page data from the root blog, except on multiblog mode, when it comes
+			// from the current blog
+			$posts_table_name = bp_is_multiblog_mode() ? $wpdb->posts : $wpdb->get_blog_prefix( bp_get_root_blog_id() ) . 'posts';
+			$page_ids_sql     = implode( ',', wp_parse_id_list( $page_ids ) );
+			$page_names       = $wpdb->get_results( "SELECT ID, post_name, post_parent, post_title FROM {$posts_table_name} WHERE ID IN ({$page_ids_sql}) AND post_status = 'publish' " );
+
+			foreach ( (array) $page_ids as $component_id => $page_id ) {
+				foreach ( (array) $page_names as $page_name ) {
+					if ( $page_name->ID == $page_id ) {
+						if ( !isset( $pages->{$component_id} ) || !is_object( $pages->{$component_id} ) ) {
+							$pages->{$component_id} = new stdClass;
+						}
+
+						$pages->{$component_id}->name  = $page_name->post_name;
+						$pages->{$component_id}->id    = $page_name->ID;
+						$pages->{$component_id}->title = $page_name->post_title;
+						$slug[]                        = $page_name->post_name;
+
+						// Get the slug
+						while ( $page_name->post_parent != 0 ) {
+							$parent                 = $wpdb->get_results( $wpdb->prepare( "SELECT post_name, post_parent FROM {$posts_table_name} WHERE ID = %d", $page_name->post_parent ) );
+							$slug[]                 = $parent[0]->post_name;
+							$page_name->post_parent = $parent[0]->post_parent;
+						}
+
+						$pages->{$component_id}->slug = implode( '/', array_reverse( (array) $slug ) );
+					}
+
+					unset( $slug );
+				}
+			}
+		}
+
+		wp_cache_set( 'directory_pages', $pages, 'bp' );
+	}
+
+	/**
+	 * Filters the names and slugs for BuddyPress component directory pages.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param object $pages Object holding page names and slugs.
+	 */
+	return apply_filters( 'bp_core_get_directory_pages', $pages );
+}
+
+/**
+ * Creates necessary directory pages.
+ *
+ * Directory pages are those WordPress pages used by BP components to display
+ * content (eg, the 'groups' page created by BP).
+ *
+ * @since 1.7.0
+ *
+ * @param array  $components Components to create pages for.
+ * @param string $existing   'delete' if you want to delete existing page mappings
+ *                           and replace with new ones. Otherwise existing page mappings
+ *                           are kept, and the gaps filled in with new pages. Default: 'keep'.
+ */
+function bp_core_add_page_mappings( $components, $existing = 'keep' ) {
+
+	// If no value is passed, there's nothing to do.
+	if ( empty( $components ) ) {
+		return;
+	}
+
+	// Make sure that the pages are created on the root blog no matter which
+	// dashboard the setup is being run on.
+	if ( ! bp_is_root_blog() ) {
+		switch_to_blog( bp_get_root_blog_id() );
+	}
+
+	$pages = bp_core_get_directory_page_ids( 'all' );
+
+	// Delete any existing pages
+	if ( 'delete' === $existing ) {
+		foreach ( (array) $pages as $page_id ) {
+			wp_delete_post( $page_id, true );
+		}
+
+		$pages = array();
+	}
+
+	$page_titles = array(
+		'activity' => _x( 'Activity', 'Page title for the Activity directory.',       'buddypress' ),
+		'groups'   => _x( 'Groups',   'Page title for the Groups directory.',         'buddypress' ),
+		'sites'    => _x( 'Sites',    'Page title for the Sites directory.',          'buddypress' ),
+		'members'  => _x( 'Members',  'Page title for the Members directory.',        'buddypress' ),
+		'activate' => _x( 'Activate', 'Page title for the user activation screen.',   'buddypress' ),
+		'register' => _x( 'Register', 'Page title for the user registration screen.', 'buddypress' ),
+	);
+
+	$pages_to_create = array();
+	foreach ( array_keys( $components ) as $component_name ) {
+		if ( ! isset( $pages[ $component_name ] ) && isset( $page_titles[ $component_name ] ) ) {
+			$pages_to_create[ $component_name ] = $page_titles[ $component_name ];
+		}
+	}
+
+	// Register and Activate are not components, but need pages when
+	// registration is enabled
+	if ( bp_get_signup_allowed() ) {
+		foreach ( array( 'register', 'activate' ) as $slug ) {
+			if ( ! isset( $pages[ $slug ] ) ) {
+				$pages_to_create[ $slug ] = $page_titles[ $slug ];
+			}
+		}
+	}
+
+	// No need for a Sites directory unless we're on multisite
+	if ( ! is_multisite() && isset( $pages_to_create['sites'] ) ) {
+		unset( $pages_to_create['sites'] );
+	}
+
+	// Members must always have a page, no matter what
+	if ( ! isset( $pages['members'] ) && ! isset( $pages_to_create['members'] ) ) {
+		$pages_to_create['members'] = $page_titles['members'];
+	}
+
+	// Create the pages
+	foreach ( $pages_to_create as $component_name => $page_name ) {
+		$exists = get_page_by_path( $component_name );
+
+		// If page already exists, use it
+		if ( ! empty( $exists ) ) {
+			$pages[ $component_name ] = $exists->ID;
+		} else {
+			$pages[ $component_name ] = wp_insert_post( array(
+				'comment_status' => 'closed',
+				'ping_status'    => 'closed',
+				'post_status'    => 'publish',
+				'post_title'     => $page_name,
+				'post_type'      => 'page',
+			) );
+		}
+	}
+
+	// Save the page mapping
+	bp_update_option( 'bp-pages', $pages );
+
+	// If we had to switch_to_blog, go back to the original site.
+	if ( ! bp_is_root_blog() ) {
+		restore_current_blog();
+	}
+}
+
+/**
+ * Remove the entry from bp_pages when the corresponding WP page is deleted.
+ *
+ * Bails early on multisite installations when not viewing the root site.
+ * @link https://buddypress.trac.wordpress.org/ticket/6226
+ *
+ * @since 2.2.0
+ *
+ * @param int $post_id Post ID.
+ */
+function bp_core_on_directory_page_delete( $post_id ) {
+
+	// Stop if we are not on the main BP root blog
+	if ( ! bp_is_root_blog() ) {
+		return;
+	}
+
+	$page_ids       = bp_core_get_directory_page_ids( 'all' );
+	$component_name = array_search( $post_id, $page_ids );
+
+	if ( ! empty( $component_name ) ) {
+		unset( $page_ids[ $component_name ] );
+	}
+
+	bp_core_update_directory_page_ids( $page_ids );
+}
+add_action( 'delete_post', 'bp_core_on_directory_page_delete' );
+
+/**
+ * Create a default component slug from a WP page root_slug.
+ *
+ * Since 1.5, BP components get their root_slug (the slug used immediately
+ * following the root domain) from the slug of a corresponding WP page.
+ *
+ * E.g. if your BP installation at example.com has its members page at
+ * example.com/community/people, $bp->members->root_slug will be
+ * 'community/people'.
+ *
+ * By default, this function creates a shorter version of the root_slug for
+ * use elsewhere in the URL, by returning the content after the final '/'
+ * in the root_slug ('people' in the example above).
+ *
+ * Filter on 'bp_core_component_slug_from_root_slug' to override this method
+ * in general, or define a specific component slug constant (e.g.
+ * BP_MEMBERS_SLUG) to override specific component slugs.
+ *
+ * @since 1.5.0
+ *
+ * @param string $root_slug The root slug, which comes from $bp->pages->[component]->slug.
+ *
+ * @return string The short slug for use in the middle of URLs.
+ */
+function bp_core_component_slug_from_root_slug( $root_slug ) {
+	$slug_chunks = explode( '/', $root_slug );
+ 	$slug        = array_pop( $slug_chunks );
+
+	/**
+	 * Filters the default component slug from a WP page root_slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $slug      Short slug for use in the middle of URLs.
+	 * @param string $root_slug The root slug which comes from $bp->pages-[component]->slug.
+	 */
+ 	return apply_filters( 'bp_core_component_slug_from_root_slug', $slug, $root_slug );
+}
+
+/**
+ * Add support for a top-level ("root") component.
+ *
+ * This function originally (pre-1.5) let plugins add support for pages in the
+ * root of the install. These root level pages are now handled by actual
+ * WordPress pages and this function is now a convenience for compatibility
+ * with the new method.
+ *
+ * @param string $slug The slug of the component being added to the root list.
+ */
+function bp_core_add_root_component( $slug ) {
+	$bp = buddypress();
+
+	if ( empty( $bp->pages ) ) {
+		$bp->pages = bp_core_get_directory_pages();
+	}
+
+	$match = false;
+
+	// Check if the slug is registered in the $bp->pages global
+	foreach ( (array) $bp->pages as $key => $page ) {
+		if ( $key == $slug || $page->slug == $slug ) {
+			$match = true;
+		}
+	}
+
+	// Maybe create the add_root array
+	if ( empty( $bp->add_root ) ) {
+		$bp->add_root = array();
+	}
+
+	// If there was no match, add a page for this root component
+	if ( empty( $match ) ) {
+		$add_root_items   = $bp->add_root();
+		$add_root_items[] = $slug;
+		$bp->add_root     = $add_root_items;
+	}
+
+	// Make sure that this component is registered as requiring a top-level directory
+	if ( isset( $bp->{$slug} ) ) {
+		$bp->loaded_components[$bp->{$slug}->slug] = $bp->{$slug}->id;
+		$bp->{$slug}->has_directory = true;
+	}
+}
+
+/**
+ * Create WordPress pages to be used as BP component directories.
+ */
+function bp_core_create_root_component_page() {
+
+	// Get BuddyPress
+	$bp = buddypress();
+
+	$new_page_ids = array();
+
+	foreach ( (array) $bp->add_root as $slug ) {
+		$new_page_ids[ $slug ] = wp_insert_post( array(
+			'comment_status' => 'closed',
+			'ping_status'    => 'closed',
+			'post_title'     => ucwords( $slug ),
+			'post_status'    => 'publish',
+			'post_type'      => 'page'
+		) );
+	}
+
+	$page_ids = array_merge( (array) $new_page_ids, (array) bp_core_get_directory_page_ids( 'all' ) );
+	bp_core_update_directory_page_ids( $page_ids );
+}
+
+/**
+ * Add illegal blog names to WP so that root components will not conflict with blog names on a subdirectory installation.
+ *
+ * For example, it would stop someone creating a blog with the slug "groups".
+ *
+ * @todo Deprecate?
+ */
+function bp_core_add_illegal_names() {
+	update_site_option( 'illegal_names', get_site_option( 'illegal_names' ), array() );
+}
+
+/**
+ * Get the 'search' query argument for a given component.
+ *
+ * @since 2.4.0
+ *
+ * @param string $component Component name.
+ * @return string|bool Query argument on success. False on failure.
+ */
+function bp_core_get_component_search_query_arg( $component ) {
+	$query_arg = false;
+	if ( isset( buddypress()->{$component}->search_query_arg ) ) {
+		$query_arg = sanitize_title( buddypress()->{$component}->search_query_arg );
+	}
+
+	/**
+	 * Filters the query arg for a component search string.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string $query_arg Query argument.
+	 * @param string $component Component name.
+	 */
+	return apply_filters( 'bp_core_get_component_search_query_arg', $query_arg, $component );
+}
+
+/**
+ * Determine whether BuddyPress should register the bp-themes directory.
+ *
+ * @since 1.9.0
+ *
+ * @return bool True if bp-themes should be registered, false otherwise.
+ */
+function bp_do_register_theme_directory() {
+	// If bp-default exists in another theme directory, bail.
+	// This ensures that the version of bp-default in the regular themes
+	// directory will always take precedence, as part of a migration away
+	// from the version packaged with BuddyPress
+	foreach ( array_values( (array) $GLOBALS['wp_theme_directories'] ) as $directory ) {
+		if ( is_dir( $directory . '/bp-default' ) ) {
+			return false;
+		}
+	}
+
+	// If the current theme is bp-default (or a bp-default child), BP
+	// should register its directory
+	$register = 'bp-default' === get_stylesheet() || 'bp-default' === get_template();
+
+	// Legacy sites continue to have the theme registered
+	if ( empty( $register ) && ( 1 == get_site_option( '_bp_retain_bp_default' ) ) ) {
+		$register = true;
+	}
+
+	/**
+	 * Filters whether BuddyPress should register the bp-themes directory.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param bool $register If bp-themes should be registered.
+	 */
+	return apply_filters( 'bp_do_register_theme_directory', $register );
+}
+
+/** URI ***********************************************************************/
+
+/**
+ * Return the domain for the root blog.
+ *
+ * eg: http://example.com OR https://example.com
+ *
+ * @uses get_blog_option() WordPress function to fetch blog meta.
+ *
+ * @return string The domain URL for the blog.
+ */
+function bp_core_get_root_domain() {
+
+	$domain = get_home_url( bp_get_root_blog_id() );
+
+	/**
+	 * Filters the domain for the root blog.
+	 *
+	 * @since 1.0.1
+	 *
+	 * @param string $domain The domain URL for the blog.
+	 */
+	return apply_filters( 'bp_core_get_root_domain', $domain );
+}
+
+/**
+ * Perform a status-safe wp_redirect() that is compatible with BP's URI parser.
+ *
+ * @uses wp_safe_redirect()
+ *
+ * @param string $location The redirect URL.
+ * @param int    $status   Optional. The numeric code to give in the redirect
+ *                         headers. Default: 302.
+ */
+function bp_core_redirect( $location = '', $status = 302 ) {
+
+	// On some setups, passing the value of wp_get_referer() may result in an
+	// empty value for $location, which results in an error. Ensure that we
+	// have a valid URL.
+	if ( empty( $location ) ) {
+		$location = bp_get_root_domain();
+	}
+
+	// Make sure we don't call status_header() in bp_core_do_catch_uri() as this
+	// conflicts with wp_redirect() and wp_safe_redirect().
+	buddypress()->no_status_set = true;
+
+	wp_safe_redirect( $location, $status );
+	die;
+}
+
+/**
+ * Return the URL path of the referring page.
+ *
+ * This is a wrapper for `wp_get_referer()` that sanitizes the referer URL to
+ * a webroot-relative path. For example, 'http://example.com/foo/' will be
+ * reduced to '/foo/'.
+ *
+ * @since 2.3.0
+ *
+ * @return bool|string Returns false on error, a URL path on success.
+ */
+function bp_get_referer_path() {
+	$referer = wp_get_referer();
+
+	if ( false === $referer ) {
+		return false;
+	}
+
+	// Turn into an absolute path.
+	$referer = preg_replace( '|https?\://[^/]+/|', '/', $referer );
+
+	return $referer;
+}
+
+/**
+ * Get the path of the current site.
+ *
+ * @global object $current_site
+ *
+ * @return string URL to the current site.
+ */
+function bp_core_get_site_path() {
+	global $current_site;
+
+	if ( is_multisite() ) {
+		$site_path = $current_site->path;
+	} else {
+		$site_path = (array) explode( '/', home_url() );
+
+		if ( count( $site_path ) < 2 ) {
+			$site_path = '/';
+		} else {
+			// Unset the first three segments (http(s)://example.com part)
+			unset( $site_path[0] );
+			unset( $site_path[1] );
+			unset( $site_path[2] );
+
+			if ( !count( $site_path ) ) {
+				$site_path = '/';
+			} else {
+				$site_path = '/' . implode( '/', $site_path ) . '/';
+			}
+		}
+	}
+
+	/**
+	 * Filters the path of the current site.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $site_path URL to the current site.
+	 */
+	return apply_filters( 'bp_core_get_site_path', $site_path );
+}
+
+/** Time **********************************************************************/
+
+/**
+ * Get the current GMT time to save into the DB.
+ *
+ * @since 1.2.6
+ *
+ * @param bool   $gmt  True to use GMT (rather than local) time. Default: true.
+ * @param string $type See the 'type' parameter in {@link current_time()}.
+ *                     Default: 'mysql'.
+ *
+ * @return string Current time in 'Y-m-d h:i:s' format.
+ */
+function bp_core_current_time( $gmt = true, $type = 'mysql' ) {
+
+	/**
+	 * Filters the current GMT time to save into the DB.
+	 *
+	 * @since 1.2.6
+	 *
+	 * @param string $value Current GMT time.
+	 */
+	return apply_filters( 'bp_core_current_time', current_time( $type, $gmt ) );
+}
+
+/**
+ * Get an English-language representation of the time elapsed since a given date.
+ *
+ * Based on function created by Dunstan Orchard - http://1976design.com
+ *
+ * This function will return an English representation of the time elapsed
+ * since a given date.
+ * eg: 2 hours and 50 minutes
+ * eg: 4 days
+ * eg: 4 weeks and 6 days
+ *
+ * Note that fractions of minutes are not represented in the return string. So
+ * an interval of 3 minutes will be represented by "3 minutes ago", as will an
+ * interval of 3 minutes 59 seconds.
+ *
+ * @uses apply_filters() Filter 'bp_core_time_since_pre' to bypass BP's calculations.
+ * @uses apply_filters() Filter 'bp_core_time_since' to modify BP's calculations.
+ *
+ * @param int|string $older_date The earlier time from which you're calculating
+ *                               the time elapsed. Enter either as an integer Unix timestamp,
+ *                               or as a date string of the format 'Y-m-d h:i:s'.
+ * @param int|bool   $newer_date Optional. Unix timestamp of date to compare older
+ *                               date to. Default: false (current time).
+ *
+ * @return string String representing the time since the older date, eg
+ *         "2 hours and 50 minutes".
+ */
+function bp_core_time_since( $older_date, $newer_date = false ) {
+
+	/**
+	 * Filters whether or not to bypass BuddyPress' time_since calculations.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param bool   $value      Whether or not to bypass.
+	 * @param string $older_date Earlier time from which we're calculating time elapsed.
+	 * @param string $newer_date Unix timestamp of date to compare older time to.
+	 */
+	$pre_value = apply_filters( 'bp_core_time_since_pre', false, $older_date, $newer_date );
+	if ( false !== $pre_value ) {
+		return $pre_value;
+	}
+
+	/**
+	 * Filters the value to use if the time since is unknown.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $value String representing the time since the older date.
+	 */
+	$unknown_text   = apply_filters( 'bp_core_time_since_unknown_text',   __( 'sometime',  'buddypress' ) );
+
+	/**
+	 * Filters the value to use if the time since is right now.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $value String representing the time since the older date.
+	 */
+	$right_now_text = apply_filters( 'bp_core_time_since_right_now_text', __( 'right now', 'buddypress' ) );
+
+	/**
+	 * Filters the value to use if the time since is some time ago.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $value String representing the time since the older date.
+	 */
+	$ago_text       = apply_filters( 'bp_core_time_since_ago_text',       __( '%s ago',    'buddypress' ) );
+
+	// array of time period chunks
+	$chunks = array(
+		YEAR_IN_SECONDS,
+		30 * DAY_IN_SECONDS,
+		WEEK_IN_SECONDS,
+		DAY_IN_SECONDS,
+		HOUR_IN_SECONDS,
+		MINUTE_IN_SECONDS,
+		1
+	);
+
+	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] );
+	}
+
+	/**
+	 * $newer_date will equal false if we want to know the time elapsed between
+	 * a date and the current time. $newer_date will have a value if we want to
+	 * work out time elapsed between two known dates.
+	 */
+	$newer_date = ( !$newer_date ) ? bp_core_current_time( true, 'timestamp' ) : $newer_date;
+
+	// Difference in seconds
+	$since = $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 {
+
+		// Step one: the first chunk
+		for ( $i = 0, $j = count( $chunks ); $i < $j; ++$i ) {
+			$seconds = $chunks[$i];
+
+			// 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
+			switch ( $seconds ) {
+				case YEAR_IN_SECONDS :
+					$output = sprintf( _n( '%s year',   '%s years',   $count, 'buddypress' ), $count );
+					break;
+				case 30 * DAY_IN_SECONDS :
+					$output = sprintf( _n( '%s month',  '%s months',  $count, 'buddypress' ), $count );
+					break;
+				case WEEK_IN_SECONDS :
+					$output = sprintf( _n( '%s week',   '%s weeks',   $count, 'buddypress' ), $count );
+					break;
+				case DAY_IN_SECONDS :
+					$output = sprintf( _n( '%s day',    '%s days',    $count, 'buddypress' ), $count );
+					break;
+				case HOUR_IN_SECONDS :
+					$output = sprintf( _n( '%s hour',   '%s hours',   $count, 'buddypress' ), $count );
+					break;
+				case MINUTE_IN_SECONDS :
+					$output = sprintf( _n( '%s minute', '%s minutes', $count, 'buddypress' ), $count );
+					break;
+				default:
+					$output = sprintf( _n( '%s second', '%s seconds', $count, 'buddypress' ), $count );
+			}
+
+			// Step two: the second chunk
+			// A quirk in the implementation means that this
+			// condition fails in the case of minutes and seconds.
+			// We've left the quirk in place, since fractions of a
+			// minute are not a useful piece of information for our
+			// purposes
+			if ( $i + 2 < $j ) {
+				$seconds2 = $chunks[$i + 1];
+				$count2   = floor( ( $since - ( $seconds * $count ) ) / $seconds2 );
+
+				// Add to output var
+				if ( 0 != $count2 ) {
+					$output .= _x( ',', 'Separator in time since', 'buddypress' ) . ' ';
+
+					switch ( $seconds2 ) {
+						case 30 * DAY_IN_SECONDS :
+							$output .= sprintf( _n( '%s month',  '%s months',  $count2, 'buddypress' ), $count2 );
+							break;
+						case WEEK_IN_SECONDS :
+							$output .= sprintf( _n( '%s week',   '%s weeks',   $count2, 'buddypress' ), $count2 );
+							break;
+						case DAY_IN_SECONDS :
+							$output .= sprintf( _n( '%s day',    '%s days',    $count2, 'buddypress' ), $count2 );
+							break;
+						case HOUR_IN_SECONDS :
+							$output .= sprintf( _n( '%s hour',   '%s hours',   $count2, 'buddypress' ), $count2 );
+							break;
+						case MINUTE_IN_SECONDS :
+							$output .= sprintf( _n( '%s minute', '%s minutes', $count2, 'buddypress' ), $count2 );
+							break;
+						default:
+							$output .= sprintf( _n( '%s second', '%s seconds', $count2, 'buddypress' ), $count2 );
+					}
+				}
+			}
+
+			// No output, so happened right now
+			if ( ! (int) trim( $output ) ) {
+				$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 );
+	}
+
+	/**
+	 * Filters the English-language representation of the time elapsed since a given date.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $output     Final 'time since' string.
+	 * @param string $older_date Earlier time from which we're calculating time elapsed.
+	 * @param string $newer_date Unix timestamp of date to compare older time to.
+	 */
+	return apply_filters( 'bp_core_time_since', $output, $older_date, $newer_date );
+}
+
+/** Messages ******************************************************************/
+
+/**
+ * Add a feedback (error/success) message to the WP cookie so it can be displayed after the page reloads.
+ *
+ * @param string $message Feedback message to be displayed.
+ * @param string $type    Message type. 'updated', 'success', 'error', 'warning'.
+ *                        Default: 'success'.
+ */
+function bp_core_add_message( $message, $type = '' ) {
+
+	// Success is the default
+	if ( empty( $type ) ) {
+		$type = 'success';
+	}
+
+	// Send the values to the cookie for page reload display
+	@setcookie( 'bp-message',      $message, time() + 60 * 60 * 24, COOKIEPATH );
+	@setcookie( 'bp-message-type', $type,    time() + 60 * 60 * 24, COOKIEPATH );
+
+	// Get BuddyPress
+	$bp = buddypress();
+
+	/***
+	 * Send the values to the $bp global so we can still output messages
+	 * without a page reload
+	 */
+	$bp->template_message      = $message;
+	$bp->template_message_type = $type;
+}
+
+/**
+ * Set up the display of the 'template_notices' feedback message.
+ *
+ * Checks whether there is a feedback message in the WP cookie and, if so, adds
+ * a "template_notices" action so that the message can be parsed into the
+ * template and displayed to the user.
+ *
+ * After the message is displayed, it removes the message vars from the cookie
+ * so that the message is not shown to the user multiple times.
+ *
+ * @uses setcookie() Sets a cookie value for the user.
+ */
+function bp_core_setup_message() {
+
+	// Get BuddyPress
+	$bp = buddypress();
+
+	if ( empty( $bp->template_message ) && isset( $_COOKIE['bp-message'] ) ) {
+		$bp->template_message = stripslashes( $_COOKIE['bp-message'] );
+	}
+
+	if ( empty( $bp->template_message_type ) && isset( $_COOKIE['bp-message-type'] ) ) {
+		$bp->template_message_type = stripslashes( $_COOKIE['bp-message-type'] );
+	}
+
+	add_action( 'template_notices', 'bp_core_render_message' );
+
+	if ( isset( $_COOKIE['bp-message'] ) ) {
+		@setcookie( 'bp-message', false, time() - 1000, COOKIEPATH );
+	}
+
+	if ( isset( $_COOKIE['bp-message-type'] ) ) {
+		@setcookie( 'bp-message-type', false, time() - 1000, COOKIEPATH );
+	}
+}
+add_action( 'bp_actions', 'bp_core_setup_message', 5 );
+
+/**
+ * Render the 'template_notices' feedback message.
+ *
+ * The hook action 'template_notices' is used to call this function, it is not
+ * called directly.
+ */
+function bp_core_render_message() {
+
+	// Get BuddyPress
+	$bp = buddypress();
+
+	if ( !empty( $bp->template_message ) ) :
+		$type    = ( 'success' === $bp->template_message_type ) ? 'updated' : 'error';
+
+		/**
+		 * Filters the 'template_notices' feedback message content.
+		 *
+		 * @since 1.5.5
+		 *
+		 * @param string $template_message Feedback message content.
+		 * @param string $type             The type of message being displayed.
+		 *                                 Either 'updated' or 'error'.
+		 */
+		$content = apply_filters( 'bp_core_render_message_content', $bp->template_message, $type ); ?>
+
+		<div id="message" class="bp-template-notice <?php echo esc_attr( $type ); ?>">
+
+			<?php echo $content; ?>
+
+		</div>
+
+	<?php
+
+		/**
+		 * Fires after the display of any template_notices feedback messages.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_core_render_message' );
+
+	endif;
+}
+
+/** Last active ***************************************************************/
+
+/**
+ * Listener function for the logged-in user's 'last_activity' metadata.
+ *
+ * Many functions use a "last active" feature to show the length of time since
+ * the user was last active. This function will update that time as a usermeta
+ * setting for the user every 5 minutes while the user is actively browsing the
+ * site.
+ *
+ * @uses bp_update_user_meta() BP function to update user metadata in the
+ *       usermeta table.
+ *
+ * @return bool|null Returns false if there is nothing to do.
+ */
+function bp_core_record_activity() {
+
+	// Bail if user is not logged in
+	if ( ! is_user_logged_in() ) {
+		return false;
+	}
+
+	// Get the user ID
+	$user_id = bp_loggedin_user_id();
+
+	// Bail if user is not active
+	if ( bp_is_user_inactive( $user_id ) ) {
+		return false;
+	}
+
+	// Get the user's last activity
+	$activity = bp_get_user_last_activity( $user_id );
+
+	// Make sure it's numeric
+	if ( ! is_numeric( $activity ) ) {
+		$activity = strtotime( $activity );
+	}
+
+	// Get current time
+	$current_time = bp_core_current_time();
+
+	// Use this action to detect the very first activity for a given member
+	if ( empty( $activity ) ) {
+
+		/**
+		 * Fires inside the recording of an activity item.
+		 *
+		 * Use this action to detect the very first activity for a given member.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param int $user_id ID of the user whose activity is recorded.
+		 */
+		do_action( 'bp_first_activity_for_member', $user_id );
+	}
+
+	// If it's been more than 5 minutes, record a newer last-activity time
+	if ( empty( $activity ) || ( strtotime( $current_time ) >= strtotime( '+5 minutes', $activity ) ) ) {
+		bp_update_user_last_activity( $user_id, $current_time );
+	}
+}
+add_action( 'wp_head', 'bp_core_record_activity' );
+
+/**
+ * Format last activity string based on time since date given.
+ *
+ * @uses bp_core_time_since() This function will return an English
+ *       representation of the time elapsed.
+ *
+ * @param int|string $last_activity_date The date of last activity.
+ * @param string     $string             A sprintf()-able statement of the form 'active %s'.
+ *
+ * @return string $last_active A string of the form '3 years ago'.
+ */
+function bp_core_get_last_activity( $last_activity_date = '', $string = '' ) {
+
+	// Setup a default string if none was passed
+	$string = empty( $string )
+		? '%s'     // Gettext placeholder
+		: $string;
+
+	// Use the string if a last activity date was passed
+	$last_active = empty( $last_activity_date )
+		? __( 'Not recently active', 'buddypress' )
+		: sprintf( $string, bp_core_time_since( $last_activity_date ) );
+
+	/**
+	 * Filters last activity string based on time since date given.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $last_active        Last activity string based on time since date given.
+	 * @param string $last_activity_date The date of last activity.
+	 * @param string $string             A sprintf()-able statement of the form 'active %s'.
+	 */
+	return apply_filters( 'bp_core_get_last_activity', $last_active, $last_activity_date, $string );
+}
+
+/** Meta **********************************************************************/
+
+/**
+ * Get the meta_key for a given piece of user metadata
+ *
+ * BuddyPress stores a number of pieces of userdata in the WordPress central
+ * usermeta table. In order to allow plugins to enable multiple instances of
+ * BuddyPress on a single WP installation, BP's usermeta keys are filtered
+ * through this function, so that they can be altered on the fly.
+ *
+ * Plugin authors should use BP's _user_meta() functions, which bakes in
+ * bp_get_user_meta_key():
+ *    $friend_count = bp_get_user_meta( $user_id, 'total_friend_count', true );
+ * If you must use WP's _user_meta() functions directly for some reason, you
+ * should use this function to determine the $key parameter, eg
+ *    $friend_count = get_user_meta( $user_id, bp_get_user_meta_key( 'total_friend_count' ), true );
+ * If using the WP functions, do not not hardcode your meta keys.
+ *
+ * @since 1.5.0
+ *
+ * @uses apply_filters() Filter 'bp_get_user_meta_key' to modify keys individually.
+ *
+ * @param string|bool $key The usermeta meta_key.
+ *
+ * @return string $key The usermeta meta_key.
+ */
+function bp_get_user_meta_key( $key = false ) {
+
+	/**
+	 * Filters the meta_key for a given piece of user metadata.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $key The usermeta meta key.
+	 */
+	return apply_filters( 'bp_get_user_meta_key', $key );
+}
+
+/**
+ * Get a piece of usermeta.
+ *
+ * This is a wrapper for get_user_meta() that allows for easy use of
+ * bp_get_user_meta_key(), thereby increasing compatibility with non-standard
+ * BP setups.
+ *
+ * @since 1.5.0
+ *
+ * @see get_user_meta() For complete details about parameters and return values.
+ * @uses bp_get_user_meta_key() For a filterable version of the meta key.
+ *
+ * @param int    $user_id The ID of the user whose meta you're fetching.
+ * @param string $key     The meta key to retrieve.
+ * @param bool   $single  Whether to return a single value.
+ *
+ * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
+ *         is true.
+ */
+function bp_get_user_meta( $user_id, $key, $single = false ) {
+	return get_user_meta( $user_id, bp_get_user_meta_key( $key ), $single );
+}
+
+/**
+ * Update a piece of usermeta.
+ *
+ * This is a wrapper for update_user_meta() that allows for easy use of
+ * bp_get_user_meta_key(), thereby increasing compatibility with non-standard
+ * BP setups.
+ *
+ * @since 1.5.0
+ *
+ * @see update_user_meta() For complete details about parameters and return values.
+ * @uses bp_get_user_meta_key() For a filterable version of the meta key.
+ *
+ * @param int    $user_id    The ID of the user whose meta you're setting.
+ * @param string $key        The meta key to set.
+ * @param mixed  $value      Metadata value.
+ * @param mixed  $prev_value Optional. Previous value to check before removing.
+ *
+ * @return bool False on failure, true on success.
+ */
+function bp_update_user_meta( $user_id, $key, $value, $prev_value = '' ) {
+	return update_user_meta( $user_id, bp_get_user_meta_key( $key ), $value, $prev_value );
+}
+
+/**
+ * Delete a piece of usermeta.
+ *
+ * This is a wrapper for delete_user_meta() that allows for easy use of
+ * bp_get_user_meta_key(), thereby increasing compatibility with non-standard
+ * BP setups.
+ *
+ * @since 1.5.0
+ *
+ * @see delete_user_meta() For complete details about parameters and return values.
+ * @uses bp_get_user_meta_key() For a filterable version of the meta key.
+ *
+ * @param int    $user_id The ID of the user whose meta you're deleting.
+ * @param string $key     The meta key to delete.
+ * @param mixed  $value   Optional. Metadata value.
+ *
+ * @return bool False for failure. True for success.
+ */
+function bp_delete_user_meta( $user_id, $key, $value = '' ) {
+	return delete_user_meta( $user_id, bp_get_user_meta_key( $key ), $value );
+}
+
+/** Embeds ********************************************************************/
+
+/**
+ * Initializes {@link BP_Embed} after everything is loaded.
+ *
+ * @since 1.5.0
+ */
+function bp_embed_init() {
+
+	// Get BuddyPress
+	$bp = buddypress();
+
+	if ( empty( $bp->embed ) ) {
+		$bp->embed = new BP_Embed();
+	}
+}
+add_action( 'bp_init', 'bp_embed_init', 9 );
+
+/**
+ * Are oembeds allowed in activity items?
+ *
+ * @since 1.5.0
+ *
+ * @return bool False when activity embed support is disabled; true when
+ *              enabled. Default: true.
+ */
+function bp_use_embed_in_activity() {
+
+	/**
+	 * Filters whether or not oEmbeds are allowed in activity items.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $value Whether or not oEmbeds are allowed.
+	 */
+	return apply_filters( 'bp_use_oembed_in_activity', !defined( 'BP_EMBED_DISABLE_ACTIVITY' ) || !BP_EMBED_DISABLE_ACTIVITY );
+}
+
+/**
+ * Are oembeds allowed in activity replies?
+ *
+ * @since 1.5.0
+ *
+ * @return bool False when activity replies embed support is disabled; true
+ *              when enabled. Default: true.
+ */
+function bp_use_embed_in_activity_replies() {
+
+	/**
+	 * Filters whether or not oEmbeds are allowed in activity replies.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $value Whether or not oEmbeds are allowed.
+	 */
+	return apply_filters( 'bp_use_embed_in_activity_replies', !defined( 'BP_EMBED_DISABLE_ACTIVITY_REPLIES' ) || !BP_EMBED_DISABLE_ACTIVITY_REPLIES );
+}
+
+/**
+ * Are oembeds allowed in forum posts?
+ *
+ * @since 1.5.0
+ *
+ * @return bool False when forum post embed support is disabled; true when
+ *              enabled. Default: true.
+ */
+function bp_use_embed_in_forum_posts() {
+
+	/**
+	 * Filters whether or not oEmbeds are allowed in forum posts.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $value Whether or not oEmbeds are allowed.
+	 */
+	return apply_filters( 'bp_use_embed_in_forum_posts', !defined( 'BP_EMBED_DISABLE_FORUM_POSTS' ) || !BP_EMBED_DISABLE_FORUM_POSTS );
+}
+
+/**
+ * Are oembeds allowed in private messages?
+ *
+ * @since 1.5.0
+ *
+ * @return bool False when private message embed support is disabled; true when
+ *              enabled. Default: true.
+ */
+function bp_use_embed_in_private_messages() {
+
+	/**
+	 * Filters whether or not oEmbeds are allowed in private messages.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $value Whether or not oEmbeds are allowed.
+	 */
+	return apply_filters( 'bp_use_embed_in_private_messages', !defined( 'BP_EMBED_DISABLE_PRIVATE_MESSAGES' ) || !BP_EMBED_DISABLE_PRIVATE_MESSAGES );
+}
+
+/** Admin *********************************************************************/
+
+/**
+ * Output the correct admin URL based on BuddyPress and WordPress configuration.
+ *
+ * @since 1.5.0
+ *
+ * @see bp_get_admin_url() For description of parameters.
+ *
+ * @param string $path   See {@link bp_get_admin_url()}.
+ * @param string $scheme See {@link bp_get_admin_url()}.
+ */
+function bp_admin_url( $path = '', $scheme = 'admin' ) {
+	echo esc_url( bp_get_admin_url( $path, $scheme ) );
+}
+	/**
+	 * Return the correct admin URL based on BuddyPress and WordPress configuration.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses bp_core_do_network_admin()
+	 * @uses network_admin_url()
+	 * @uses admin_url()
+	 *
+	 * @param string $path   Optional. The sub-path under /wp-admin to be
+	 *                       appended to the admin URL.
+	 * @param string $scheme The scheme to use. Default is 'admin', which
+	 *                       obeys {@link force_ssl_admin()} and {@link is_ssl()}. 'http'
+	 *                       or 'https' can be passed to force those schemes.
+	 *
+	 * @return string Admin url link with optional path appended.
+	 */
+	function bp_get_admin_url( $path = '', $scheme = 'admin' ) {
+
+		// Links belong in network admin
+		if ( bp_core_do_network_admin() ) {
+			$url = network_admin_url( $path, $scheme );
+
+		// Links belong in site admin
+		} else {
+			$url = admin_url( $path, $scheme );
+		}
+
+		return $url;
+	}
+
+/**
+ * Should BuddyPress appear in network admin (vs a single site Dashboard)?
+ *
+ * Because BuddyPress can be installed in multiple ways and with multiple
+ * configurations, we need to check a few things to be confident about where
+ * to hook into certain areas of WordPress's admin.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_is_network_activated()
+ * @uses bp_is_multiblog_mode()
+ *
+ * @return bool True if the BP admin screen should appear in the Network Admin,
+ *              otherwise false.
+ */
+function bp_core_do_network_admin() {
+
+	// Default
+	$retval = bp_is_network_activated();
+
+	if ( bp_is_multiblog_mode() ) {
+		$retval = false;
+	}
+
+	/**
+	 * Filters whether or not BuddyPress should appear in network admin.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $retval Whether or not BuddyPress should be in the network admin.
+	 */
+	return (bool) apply_filters( 'bp_core_do_network_admin', $retval );
+}
+
+/**
+ * Return the action name that BuddyPress nav setup callbacks should be hooked to.
+ *
+ * Functions used to set up BP Dashboard pages (wrapping such admin-panel
+ * functions as add_submenu_page()) should use bp_core_admin_hook() for the
+ * first parameter in add_action(). BuddyPress will then determine
+ * automatically whether to load the panels in the Network Admin. Ie:
+ *
+ *     add_action( bp_core_admin_hook(), 'myplugin_dashboard_panel_setup' );
+ *
+ * @return string $hook The proper hook ('network_admin_menu' or 'admin_menu').
+ */
+function bp_core_admin_hook() {
+	$hook = bp_core_do_network_admin() ? 'network_admin_menu' : 'admin_menu';
+
+	/**
+	 * Filters the action name that BuddyPress nav setup callbacks should be hooked to.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $hook Action name to be attached to.
+	 */
+	return apply_filters( 'bp_core_admin_hook', $hook );
+}
+
+/** Multisite *****************************************************************/
+
+/**
+ * Is this the root blog?
+ *
+ * @since 1.5.0
+ *
+ * @param int $blog_id Optional. Default: the ID of the current blog.
+ *
+ * @return bool $is_root_blog Returns true if this is bp_get_root_blog_id().
+ */
+function bp_is_root_blog( $blog_id = 0 ) {
+
+	// Assume false
+	$is_root_blog = false;
+
+	// Use current blog if no ID is passed
+	if ( empty( $blog_id ) || ! is_int( $blog_id ) ) {
+		$blog_id = get_current_blog_id();
+	}
+
+	// Compare to root blog ID
+	if ( bp_get_root_blog_id() === $blog_id ) {
+		$is_root_blog = true;
+	}
+
+	/**
+	 * Filters whether or not we're on the root blog.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $is_root_blog Whether or not we're on the root blog.
+	 */
+	return (bool) apply_filters( 'bp_is_root_blog', (bool) $is_root_blog );
+}
+
+/**
+ * Get the ID of the root blog.
+ *
+ * The "root blog" is the blog on a WordPress network where BuddyPress content
+ * appears (where member profile URLs resolve, where a given theme is loaded,
+ * etc.).
+ *
+ * @since 1.5.0
+ *
+ * @return int The root site ID.
+ */
+function bp_get_root_blog_id() {
+
+	/**
+	 * Filters the ID for the root blog.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $root_blog_id ID for the root blog.
+	 */
+	return (int) apply_filters( 'bp_get_root_blog_id', (int) buddypress()->root_blog_id );
+}
+
+/**
+ * Are we running multiblog mode?
+ *
+ * Note that BP_ENABLE_MULTIBLOG is different from (but dependent on) WordPress
+ * Multisite. "Multiblog" is BuddyPress setup that allows BuddyPress components
+ * to be viewed on every blog on the network, each with their own settings.
+ *
+ * Thus, instead of having all 'boonebgorges' links go to
+ *   http://example.com/members/boonebgorges
+ * on the root blog, each blog will have its own version of the same content, eg
+ *   http://site2.example.com/members/boonebgorges (for subdomains)
+ *   http://example.com/site2/members/boonebgorges (for subdirectories)
+ *
+ * Multiblog mode is disabled by default, meaning that all BuddyPress content
+ * must be viewed on the root blog. It's also recommended not to use the
+ * BP_ENABLE_MULTIBLOG constant beyond 1.7, as BuddyPress can now be activated
+ * on individual sites.
+ *
+ * Why would you want to use this? Originally it was intended to allow
+ * BuddyPress to live in mu-plugins and be visible on mapped domains. This is
+ * a very small use-case with large architectural shortcomings, so do not go
+ * down this road unless you specifically need to.
+ *
+ * @since 1.5.0
+ *
+ * @uses apply_filters() Filter 'bp_is_multiblog_mode' to alter.
+ *
+ * @return bool False when multiblog mode is disabled; true when enabled.
+ *              Default: false.
+ */
+function bp_is_multiblog_mode() {
+
+	// Setup some default values
+	$retval         = false;
+	$is_multisite   = is_multisite();
+	$network_active = bp_is_network_activated();
+	$is_multiblog   = defined( 'BP_ENABLE_MULTIBLOG' ) && BP_ENABLE_MULTIBLOG;
+
+	// Multisite, Network Activated, and Specifically Multiblog
+	if ( $is_multisite && $network_active && $is_multiblog ) {
+		$retval = true;
+
+	// Multisite, but not network activated
+	} elseif ( $is_multisite && ! $network_active ) {
+		$retval = true;
+	}
+
+	/**
+	 * Filters whether or not we're running in multiblog mode.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $retval Whether or not we're running multiblog mode.
+	 */
+	return apply_filters( 'bp_is_multiblog_mode', $retval );
+}
+
+/**
+ * Is BuddyPress active at the network level for this network?
+ *
+ * Used to determine admin menu placement, and where settings and options are
+ * stored. If you're being *really* clever and manually pulling BuddyPress in
+ * with an mu-plugin or some other method, you'll want to filter
+ * 'bp_is_network_activated' and override the auto-determined value.
+ *
+ * @since 1.7.0
+ *
+ * @return bool True if BuddyPress is network activated.
+ */
+function bp_is_network_activated() {
+
+	// Default to is_multisite()
+	$retval  = is_multisite();
+
+	// Check the sitewide plugins array
+	$base    = buddypress()->basename;
+	$plugins = get_site_option( 'active_sitewide_plugins' );
+
+	// Override is_multisite() if not network activated
+	if ( ! is_array( $plugins ) || ! isset( $plugins[ $base ] ) ) {
+		$retval = false;
+	}
+
+	/**
+	 * Filters whether or not we're active at the network level.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param bool $retval Whether or not we're network activated.
+	 */
+	return (bool) apply_filters( 'bp_is_network_activated', $retval );
+}
+
+/** Global Manipulators *******************************************************/
+
+/**
+ * Set the "is_directory" global.
+ *
+ * @param bool   $is_directory Optional. Default: false.
+ * @param string $component    Optional. Component name. Default: the current
+ *                             component.
+ */
+function bp_update_is_directory( $is_directory = false, $component = '' ) {
+
+	if ( empty( $component ) ) {
+		$component = bp_current_component();
+	}
+
+	/**
+	 * Filters the "is_directory" global value.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool   $is_directory Whether or not we're "is_directory".
+	 * @param string $component    Component name. Default: the current component.
+	 */
+	buddypress()->is_directory = apply_filters( 'bp_update_is_directory', $is_directory, $component );
+}
+
+/**
+ * Set the "is_item_admin" global.
+ *
+ * @param bool   $is_item_admin Optional. Default: false.
+ * @param string $component     Optional. Component name. Default: the current
+ *                              component.
+ */
+function bp_update_is_item_admin( $is_item_admin = false, $component = '' ) {
+
+	if ( empty( $component ) ) {
+		$component = bp_current_component();
+	}
+
+	/**
+	 * Filters the "is_item_admin" global value.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool   $is_item_admin Whether or not we're "is_item_admin".
+	 * @param string $component     Component name. Default: the current component.
+	 */
+	buddypress()->is_item_admin = apply_filters( 'bp_update_is_item_admin', $is_item_admin, $component );
+}
+
+/**
+ * Set the "is_item_mod" global.
+ *
+ * @param bool   $is_item_mod Optional. Default: false.
+ * @param string $component   Optional. Component name. Default: the current
+ *                            component.
+ */
+function bp_update_is_item_mod( $is_item_mod = false, $component = '' ) {
+
+	if ( empty( $component ) ) {
+		$component = bp_current_component();
+	}
+
+	/**
+	 * Filters the "is_item_mod" global value.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool   $is_item_mod Whether or not we're "is_item_mod".
+	 * @param string $component   Component name. Default: the current component.
+	 */
+	buddypress()->is_item_mod = apply_filters( 'bp_update_is_item_mod', $is_item_mod, $component );
+}
+
+/**
+ * Trigger a 404.
+ *
+ * @since 1.5.0
+ *
+ * @global WP_Query $wp_query WordPress query object.
+ *
+ * @param string $redirect If 'remove_canonical_direct', remove WordPress' "helpful"
+ *                         redirect_canonical action. Default: 'remove_canonical_redirect'.
+ */
+function bp_do_404( $redirect = 'remove_canonical_direct' ) {
+	global $wp_query;
+
+	/**
+	 * Fires inside the triggering of a 404.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $redirect Redirect type used to determine if redirect_canonical
+	 *                         function should be be removed.
+	 */
+	do_action( 'bp_do_404', $redirect );
+
+	$wp_query->set_404();
+	status_header( 404 );
+	nocache_headers();
+
+	if ( 'remove_canonical_direct' === $redirect ) {
+		remove_action( 'template_redirect', 'redirect_canonical' );
+	}
+}
+
+/** Nonces ********************************************************************/
+
+/**
+ * Makes sure the user requested an action from another page on this site.
+ *
+ * To avoid security exploits within the theme.
+ *
+ * @since 1.6.0
+ *
+ * @uses do_action() Calls 'bp_verify_nonce_request' on $action.
+ *
+ * @param string $action    Action nonce.
+ * @param string $query_arg Where to look for nonce in $_REQUEST.
+ *
+ * @return bool True if the nonce is verified, otherwise false.
+ */
+function bp_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 in home_url()
+	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        = is_ssl() ? 'https://' : 'http://';
+	$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 1.9.0
+	 *
+	 * @param string $requested_url The requested URL.
+	 */
+	$matched_url = apply_filters( 'bp_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 1.6.0
+	 *
+	 * @param string $action Action nonce.
+	 * @param bool   $result Boolean result of nonce verification.
+	 */
+	do_action( 'bp_verify_nonce_request', $action, $result );
+
+	return $result;
+}
+
+/** Requests ******************************************************************/
+
+/**
+ * Return true|false if this is a POST request.
+ *
+ * @since 1.9.0
+ * @return bool
+ */
+function bp_is_post_request() {
+	return (bool) ( 'POST' === strtoupper( $_SERVER['REQUEST_METHOD'] ) );
+}
+
+/**
+ * Return true|false if this is a GET request.
+ *
+ * @since 1.9.0
+ * @return bool
+ */
+function bp_is_get_request() {
+	return (bool) ( 'GET' === strtoupper( $_SERVER['REQUEST_METHOD'] ) );
+}
+
+
+/** Miscellaneous hooks *******************************************************/
+
+/**
+ * Load the buddypress translation file for current language.
+ *
+ * @see load_textdomain() for a description of return values.
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_core_load_buddypress_textdomain() {
+	$domain = 'buddypress';
+
+	/**
+	 * Filters the locale to be loaded for the language files.
+	 *
+	 * @since 1.0.2
+	 *
+	 * @param string $value Current locale for the install.
+	 */
+	$mofile_custom = sprintf( '%s-%s.mo', $domain, apply_filters( 'buddypress_locale', get_locale() ) );
+
+	/**
+	 * Filters the locations to load language files from.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $value Array of directories to check for language files in.
+	 */
+	$locations = apply_filters( 'buddypress_locale_locations', array(
+		trailingslashit( WP_LANG_DIR . '/' . $domain  ),
+		trailingslashit( WP_LANG_DIR ),
+	) );
+
+	// Try custom locations in WP_LANG_DIR
+	foreach ( $locations as $location ) {
+		if ( load_textdomain( 'buddypress', $location . $mofile_custom ) ) {
+			return true;
+		}
+ 	}
+
+	// default to WP and glotpress
+	return load_plugin_textdomain( $domain );
+}
+add_action ( 'bp_core_loaded', 'bp_core_load_buddypress_textdomain' );
+
+/**
+ * A JavaScript-free implementation of the search functions in BuddyPress.
+ *
+ * @param string $slug The slug to redirect to for searching.
+ */
+function bp_core_action_search_site( $slug = '' ) {
+
+	if ( ! bp_is_current_component( bp_get_search_slug() ) ) {
+		return;
+	}
+
+	if ( empty( $_POST['search-terms'] ) ) {
+		bp_core_redirect( bp_get_root_domain() );
+		return;
+	}
+
+	$search_terms = stripslashes( $_POST['search-terms'] );
+	$search_which = !empty( $_POST['search-which'] ) ? $_POST['search-which'] : '';
+	$query_string = '/?s=';
+
+	if ( empty( $slug ) ) {
+		switch ( $search_which ) {
+			case 'posts':
+				$slug = '';
+				$var  = '/?s=';
+
+				// If posts aren't displayed on the front page, find the post page's slug.
+				if ( 'page' == get_option( 'show_on_front' ) ) {
+					$page = get_post( get_option( 'page_for_posts' ) );
+
+					if ( !is_wp_error( $page ) && !empty( $page->post_name ) ) {
+						$slug = $page->post_name;
+						$var  = '?s=';
+					}
+				}
+				break;
+
+			case 'blogs':
+				$slug = bp_is_active( 'blogs' )  ? bp_get_blogs_root_slug()  : '';
+				break;
+
+			case 'forums':
+				$slug = bp_is_active( 'forums' ) ? bp_get_forums_root_slug() : '';
+				$query_string = '/?fs=';
+				break;
+
+			case 'groups':
+				$slug = bp_is_active( 'groups' ) ? bp_get_groups_root_slug() : '';
+				break;
+
+			case 'members':
+			default:
+				$slug = bp_get_members_root_slug();
+				break;
+		}
+
+		if ( empty( $slug ) && 'posts' != $search_which ) {
+			bp_core_redirect( bp_get_root_domain() );
+			return;
+		}
+	}
+
+	/**
+	 * Filters the constructed url for use with site searching.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value        URL for use with site searching.
+	 * @param array  $search_terms Array of search terms.
+	 */
+	bp_core_redirect( apply_filters( 'bp_core_search_site', home_url( $slug . $query_string . urlencode( $search_terms ) ), $search_terms ) );
+}
+add_action( 'bp_init', 'bp_core_action_search_site', 7 );
+
+/**
+ * Print the generation time in the footer of the site.
+ */
+function bp_core_print_generation_time() {
+?>
+
+<!-- Generated in <?php timer_stop(1); ?> seconds. (<?php echo get_num_queries(); ?> q) -->
+
+	<?php
+}
+add_action( 'wp_footer', 'bp_core_print_generation_time' );
+
+/**
+ * Remove "prev" and "next" relational links from <head> on BuddyPress pages.
+ *
+ * WordPress automatically generates these relational links to the current
+ * page.  However, BuddyPress doesn't adhere to these links.  In this
+ * function, we remove these links when on a BuddyPress page.  This also
+ * prevents additional, unnecessary queries from running.
+ *
+ * @since 2.1.0
+ */
+function bp_remove_adjacent_posts_rel_link() {
+	if ( ! is_buddypress() ) {
+		return;
+	}
+
+	remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0 );
+}
+add_action( 'bp_init', 'bp_remove_adjacent_posts_rel_link' );
+
+/**
+ * Strip the span count of a menu item or of a title part.
+ *
+ * @since 2.2.2
+ *
+ * @param string $title_part Title part to clean up.
+ * @return string
+ */
+function _bp_strip_spans_from_title( $title_part = '' ) {
+	$title = $title_part;
+	$span = strpos( $title, '<span' );
+	if ( false !== $span ) {
+		$title = substr( $title, 0, $span - 1 );
+	}
+	return trim( $title );
+}
+
+/** Nav Menu ******************************************************************/
+
+/**
+ * Create fake "post" objects for BP's logged-in nav menu for use in the WordPress "Menus" settings page.
+ *
+ * WordPress nav menus work by representing post or tax term data as a custom
+ * post type, which is then used to populate the checkboxes that appear on
+ * Dashboard > Appearance > Menu as well as the menu as rendered on the front
+ * end. Most of the items in the BuddyPress set of nav items are neither posts
+ * nor tax terms, so we fake a post-like object so as to be compatible with the
+ * menu.
+ *
+ * This technique also allows us to generate links dynamically, so that, for
+ * example, "My Profile" will always point to the URL of the profile of the
+ * logged-in user.
+ *
+ * @since 1.9.0
+ *
+ * @return mixed A URL or an array of dummy pages.
+ */
+function bp_nav_menu_get_loggedin_pages() {
+
+	// Try to catch the cached version first
+	if ( ! empty( buddypress()->wp_nav_menu_items->loggedin ) ) {
+		return buddypress()->wp_nav_menu_items->loggedin;
+	}
+
+	// Pull up a list of items registered in BP's top-level nav array
+	$bp_menu_items = buddypress()->bp_nav;
+
+	// Alphabetize
+	$bp_menu_items = bp_alpha_sort_by_key( $bp_menu_items, 'name' );
+
+	// Some BP nav menu items will not be represented in bp_nav, because
+	// they are not real BP components. We add them manually here.
+	$bp_menu_items[] = array(
+		'name' => __( 'Log Out', 'buddypress' ),
+		'slug' => 'logout',
+		'link' => wp_logout_url(),
+	);
+
+	// If there's nothing to show, we're done
+	if ( count( $bp_menu_items ) < 1 ) {
+		return false;
+	}
+
+	$page_args = array();
+
+	foreach ( $bp_menu_items as $bp_item ) {
+
+		// Remove <span>number</span>
+		$item_name = _bp_strip_spans_from_title( $bp_item['name'] );
+
+		$page_args[ $bp_item['slug'] ] = (object) array(
+			'ID'             => -1,
+			'post_title'     => $item_name,
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_excerpt'   => $bp_item['slug'],
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'comment_status' => 'closed',
+			'guid'           => $bp_item['link']
+		);
+	}
+
+	if ( empty( buddypress()->wp_nav_menu_items ) ) {
+		buddypress()->wp_nav_menu_items = new stdClass;
+	}
+
+	buddypress()->wp_nav_menu_items->loggedin = $page_args;
+
+	return $page_args;
+}
+
+/**
+ * Create fake "post" objects for BP's logged-out nav menu for use in the WordPress "Menus" settings page.
+ *
+ * WordPress nav menus work by representing post or tax term data as a custom
+ * post type, which is then used to populate the checkboxes that appear on
+ * Dashboard > Appearance > Menu as well as the menu as rendered on the front
+ * end. Most of the items in the BuddyPress set of nav items are neither posts
+ * nor tax terms, so we fake a post-like object so as to be compatible with the
+ * menu.
+ *
+ * @since 1.9.0
+ *
+ * @return mixed A URL or an array of dummy pages.
+ */
+function bp_nav_menu_get_loggedout_pages() {
+
+	// Try to catch the cached version first
+	if ( ! empty( buddypress()->wp_nav_menu_items->loggedout ) ) {
+		return buddypress()->wp_nav_menu_items->loggedout;
+	}
+
+	$bp_menu_items = array();
+
+	// Some BP nav menu items will not be represented in bp_nav, because
+	// they are not real BP components. We add them manually here.
+	$bp_menu_items[] = array(
+		'name' => __( 'Log In', 'buddypress' ),
+		'slug' => 'login',
+		'link' => wp_login_url(),
+	);
+
+	// The Register page will not always be available (ie, when
+	// registration is disabled)
+	$bp_directory_page_ids = bp_core_get_directory_page_ids();
+
+	if( ! empty( $bp_directory_page_ids['register'] ) ) {
+		$register_page = get_post( $bp_directory_page_ids['register'] );
+		$bp_menu_items[] = array(
+			'name' => $register_page->post_title,
+			'slug' => 'register',
+			'link' => get_permalink( $register_page->ID ),
+		);
+	}
+
+	// If there's nothing to show, we're done
+	if ( count( $bp_menu_items ) < 1 ) {
+		return false;
+	}
+
+	$page_args = array();
+
+	foreach ( $bp_menu_items as $bp_item ) {
+		$page_args[ $bp_item['slug'] ] = (object) array(
+			'ID'             => -1,
+			'post_title'     => $bp_item['name'],
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_excerpt'   => $bp_item['slug'],
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'comment_status' => 'closed',
+			'guid'           => $bp_item['link']
+		);
+	}
+
+	if ( empty( buddypress()->wp_nav_menu_items ) ) {
+		buddypress()->wp_nav_menu_items = new stdClass;
+	}
+
+	buddypress()->wp_nav_menu_items->loggedout = $page_args;
+
+	return $page_args;
+}
+
+/**
+ * Get the URL for a BuddyPress WP nav menu item, based on slug.
+ *
+ * BuddyPress-specific WP nav menu items have dynamically generated URLs,
+ * based on the identity of the current user. This function lets you fetch the
+ * proper URL for a given nav item slug (such as 'login' or 'messages').
+ *
+ * @since 1.9.0
+ *
+ * @param string $slug The slug of the nav item: login, register, or one of the
+ *                     slugs from buddypress()->bp_nav.
+ *
+ * @return string $nav_item_url The URL generated for the current user.
+ */
+function bp_nav_menu_get_item_url( $slug ) {
+	$nav_item_url   = '';
+	$nav_menu_items = bp_nav_menu_get_loggedin_pages();
+
+	if ( isset( $nav_menu_items[ $slug ] ) ) {
+		$nav_item_url = $nav_menu_items[ $slug ]->guid;
+	}
+
+	return $nav_item_url;
+}
+
+/** Suggestions***************************************************************/
+
+/**
+ * BuddyPress Suggestions API for types of at-mentions.
+ *
+ * This is used to power BuddyPress' at-mentions suggestions, but it is flexible enough to be used
+ * for similar kinds of future requirements, or those implemented by third-party developers.
+ *
+ * @since 2.1.0
+ *
+ * @param array $args
+ *
+ * @return array|WP_Error Array of results. If there were any problems, returns a WP_Error object.
+ */
+function bp_core_get_suggestions( $args ) {
+	$args = bp_parse_args( $args, array(), 'get_suggestions' );
+
+	if ( ! $args['type'] ) {
+		return new WP_Error( 'missing_parameter' );
+	}
+
+	// Members @name suggestions.
+	if ( $args['type'] === 'members' ) {
+		$class = 'BP_Members_Suggestions';
+
+		// Members @name suggestions for users in a specific Group.
+		if ( isset( $args['group_id'] ) ) {
+			$class = 'BP_Groups_Member_Suggestions';
+		}
+
+	} else {
+
+		/**
+		 * Filters the default suggestions service to use.
+		 *
+		 * Use this hook to tell BP the name of your class
+		 * if you've built a custom suggestions service.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $value Custom class to use. Default: none.
+		 * @param array  $args  Array of arguments for sugggestions.
+		 */
+		$class = apply_filters( 'bp_suggestions_services', '', $args );
+	}
+
+	if ( ! $class || ! class_exists( $class ) ) {
+		return new WP_Error( 'missing_parameter' );
+	}
+
+
+	$suggestions = new $class( $args );
+	$validation  = $suggestions->validate();
+
+	if ( is_wp_error( $validation ) ) {
+		$retval = $validation;
+	} else {
+		$retval = $suggestions->get_suggestions();
+	}
+
+	/**
+	 * Filters the available type of at-mentions.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param array|WP_Error $retval Array of results or WP_Error object.
+	 * @param array          $args   Array of arguments for suggestions.
+	 */
+	return apply_filters( 'bp_core_get_suggestions', $retval, $args );
+}
+
+/**
+ * Set data from the BP root blog's upload directory.
+ *
+ * Handy for multisite instances because all uploads are made on the BP root
+ * blog and we need to query the BP root blog for the upload directory data.
+ *
+ * This function ensures that we only need to use {@link switch_to_blog()}
+ * once to get what we need.
+ *
+ * @since 2.3.0
+ *
+ * @uses  is_multisite()
+ * @uses  bp_is_root_blog()
+ * @uses  switch_to_blog()
+ * @uses  wp_upload_dir()
+ * @uses  restore_current_blog()
+ */
+function bp_upload_dir() {
+	$bp = buddypress();
+
+	if ( empty( $bp->upload_dir ) ) {
+		$need_switch = (bool) ( is_multisite() && ! bp_is_root_blog() );
+
+		// Maybe juggle to root blog
+		if ( true === $need_switch ) {
+			switch_to_blog( bp_get_root_blog_id() );
+		}
+
+		// Get the upload directory (maybe for root blog)
+		$wp_upload_dir = wp_upload_dir();
+
+		// Maybe juggle back to current blog
+		if ( true === $need_switch ) {
+			restore_current_blog();
+		}
+
+		// Bail if an error occurred
+		if ( ! empty( $wp_upload_dir['error'] ) ) {
+			return false;
+		}
+
+		$bp->upload_dir = $wp_upload_dir;
+	}
+
+	return $bp->upload_dir;
+}
Index: /tags/2.4.3/src/bp-core/bp-core-loader.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-loader.php	(revision 10410)
@@ -0,0 +1,292 @@
+<?php
+/**
+ * BuddyPress Core Loader.
+ *
+ * Core contains the commonly used functions, classes, and APIs.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+class BP_Core extends BP_Component {
+
+	/**
+	 * Start the members component creation process.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses BP_Core::bootstrap()
+	 */
+	public function __construct() {
+		parent::start(
+			'core',
+			__( 'BuddyPress Core', 'buddypress' ),
+			buddypress()->plugin_dir
+		);
+
+		$this->bootstrap();
+	}
+
+	/**
+	 * Populate the global data needed before BuddyPress can continue.
+	 *
+	 * This involves figuring out the currently required, activated, deactivated,
+	 * and optional components.
+	 *
+	 * @since 1.5.0
+	 */
+	private function bootstrap() {
+		$bp = buddypress();
+
+		/**
+		 * Fires before the loading of individual components and after BuddyPress Core.
+		 *
+		 * Allows plugins to run code ahead of the other components.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_core_loaded' );
+
+		/** Components ********************************************************/
+
+		/**
+		 * Filters the included and optional components.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param array $value Array of included and optional components.
+		 */
+		$bp->optional_components = apply_filters( 'bp_optional_components', array( 'activity', 'blogs', 'forums', 'friends', 'groups', 'messages', 'notifications', 'settings', 'xprofile' ) );
+
+		/**
+		 * Filters the required components.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param array $value Array of required components.
+		 */
+		$bp->required_components = apply_filters( 'bp_required_components', array( 'members' ) );
+
+		// Get a list of activated components
+		if ( $active_components = bp_get_option( 'bp-active-components' ) ) {
+
+			/** This filter is documented in bp-core/admin/bp-core-admin-components.php */
+			$bp->active_components      = apply_filters( 'bp_active_components', $active_components );
+
+			/**
+			 * Filters the deactivated components.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param array $value Array of deactivated components.
+			 */
+			$bp->deactivated_components = apply_filters( 'bp_deactivated_components', array_values( array_diff( array_values( array_merge( $bp->optional_components, $bp->required_components ) ), array_keys( $bp->active_components ) ) ) );
+
+		// Pre 1.5 Backwards compatibility
+		} elseif ( $deactivated_components = bp_get_option( 'bp-deactivated-components' ) ) {
+
+			// Trim off namespace and filename
+			foreach ( array_keys( (array) $deactivated_components ) as $component ) {
+				$trimmed[] = str_replace( '.php', '', str_replace( 'bp-', '', $component ) );
+			}
+
+			/** This filter is documented in bp-core/bp-core-loader.php */
+			$bp->deactivated_components = apply_filters( 'bp_deactivated_components', $trimmed );
+
+			// Setup the active components
+			$active_components     = array_fill_keys( array_diff( array_values( array_merge( $bp->optional_components, $bp->required_components ) ), array_values( $bp->deactivated_components ) ), '1' );
+
+			/** This filter is documented in bp-core/admin/bp-core-admin-components.php */
+			$bp->active_components = apply_filters( 'bp_active_components', $bp->active_components );
+
+		// Default to all components active
+		} else {
+
+			// Set globals
+			$bp->deactivated_components = array();
+
+			// Setup the active components
+			$active_components     = array_fill_keys( array_values( array_merge( $bp->optional_components, $bp->required_components ) ), '1' );
+
+			/** This filter is documented in bp-core/admin/bp-core-admin-components.php */
+			$bp->active_components = apply_filters( 'bp_active_components', $bp->active_components );
+		}
+
+		// Loop through optional components
+		foreach( $bp->optional_components as $component ) {
+			if ( bp_is_active( $component ) && file_exists( $bp->plugin_dir . '/bp-' . $component . '/bp-' . $component . '-loader.php' ) ) {
+				include( $bp->plugin_dir . '/bp-' . $component . '/bp-' . $component . '-loader.php' );
+			}
+		}
+
+		// Loop through required components
+		foreach( $bp->required_components as $component ) {
+			if ( file_exists( $bp->plugin_dir . '/bp-' . $component . '/bp-' . $component . '-loader.php' ) ) {
+				include( $bp->plugin_dir . '/bp-' . $component . '/bp-' . $component . '-loader.php' );
+			}
+		}
+
+		// Add Core to required components
+		$bp->required_components[] = 'core';
+
+		/**
+		 * Fires after the loading of individual components.
+		 *
+		 * @since 2.0.0
+		 */
+		do_action( 'bp_core_components_included' );
+	}
+
+	/**
+	 * Include bp-core files.
+	 *
+	 * @see BP_Component::includes() for description of parameters.
+	 *
+	 * @param array $includes See {@link BP_Component::includes()}.
+	 */
+	public function includes( $includes = array() ) {
+
+		if ( ! is_admin() ) {
+			return;
+		}
+
+		$includes = array(
+			'admin'
+		);
+
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Set up bp-core global settings.
+	 *
+	 * Sets up a majority of the BuddyPress globals that require a minimal
+	 * amount of processing, meaning they cannot be set in the BuddyPress class.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_globals() for description of parameters.
+	 *
+	 * @param array $args See {@link BP_Component::setup_globals()}.
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		/** Database **********************************************************/
+
+		// Get the base database prefix
+		if ( empty( $bp->table_prefix ) ) {
+			$bp->table_prefix = bp_core_get_table_prefix();
+		}
+
+		// The domain for the root of the site where the main blog resides
+		if ( empty( $bp->root_domain ) ) {
+			$bp->root_domain = bp_core_get_root_domain();
+		}
+
+		// Fetches all of the core BuddyPress settings in one fell swoop
+		if ( empty( $bp->site_options ) ) {
+			$bp->site_options = bp_core_get_root_options();
+		}
+
+		// The names of the core WordPress pages used to display BuddyPress content
+		if ( empty( $bp->pages ) ) {
+			$bp->pages = bp_core_get_directory_pages();
+		}
+
+		/** Basic current user data *******************************************/
+
+		// Logged in user is the 'current_user'
+		$current_user            = wp_get_current_user();
+
+		// The user ID of the user who is currently logged in.
+		$bp->loggedin_user       = new stdClass;
+		$bp->loggedin_user->id   = isset( $current_user->ID ) ? $current_user->ID : 0;
+
+		/** Avatars ***********************************************************/
+
+		// Fetches the default Gravatar image to use if the user/group/blog has no avatar or gravatar
+		$bp->grav_default        = new stdClass;
+
+		/**
+		 * Filters the default user Gravatar.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Default user Gravatar.
+		 */
+		$bp->grav_default->user  = apply_filters( 'bp_user_gravatar_default',  $bp->site_options['avatar_default'] );
+
+		/**
+		 * Filters the default group Gravatar.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Default group Gravatar.
+		 */
+		$bp->grav_default->group = apply_filters( 'bp_group_gravatar_default', $bp->grav_default->user );
+
+		/**
+		 * Filters the default blog Gravatar.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Default blog Gravatar.
+		 */
+		$bp->grav_default->blog  = apply_filters( 'bp_blog_gravatar_default',  $bp->grav_default->user );
+
+		// Notifications table. Included here for legacy purposes. Use
+		// bp-notifications instead.
+		$bp->core->table_name_notifications = $bp->table_prefix . 'bp_notifications';
+
+		/**
+		 * Used to determine if user has admin rights on current content. If the
+		 * logged in user is viewing their own profile and wants to delete
+		 * something, is_item_admin is used. This is a generic variable so it
+		 * can be used by other components. It can also be modified, so when
+		 * viewing a group 'is_item_admin' would be 'true' if they are a group
+		 * admin, and 'false' if they are not.
+		 */
+		bp_update_is_item_admin( bp_user_has_access(), 'core' );
+
+		// Is the logged in user is a mod for the current item?
+		bp_update_is_item_mod( false,                  'core' );
+
+		/**
+		 * Fires at the end of the setup of bp-core globals setting.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_core_setup_globals' );
+	}
+
+	/**
+	 * Setup cache groups
+	 *
+	 * @since 2.2.0
+	 */
+	public function setup_cache_groups() {
+
+		// Global groups
+		wp_cache_add_global_groups( array(
+			'bp'
+		) );
+
+		parent::setup_cache_groups();
+	}
+}
+
+/**
+ * Set up the BuddyPress Core component.
+ *
+ * @since 1.6.0
+ *
+ * @global BuddyPress $bp BuddyPress global settings object.
+ */
+function bp_setup_core() {
+	buddypress()->core = new BP_Core();
+}
+add_action( 'bp_loaded', 'bp_setup_core', 0 );
Index: /tags/2.4.3/src/bp-core/bp-core-moderation.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-moderation.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-moderation.php	(revision 10410)
@@ -0,0 +1,339 @@
+<?php
+/**
+ * BuddyPress Moderation Functions.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @since 1.6.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/** Moderation ****************************************************************/
+
+/**
+ * Check for flooding.
+ *
+ * Check to make sure that a user is not making too many posts in a short amount
+ * of time.
+ *
+ * @since 1.6.0
+ *
+ * @uses current_user_can() To check if the current user can throttle.
+ * @uses bp_get_option() To get the throttle time.
+ * @uses get_transient() To get the last posted transient of the ip.
+ * @uses get_user_meta() To get the last posted meta of the user.
+ *
+ * @param int $user_id User id to check for flood.
+ *
+ * @return bool True if there is no flooding, false if there is.
+ */
+function bp_core_check_for_flood( $user_id = 0 ) {
+
+	// Option disabled. No flood checks.
+	if ( !$throttle_time = bp_get_option( '_bp_throttle_time' ) ) {
+		return true;
+	}
+
+	// Bail if no user ID passed
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	$last_posted = get_user_meta( $user_id, '_bp_last_posted', true );
+	if ( isset( $last_posted ) && ( time() < ( $last_posted + $throttle_time ) ) && !current_user_can( 'throttle' ) ) {
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Check for moderation keys and too many links.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_current_author_ip() To get current user IP address.
+ * @uses bp_current_author_ua() To get current user agent.
+ * @uses bp_current_user_can() Allow super admins to bypass blacklist.
+ *
+ * @param int    $user_id Topic or reply author ID.
+ * @param string $title   The title of the content.
+ * @param string $content The content being posted.
+ *
+ * @return bool True if test is passed, false if fail.
+ */
+function bp_core_check_for_moderation( $user_id = 0, $title = '', $content = '' ) {
+
+	/**
+	 * Filters whether or not to bypass checking for moderation keys and too many links.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param bool   $value   Whether or not to bypass checking. Default false.
+	 * @param int    $user_id Topic of reply author ID.
+	 * @param string $title   The title of the content.
+	 * @param string $content $the content being posted.
+	 */
+	if ( apply_filters( 'bp_bypass_check_for_moderation', false, $user_id, $title, $content ) ) {
+		return true;
+	}
+
+	// Bail if super admin is author
+	if ( is_super_admin( $user_id ) ) {
+		return true;
+	}
+
+	// Define local variable(s)
+	$_post     = array();
+	$match_out = '';
+
+	/** User Data *************************************************************/
+
+	if ( ! empty( $user_id ) ) {
+
+		// Get author data
+		$user = get_userdata( $user_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'] = bp_core_current_user_ip();
+	$_post['user_ua'] = bp_core_current_user_ua();
+
+	// Post title and content
+	$_post['title']   = $title;
+	$_post['content'] = $content;
+
+	/** Max Links *************************************************************/
+
+	$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'] ) ) {
+
+			/**
+			 * Filters the maximum amount of links allowed to include the user's URL.
+			 *
+			 * @since 1.6.0
+			 *
+			 * @param string $num_links How many links found.
+			 * @param string $value     User's url.
+			 */
+			$num_links = apply_filters( 'comment_max_links_url', $num_links, $_post['url'] );
+		}
+
+		// Das ist zu viele links!
+		if ( $num_links >= $max_links ) {
+			return false;
+		}
+	}
+
+	/** Blacklist *************************************************************/
+
+	// Get the moderation keys
+	$blacklist = trim( get_option( 'moderation_keys' ) );
+
+	// Bail if blacklist is empty
+	if ( ! empty( $blacklist ) ) {
+
+		// Get words separated by new lines
+		$words = explode( "\n", $blacklist );
+
+		// 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;
+}
+
+/**
+ * Check for blocked keys.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_current_author_ip() To get current user IP address.
+ * @uses bp_current_author_ua() To get current user agent.
+ * @uses bp_current_user_can() Allow super admins to bypass blacklist.
+ *
+ * @param int    $user_id Topic or reply author ID.
+ * @param string $title   The title of the content.
+ * @param string $content The content being posted.
+ *
+ * @return bool True if test is passed, false if fail.
+ */
+function bp_core_check_for_blacklist( $user_id = 0, $title = '', $content = '' ) {
+
+	/**
+	 * Filters whether or not to bypass checking for blocked keys.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param bool   $value   Whether or not to bypass checking. Default false.
+	 * @param int    $user_id Topic of reply author ID.
+	 * @param string $title   The title of the content.
+	 * @param string $content $the content being posted.
+	 */
+	if ( apply_filters( 'bp_bypass_check_for_blacklist', false, $user_id, $title, $content ) ) {
+		return true;
+	}
+
+	// Bail if super admin is author
+	if ( is_super_admin( $user_id ) ) {
+		return true;
+	}
+
+	// Define local variable
+	$_post = array();
+
+	/** Blacklist *************************************************************/
+
+	// Get the moderation keys
+	$blacklist = trim( get_option( 'blacklist_keys' ) );
+
+	// Bail if blacklist is empty
+	if ( empty( $blacklist ) ) {
+		return true;
+	}
+
+	/** User Data *************************************************************/
+
+	// Map current user data
+	if ( ! empty( $user_id ) ) {
+
+		// Get author data
+		$user = get_userdata( $user_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'] = bp_core_current_user_ip();
+	$_post['user_ua'] = bp_core_current_user_ua();
+
+	// Post title and content
+	$_post['title']   = $title;
+	$_post['content'] = $content;
+
+	/** Words *****************************************************************/
+
+	// Get words separated by new lines
+	$words = explode( "\n", $blacklist );
+
+	// 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;
+}
+
+/**
+ * Get the current user's IP address.
+ *
+ * @since 1.6.0
+ *
+ * @return string IP address.
+ */
+function bp_core_current_user_ip() {
+	$retval = preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] );
+
+	/**
+	 * Filters the current user's IP address.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $retval Current user's IP Address.
+	 */
+	return apply_filters( 'bp_core_current_user_ip', $retval );
+}
+
+/**
+ * Get the current user's user-agent.
+ *
+ * @since 1.6.0
+ *
+ * @return string User agent string.
+ */
+function bp_core_current_user_ua() {
+
+	// Sanity check the user agent
+	if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
+		$retval = substr( $_SERVER['HTTP_USER_AGENT'], 0, 254 );
+	} else {
+		$retval = '';
+	}
+
+	/**
+	 * Filters the current user's user-agent.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $retval Current user's user-agent.
+	 */
+	return apply_filters( 'bp_core_current_user_ua', $retval );
+}
Index: /tags/2.4.3/src/bp-core/bp-core-options.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-options.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-options.php	(revision 10410)
@@ -0,0 +1,914 @@
+<?php
+/**
+ * BuddyPress Options.
+ *
+ * @package BuddyPress
+ * @subpackage Options
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Get the default site options and their values.
+ *
+ * @since 1.6.0
+ *
+ * @return array Filtered option names and values.
+ */
+function bp_get_default_options() {
+
+	// Default options
+	$options = array (
+
+		/** Components ********************************************************/
+
+		'bp-deactivated-components'            => array(),
+
+		/** bbPress ***********************************************************/
+
+		// Legacy bbPress config location
+		'bb-config-location'                   => ABSPATH . 'bb-config.php',
+
+		/** XProfile **********************************************************/
+
+		// Base profile groups name
+		'bp-xprofile-base-group-name'          => 'Base',
+
+		// Base fullname field name
+		'bp-xprofile-fullname-field-name'      => 'Name',
+
+		/** Blogs *************************************************************/
+
+		// Used to decide if blogs need indexing
+		'bp-blogs-first-install'               => false,
+
+		/** Settings **********************************************************/
+
+		// Disable the WP to BP profile sync
+		'bp-disable-profile-sync'              => false,
+
+		// Hide the Toolbar for logged out users
+		'hide-loggedout-adminbar'              => false,
+
+		// Avatar uploads
+		'bp-disable-avatar-uploads'            => false,
+
+		// Cover image uploads
+		'bp-disable-cover-image-uploads'       => false,
+
+		// Group Profile Photos
+		'bp-disable-group-avatar-uploads'      => false,
+
+		// Group Cover image uploads
+		'bp-disable-group-cover-image-uploads' => false,
+
+		// Allow users to delete their own accounts
+		'bp-disable-account-deletion'          => false,
+
+		// Allow comments on blog and forum activity items
+		'bp-disable-blogforum-comments'        => true,
+
+		// The ID for the current theme package.
+		'_bp_theme_package_id'                 => 'legacy',
+
+		/** Groups ************************************************************/
+
+		// @todo Move this into the groups component
+
+		// Restrict group creation to super admins
+		'bp_restrict_group_creation'           => false,
+
+		/** Akismet ***********************************************************/
+
+		// Users from all sites can post
+		'_bp_enable_akismet'                   => true,
+
+		/** Activity HeartBeat ************************************************/
+
+		// HeartBeat is on to refresh activities
+		'_bp_enable_heartbeat_refresh'         => true,
+
+		/** BuddyBar **********************************************************/
+
+		// Force the BuddyBar
+		'_bp_force_buddybar'                   => false,
+
+		/** Legacy theme *********************************************/
+
+		// Whether to register the bp-default themes directory
+		'_bp_retain_bp_default'                => false,
+
+		/** Widgets **************************************************/
+		'widget_bp_core_login_widget'                => false,
+		'widget_bp_core_members_widget'              => false,
+		'widget_bp_core_whos_online_widget'          => false,
+		'widget_bp_core_recently_active_widget'      => false,
+		'widget_bp_groups_widget'                    => false,
+		'widget_bp_messages_sitewide_notices_widget' => false,
+	);
+
+	/**
+	 * Filters the default options to be set upon activation.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $options Array of default options to set.
+	 */
+	return apply_filters( 'bp_get_default_options', $options );
+}
+
+/**
+ * Add default options when BuddyPress is first activated.
+ *
+ * Only called once when BuddyPress is activated.
+ * Non-destructive, so existing settings will not be overridden.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_default_options() To get default options.
+ * @uses add_option() Adds default options.
+ * @uses do_action() Calls 'bp_add_options'.
+ */
+function bp_add_options() {
+
+	// Get the default options and values
+	$options = bp_get_default_options();
+
+	// Add default options
+	foreach ( $options as $key => $value ) {
+		bp_add_option( $key, $value );
+	}
+
+	/**
+	 * Fires after the addition of default options when BuddyPress is first activated.
+	 *
+	 * Allows previously activated plugins to append their own options.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_add_options' );
+}
+
+/**
+ * Delete default options.
+ *
+ * Hooked to bp_uninstall, it is only called once when BuddyPress is uninstalled.
+ * This is destructive, so existing settings will be destroyed.
+ *
+ * Currently unused.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_default_options() To get default options.
+ * @uses delete_option() Removes default options.
+ * @uses do_action() Calls 'bp_delete_options'.
+ */
+function bp_delete_options() {
+
+	// Get the default options and values
+	$options = bp_get_default_options();
+
+	// Add default options
+	foreach ( array_keys( $options ) as $key ) {
+		delete_option( $key );
+	}
+
+	/**
+	 * Fires after the deletion of default options when BuddyPress is first deactivated.
+	 *
+	 * Allows previously activated plugins to append their own options.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_delete_options' );
+}
+
+/**
+ * Add filters to each BP option, allowing them to be overloaded from inside the $bp->options array.
+ *
+ * Currently unused.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_default_options() To get default options.
+ * @uses add_filter() To add filters to 'pre_option_{$key}'.
+ * @uses do_action() Calls 'bp_add_option_filters'.
+ */
+function bp_setup_option_filters() {
+
+	// Get the default options and values
+	$options = bp_get_default_options();
+
+	// Add filters to each BuddyPress option
+	foreach ( array_keys( $options ) as $key ) {
+		add_filter( 'pre_option_' . $key, 'bp_pre_get_option' );
+	}
+
+	/**
+	 * Fires after the addition of filters to each BuddyPress option.
+	 *
+	 * Allows previously activated plugins to append their own options.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_setup_option_filters' );
+}
+
+/**
+ * Filter default options and allow them to be overloaded from inside the $bp->options array.
+ *
+ * Currently unused.
+ *
+ * @since 1.6.0
+ *
+ * @param bool $value Optional. Default value false.
+ *
+ * @return mixed False if not overloaded, mixed if set.
+ */
+function bp_pre_get_option( $value = false ) {
+	$bp = buddypress();
+
+	// Get the name of the current filter so we can manipulate it
+	$filter = current_filter();
+
+	// Remove the filter prefix
+	$option = str_replace( 'pre_option_', '', $filter );
+
+	// Check the options global for preset value
+	if ( ! empty( $bp->options[ $option ] ) ) {
+		$value = $bp->options[ $option ];
+	}
+
+	// Always return a value, even if false
+	return $value;
+}
+
+/**
+ * Retrieve an option.
+ *
+ * This is a wrapper for {@link get_blog_option()}, which in turn stores settings data
+ * (such as bp-pages) on the appropriate blog, given your current setup.
+ *
+ * The 'bp_get_option' filter is primarily for backward-compatibility.
+ *
+ * @since 1.2.0
+ *
+ * @uses bp_get_root_blog_id()
+ *
+ * @param string $option_name The option to be retrieved.
+ * @param string $default     Optional. Default value to be returned if the option
+ *                            isn't set. See {@link get_blog_option()}.
+ *
+ * @return mixed The value for the option.
+ */
+function bp_get_option( $option_name, $default = '' ) {
+	$value = get_blog_option( bp_get_root_blog_id(), $option_name, $default );
+
+	/**
+	 * Filters the option value for the requested option.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param mixed $value The value for the option.
+	 */
+	return apply_filters( 'bp_get_option', $value );
+}
+
+/**
+ * Add an option.
+ *
+ * This is a wrapper for {@link add_blog_option()}, which in turn stores
+ * settings data on the appropriate blog, given your current setup.
+ *
+ * @since 2.0.0
+ *
+ * @param string $option_name The option key to be set.
+ * @param mixed  $value       The value to be set.
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_add_option( $option_name, $value ) {
+	return add_blog_option( bp_get_root_blog_id(), $option_name, $value );
+}
+
+/**
+ * Save an option.
+ *
+ * This is a wrapper for {@link update_blog_option()}, which in turn stores
+ * settings data (such as bp-pages) on the appropriate blog, given your current
+ * setup.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_root_blog_id()
+ *
+ * @param string $option_name The option key to be set.
+ * @param string $value       The value to be set.
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_update_option( $option_name, $value ) {
+	return update_blog_option( bp_get_root_blog_id(), $option_name, $value );
+}
+
+/**
+ * Delete an option.
+ *
+ * This is a wrapper for {@link delete_blog_option()}, which in turn deletes
+ * settings data (such as bp-pages) on the appropriate blog, given your current
+ * setup.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_root_blog_id()
+ *
+ * @param string $option_name The option key to be deleted.
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_delete_option( $option_name ) {
+	return delete_blog_option( bp_get_root_blog_id(), $option_name );
+}
+
+/**
+ * Copy BP options from a single site to multisite config.
+ *
+ * Run when switching from single to multisite and we need to copy blog options
+ * to site options.
+ *
+ * This function is no longer used.
+ *
+ * @deprecated 1.6.0
+ *
+ * @param array $keys
+ *
+ * @return bool
+ */
+function bp_core_activate_site_options( $keys = array() ) {
+
+	if ( !empty( $keys ) && is_array( $keys ) ) {
+		$bp = buddypress();
+
+		$errors = false;
+
+		foreach ( $keys as $key => $default ) {
+			if ( empty( $bp->site_options[ $key ] ) ) {
+				$bp->site_options[ $key ] = bp_get_option( $key, $default );
+
+				if ( !bp_update_option( $key, $bp->site_options[ $key ] ) ) {
+					$errors = true;
+				}
+			}
+		}
+
+		if ( empty( $errors ) ) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/**
+ * Fetch global BP options.
+ *
+ * BuddyPress uses common options to store configuration settings. Many of these
+ * settings are needed at run time. Instead of fetching them all and adding many
+ * initial queries to each page load, let's fetch them all in one go.
+ *
+ * @todo Use settings API and audit these methods.
+ *
+ * @return array $root_blog_options_meta List of options.
+ */
+function bp_core_get_root_options() {
+	global $wpdb;
+
+	// Get all the BuddyPress settings, and a few useful WP ones too
+	$root_blog_options                   = bp_get_default_options();
+	$root_blog_options['registration']   = '0';
+	$root_blog_options['avatar_default'] = 'mysteryman';
+	$root_blog_option_keys               = array_keys( $root_blog_options );
+
+	// Do some magic to get all the root blog options in 1 swoop
+	// Check cache first - We cache here instead of using the standard WP
+	// settings cache because the current blog may not be the root blog,
+	// and it's not practical to access the cache across blogs
+	$root_blog_options_meta = wp_cache_get( 'root_blog_options', 'bp' );
+
+	if ( false === $root_blog_options_meta ) {
+		$blog_options_keys      = "'" . join( "', '", (array) $root_blog_option_keys ) . "'";
+		$blog_options_table	    = bp_is_multiblog_mode() ? $wpdb->options : $wpdb->get_blog_prefix( bp_get_root_blog_id() ) . 'options';
+		$blog_options_query     = "SELECT option_name AS name, option_value AS value FROM {$blog_options_table} WHERE option_name IN ( {$blog_options_keys} )";
+		$root_blog_options_meta = $wpdb->get_results( $blog_options_query );
+
+		// On Multisite installations, some options must always be fetched from sitemeta
+		if ( is_multisite() ) {
+
+			/**
+			 * Filters multisite options retrieved from sitemeta.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param array $value Array of multisite options from sitemeta table.
+			 */
+			$network_options = apply_filters( 'bp_core_network_options', array(
+				'tags_blog_id'       => '0',
+				'sitewide_tags_blog' => '',
+				'registration'       => '0',
+				'fileupload_maxk'    => '1500'
+			) );
+
+			$current_site           = get_current_site();
+			$network_option_keys    = array_keys( $network_options );
+			$sitemeta_options_keys  = "'" . join( "', '", (array) $network_option_keys ) . "'";
+			$sitemeta_options_query = $wpdb->prepare( "SELECT meta_key AS name, meta_value AS value FROM {$wpdb->sitemeta} WHERE meta_key IN ( {$sitemeta_options_keys} ) AND site_id = %d", $current_site->id );
+			$network_options_meta   = $wpdb->get_results( $sitemeta_options_query );
+
+			// Sitemeta comes second in the merge, so that network 'registration' value wins
+			$root_blog_options_meta = array_merge( $root_blog_options_meta, $network_options_meta );
+		}
+
+		// Missing some options, so do some one-time fixing
+		if ( empty( $root_blog_options_meta ) || ( count( $root_blog_options_meta ) < count( $root_blog_option_keys ) ) ) {
+
+			// Get a list of the keys that are already populated
+			$existing_options = array();
+			foreach( $root_blog_options_meta as $already_option ) {
+				$existing_options[$already_option->name] = $already_option->value;
+			}
+
+			// Unset the query - We'll be resetting it soon
+			unset( $root_blog_options_meta );
+
+			// Loop through options
+			foreach ( $root_blog_options as $old_meta_key => $old_meta_default ) {
+
+				if ( isset( $existing_options[$old_meta_key] ) ) {
+					continue;
+				}
+
+				// Get old site option
+				if ( is_multisite() ) {
+					$old_meta_value = get_site_option( $old_meta_key );
+				}
+
+				// No site option so look in root blog
+				if ( empty( $old_meta_value ) ) {
+					$old_meta_value = bp_get_option( $old_meta_key, $old_meta_default );
+				}
+
+				// Update the root blog option
+				bp_update_option( $old_meta_key, $old_meta_value );
+
+				// Update the global array
+				$root_blog_options_meta[$old_meta_key] = $old_meta_value;
+
+				// Clear out the value for the next time around
+				unset( $old_meta_value );
+			}
+
+			$root_blog_options_meta = array_merge( $root_blog_options_meta, $existing_options );
+			unset( $existing_options );
+
+		// We're all matched up
+		} else {
+			// Loop through our results and make them usable
+			foreach ( $root_blog_options_meta as $root_blog_option ) {
+				$root_blog_options[$root_blog_option->name] = $root_blog_option->value;
+			}
+
+			// Copy the options no the return val
+			$root_blog_options_meta = $root_blog_options;
+
+			// Clean up our temporary copy
+			unset( $root_blog_options );
+		}
+
+		wp_cache_set( 'root_blog_options', $root_blog_options_meta, 'bp' );
+	}
+
+	/**
+	 * Filters the global BP options.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $root_blog_options_meta Array of global BP options.
+	 */
+	return apply_filters( 'bp_core_get_root_options', $root_blog_options_meta );
+}
+
+/**
+ * Get a root option.
+ *
+ * "Root options" are those that apply across an entire installation, and are fetched only a single
+ * time during a pageload and stored in `buddypress()->site_options` to prevent future lookups.
+ * See {@see bp_core_get_root_options()}.
+ *
+ * @since 2.3.0
+ *
+ * @param  string $option Name of the option key.
+ *
+ * @return mixed Value, if found.
+ */
+function bp_core_get_root_option( $option ) {
+	$bp = buddypress();
+
+	if ( ! isset( $bp->site_options ) ) {
+		$bp->site_options = bp_core_get_root_options();
+	}
+
+	$value = '';
+	if ( isset( $bp->site_options[ $option ] ) ) {
+		$value = $bp->site_options[ $option ];
+	}
+
+	return $value;
+}
+
+/** Active? *******************************************************************/
+
+/**
+ * Is profile syncing disabled?
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_option() To get the profile sync option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: true.
+ *
+ * @return bool True if profile sync is enabled, otherwise false.
+ */
+function bp_disable_profile_sync( $default = false ) {
+
+	/**
+	 * Filters whether or not profile syncing is disabled.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Whether or not syncing is disabled.
+	 */
+	return (bool) apply_filters( 'bp_disable_profile_sync', (bool) bp_get_option( 'bp-disable-profile-sync', $default ) );
+}
+
+/**
+ * Is the Toolbar hidden for logged out users?
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_option() To get the logged out Toolbar option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: true.
+ *
+ * @return bool True if the admin bar should be hidden for logged-out users,
+ *              otherwise false.
+ */
+function bp_hide_loggedout_adminbar( $default = true ) {
+
+	/**
+	 * Filters whether or not the toolbar is hidden for logged out users.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Whether or not the toolbar is hidden.
+	 */
+	return (bool) apply_filters( 'bp_hide_loggedout_adminbar', (bool) bp_get_option( 'hide-loggedout-adminbar', $default ) );
+}
+
+/**
+ * Are members able to upload their own avatars?
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_option() To get the avatar uploads option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: true.
+ *
+ * @return bool True if avatar uploads are disabled, otherwise false.
+ */
+function bp_disable_avatar_uploads( $default = true ) {
+
+	/**
+	 * Filters whether or not members are able to upload their own avatars.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Whether or not members are able to upload their own avatars.
+	 */
+	return (bool) apply_filters( 'bp_disable_avatar_uploads', (bool) bp_get_option( 'bp-disable-avatar-uploads', $default ) );
+}
+
+/**
+ * Are members able to upload their own cover images?
+ *
+ * @since 2.4.0
+ *
+ * @uses bp_get_option() To get the cover image uploads option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: false.
+ *
+ * @return bool True if cover image uploads are disabled, otherwise false.
+ */
+function bp_disable_cover_image_uploads( $default = false ) {
+
+	/**
+	 * Filters whether or not members are able to upload their own cover images.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param bool $value Whether or not members are able to upload their own cover images.
+	 */
+	return (bool) apply_filters( 'bp_disable_cover_image_uploads', (bool) bp_get_option( 'bp-disable-cover-image-uploads', $default ) );
+}
+
+/**
+ * Are group avatars disabled?
+ *
+ * For backward compatibility, this option falls back on the value of 'bp-disable-avatar-uploads' when no value is
+ * found in the database.
+ *
+ * @since 2.3.0
+ *
+ * @param bool|null $default Optional. Fallback value if not found in the database.
+ *                           Defaults to the value of `bp_disable_avatar_uploads()`.
+ *
+ * @return bool True if group avatar uploads are disabled, otherwise false.
+ */
+function bp_disable_group_avatar_uploads( $default = null ) {
+	$disabled = bp_get_option( 'bp-disable-group-avatar-uploads', '' );
+
+	if ( '' === $disabled ) {
+		if ( is_null( $default ) ) {
+			$disabled = bp_disable_avatar_uploads();
+		} else {
+			$disabled = $default;
+		}
+	}
+
+	/**
+	 * Filters whether or not members are able to upload group avatars.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param bool $disabled Whether or not members are able to upload their groups avatars.
+	 * @param bool $default  Default value passed to the function.
+	 */
+	return (bool) apply_filters( 'bp_disable_group_avatar_uploads', $disabled, $default );
+}
+
+/**
+ * Are group cover images disabled?
+ *
+ * @since 2.4.0
+ *
+ * @uses bp_get_option() To get the group cover image uploads option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: false.
+ *
+ * @return bool True if group cover image uploads are disabled, otherwise false.
+ */
+function bp_disable_group_cover_image_uploads( $default = false ) {
+
+	/**
+	 * Filters whether or not members are able to upload group cover images.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param bool $value Whether or not members are able to upload thier groups cover images.
+	 */
+	return (bool) apply_filters( 'bp_disable_group_cover_image_uploads', (bool) bp_get_option( 'bp-disable-group-cover-image-uploads', $default ) );
+}
+
+/**
+ * Are members able to delete their own accounts?
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_option() To get the account deletion option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: true.
+ *
+ * @return bool True if users are able to delete their own accounts, otherwise
+ *              false.
+ */
+function bp_disable_account_deletion( $default = false ) {
+
+	/**
+	 * Filters whether or not members are able to delete their own accounts.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Whether or not members are able to delete their own accounts.
+	 */
+	return apply_filters( 'bp_disable_account_deletion', (bool) bp_get_option( 'bp-disable-account-deletion', $default ) );
+}
+
+/**
+ * Are blog and forum activity stream comments disabled?
+ *
+ * @since 1.6.0
+ *
+ * @todo split and move into blog and forum components.
+ * @uses bp_get_option() To get the blog/forum comments option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: false.
+ *
+ * @return bool True if activity comments are disabled for blog and forum
+ *              items, otherwise false.
+ */
+function bp_disable_blogforum_comments( $default = false ) {
+
+	/**
+	 * Filters whether or not blog and forum activity stream comments are disabled.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Whether or not blog and forum activity stream comments are disabled.
+	 */
+	return (bool) apply_filters( 'bp_disable_blogforum_comments', (bool) bp_get_option( 'bp-disable-blogforum-comments', $default ) );
+}
+
+/**
+ * Is group creation turned off?
+ *
+ * @since 1.6.0
+ *
+ * @todo Move into groups component.
+ * @uses bp_get_option() To get the group creation.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: true.
+ *
+ * @return bool True if group creation is restricted, otherwise false.
+ */
+function bp_restrict_group_creation( $default = true ) {
+
+	/**
+	 * Filters whether or not group creation is turned off.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Whether or not group creation is turned off.
+	 */
+	return (bool) apply_filters( 'bp_restrict_group_creation', (bool) bp_get_option( 'bp_restrict_group_creation', $default ) );
+}
+
+/**
+ * Should the old BuddyBar be forced in place of the WP admin bar?
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_option() To get the BuddyBar option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: true.
+ *
+ * @return bool True if the BuddyBar should be forced on, otherwise false.
+ */
+function bp_force_buddybar( $default = true ) {
+
+	/**
+	 * Filters whether or not BuddyBar should be forced in place of WP Admin Bar.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Whether or not BuddyBar should be forced in place of WP Admin Bar.
+	 */
+	return (bool) apply_filters( 'bp_force_buddybar', (bool) bp_get_option( '_bp_force_buddybar', $default ) );
+}
+
+/**
+ * Output the group forums root parent forum id.
+ *
+ * @since 1.6.0
+ *
+ * @param bool|string $default Optional. Default: '0'.
+ */
+function bp_group_forums_root_id( $default = '0' ) {
+	echo bp_get_group_forums_root_id( $default );
+}
+	/**
+	 * Return the group forums root parent forum id.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses bp_get_option() To get the root forum ID from the database.
+	 *
+	 * @param bool|string $default Optional. Default: '0'.
+	 *
+	 * @return int The ID of the group forums root forum.
+	 */
+	function bp_get_group_forums_root_id( $default = '0' ) {
+
+		/**
+		 * Filters the group forums root parent forum id.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param int $value The group forums root parent forum id.
+		 */
+		return (int) apply_filters( 'bp_get_group_forums_root_id', (int) bp_get_option( '_bp_group_forums_root_id', $default ) );
+	}
+
+/**
+ * Check whether BuddyPress Group Forums are enabled.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_option() To get the group forums option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: true.
+ *
+ * @return bool True if group forums are active, otherwise false.
+ */
+function bp_is_group_forums_active( $default = true ) {
+
+	/**
+	 * Filters whether or not BuddyPress Group Forums are enabled.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Whether or not BuddyPress Group Forums are enabled.
+	 */
+	return (bool) apply_filters( 'bp_is_group_forums_active', (bool) bp_get_option( '_bp_enable_group_forums', $default ) );
+}
+
+/**
+ * Check whether Akismet is enabled.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_option() To get the Akismet option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: true.
+ *
+ * @return bool True if Akismet is enabled, otherwise false.
+ */
+function bp_is_akismet_active( $default = true ) {
+
+	/**
+	 * Filters whether or not Akismet is enabled.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $value Whether or not Akismet is enabled.
+	 */
+	return (bool) apply_filters( 'bp_is_akismet_active', (bool) bp_get_option( '_bp_enable_akismet', $default ) );
+}
+
+/**
+ * Check whether Activity Heartbeat refresh is enabled.
+ *
+ * @since 2.0.0
+ *
+ * @uses bp_get_option() To get the Heartbeat option.
+ *
+ * @param bool $default Optional. Fallback value if not found in the database.
+ *                      Default: true.
+ *
+ * @return bool True if Heartbeat refresh is enabled, otherwise false.
+ */
+function bp_is_activity_heartbeat_active( $default = true ) {
+
+	/**
+	 * Filters whether or not Activity Heartbeat refresh is enabled.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param bool $value Whether or not Activity Heartbeat refresh is enabled.
+	 */
+	return (bool) apply_filters( 'bp_is_activity_heartbeat_active', (bool) bp_get_option( '_bp_enable_heartbeat_refresh', $default ) );
+}
+
+/**
+ * Get the current theme package ID.
+ *
+ * @since 1.7.0
+ *
+ * @uses get_option() To get the theme package option.
+ *
+ * @param string $default Optional. Fallback value if not found in the database.
+ *                        Default: 'legacy'.
+ *
+ * @return string ID of the theme package.
+ */
+function bp_get_theme_package_id( $default = 'legacy' ) {
+
+	/**
+	 * Filters the current theme package ID.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $value The current theme package ID.
+	 */
+	return apply_filters( 'bp_get_theme_package_id', bp_get_option( '_bp_theme_package_id', $default ) );
+}
Index: /tags/2.4.3/src/bp-core/bp-core-taxonomy.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-taxonomy.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-taxonomy.php	(revision 10410)
@@ -0,0 +1,117 @@
+<?php
+/**
+ * BuddyPress taxonomy functions.
+ *
+ * Most BuddyPress taxonomy functions are wrappers for their WordPress counterparts.
+ * Because BuddyPress can be activated in various ways in a network environment, we
+ * must switch to the root blog before using the WP functions.
+ *
+ * @since 2.2.0
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register our default taxonomies.
+ *
+ * @since 2.2.0
+ */
+function bp_register_default_taxonomies() {
+	// Member Type.
+	register_taxonomy( 'bp_member_type', 'user', array(
+		'public' => false,
+	) );
+}
+add_action( 'bp_register_taxonomies', 'bp_register_default_taxonomies' );
+
+/**
+ * Set taxonomy terms on a BuddyPress object.
+ *
+ * @since 2.2.0
+ *
+ * @see wp_set_object_terms() for a full description of function and parameters.
+ *
+ * @param int          $object_id Object ID.
+ * @param string|array $terms     Term or terms to set.
+ * @param string       $taxonomy  Taxonomy name.
+ * @param bool         $append    Optional. True to append terms to existing terms. Default: false.
+ *
+ * @return array Array of term taxonomy IDs.
+ */
+function bp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) {
+	$is_root_blog = bp_is_root_blog();
+
+	if ( ! $is_root_blog ) {
+		switch_to_blog( bp_get_root_blog_id() );
+	}
+
+	$retval = wp_set_object_terms( $object_id, $terms, $taxonomy, $append );
+
+	if ( ! $is_root_blog ) {
+		restore_current_blog();
+	}
+
+	return $retval;
+}
+
+/**
+ * Get taxonomy terms for a BuddyPress object.
+ *
+ * @since 2.2.0
+ *
+ * @see wp_get_object_terms() for a full description of function and parameters.
+ *
+ * @param int|array    $object_ids ID or IDs of objects.
+ * @param string|array $taxonomies Name or names of taxonomies to match.
+ * @param array        $args       See {@see wp_get_object_terms()}.
+ *
+ * @return array
+ */
+function bp_get_object_terms( $object_ids, $taxonomies, $args = array() ) {
+	$is_root_blog = bp_is_root_blog();
+
+	if ( ! $is_root_blog ) {
+		switch_to_blog( bp_get_root_blog_id() );
+	}
+
+	$retval = wp_get_object_terms( $object_ids, $taxonomies, $args );
+
+	if ( ! $is_root_blog ) {
+		restore_current_blog();
+	}
+
+	return $retval;
+}
+
+/**
+ * Remove taxonomy terms on a BuddyPress object.
+ *
+ * @since 2.3.0
+ *
+ * @see wp_remove_object_terms() for a full description of function and parameters.
+ *
+ * @param int          $object_id Object ID.
+ * @param string|array $terms     Term or terms to remove.
+ * @param string       $taxonomy  Taxonomy name.
+ *
+ * @return bool|WP_Error True on success, false or WP_Error on failure.
+ */
+function bp_remove_object_terms( $object_id, $terms, $taxonomy ) {
+	$is_root_blog = bp_is_root_blog();
+
+	if ( ! $is_root_blog ) {
+		switch_to_blog( bp_get_root_blog_id() );
+	}
+
+	$retval = wp_remove_object_terms( $object_id, $terms, $taxonomy );
+
+	if ( ! $is_root_blog ) {
+		restore_current_blog();
+	}
+
+	return $retval;
+}
Index: /tags/2.4.3/src/bp-core/bp-core-template-loader.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-template-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-template-loader.php	(revision 10410)
@@ -0,0 +1,568 @@
+<?php
+/**
+ * BuddyPress 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 BuddyPress templates might exist.
+ *
+ * @package BuddyPress
+ * @subpackage TemplateFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Get a BuddyPress template part for display in a theme.
+ *
+ * @since 1.7.0
+ *
+ * @uses bp_locate_template()
+ * @uses load_template()
+ * @uses get_template_part()
+ *
+ * @param string $slug Template part slug. Used to generate filenames,
+ *                     eg 'friends' for 'friends.php'.
+ * @param string $name Optional. Template part name. Used to generate
+ *                     secondary filenames, eg 'personal' for 'activity-personal.php'.
+ *
+ * @return string Path to located template. See {@link bp_locate_template()}.
+ */
+function bp_get_template_part( $slug, $name = null ) {
+
+	/**
+	 * Fires at the start of bp_get_template_part().
+	 *
+	 * This is a variable hook that is dependent on the slug passed in.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $slug Template part slug requested.
+	 * @param string $name Template part name requested.
+	 */
+	do_action( 'get_template_part_' . $slug, $slug, $name );
+
+	// Setup possible parts
+	$templates = array();
+	if ( isset( $name ) ) {
+		$templates[] = $slug . '-' . $name . '.php';
+	}
+	$templates[] = $slug . '.php';
+
+	/**
+	 * Filters the template parts to be loaded.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array  $templates Array of templates located.
+	 * @param string $slug      Template part slug requested.
+	 * @param string $name      Template part name requested.
+	 */
+	$templates = apply_filters( 'bp_get_template_part', $templates, $slug, $name );
+
+	// Return the part that is found
+	return bp_locate_template( $templates, true, false );
+}
+
+/**
+ * Retrieve the name of the highest priority template file that exists.
+ *
+ * Searches in the STYLESHEETPATH before TEMPLATEPATH 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 1.7.0
+ *
+ * @param string|array $template_names Template file(s) to search for, in order.
+ * @param bool         $load           Optional. If true, the template file will be loaded when
+ *                                     found. If false, the path will be returned. Default: false.
+ * @param bool         $require_once   Optional. Whether to require_once or require. Has
+ *                                     no effect if $load is false. Default: true.
+ *
+ * @return string The template filename if one is located.
+ */
+function bp_locate_template( $template_names, $load = false, $require_once = true ) {
+
+	// No file found yet
+	$located            = false;
+	$template_locations = bp_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 BuddyPress 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
+	 * 'bp_get_template_part' or add a new location to the template stack.
+	 */
+	do_action( 'bp_locate_template', $located, $template_name, $template_names, $template_locations, $load, $require_once );
+
+	// Maybe load the template if one was located
+	$use_themes = defined( 'WP_USE_THEMES' ) && WP_USE_THEMES;
+	$doing_ajax = defined( 'DOING_AJAX' ) && DOING_AJAX;
+	if ( ( $use_themes || $doing_ajax ) && ( true == $load ) && ! empty( $located ) ) {
+		load_template( $located, $require_once );
+	}
+
+	return $located;
+}
+
+/**
+ * Register a new template stack location.
+ *
+ * This allows for templates to live in places beyond just the parent/child
+ * relationship, to allow for custom template locations. Used in conjunction
+ * with bp_locate_template(), this allows for easy template overrides.
+ *
+ * @since 1.7.0
+ *
+ * @param string $location_callback Callback function that returns the stack location.
+ * @param int    $priority          Optional. The priority parameter as passed to
+ *                                  add_filter(). Default: 10.
+ *
+ * @return bool See {@link add_filter()}.
+ */
+function bp_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( 'bp_template_stack', $location_callback, (int) $priority );
+}
+
+/**
+ * Deregister a previously registered template stack location.
+ *
+ * @since 1.7.0
+ *
+ * @see bp_register_template_stack()
+ *
+ * @param string $location_callback Callback function that returns the stack location.
+ * @param int    $priority          Optional. The priority parameter passed to
+ *                                  {@link bp_register_template_stack()}. Default: 10.
+ *
+ * @return bool See {@link remove_filter()}.
+ */
+function bp_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;
+	}
+
+	// Add location callback to template stack
+	return remove_filter( 'bp_template_stack', $location_callback, (int) $priority );
+}
+
+/**
+ * Get the "template stack", a list of registered directories where templates can be found.
+ *
+ * Calls the functions added to the 'bp_template_stack' filter hook, and return
+ * an array of the template locations.
+ *
+ * @see bp_register_template_stack()
+ *
+ * @since 1.7.0
+ *
+ * @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 bp_get_template_stack() {
+	global $wp_filter, $merged_filters, $wp_current_filter;
+
+	// Setup some default variables
+	$tag  = 'bp_template_stack';
+	$args = $stack = array();
+
+	// Add 'bp_template_stack' to the current filter array
+	$wp_current_filter[] = $tag;
+
+	// Sort
+	if ( class_exists( 'WP_Hook' ) ) {
+		$filter = $wp_filter[ $tag ]->callbacks;
+	} else {
+		$filter = &$wp_filter[ $tag ];
+
+		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 'bp_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 'bp_template_stack' from the current filter array
+	array_pop( $wp_current_filter );
+
+	// Remove empties and duplicates
+	$stack = array_unique( array_filter( $stack ) );
+
+	/**
+	 * Filters the "template stack" list of registered directories where templates can be found.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $stack Array of registered directories for template locations.
+	 */
+	return (array) apply_filters( 'bp_get_template_stack', $stack ) ;
+}
+
+/**
+ * Put a template part into an output buffer, and return it.
+ *
+ * @since 1.7.0
+ *
+ * @see bp_get_template_part() for a description of $slug and $name params.
+ *
+ * @param string $slug See {@link bp_get_template_part()}.
+ * @param string $name See {@link bp_get_template_part()}.
+ * @param bool   $echo If true, template content will be echoed. If false,
+ *                     returned. Default: true.
+ *
+ * @return string|null If $echo, returns the template content.
+ */
+function bp_buffer_template_part( $slug, $name = null, $echo = true ) {
+	ob_start();
+
+	// Remove 'bp_replace_the_content' filter to prevent infinite loops
+	remove_filter( 'the_content', 'bp_replace_the_content' );
+
+	bp_get_template_part( $slug, $name );
+
+	// Remove 'bp_replace_the_content' filter to prevent infinite loops
+	add_filter( 'the_content', 'bp_replace_the_content' );
+
+	// 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 the 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 bp_locate_template()}. Allows for more generic template
+ * locations without the use of the other get_*_template() functions.
+ *
+ * @since 1.7.0
+ *
+ * @uses bp_set_theme_compat_templates()
+ * @uses bp_locate_template()
+ * @uses bp_set_theme_compat_template()
+ *
+ * @param string $type      Filename without extension.
+ * @param array  $templates An optional list of template candidates.
+ *
+ * @return string Full path to file.
+ */
+function bp_get_query_template( $type, $templates = array() ) {
+	$type = preg_replace( '|[^a-z0-9-]+|', '', $type );
+
+	if ( empty( $templates ) ) {
+		$templates = array( "{$type}.php" );
+	}
+
+	/**
+	 * Filters possible file paths to check for for a template.
+	 *
+	 * This is a variable filter based on the type passed into
+	 * bp_get_query_template.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $templates Array of template files already prepared.
+	 */
+	$templates = apply_filters( "bp_get_{$type}_template", $templates );
+
+	// Filter possible templates, try to match one, and set any BuddyPress theme
+	// compat properties so they can be cross-checked later.
+	$templates = bp_set_theme_compat_templates( $templates );
+	$template  = bp_locate_template( $templates );
+	$template  = bp_set_theme_compat_template( $template );
+
+	/**
+	 * Filters the path to a template file.
+	 *
+	 * This is a variable filter based on the type passed into
+	 * bp_get_query_template.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $template Path to the most appropriate found template file.
+	 */
+	return apply_filters( "bp_{$type}_template", $template );
+}
+
+/**
+ * Get the possible subdirectories to check for templates in.
+ *
+ * @since 1.7.0
+ *
+ * @param array $templates Templates we are looking for.
+ *
+ * @return array Possible subfolders to look in.
+ */
+function bp_get_template_locations( $templates = array() ) {
+	$locations = array(
+		'buddypress',
+		'community',
+		''
+	);
+
+	/**
+	 * Filters the possible subdirectories to check for templates in.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $locations Array of subfolders to look in.
+	 * @param array $templates Array of templates we are looking for.
+	 */
+	return apply_filters( 'bp_get_template_locations', $locations, $templates );
+}
+
+/**
+ * Add template locations to template files being searched for.
+ *
+ * @since 1.7.0
+ *
+ * @param array $stacks Array of template locations.
+ *
+ * @return array() Array of all template locations registered so far.
+ */
+function bp_add_template_stack_locations( $stacks = array() ) {
+	$retval = array();
+
+	// Get alternate locations
+	$locations = bp_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 );
+		}
+	}
+
+	/**
+	 * Filters the template locations to template files being searched for.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $value  Array of all template locations registered so far.
+	 * @param array $stacks Array of template locations.
+	 */
+	return apply_filters( 'bp_add_template_stack_locations', array_unique( $retval ), $stacks );
+}
+
+/**
+ * Add checks for BuddyPress conditions to 'parse_query' action.
+ *
+ * @since 1.7.0
+ *
+ * @param WP_Query $posts_query
+ */
+function bp_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;
+	}
+
+	/**
+	 * Fires at the end of the bp_parse_query function.
+	 *
+	 * Allow BuddyPress components to parse the main query.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param WP_Query $posts_query WP_Query instance. Passed by reference.
+	 */
+	do_action_ref_array( 'bp_parse_query', array( &$posts_query ) );
+}
+
+/**
+ * Possibly intercept the template being loaded.
+ *
+ * Listens to the 'template_include' filter and waits for any BuddyPress 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 1.7.0
+ *
+ * @param string $template
+ *
+ * @return string The path to the template file that is being used.
+ */
+function bp_template_include_theme_supports( $template = '' ) {
+
+	/**
+	 * Filters whether or not to override the template being loaded in parent/child themes.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param bool   $value    Whether or not there is a file override. Default false.
+	 * @param string $template The path to the template file that is being used.
+	 */
+	$new_template = apply_filters( 'bp_get_root_template', false, $template );
+
+	// A BuddyPress template file was located, so override the WordPress
+	// template and use it to switch off BuddyPress's theme compatibility.
+	if ( ! empty( $new_template ) ) {
+		$template = bp_set_template_included( $new_template );
+	}
+
+	/**
+	 * Filters the final template being loaded in parent/child themes.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $template The path to the template file that is being used.
+	 */
+	return apply_filters( 'bp_template_include_theme_supports', $template );
+}
+
+/**
+ * Set the included template.
+ *
+ * @since 1.8.0
+ *
+ * @param mixed $template Default: false.
+ *
+ * @return mixed False if empty. Template name if template included.
+ */
+function bp_set_template_included( $template = false ) {
+	buddypress()->theme_compat->found_template = $template;
+
+	return buddypress()->theme_compat->found_template;
+}
+
+/**
+ * Is a BuddyPress template being included?
+ *
+ * @since 1.8.0
+ *
+ * @return bool True if yes, false if no.
+ */
+function bp_is_template_included() {
+	return ! empty( buddypress()->theme_compat->found_template );
+}
+
+/**
+ * Attempt to load a custom BP functions file, similar to each themes functions.php file.
+ *
+ * @since 1.7.0
+ *
+ * @global string $pagenow
+ * @uses bp_locate_template()
+ */
+function bp_load_theme_functions() {
+	global $pagenow, $wp_query;
+
+	// do not load our custom BP functions file if theme compat is disabled
+	if ( ! bp_use_theme_compat_with_current_theme() ) {
+		return;
+	}
+
+	// Do not include on BuddyPress deactivation
+	if ( bp_is_deactivation() ) {
+		return;
+	}
+
+	// If the $wp_query global is empty (the main query has not been run,
+	// or has been reset), load_template() will fail at setting certain
+	// global values. This does not happen on a normal page load, but can
+	// cause problems when running automated tests
+	if ( ! is_a( $wp_query, 'WP_Query' ) ) {
+		return;
+	}
+
+	// Only include if not installing or if activating via wp-activate.php
+	if ( ! defined( 'WP_INSTALLING' ) || 'wp-activate.php' === $pagenow ) {
+		bp_locate_template( 'buddypress-functions.php', true );
+	}
+}
+
+/**
+ * Get the templates to use as the endpoint for BuddyPress template parts.
+ *
+ * @since 1.7.0
+ * @since 2.4.0 Added singular.php to stack
+ *
+ * @return array Array of possible root level wrapper template files.
+ */
+function bp_get_theme_compat_templates() {
+	return bp_get_query_template( 'buddypress', array(
+		'plugin-buddypress.php',
+		'buddypress.php',
+		'community.php',
+		'generic.php',
+		'page.php',
+		'single.php',
+		'singular.php',
+		'index.php'
+	) );
+}
Index: /tags/2.4.3/src/bp-core/bp-core-template.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-template.php	(revision 10410)
@@ -0,0 +1,3517 @@
+<?php
+/**
+ * Core component template tag functions.
+ *
+ * @package BuddyPress
+ * @subpackage TemplateFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the "options nav", the secondary-level single item navigation menu.
+ *
+ * Uses the $bp->bp_options_nav global to render out the sub navigation for the
+ * current component. Each component adds to its sub navigation array within
+ * its own setup_nav() function.
+ *
+ * This sub navigation array is the secondary level navigation, so for profile
+ * it contains:
+ *      [Public, Edit Profile, Change Avatar]
+ *
+ * The function will also analyze the current action for the current component
+ * to determine whether or not to highlight a particular sub nav item.
+ *
+ * @uses bp_get_user_nav() Renders the navigation for a profile of a currently
+ *       viewed user.
+ *
+ * @param string $parent_slug
+ *
+ * @return string
+ */
+function bp_get_options_nav( $parent_slug = '' ) {
+	$bp = buddypress();
+
+	// If we are looking at a member profile, then the we can use the current
+	// component as an index. Otherwise we need to use the component's root_slug
+	$component_index = !empty( $bp->displayed_user ) ? bp_current_component() : bp_get_root_slug( bp_current_component() );
+	$selected_item   = bp_current_action();
+
+	if ( ! bp_is_single_item() ) {
+		if ( !isset( $bp->bp_options_nav[$component_index] ) || count( $bp->bp_options_nav[$component_index] ) < 1 ) {
+			return false;
+		} else {
+			$the_index = $component_index;
+		}
+	} else {
+		$current_item = bp_current_item();
+
+		if ( ! empty( $parent_slug ) ) {
+			$current_item  = $parent_slug;
+			$selected_item = bp_action_variable( 0 );
+		}
+
+		if ( !isset( $bp->bp_options_nav[$current_item] ) || count( $bp->bp_options_nav[$current_item] ) < 1 ) {
+			return false;
+		} else {
+			$the_index = $current_item;
+		}
+	}
+
+	// Loop through each navigation item
+	foreach ( (array) $bp->bp_options_nav[$the_index] as $subnav_item ) {
+		if ( empty( $subnav_item['user_has_access'] ) ) {
+			continue;
+		}
+
+		// If the current action or an action variable matches the nav item id, then add a highlight CSS class.
+		if ( $subnav_item['slug'] == $selected_item ) {
+			$selected = ' class="current selected"';
+		} else {
+			$selected = '';
+		}
+
+		// List type depends on our current component
+		$list_type = bp_is_group() ? 'groups' : 'personal';
+
+		/**
+		 * Filters the "options nav", the secondary-level single item navigation menu.
+		 *
+		 * This is a dynamic filter that is dependent on the provided css_id value.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value         HTML list item for the submenu item.
+		 * @param array  $subnav_item   Submenu array item being displayed.
+		 * @param string $selected_item Current action.
+		 */
+		echo apply_filters( 'bp_get_options_nav_' . $subnav_item['css_id'], '<li id="' . esc_attr( $subnav_item['css_id'] . '-' . $list_type . '-li' ) . '" ' . $selected . '><a id="' . esc_attr( $subnav_item['css_id'] ) . '" href="' . esc_url( $subnav_item['link'] ) . '">' . $subnav_item['name'] . '</a></li>', $subnav_item, $selected_item );
+	}
+}
+
+/**
+ * Get the 'bp_options_title' property from the BP global.
+ *
+ * Not currently used in BuddyPress.
+ * @todo Deprecate.
+ */
+function bp_get_options_title() {
+	$bp = buddypress();
+
+	if ( empty( $bp->bp_options_title ) ) {
+		$bp->bp_options_title = __( 'Options', 'buddypress' );
+	}
+
+	echo apply_filters( 'bp_get_options_title', esc_attr( $bp->bp_options_title ) );
+}
+
+/**
+ * Get the directory title for a component.
+ *
+ * Used for the <title> element and the page header on the component directory
+ * page.
+ *
+ * @since 2.0.0
+ *
+ * @param string $component
+ *
+ * @return string
+ */
+function bp_get_directory_title( $component = '' ) {
+	$title = '';
+
+	// Use the string provided by the component
+	if ( ! empty( buddypress()->{$component}->directory_title ) ) {
+		$title = buddypress()->{$component}->directory_title;
+
+	// If none is found, concatenate
+	} elseif ( isset( buddypress()->{$component}->name ) ) {
+		$title = sprintf( __( '%s Directory', 'buddypress' ), buddypress()->{$component}->name );
+	}
+
+	/**
+	 * Filters the directory title for a component.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $title     Text to be used in <title> tag.
+	 * @param string $component Current componet being displayed.
+	 */
+	return apply_filters( 'bp_get_directory_title', $title, $component );
+}
+
+/** Avatars *******************************************************************/
+
+/**
+ * Check to see if there is an options avatar.
+ *
+ * An options avatar is an avatar for something like a group, or a friend.
+ * Basically an avatar that appears in the sub nav options bar.
+ *
+ * Not currently used in BuddyPress.
+ *
+ * @return bool $value Returns true if an options avatar has been set, otherwise false.
+ */
+function bp_has_options_avatar() {
+	return (bool) buddypress()->bp_options_avatar;
+}
+
+/**
+ * Output the options avatar.
+ *
+ * Not currently used in BuddyPress.
+ *
+ * @todo Deprecate.
+ */
+function bp_get_options_avatar() {
+	echo apply_filters( 'bp_get_options_avatar', buddypress()->bp_options_avatar );
+}
+
+/**
+ * Output a comment author's avatar.
+ *
+ * Not currently used in BuddyPress.
+ */
+function bp_comment_author_avatar() {
+	global $comment;
+
+	if ( function_exists( 'bp_core_fetch_avatar' ) ) {
+		echo apply_filters( 'bp_comment_author_avatar', bp_core_fetch_avatar( array(
+			'item_id' => $comment->user_id,
+			'type'    => 'thumb',
+			'alt'     => sprintf( __( 'Profile photo of %s', 'buddypress' ), bp_core_get_user_displayname( $comment->user_id ) )
+		) ) );
+	} elseif ( function_exists( 'get_avatar' ) ) {
+		get_avatar();
+	}
+}
+
+/**
+ * Output a post author's avatar.
+ *
+ * Not currently used in BuddyPress.
+ */
+function bp_post_author_avatar() {
+	global $post;
+
+	if ( function_exists( 'bp_core_fetch_avatar' ) ) {
+		echo apply_filters( 'bp_post_author_avatar', bp_core_fetch_avatar( array(
+			'item_id' => $post->post_author,
+			'type'    => 'thumb',
+			'alt'     => sprintf( __( 'Profile photo of %s', 'buddypress' ), bp_core_get_user_displayname( $post->post_author ) )
+		) ) );
+	} elseif ( function_exists( 'get_avatar' ) ) {
+		get_avatar();
+	}
+}
+
+/**
+ * Output the current avatar upload step.
+ */
+function bp_avatar_admin_step() {
+	echo bp_get_avatar_admin_step();
+}
+	/**
+	 * Return the current avatar upload step.
+	 *
+	 * @return string The current avatar upload step. Returns 'upload-image'
+	 *         if none is found.
+	 */
+	function bp_get_avatar_admin_step() {
+		$bp   = buddypress();
+		$step = isset( $bp->avatar_admin->step )
+			? $step = $bp->avatar_admin->step
+			: 'upload-image';
+
+		/**
+		 * Filters the current avatar upload step.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $step The current avatar upload step.
+		 */
+		return apply_filters( 'bp_get_avatar_admin_step', $step );
+	}
+
+/**
+ * Output the URL of the avatar to crop.
+ */
+function bp_avatar_to_crop() {
+	echo bp_get_avatar_to_crop();
+}
+	/**
+	 * Return the URL of the avatar to crop.
+	 *
+	 * @return string URL of the avatar awaiting cropping.
+	 */
+	function bp_get_avatar_to_crop() {
+		$bp  = buddypress();
+		$url = isset( $bp->avatar_admin->image->url )
+			? $bp->avatar_admin->image->url
+			: '';
+
+		/**
+		 * Filters the URL of the avatar to crop.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $url URL for the avatar.
+		 */
+		return apply_filters( 'bp_get_avatar_to_crop', $url );
+	}
+
+/**
+ * Output the relative file path to the avatar to crop.
+ */
+function bp_avatar_to_crop_src() {
+	echo bp_get_avatar_to_crop_src();
+}
+	/**
+	 * Return the relative file path to the avatar to crop.
+	 *
+	 * @return string Relative file path to the avatar.
+	 */
+	function bp_get_avatar_to_crop_src() {
+		$bp  = buddypress();
+		$src = isset( $bp->avatar_admin->image->dir )
+			? str_replace( WP_CONTENT_DIR, '', $bp->avatar_admin->image->dir )
+			: '';
+
+		/**
+		 * Filters the relative file path to the avatar to crop.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $src Relative file path for the avatar.
+		 */
+		return apply_filters( 'bp_get_avatar_to_crop_src', $src );
+	}
+
+/**
+ * Output the avatar cropper <img> markup.
+ *
+ * No longer used in BuddyPress.
+ *
+ * @todo Deprecate.
+ */
+function bp_avatar_cropper() {
+?>
+	<img id="avatar-to-crop" class="avatar" src="<?php echo esc_url( buddypress()->avatar_admin->image ); ?>" />
+<?php
+}
+
+/**
+ * Output the name of the BP site. Used in RSS headers.
+ */
+function bp_site_name() {
+	echo bp_get_site_name();
+}
+	/**
+	 * Returns the name of the BP site. Used in RSS headers.
+	 *
+	 * @since 1.6.0
+	 */
+	function bp_get_site_name() {
+
+		/**
+		 * Filters the name of the BP site. Used in RSS headers.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Current BP site name.
+		 */
+		return apply_filters( 'bp_site_name', get_bloginfo( 'name', 'display' ) );
+	}
+
+/**
+ * Format a date based on a UNIX timestamp.
+ *
+ * This function can be used to turn a UNIX timestamp into a properly formatted
+ * (and possibly localized) string, userful for ouputting the date & time an
+ * action took place.
+ *
+ * Not to be confused with `bp_core_time_since()`, this function is best used
+ * for displaying a more exact date and time vs. a human-readable time.
+ *
+ * Note: This function may be improved or removed at a later date, as it is
+ * hardly used and adds an additional layer of complexity to calculating dates
+ * and times together with timezone offsets and i18n.
+ *
+ * @since 1.1.0
+ *
+ * @param int|string  $time         The UNIX timestamp to be formatted.
+ * @param bool        $exclude_time Optional. True to return only the month + day, false
+ *                                  to return month, day, and time. Default: false.
+ * @param bool        $gmt          Optional. True to display in local time, false to
+ *                                  leave in GMT. Default: true.
+ *
+ * @return mixed A string representation of $time, in the format
+ *               "March 18, 2014 at 2:00 pm" (or whatever your
+ *               'date_format' and 'time_format' settings are
+ *               on your root blog). False on failure.
+ */
+function bp_format_time( $time = '', $exclude_time = false, $gmt = true ) {
+
+	// Bail if time is empty or not numeric
+	// @todo We should output something smarter here
+	if ( empty( $time ) || ! is_numeric( $time ) ) {
+		return false;
+	}
+
+	// Get GMT offset from root blog
+	if ( true === $gmt ) {
+
+		// Use Timezone string if set
+		$timezone_string = bp_get_option( 'timezone_string' );
+		if ( ! empty( $timezone_string ) ) {
+			$timezone_object = timezone_open( $timezone_string );
+			$datetime_object = date_create( "@{$time}" );
+			$timezone_offset = timezone_offset_get( $timezone_object, $datetime_object ) / HOUR_IN_SECONDS;
+
+		// Fall back on less reliable gmt_offset
+		} else {
+			$timezone_offset = bp_get_option( 'gmt_offset' );
+		}
+
+		// Calculate time based on the offset
+		$calculated_time = $time + ( $timezone_offset * HOUR_IN_SECONDS );
+
+	// No localizing, so just use the time that was submitted
+	} else {
+		$calculated_time = $time;
+	}
+
+	// Formatted date: "March 18, 2014"
+	$formatted_date = date_i18n( bp_get_option( 'date_format' ), $calculated_time, $gmt );
+
+	// Should we show the time also?
+	if ( true !== $exclude_time ) {
+
+		// Formatted time: "2:00 pm"
+		$formatted_time = date_i18n( bp_get_option( 'time_format' ), $calculated_time, $gmt );
+
+		// Return string formatted with date and time
+		$formatted_date = sprintf( esc_html__( '%1$s at %2$s', 'buddypress' ), $formatted_date, $formatted_time );
+	}
+
+	/**
+	 * Filters the date based on a UNIX timestamp.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $formatted_date Formatted date from the timestamp.
+	 */
+	return apply_filters( 'bp_format_time', $formatted_date );
+}
+
+/**
+ * Select between two dynamic strings, according to context.
+ *
+ * This function can be used in cases where a phrase used in a template will
+ * differ for a user looking at his own profile and a user looking at another
+ * user's profile (eg, "My Friends" and "Joe's Friends"). Pass both versions
+ * of the phrase, and bp_word_or_name() will detect which is appropriate, and
+ * do the necessary argument swapping for dynamic phrases.
+ *
+ * @param string $youtext  The "you" version of the phrase (eg "Your Friends").
+ * @param string $nametext The other-user version of the phrase. Should be in
+ *                         a format appropriate for sprintf() - use %s in place of the displayed
+ *                         user's name (eg "%'s Friends").
+ * @param bool $capitalize Optional. Force into title case. Default: true.
+ * @param bool $echo       Optional. True to echo the results, false to return them.
+ *                         Default: true.
+ *
+ * @return string|null $nametext If ! $echo, returns the appropriate string.
+ */
+function bp_word_or_name( $youtext, $nametext, $capitalize = true, $echo = true ) {
+
+	if ( ! empty( $capitalize ) ) {
+		$youtext = bp_core_ucfirst( $youtext );
+	}
+
+	if ( bp_displayed_user_id() == bp_loggedin_user_id() ) {
+		if ( true == $echo ) {
+
+			/**
+			 * Filters the text used based on context of own profile or someone else's profile.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param string $youtext Context-determined string to display.
+			 */
+			echo apply_filters( 'bp_word_or_name', $youtext );
+		} else {
+
+			/** This filter is documented in bp-core/bp-core-template.php */
+			return apply_filters( 'bp_word_or_name', $youtext );
+		}
+	} else {
+		$fullname = bp_get_displayed_user_fullname();
+		$fullname = (array) explode( ' ', $fullname );
+		$nametext = sprintf( $nametext, $fullname[0] );
+		if ( true == $echo ) {
+
+			/** This filter is documented in bp-core/bp-core-template.php */
+			echo apply_filters( 'bp_word_or_name', $nametext );
+		} else {
+
+			/** This filter is documented in bp-core/bp-core-template.php */
+			return apply_filters( 'bp_word_or_name', $nametext );
+		}
+	}
+}
+
+/**
+ * Do the 'bp_styles' action, and call wp_print_styles().
+ *
+ * No longer used in BuddyPress.
+ *
+ * @todo Deprecate.
+ */
+function bp_styles() {
+	do_action( 'bp_styles' );
+	wp_print_styles();
+}
+
+/** Search Form ***************************************************************/
+
+/**
+ * Return the "action" attribute for search forms.
+ *
+ * @return string URL action attribute for search forms, eg example.com/search/.
+ */
+function bp_search_form_action() {
+
+	/**
+	 * Filters the "action" attribute for search forms.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Search form action url.
+	 */
+	return apply_filters( 'bp_search_form_action', trailingslashit( bp_get_root_domain() . '/' . bp_get_search_slug() ) );
+}
+
+/**
+ * Generate the basic search form as used in BP-Default's header.
+ *
+ * @since 1.0.0
+ *
+ * @return string HTML <select> element.
+ */
+function bp_search_form_type_select() {
+
+	$options = array();
+
+	if ( bp_is_active( 'xprofile' ) ) {
+		$options['members'] = _x( 'Members', 'search form', 'buddypress' );
+	}
+
+	if ( bp_is_active( 'groups' ) ) {
+		$options['groups']  = _x( 'Groups', 'search form', 'buddypress' );
+	}
+
+	if ( bp_is_active( 'blogs' ) && is_multisite() ) {
+		$options['blogs']   = _x( 'Blogs', 'search form', 'buddypress' );
+	}
+
+	if ( bp_is_active( 'forums' ) && bp_forums_is_installed_correctly() && bp_forums_has_directory() ) {
+		$options['forums']  = _x( 'Forums', 'search form', 'buddypress' );
+	}
+
+	$options['posts'] = _x( 'Posts', 'search form', 'buddypress' );
+
+	// Eventually this won't be needed and a page will be built to integrate all search results.
+	$selection_box  = '<label for="search-which" class="accessibly-hidden">' . _x( 'Search these:', 'search form', 'buddypress' ) . '</label>';
+	$selection_box .= '<select name="search-which" id="search-which" style="width: auto">';
+
+	/**
+	 * Filters all of the component options available for search scope.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $options Array of options to add to select field.
+	 */
+	$options = apply_filters( 'bp_search_form_type_select_options', $options );
+	foreach( (array) $options as $option_value => $option_title ) {
+		$selection_box .= sprintf( '<option value="%s">%s</option>', $option_value, $option_title );
+	}
+
+	$selection_box .= '</select>';
+
+	/**
+	 * Filters the complete <select> input used for search scope.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $selection_box <select> input for selecting search scope.
+	 */
+	return apply_filters( 'bp_search_form_type_select', $selection_box );
+}
+
+/**
+ * Output the default text for the search box for a given component.
+ *
+ * @since 1.5.0
+ *
+ * @see bp_get_search_default_text()
+ *
+ * @param string $component See {@link bp_get_search_default_text()}.
+ */
+function bp_search_default_text( $component = '' ) {
+	echo bp_get_search_default_text( $component );
+}
+	/**
+	 * Return the default text for the search box for a given component.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $component Component name. Default: current component.
+	 *
+	 * @return string Placeholder text for search field.
+	 */
+	function bp_get_search_default_text( $component = '' ) {
+
+		$bp = buddypress();
+
+		if ( empty( $component ) ) {
+			$component = bp_current_component();
+		}
+
+		$default_text = __( 'Search anything...', 'buddypress' );
+
+		// Most of the time, $component will be the actual component ID
+		if ( !empty( $component ) ) {
+			if ( !empty( $bp->{$component}->search_string ) ) {
+				$default_text = $bp->{$component}->search_string;
+			} else {
+				// When the request comes through AJAX, we need to get the component
+				// name out of $bp->pages
+				if ( !empty( $bp->pages->{$component}->slug ) ) {
+					$key = $bp->pages->{$component}->slug;
+					if ( !empty( $bp->{$key}->search_string ) ) {
+						$default_text = $bp->{$key}->search_string;
+					}
+				}
+			}
+		}
+
+		/**
+		 * Filters the default text for the search box for a given component.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $default_text Default text for search box.
+		 * @param string $component    Current component displayed.
+		 */
+		return apply_filters( 'bp_get_search_default_text', $default_text, $component );
+	}
+
+/**
+ * Fire the 'bp_custom_profile_boxes' action.
+ *
+ * No longer used in BuddyPress.
+ *
+ * @todo Deprecate.
+ */
+function bp_custom_profile_boxes() {
+	do_action( 'bp_custom_profile_boxes' );
+}
+
+/**
+ * Fire the 'bp_custom_profile_sidebar_boxes' action.
+ *
+ * No longer used in BuddyPress.
+ *
+ * @todo Deprecate.
+ */
+function bp_custom_profile_sidebar_boxes() {
+	do_action( 'bp_custom_profile_sidebar_boxes' );
+}
+
+/**
+ * Output the attributes for a form field.
+ *
+ * @since 2.2.0
+ *
+ * @param string $name       The field name to output attributes for.
+ * @param array  $attributes Array of existing attributes to add.
+ */
+function bp_form_field_attributes( $name = '', $attributes = array() ) {
+	echo bp_get_form_field_attributes( $name, $attributes );
+}
+	/**
+	 * Get the attributes for a form field.
+	 *
+	 * Primarily to add better support for touchscreen devices, but plugin devs
+	 * can use the 'bp_get_form_field_extra_attributes' filter for further
+	 * manipulation.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string $name       The field name to get attributes for.
+	 * @param array  $attributes Array of existing attributes to add.
+	 * @return string
+	 */
+	function bp_get_form_field_attributes( $name = '', $attributes = array() ) {
+		$retval = '';
+
+		if ( empty( $attributes ) ) {
+			$attributes = array();
+		}
+
+		$name = strtolower( $name );
+
+		switch ( $name ) {
+			case 'username' :
+			case 'blogname' :
+				$attributes['autocomplete']   = 'off';
+				$attributes['autocapitalize'] = 'none';
+				break;
+
+			case 'email' :
+				if ( wp_is_mobile() ) {
+					$attributes['autocapitalize'] = 'none';
+				}
+				break;
+
+			case 'password' :
+				$attributes['spellcheck']   = 'false';
+				$attributes['autocomplete'] = 'off';
+
+				if ( wp_is_mobile() ) {
+					$attributes['autocorrect']    = 'false';
+					$attributes['autocapitalize'] = 'none';
+				}
+				break;
+		}
+
+		/**
+		 * Filter the attributes for a field before rendering output.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param array  $attributes The field attributes.
+		 * @param string $name       The field name.
+		 */
+		$attributes = (array) apply_filters( 'bp_get_form_field_attributes', $attributes, $name );
+
+		foreach( $attributes as $attr => $value ) {
+			$retval .= sprintf( ' %s="%s"', sanitize_key( $attr ), esc_attr( $value ) );
+		}
+
+		return $retval;
+	}
+
+/**
+ * Create and output a button.
+ *
+ * @see bp_get_button()
+ *
+ * @param array|string $args See {@link BP_Button}.
+ */
+function bp_button( $args = '' ) {
+	echo bp_get_button( $args );
+}
+	/**
+	 * Create and return a button.
+	 *
+	 * @see BP_Button for a description of arguments and return value.
+	 *
+	 * @param array|string $args See {@link BP_Button}.
+	 * @return string HTML markup for the button.
+	 */
+	function bp_get_button( $args = '' ) {
+		$button = new BP_Button( $args );
+
+		/**
+		 * Filters the requested button output.
+		 *
+		 * @since 1.2.6
+		 *
+		 * @param string    $contents  Button context to be used.
+		 * @param array     $args      Array of args for the button.
+		 * @param BP_Button $button    BP_Button object.
+		 */
+		return apply_filters( 'bp_get_button', $button->contents, $args, $button );
+	}
+
+/**
+ * Truncate text.
+ *
+ * Cuts a string to the length of $length and replaces the last characters
+ * with the ending if the text is longer than length.
+ *
+ * This function is borrowed from CakePHP v2.0, under the MIT license. See
+ * http://book.cakephp.org/view/1469/Text#truncate-1625
+ *
+ * ### Options:
+ *
+ * - `ending` Will be used as Ending and appended to the trimmed string.
+ * - `exact` If false, $text will not be cut mid-word.
+ * - `html` If true, HTML tags would be handled correctly.
+ * - `filter_shortcodes` If true, shortcodes will be stripped before truncating.
+ *
+ * @param string $text   String to truncate.
+ * @param int    $length Optional. Length of returned string, including ellipsis.
+ *                       Default: 225.
+ * @param array $options {
+ *     An array of HTML attributes and options. Each item is optional.
+ *     @type string $ending            The string used after truncation.
+ *                                     Default: ' [&hellip;]'.
+ *     @type bool   $exact             If true, $text will be trimmed to exactly $length.
+ *                                     If false, $text will not be cut mid-word. Default: false.
+ *     @type bool   $html              If true, don't include HTML tags when calculating
+ *                                     excerpt length. Default: true.
+ *     @type bool   $filter_shortcodes If true, shortcodes will be stripped.
+ *                                     Default: true.
+ * }
+ * @return string Trimmed string.
+ */
+function bp_create_excerpt( $text, $length = 225, $options = array() ) {
+
+	// Backward compatibility. The third argument used to be a boolean $filter_shortcodes
+	$filter_shortcodes_default = is_bool( $options ) ? $options : true;
+
+	$r = bp_parse_args( $options, array(
+		'ending'            => __( ' [&hellip;]', 'buddypress' ),
+		'exact'             => false,
+		'html'              => true,
+		'filter_shortcodes' => $filter_shortcodes_default
+	), 'create_excerpt' );
+
+	// Save the original text, to be passed along to the filter
+	$original_text = $text;
+
+	/**
+	 * Filters the excerpt length to trim text to.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $length Length of returned string, including ellipsis.
+	 */
+	$length = apply_filters( 'bp_excerpt_length',      $length      );
+
+	/**
+	 * Filters the excerpt appended text value.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $value Text to append to the end of the excerpt.
+	 */
+	$ending = apply_filters( 'bp_excerpt_append_text', $r['ending'] );
+
+	// Remove shortcodes if necessary
+	if ( ! empty( $r['filter_shortcodes'] ) ) {
+		$text = strip_shortcodes( $text );
+	}
+
+	// When $html is true, the excerpt should be created without including HTML tags in the
+	// excerpt length
+	if ( ! empty( $r['html'] ) ) {
+
+		// The text is short enough. No need to truncate
+		if ( mb_strlen( preg_replace( '/<.*?>/', '', $text ) ) <= $length ) {
+			return $text;
+		}
+
+		$totalLength = mb_strlen( strip_tags( $ending ) );
+		$openTags    = array();
+		$truncate    = '';
+
+		// Find all the tags and HTML comments and put them in a stack for later use
+		preg_match_all( '/(<\/?([\w+!]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER );
+
+		foreach ( $tags as $tag ) {
+			// Process tags that need to be closed
+			if ( !preg_match( '/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s',  $tag[2] ) ) {
+				if ( preg_match( '/<[\w]+[^>]*>/s', $tag[0] ) ) {
+					array_unshift( $openTags, $tag[2] );
+				} elseif ( preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag ) ) {
+					$pos = array_search( $closeTag[1], $openTags );
+					if ( $pos !== false ) {
+						array_splice( $openTags, $pos, 1 );
+					}
+				}
+			}
+
+			$truncate     .= $tag[1];
+			$contentLength = mb_strlen( preg_replace( '/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3] ) );
+
+			if ( $contentLength + $totalLength > $length ) {
+				$left = $length - $totalLength;
+				$entitiesLength = 0;
+				if ( preg_match_all( '/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE ) ) {
+					foreach ( $entities[0] as $entity ) {
+						if ( $entity[1] + 1 - $entitiesLength <= $left ) {
+							$left--;
+							$entitiesLength += mb_strlen( $entity[0] );
+						} else {
+							break;
+						}
+					}
+				}
+
+				$truncate .= mb_substr( $tag[3], 0 , $left + $entitiesLength );
+				break;
+			} else {
+				$truncate .= $tag[3];
+				$totalLength += $contentLength;
+			}
+			if ( $totalLength >= $length ) {
+				break;
+			}
+		}
+	} else {
+		if ( mb_strlen( $text ) <= $length ) {
+			return $text;
+		} else {
+			$truncate = mb_substr( $text, 0, $length - mb_strlen( $ending ) );
+		}
+	}
+
+	// If $exact is false, we can't break on words
+	if ( empty( $r['exact'] ) ) {
+		// Find the position of the last space character not part of a tag.
+		preg_match_all( '/<[a-z\!\/][^>]*>/', $truncate, $_truncate_tags, PREG_OFFSET_CAPTURE );
+
+		// Rekey tags by the string index of their last character.
+		$truncate_tags = array();
+		if ( ! empty( $_truncate_tags[0] ) ) {
+			foreach ( $_truncate_tags[0] as $_tt ) {
+				$_tt['start'] = $_tt[1];
+				$_tt['end']   = $_tt[1] + strlen( $_tt[0] );
+				$truncate_tags[ $_tt['end'] ] = $_tt;
+			}
+		}
+
+		$truncate_length = mb_strlen( $truncate );
+		$spacepos = $truncate_length + 1;
+		for ( $pos = $truncate_length - 1; $pos >= 0; $pos-- ) {
+			// Word boundaries are spaces and the close of HTML tags, when the tag is preceded by a space.
+			$is_word_boundary = ' ' === $truncate[ $pos ];
+			if ( ! $is_word_boundary && isset( $truncate_tags[ $pos - 1 ] ) ) {
+				$preceding_tag    = $truncate_tags[ $pos - 1 ];
+				if ( ' ' === $truncate[ $preceding_tag['start'] - 1 ] ) {
+					$is_word_boundary = true;
+					break;
+				}
+			}
+
+			if ( ! $is_word_boundary ) {
+				continue;
+			}
+
+			// If there are no tags in the string, the first space found is the right one.
+			if ( empty( $truncate_tags ) ) {
+				$spacepos = $pos;
+				break;
+			}
+
+			// Look at each tag to see if the space is inside of it.
+			$intag = false;
+			foreach ( $truncate_tags as $tt ) {
+				if ( $pos > $tt['start'] && $pos < $tt['end'] ) {
+					$intag = true;
+					break;
+				}
+			}
+
+			if ( ! $intag ) {
+				$spacepos = $pos;
+				break;
+			}
+		}
+
+		if ( $r['html'] ) {
+			$bits = mb_substr( $truncate, $spacepos );
+			preg_match_all( '/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER );
+			if ( !empty( $droppedTags ) ) {
+				foreach ( $droppedTags as $closingTag ) {
+					if ( !in_array( $closingTag[1], $openTags ) ) {
+						array_unshift( $openTags, $closingTag[1] );
+					}
+				}
+			}
+		}
+
+		$truncate = rtrim( mb_substr( $truncate, 0, $spacepos ) );
+	}
+	$truncate .= $ending;
+
+	if ( !empty( $r['html'] ) ) {
+		foreach ( $openTags as $tag ) {
+			$truncate .= '</' . $tag . '>';
+		}
+	}
+
+	/**
+	 * Filters the final generated excerpt.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string $truncate      Generated excerpt.
+	 * @param string $original_text Original text provided.
+	 * @param int    $length        Length of returned string, including ellipsis.
+	 * @param array  $options       Array of HTML attributes and options.
+	 */
+	return apply_filters( 'bp_create_excerpt', $truncate, $original_text, $length, $options );
+}
+add_filter( 'bp_create_excerpt', 'stripslashes_deep'  );
+add_filter( 'bp_create_excerpt', 'force_balance_tags' );
+
+/**
+ * Output the total member count for the site.
+ */
+function bp_total_member_count() {
+	echo bp_get_total_member_count();
+}
+	/**
+	 * Return the total member count in your BP instance.
+	 *
+	 * Since BuddyPress 1.6, this function has used bp_core_get_active_member_count(),
+	 * which counts non-spam, non-deleted users who have last_activity.
+	 * This value will correctly match the total member count number used
+	 * for pagination on member directories.
+	 *
+	 * Before BuddyPress 1.6, this function used bp_core_get_total_member_count(),
+	 * which did not take into account last_activity, and thus often
+	 * resulted in higher counts than shown by member directory pagination.
+	 *
+	 * @return int Member count.
+	 */
+	function bp_get_total_member_count() {
+
+		/**
+		 * Filters the total member count in your BP instance.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $value Member count.
+		 */
+		return apply_filters( 'bp_get_total_member_count', bp_core_get_active_member_count() );
+	}
+	add_filter( 'bp_get_total_member_count', 'bp_core_number_format' );
+
+/**
+ * Output whether blog signup is allowed.
+ *
+ * @todo Deprecate. It doesn't make any sense to echo a boolean.
+ */
+function bp_blog_signup_allowed() {
+	echo bp_get_blog_signup_allowed();
+}
+	/**
+	 * Is blog signup allowed?
+	 *
+	 * Returns true if is_multisite() and blog creation is enabled at
+	 * Network Admin > Settings.
+	 *
+	 * @return bool True if blog signup is allowed, otherwise false.
+	 */
+	function bp_get_blog_signup_allowed() {
+
+		if ( ! is_multisite() ) {
+			return false;
+		}
+
+		$status = bp_core_get_root_option( 'registration' );
+		if ( ( 'none' !== $status ) && ( 'user' !== $status ) ) {
+			return true;
+		}
+
+		return false;
+	}
+
+/**
+ * Check whether an activation has just been completed.
+ *
+ * @return bool True if the activation_complete global flag has been set,
+ *              otherwise false.
+ */
+function bp_account_was_activated() {
+	$bp                  = buddypress();
+	$activation_complete = !empty( $bp->activation_complete )
+		? $bp->activation_complete
+		: false;
+
+	return $activation_complete;
+}
+
+/**
+ * Check whether registrations require activation on this installation.
+ *
+ * On a normal BuddyPress installation, all registrations require email
+ * activation. This filter exists so that customizations that omit activation
+ * can remove certain notification text from the registration screen.
+ *
+ * @return bool True by default.
+ */
+function bp_registration_needs_activation() {
+
+	/**
+	 * Filters whether registrations require activation on this installation.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param bool $value Whether registrations require activation. Default true.
+	 */
+	return apply_filters( 'bp_registration_needs_activation', true );
+}
+
+/**
+ * Retrieve a client friendly version of the root blog name.
+ *
+ * The blogname option is escaped with esc_html on the way into the database in
+ * sanitize_option, we want to reverse this for the plain text arena of emails.
+ *
+ * @since 1.7.0
+ *
+ * @see https://buddypress.trac.wordpress.org/ticket/4401
+ *
+ * @param array $args {
+ *     Array of optional parameters.
+ *     @type string $before  String to appear before the site name in the
+ *                           email subject. Default: '['.
+ *     @type string $after   String to appear after the site name in the
+ *                           email subject. Default: ']'.
+ *     @type string $default The default site name, to be used when none is
+ *                           found in the database. Default: 'Community'.
+ *     @type string $text    Text to append to the site name (ie, the main text of
+ *                           the email subject).
+ * }
+ * @return string Sanitized email subject.
+ */
+function bp_get_email_subject( $args = array() ) {
+
+	$r = bp_parse_args( $args, array(
+		'before'  => '[',
+		'after'   => ']',
+		'default' => __( 'Community', 'buddypress' ),
+		'text'    => ''
+	), 'get_email_subject' );
+
+	$subject = $r['before'] . wp_specialchars_decode( bp_get_option( 'blogname', $r['default'] ), ENT_QUOTES ) . $r['after'] . ' ' . $r['text'];
+
+	/**
+	 * Filters a client friendly version of the root blog name.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $subject Client friendy version of the root blog name.
+	 * @param array  $r       Array of arguments for the email subject.
+	 */
+	return apply_filters( 'bp_get_email_subject', $subject, $r );
+}
+
+/**
+ * Allow templates to pass parameters directly into the template loops via AJAX.
+ *
+ * For the most part this will be filtered in a theme's functions.php for
+ * example in the default theme it is filtered via bp_dtheme_ajax_querystring().
+ *
+ * By using this template tag in the templates it will stop them from showing
+ * errors if someone copies the templates from the default theme into another
+ * WordPress theme without coping the functions from functions.php.
+ *
+ * @param string|bool $object
+ *
+ * @return string The AJAX querystring.
+ */
+function bp_ajax_querystring( $object = false ) {
+	$bp = buddypress();
+
+	if ( ! isset( $bp->ajax_querystring ) ) {
+		$bp->ajax_querystring = '';
+	}
+
+	/**
+	 * Filters the template paramenters to be used in the query string.
+	 *
+	 * Allows templates to pass parameters into the template loops via AJAX.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $ajax_querystring Current query string.
+	 * @param string $object           Current template component.
+	 */
+	return apply_filters( 'bp_ajax_querystring', $bp->ajax_querystring, $object );
+}
+
+/** Template Classes and _is functions ****************************************/
+
+/**
+ * Return the name of the current component.
+ *
+ * @return string Component name.
+ */
+function bp_current_component() {
+	$bp                = buddypress();
+	$current_component = !empty( $bp->current_component )
+		? $bp->current_component
+		: false;
+
+	/**
+	 * Filters the name of the current component.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string|bool $current_component Current component if available or false.
+	 */
+	return apply_filters( 'bp_current_component', $current_component );
+}
+
+/**
+ * Return the name of the current action.
+ *
+ * @return string Action name.
+ */
+function bp_current_action() {
+	$bp             = buddypress();
+	$current_action = !empty( $bp->current_action )
+		? $bp->current_action
+		: '';
+
+	/**
+	 * Filters the name of the current action.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $current_action Current action.
+	 */
+	return apply_filters( 'bp_current_action', $current_action );
+}
+
+/**
+ * Return the name of the current item.
+ *
+ * @return string|bool
+ */
+function bp_current_item() {
+	$bp           = buddypress();
+	$current_item = !empty( $bp->current_item )
+		? $bp->current_item
+		: false;
+
+	/**
+	 * Filters the name of the current item.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string|bool $current_item Current item if available or false.
+	 */
+	return apply_filters( 'bp_current_item', $current_item );
+}
+
+/**
+ * Return the value of $bp->action_variables.
+ *
+ * @return array|bool $action_variables The action variables array, or false
+ *                                      if the array is empty.
+ */
+function bp_action_variables() {
+	$bp               = buddypress();
+	$action_variables = !empty( $bp->action_variables )
+		? $bp->action_variables
+		: false;
+
+	/**
+	 * Filters the value of $bp->action_variables.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param array|bool $action_variables Available action variables.
+	 */
+	return apply_filters( 'bp_action_variables', $action_variables );
+}
+
+/**
+ * Return the value of a given action variable.
+ *
+ * @since 1.5.0
+ *
+ * @param int $position The key of the action_variables array that you want.
+ *
+ * @return string|bool $action_variable The value of that position in the
+ *                                      array, or false if not found.
+ */
+function bp_action_variable( $position = 0 ) {
+	$action_variables = bp_action_variables();
+	$action_variable  = isset( $action_variables[ $position ] )
+		? $action_variables[ $position ]
+		: false;
+
+	/**
+	 * Filters the value of a given action variable.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string|bool $action_variable Requested action variable based on position.
+	 * @param int         $position        The key of the action variable requested.
+	 */
+	return apply_filters( 'bp_action_variable', $action_variable, $position );
+}
+
+/**
+ * Output the "root domain", the URL of the BP root blog.
+ */
+function bp_root_domain() {
+	echo bp_get_root_domain();
+}
+	/**
+	 * Return the "root domain", the URL of the BP root blog.
+	 *
+	 * @return string URL of the BP root blog.
+	 */
+	function bp_get_root_domain() {
+		$bp = buddypress();
+
+		if ( ! empty( $bp->root_domain ) ) {
+			$domain = $bp->root_domain;
+		} else {
+			$domain          = bp_core_get_root_domain();
+			$bp->root_domain = $domain;
+		}
+
+		/**
+		 * Filters the "root domain", the URL of the BP root blog.
+		 *
+		 * @since 1.2.4
+		 *
+		 * @param string $domain URL of the BP root blog.
+		 */
+		return apply_filters( 'bp_get_root_domain', $domain );
+	}
+
+/**
+ * Output the root slug for a given component.
+ *
+ * @since 1.5.0
+ *
+ * @param string $component The component name.
+ */
+function bp_root_slug( $component = '' ) {
+	echo bp_get_root_slug( $component );
+}
+	/**
+	 * Get the root slug for given component.
+	 *
+	 * The "root slug" is the string used when concatenating component
+	 * directory URLs. For example, on an installation where the Groups
+	 * component's directory is located at http://example.com/groups/, the
+	 * root slug for the Groups component is 'groups'. This string
+	 * generally corresponds to page_name of the component's directory
+	 * page.
+	 *
+	 * In order to maintain backward compatibility, the following procedure
+	 * is used:
+	 * 1) Use the short slug to get the canonical component name from the
+	 *    active component array.
+	 * 2) Use the component name to get the root slug out of the
+	 *    appropriate part of the $bp global.
+	 * 3) If nothing turns up, it probably means that $component is itself
+	 *    a root slug.
+	 *
+	 * Example: If your groups directory is at /community/companies, this
+	 * function first uses the short slug 'companies' (ie the current
+	 * component) to look up the canonical name 'groups' in
+	 * $bp->active_components. Then it uses 'groups' to get the root slug,
+	 * from $bp->groups->root_slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $component Optional. Defaults to the current component.
+	 *
+	 * @return string $root_slug The root slug.
+	 */
+	function bp_get_root_slug( $component = '' ) {
+		$bp        = buddypress();
+		$root_slug = '';
+
+		// Use current global component if none passed
+		if ( empty( $component ) ) {
+			$component = bp_current_component();
+		}
+
+		// Component is active
+		if ( ! empty( $bp->active_components[ $component ] ) ) {
+
+			// Backward compatibility: in legacy plugins, the canonical component id
+			// was stored as an array value in $bp->active_components
+			$component_name = ( '1' == $bp->active_components[ $component ] )
+				? $component
+				: $bp->active_components[$component];
+
+			// Component has specific root slug
+			if ( ! empty( $bp->{$component_name}->root_slug ) ) {
+				$root_slug = $bp->{$component_name}->root_slug;
+			}
+		}
+
+		// No specific root slug, so fall back to component slug
+		if ( empty( $root_slug ) ) {
+			$root_slug = $component;
+		}
+
+		/**
+		 * Filters the root slug for given component.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $root_slug Root slug for given component.
+		 * @param string $component Current component.
+		 */
+		return apply_filters( 'bp_get_root_slug', $root_slug, $component );
+	}
+
+/**
+ * Return the component name based on a root slug.
+ *
+ * @since 1.5.0
+ *
+ * @param string $root_slug Needle to our active component haystack.
+ *
+ * @return mixed False if none found, component name if found.
+ */
+function bp_get_name_from_root_slug( $root_slug = '' ) {
+	$bp = buddypress();
+
+	// If no slug is passed, look at current_component
+	if ( empty( $root_slug ) ) {
+		$root_slug = bp_current_component();
+	}
+
+	// No current component or root slug, so flee
+	if ( empty( $root_slug ) ) {
+		return false;
+	}
+
+	// Loop through active components and look for a match
+	foreach ( array_keys( $bp->active_components ) as $component ) {
+		if ( ( ! empty( $bp->{$component}->slug ) && ( $bp->{$component}->slug == $root_slug ) ) || ( ! empty( $bp->{$component}->root_slug ) && ( $bp->{$component}->root_slug === $root_slug ) ) ) {
+			return $bp->{$component}->name;
+		}
+	}
+
+	return false;
+}
+
+function bp_user_has_access() {
+	$has_access = bp_current_user_can( 'bp_moderate' ) || bp_is_my_profile();
+
+	/**
+	 * Filters whether or not a user has access.
+	 *
+	 * @since 1.2.4
+	 *
+	 * @param bool $has_access Whether or not user has access.
+	 */
+	return (bool) apply_filters( 'bp_user_has_access', $has_access );
+}
+
+/**
+ * Output the search slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_search_slug()
+ */
+function bp_search_slug() {
+	echo bp_get_search_slug();
+}
+	/**
+	 * Return the search slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string The search slug. Default: 'search'.
+	 */
+	function bp_get_search_slug() {
+
+		/**
+		 * Filters the search slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @const string BP_SEARCH_SLUG The search slug. Default "search".
+		 */
+		return apply_filters( 'bp_get_search_slug', BP_SEARCH_SLUG );
+	}
+
+/**
+ * Get the ID of the currently displayed user.
+ *
+ * @uses apply_filters() Filter 'bp_displayed_user_id' to change this value.
+ *
+ * @return int $id ID of the currently displayed user.
+ */
+function bp_displayed_user_id() {
+	$bp = buddypress();
+	$id = !empty( $bp->displayed_user->id )
+		? $bp->displayed_user->id
+		: 0;
+
+	/**
+	 * Filters the ID of the currently displayed user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id ID of the currently displayed user.
+	 */
+	return (int) apply_filters( 'bp_displayed_user_id', $id );
+}
+
+/**
+ * Get the ID of the currently logged-in user.
+ *
+ * @uses apply_filters() Filter 'bp_loggedin_user_id' to change this value.
+ *
+ * @return int ID of the logged-in user.
+ */
+function bp_loggedin_user_id() {
+	$bp = buddypress();
+	$id = !empty( $bp->loggedin_user->id )
+		? $bp->loggedin_user->id
+		: 0;
+
+	/**
+	 * Filters the ID of the currently logged-in user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id ID of the currently logged-in user.
+	 */
+	return (int) apply_filters( 'bp_loggedin_user_id', $id );
+}
+
+/** is_() functions to determine the current page *****************************/
+
+/**
+ * Check to see whether the current page belongs to the specified component.
+ *
+ * This function is designed to be generous, accepting several different kinds
+ * of value for the $component parameter. It checks $component_name against:
+ * - the component's root_slug, which matches the page slug in $bp->pages.
+ * - the component's regular slug.
+ * - the component's id, or 'canonical' name.
+ *
+ * @since 1.5.0
+ *
+ * @param string $component Name of the component being checked.
+ *
+ * @return bool Returns true if the component matches, or else false.
+ */
+function bp_is_current_component( $component = '' ) {
+
+	// Default is no match. We'll check a few places for matches
+	$is_current_component = false;
+
+	// Always return false if a null value is passed to the function
+	if ( empty( $component ) ) {
+		return false;
+	}
+
+	// Backward compatibility: 'xprofile' should be read as 'profile'
+	if ( 'xprofile' === $component ) {
+		$component = 'profile';
+	}
+
+	$bp = buddypress();
+
+	// Only check if BuddyPress found a current_component
+	if ( ! empty( $bp->current_component ) ) {
+
+		// First, check to see whether $component_name and the current
+		// component are a simple match
+		if ( $bp->current_component == $component ) {
+			$is_current_component = true;
+
+		// Since the current component is based on the visible URL slug let's
+		// check the component being passed and see if its root_slug matches
+		} elseif ( isset( $bp->{$component}->root_slug ) && $bp->{$component}->root_slug == $bp->current_component ) {
+			$is_current_component = true;
+
+		// Because slugs can differ from root_slugs, we should check them too
+		} elseif ( isset( $bp->{$component}->slug ) && $bp->{$component}->slug == $bp->current_component ) {
+			$is_current_component = true;
+
+		// Next, check to see whether $component is a canonical,
+		// non-translatable component name. If so, we can return its
+		// corresponding slug from $bp->active_components.
+		} elseif ( $key = array_search( $component, $bp->active_components ) ) {
+			if ( strstr( $bp->current_component, $key ) ) {
+				$is_current_component = true;
+			}
+
+		// If we haven't found a match yet, check against the root_slugs
+		// created by $bp->pages, as well as the regular slugs
+		} else {
+			foreach ( $bp->active_components as $id ) {
+				// If the $component parameter does not match the current_component,
+				// then move along, these are not the droids you are looking for
+				if ( empty( $bp->{$id}->root_slug ) || $bp->{$id}->root_slug != $bp->current_component ) {
+					continue;
+				}
+
+				if ( $id == $component ) {
+					$is_current_component = true;
+					break;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Filters whether the current page belongs to the specified component.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool   $is_current_component Whether or not the current page belongs to specified component.
+	 * @param string $component            Name of the component being checked.
+	 */
+ 	return apply_filters( 'bp_is_current_component', $is_current_component, $component );
+}
+
+/**
+ * Check to see whether the current page matches a given action.
+ *
+ * Along with bp_is_current_component() and bp_is_action_variable(), this
+ * function is mostly used to help determine when to use a given screen
+ * function.
+ *
+ * In BP parlance, the current_action is the URL chunk that comes directly
+ * after the current item slug. E.g., in
+ *   http://example.com/groups/my-group/members
+ * the current_action is 'members'.
+ *
+ * @since 1.5.0
+ *
+ * @param string $action The action being tested against.
+ *
+ * @return bool True if the current action matches $action.
+ */
+function bp_is_current_action( $action = '' ) {
+	return (bool) ( $action === bp_current_action() );
+}
+
+/**
+ * Check to see whether the current page matches a given action_variable.
+ *
+ * Along with bp_is_current_component() and bp_is_current_action(), this
+ * function is mostly used to help determine when to use a given screen
+ * function.
+ *
+ * In BP parlance, action_variables are an array made up of the URL chunks
+ * appearing after the current_action in a URL. For example,
+ *   http://example.com/groups/my-group/admin/group-settings
+ * $action_variables[0] is 'group-settings'.
+ *
+ * @since 1.5.0
+ *
+ * @param string   $action_variable The action_variable being tested against.
+ * @param int|bool $position        Optional. The array key you're testing against. If you
+ *                                  don't provide a $position, the function will return true if the
+ *                                  $action_variable is found *anywhere* in the action variables array.
+ *
+ * @return bool True if $action_variable matches at the $position provided.
+ */
+function bp_is_action_variable( $action_variable = '', $position = false ) {
+	$is_action_variable = false;
+
+	if ( false !== $position ) {
+		// When a $position is specified, check that slot in the action_variables array
+		if ( $action_variable ) {
+			$is_action_variable = $action_variable == bp_action_variable( $position );
+		} else {
+			// If no $action_variable is provided, we are essentially checking to see
+			// whether the slot is empty
+			$is_action_variable = !bp_action_variable( $position );
+		}
+	} else {
+		// When no $position is specified, check the entire array
+		$is_action_variable = in_array( $action_variable, (array)bp_action_variables() );
+	}
+
+	/**
+	 * Filters whether the current page matches a given action_variable.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool   $is_action_variable Whether the current page matches a given action_variable.
+	 * @param string $action_variable    The action_variable being tested against.
+	 * @param int    $position           The array key tested against.
+	 */
+	return apply_filters( 'bp_is_action_variable', $is_action_variable, $action_variable, $position );
+}
+
+/**
+ * Check against the current_item.
+ *
+ * @param string $item The item being checked.
+ *
+ * @return bool True if $item is the current item.
+ */
+function bp_is_current_item( $item = '' ) {
+	$retval = ( $item === bp_current_item() );
+
+	/**
+	 * Filters whether or not an item is the current item.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool   $retval Whether or not an item is the current item.
+	 * @param string $item   The item being checked.
+	 */
+	return (bool) apply_filters( 'bp_is_current_item', $retval, $item );
+}
+
+/**
+ * Are we looking at a single item? (group, user, etc)
+ *
+ * @return bool True if looking at a single item, otherwise false.
+ */
+function bp_is_single_item() {
+	$bp     = buddypress();
+	$retval = false;
+
+	if ( isset( $bp->is_single_item ) ) {
+		$retval = $bp->is_single_item;
+	}
+
+	/**
+	 * Filters whether or not an item is the a single item. (group, user, etc)
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $retval Whether or not an item is a single item.
+	 */
+	return (bool) apply_filters( 'bp_is_single_item', $retval );
+}
+
+/**
+ * Is the logged-in user an admin for the current item?
+ *
+ * @return bool True if the current user is an admin for the current item,
+ *              otherwise false.
+ */
+function bp_is_item_admin() {
+	$bp     = buddypress();
+	$retval = false;
+
+	if ( isset( $bp->is_item_admin ) ) {
+		$retval = $bp->is_item_admin;
+	}
+
+	/**
+	 * Filters whether or not the logged-in user is an admin for the current item.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $retval Whether or not the logged-in user is an admin.
+	 */
+	return (bool) apply_filters( 'bp_is_item_admin', $retval );
+}
+
+/**
+ * Is the logged-in user a mod for the current item?
+ *
+ * @return bool True if the current user is a mod for the current item,
+ *              otherwise false.
+ */
+function bp_is_item_mod() {
+	$bp     = buddypress();
+	$retval = false;
+
+	if ( isset( $bp->is_item_mod ) ) {
+		$retval = $bp->is_item_mod;
+	}
+
+	/**
+	 * Filters whether or not the logged-in user is a mod for the current item.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $retval Whether or not the logged-in user is a mod.
+	 */
+	return (bool) apply_filters( 'bp_is_item_mod', $retval );
+}
+
+/**
+ * Is this a component directory page?
+ *
+ * @return bool True if the current page is a component directory, otherwise false.
+ */
+function bp_is_directory() {
+	$bp     = buddypress();
+	$retval = false;
+
+	if ( isset( $bp->is_directory ) ) {
+		$retval = $bp->is_directory;
+	}
+
+	/**
+	 * Filters whether or not user is on a component directory page.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $retval Whether or not user is on a component directory page.
+	 */
+	return (bool) apply_filters( 'bp_is_directory', $retval );
+}
+
+/**
+ * Check to see if a component's URL should be in the root, not under a member page.
+ *
+ * - Yes ('groups' is root)    : http://example.com/groups/the-group
+ * - No  ('groups' is not-root): http://example.com/members/andy/groups/the-group
+ *
+ * This function is on the chopping block. It's currently only used by a few
+ * already deprecated functions.
+ *
+ * @param string $component_name
+ *
+ * @return bool True if root component, else false.
+ */
+function bp_is_root_component( $component_name = '' ) {
+	$bp     = buddypress();
+	$retval = false;
+
+	// Default to the current component if none is passed
+	if ( empty( $component_name ) ) {
+		$component_name = bp_current_component();
+	}
+
+	// Loop through active components and check for key/slug matches
+	if ( ! empty( $bp->active_components ) ) {
+		foreach ( (array) $bp->active_components as $key => $slug ) {
+			if ( ( $key === $component_name ) || ( $slug === $component_name ) ) {
+				$retval = true;
+				break;
+			}
+		}
+	}
+
+	/**
+	 * Filters whether or not a component's URL should be in the root, not under a member page.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $retval Whether or not URL should be in the root.
+	 */
+	return (bool) apply_filters( 'bp_is_root_component', $retval );
+}
+
+/**
+ * Check if the specified BuddyPress component directory is set to be the front page.
+ *
+ * Corresponds to the setting in wp-admin's Settings > Reading screen.
+ *
+ * @since 1.5.0
+ *
+ * @global int $current_blog WordPress global for the current blog.
+ *
+ * @param string $component Optional. Name of the component to check for.
+ *                          Default: current component.
+ *
+ * @return bool True if the specified component is set to be the site's front
+ *              page, otherwise false.
+ */
+function bp_is_component_front_page( $component = '' ) {
+	global $current_blog;
+
+	$bp = buddypress();
+
+	// Default to the current component if none is passed
+	if ( empty( $component ) ) {
+		$component = bp_current_component();
+	}
+
+	// Get the path for the current blog/site
+	$path = is_main_site()
+		? bp_core_get_site_path()
+		: $current_blog->path;
+
+	// Get the front page variables
+	$show_on_front = get_option( 'show_on_front' );
+	$page_on_front = get_option( 'page_on_front' );
+
+	if ( ( 'page' !== $show_on_front ) || empty( $component ) || empty( $bp->pages->{$component} ) || ( $_SERVER['REQUEST_URI'] !== $path ) ) {
+		return false;
+	}
+
+	/**
+	 * Filters whether or not the specified BuddyPress component directory is set to be the front page.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool   $value     Whether or not the specified component directory is set as front page.
+	 * @param string $component Current component being checked.
+	 */
+	return (bool) apply_filters( 'bp_is_component_front_page', ( $bp->pages->{$component}->id == $page_on_front ), $component );
+}
+
+/**
+ * Is this a blog page, ie a non-BP page?
+ *
+ * You can tell if a page is displaying BP content by whether the
+ * current_component has been defined.
+ *
+ * @return bool True if it's a non-BP page, false otherwise.
+ */
+function bp_is_blog_page() {
+
+	$is_blog_page = false;
+
+	// Generally, we can just check to see that there's no current component.
+	// The one exception is single user home tabs, where $bp->current_component
+	// is unset. Thus the addition of the bp_is_user() check.
+	if ( ! bp_current_component() && ! bp_is_user() ) {
+		$is_blog_page = true;
+	}
+
+	/**
+	 * Filters whether or not current page is a blog page or not.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $is_blog_page Whether or not current page is a blog page.
+	 */
+	return (bool) apply_filters( 'bp_is_blog_page', $is_blog_page );
+}
+
+/**
+ * Is this a BuddyPress component?
+ *
+ * You can tell if a page is displaying BP content by whether the
+ * current_component has been defined.
+ *
+ * Generally, we can just check to see that there's no current component.
+ * The one exception is single user home tabs, where $bp->current_component
+ * is unset. Thus the addition of the bp_is_user() check.
+ *
+ * @since 1.7.0
+ *
+ * @return bool True if it's a BuddyPress page, false otherwise.
+ */
+function is_buddypress() {
+	$retval = (bool) ( bp_current_component() || bp_is_user() );
+
+	/**
+	 * Filters whether or not this is a BuddyPress component.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param bool $retval Whether or not this is a BuddyPress component.
+	 */
+	return apply_filters( 'is_buddypress', $retval );
+}
+
+/** Components ****************************************************************/
+
+/**
+ * Check whether a given component (or feature of a component) is active.
+ *
+ * @since 1.2.0 See r2539.
+ * @since 2.3.0 Added $feature as a parameter.
+ *
+ * @param string $component The component name.
+ * @param string $feature   The feature name.
+ *
+ * @return bool
+ */
+function bp_is_active( $component = '', $feature = '' ) {
+	$retval = false;
+
+	// Default to the current component if none is passed
+	if ( empty( $component ) ) {
+		$component = bp_current_component();
+	}
+
+	// Is component in either the active or required components arrays
+	if ( isset( buddypress()->active_components[ $component ] ) || isset( buddypress()->required_components[ $component ] ) ) {
+		$retval = true;
+
+		// Is feature active?
+		if ( ! empty( $feature ) ) {
+			// The xProfile component is specific
+			if ( 'xprofile' === $component ) {
+				$component = 'profile';
+			}
+
+			if ( empty( buddypress()->$component->features ) || false === in_array( $feature, buddypress()->$component->features, true ) ) {
+				$retval = false;
+			}
+
+			/**
+			 * Filters whether or not a given feature for a component is active.
+			 *
+			 * This is a variable filter that is based on the component and feature
+			 * that you are checking of active status of.
+			 *
+			 * @since 2.3.0
+			 *
+			 * @param bool $retval
+			 */
+			$retval = apply_filters( "bp_is_{$component}_{$feature}_active", $retval );
+		}
+	}
+
+	/**
+	 * Filters whether or not a given component has been activated by the admin.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool   $retval    Whether or not a given component has been activated by the admin.
+	 * @param string $component Current component being checked.
+	 */
+	return apply_filters( 'bp_is_active', $retval, $component );
+}
+
+/**
+ * Check whether the current page is part of the Members component.
+ *
+ * @return bool True if the current page is part of the Members component.
+ */
+function bp_is_members_component() {
+	return (bool) bp_is_current_component( 'members' );
+}
+
+/**
+ * Check whether the current page is part of the Profile component.
+ *
+ * @return bool True if the current page is part of the Profile component.
+ */
+function bp_is_profile_component() {
+	return (bool) bp_is_current_component( 'xprofile' );
+}
+
+/**
+ * Check whether the current page is part of the Activity component.
+ *
+ * @return bool True if the current page is part of the Activity component.
+ */
+function bp_is_activity_component() {
+	return (bool) bp_is_current_component( 'activity' );
+}
+
+/**
+ * Check whether the current page is part of the Blogs component.
+ *
+ * @return bool True if the current page is part of the Blogs component.
+ */
+function bp_is_blogs_component() {
+	return (bool) ( is_multisite() && bp_is_current_component( 'blogs' ) );
+}
+
+/**
+ * Check whether the current page is part of the Messages component.
+ *
+ * @return bool True if the current page is part of the Messages component.
+ */
+function bp_is_messages_component() {
+	return (bool) bp_is_current_component( 'messages' );
+}
+
+/**
+ * Check whether the current page is part of the Friends component.
+ *
+ * @return bool True if the current page is part of the Friends component.
+ */
+function bp_is_friends_component() {
+	return (bool) bp_is_current_component( 'friends' );
+}
+
+/**
+ * Check whether the current page is part of the Groups component.
+ *
+ * @return bool True if the current page is part of the Groups component.
+ */
+function bp_is_groups_component() {
+	return (bool) bp_is_current_component( 'groups' );
+}
+
+/**
+ * Check whether the current page is part of the Forums component.
+ *
+ * @return bool True if the current page is part of the Forums component.
+ */
+function bp_is_forums_component() {
+	return (bool) bp_is_current_component( 'forums' );
+}
+
+/**
+ * Check whether the current page is part of the Notifications component.
+ *
+ * @since 1.9.0
+ *
+ * @return bool True if the current page is part of the Notifications component.
+ */
+function bp_is_notifications_component() {
+	return (bool) bp_is_current_component( 'notifications' );
+}
+
+/**
+ * Check whether the current page is part of the Settings component.
+ *
+ * @return bool True if the current page is part of the Settings component.
+ */
+function bp_is_settings_component() {
+	return (bool) bp_is_current_component( 'settings' );
+}
+
+/**
+ * Is the current component an active core component?
+ *
+ * Use this function when you need to check if the current component is an
+ * active core component of BuddyPress. If the current component is inactive, it
+ * will return false. If the current component is not part of BuddyPress core,
+ * it will return false. If the current component is active, and is part of
+ * BuddyPress core, it will return true.
+ *
+ * @return bool True if the current component is active and is one of BP's
+ *              packaged components.
+ */
+function bp_is_current_component_core() {
+	$retval = false;
+
+	foreach ( bp_core_get_packaged_component_ids() as $active_component ) {
+		if ( bp_is_current_component( $active_component ) ) {
+			$retval = true;
+			break;
+		}
+	}
+
+	return $retval;
+}
+
+/** Activity ******************************************************************/
+
+/**
+ * Is the current page the activity directory?
+ *
+ * @since 2.0.0
+ *
+ * @return True if the current page is the activity directory.
+ */
+function bp_is_activity_directory() {
+	if ( ! bp_displayed_user_id() && bp_is_activity_component() && ! bp_current_action() ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Is the current page a single activity item permalink?
+ *
+ * @return True if the current page is a single activity item permalink.
+ */
+function bp_is_single_activity() {
+	return (bool) ( bp_is_activity_component() && is_numeric( bp_current_action() ) );
+}
+
+/** User **********************************************************************/
+
+/**
+ * Is the current page the members directory?
+ *
+ * @since 2.0.0
+ *
+ * @return True if the current page is the members directory.
+ */
+function bp_is_members_directory() {
+	if ( ! bp_is_user() && bp_is_members_component() ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Is the current page part of the profile of the logged-in user?
+ *
+ * Will return true for any subpage of the logged-in user's profile, eg
+ * http://example.com/members/joe/friends/.
+ *
+ * @return True if the current page is part of the profile of the logged-in user.
+ */
+function bp_is_my_profile() {
+	if ( is_user_logged_in() && bp_loggedin_user_id() == bp_displayed_user_id() ) {
+		$my_profile = true;
+	} else {
+		$my_profile = false;
+	}
+
+	/**
+	 * Filters whether or not current page is part of the profile for the logged-in user.
+	 *
+	 * @since 1.2.4
+	 *
+	 * @param bool $my_profile Whether or not current page is part of the profile for the logged-in user.
+	 */
+	return apply_filters( 'bp_is_my_profile', $my_profile );
+}
+
+/**
+ * Is the current page a user page?
+ *
+ * Will return true anytime there is a displayed user.
+ *
+ * @return True if the current page is a user page.
+ */
+function bp_is_user() {
+	return (bool) bp_displayed_user_id();
+}
+
+/**
+ * Is the current page a user's activity stream page?
+ *
+ * Eg http://example.com/members/joe/activity/ (or any subpages thereof).
+ *
+ * @return True if the current page is a user's activity stream page.
+ */
+function bp_is_user_activity() {
+	return (bool) ( bp_is_user() && bp_is_activity_component() );
+}
+
+/**
+ * Is the current page a user's Friends activity stream?
+ *
+ * Eg http://example.com/members/joe/friends/
+ *
+ * @return True if the current page is a user's Friends activity stream.
+ */
+function bp_is_user_friends_activity() {
+
+	if ( ! bp_is_active( 'friends' ) ) {
+		return false;
+	}
+
+	$slug = bp_get_friends_slug();
+
+	if ( empty( $slug ) ) {
+		$slug = 'friends';
+	}
+
+	if ( bp_is_user_activity() && bp_is_current_action( $slug ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Is the current page a user's Groups activity stream?
+ *
+ * Eg http://example.com/members/joe/groups/
+ *
+ * @return True if the current page is a user's Groups activity stream.
+ */
+function bp_is_user_groups_activity() {
+
+	if ( ! bp_is_active( 'groups' ) ) {
+		return false;
+	}
+
+	$slug = ( bp_get_groups_slug() )
+		? bp_get_groups_slug()
+		: 'groups';
+
+	if ( bp_is_user_activity() && bp_is_current_action( $slug ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Is the current page part of a user's extended profile?
+ *
+ * Eg http://example.com/members/joe/profile/ (or a subpage thereof).
+ *
+ * @return True if the current page is part of a user's extended profile.
+ */
+function bp_is_user_profile() {
+	return (bool) ( bp_is_profile_component() || bp_is_current_component( 'profile' ) );
+}
+
+/**
+ * Is the current page part of a user's profile editing section?
+ *
+ * Eg http://example.com/members/joe/profile/edit/ (or a subpage thereof).
+ *
+ * @return True if the current page is a user's profile edit page.
+ */
+function bp_is_user_profile_edit() {
+	return (bool) ( bp_is_profile_component() && bp_is_current_action( 'edit' ) );
+}
+
+function bp_is_user_change_avatar() {
+	return (bool) ( bp_is_profile_component() && bp_is_current_action( 'change-avatar' ) );
+}
+
+/**
+ * Is the current page the a user's change cover image profile page?
+ *
+ * Eg http://example.com/members/joe/profile/change-cover-image/ (or a subpage thereof).
+ *
+ * @since  2.4.0
+ *
+ * @return True if the current page is a user's profile edit cover image page.
+ */
+function bp_is_user_change_cover_image() {
+	return (bool) ( bp_is_profile_component() && bp_is_current_action( 'change-cover-image' ) );
+}
+
+/**
+ * Is this a user's forums page?
+ *
+ * Eg http://example.com/members/joe/forums/ (or a subpage thereof).
+ *
+ * @return bool True if the current page is a user's forums page.
+ */
+function bp_is_user_forums() {
+
+	if ( ! bp_is_active( 'forums' ) ) {
+		return false;
+	}
+
+	if ( bp_is_user() && bp_is_forums_component() ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Is this a user's "Topics Started" page?
+ *
+ * Eg http://example.com/members/joe/forums/topics/.
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if the current page is a user's Topics Started page.
+ */
+function bp_is_user_forums_started() {
+	return (bool) ( bp_is_user_forums() && bp_is_current_action( 'topics' ) );
+}
+
+/**
+ * Is this a user's "Replied To" page?
+ *
+ * Eg http://example.com/members/joe/forums/replies/.
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if the current page is a user's Replied To forums page.
+ */
+function bp_is_user_forums_replied_to() {
+	return (bool) ( bp_is_user_forums() && bp_is_current_action( 'replies' ) );
+}
+
+/**
+ * Is the current page part of a user's Groups page?
+ *
+ * Eg http://example.com/members/joe/groups/ (or a subpage thereof).
+ *
+ * @return bool True if the current page is a user's Groups page.
+ */
+function bp_is_user_groups() {
+	return (bool) ( bp_is_user() && bp_is_groups_component() );
+}
+
+/**
+ * Is the current page part of a user's Blogs page?
+ *
+ * Eg http://example.com/members/joe/blogs/ (or a subpage thereof).
+ *
+ * @return bool True if the current page is a user's Blogs page.
+ */
+function bp_is_user_blogs() {
+	return (bool) ( bp_is_user() && bp_is_blogs_component() );
+}
+
+/**
+ * Is the current page a user's Recent Blog Posts page?
+ *
+ * Eg http://example.com/members/joe/blogs/recent-posts/.
+ *
+ * @return bool True if the current page is a user's Recent Blog Posts page.
+ */
+function bp_is_user_recent_posts() {
+	return (bool) ( bp_is_user_blogs() && bp_is_current_action( 'recent-posts' ) );
+}
+
+/**
+ * Is the current page a user's Recent Blog Comments page?
+ *
+ * Eg http://example.com/members/joe/blogs/recent-comments/.
+ *
+ * @return bool True if the current page is a user's Recent Blog Comments page.
+ */
+function bp_is_user_recent_commments() {
+	return (bool) ( bp_is_user_blogs() && bp_is_current_action( 'recent-comments' ) );
+}
+
+/**
+ * Is the current page a user's Friends page?
+ *
+ * Eg http://example.com/members/joe/blogs/friends/ (or a subpage thereof).
+ *
+ * @return bool True if the current page is a user's Friends page.
+ */
+function bp_is_user_friends() {
+	return (bool) ( bp_is_user() && bp_is_friends_component() );
+}
+
+/**
+ * Is the current page a user's Friend Requests page?
+ *
+ * Eg http://example.com/members/joe/friends/requests/.
+ *
+ * @return bool True if the current page is a user's Friends Requests page.
+ */
+function bp_is_user_friend_requests() {
+	return (bool) ( bp_is_user_friends() && bp_is_current_action( 'requests' ) );
+}
+
+/**
+ * Is this a user's notifications page?
+ *
+ * Eg http://example.com/members/joe/notifications/ (or a subpage thereof).
+ *
+ * @since 1.9.0
+ *
+ * @return bool True if the current page is a user's Notifications page.
+ */
+function bp_is_user_notifications() {
+	return (bool) ( bp_is_user() && bp_is_notifications_component() );
+}
+
+/**
+ * Is this a user's settings page?
+ *
+ * Eg http://example.com/members/joe/settings/ (or a subpage thereof).
+ *
+ * @return bool True if the current page is a user's Settings page.
+ */
+function bp_is_user_settings() {
+	return (bool) ( bp_is_user() && bp_is_settings_component() );
+}
+
+/**
+ * Is this a user's General Settings page?
+ *
+ * Eg http://example.com/members/joe/settings/general/.
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if the current page is a user's General Settings page.
+ */
+function bp_is_user_settings_general() {
+	return (bool) ( bp_is_user_settings() && bp_is_current_action( 'general' ) );
+}
+
+/**
+ * Is this a user's Notification Settings page?
+ *
+ * Eg http://example.com/members/joe/settings/notifications/.
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if the current page is a user's Notification Settings page.
+ */
+function bp_is_user_settings_notifications() {
+	return (bool) ( bp_is_user_settings() && bp_is_current_action( 'notifications' ) );
+}
+
+/**
+ * Is this a user's Account Deletion page?
+ *
+ * Eg http://example.com/members/joe/settings/delete-account/.
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if the current page is a user's Delete Account page.
+ */
+function bp_is_user_settings_account_delete() {
+	return (bool) ( bp_is_user_settings() && bp_is_current_action( 'delete-account' ) );
+}
+
+/**
+ * Is this a user's profile settings?
+ *
+ * Eg http://example.com/members/joe/settings/profile/.
+ *
+ * @since 2.0.0
+ *
+ * @return bool True if the current page is a user's Profile Settings page.
+ */
+function bp_is_user_settings_profile() {
+	return (bool) ( bp_is_user_settings() && bp_is_current_action( 'profile' ) );
+}
+
+/** Groups ********************************************************************/
+
+/**
+ * Is the current page the groups directory ?
+ *
+ * @since 2.0.0
+ *
+ * @return True if the current page is the groups directory.
+ */
+function bp_is_groups_directory() {
+	if ( bp_is_groups_component() && ! bp_current_action() && ! bp_current_item() ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Does the current page belong to a single group?
+ *
+ * Will return true for any subpage of a single group.
+ *
+ * @return bool True if the current page is part of a single group.
+ */
+function bp_is_group() {
+	$retval = bp_is_active( 'groups' );
+
+	if ( ! empty( $retval ) ) {
+		$retval = bp_is_groups_component() && groups_get_current_group();
+	}
+
+	return (bool) $retval;
+}
+
+/**
+ * Is the current page a single group's home page?
+ *
+ * URL will vary depending on which group tab is set to be the "home". By
+ * default, it's the group's recent activity.
+ *
+ * @return bool True if the current page is a single group's home page.
+ */
+function bp_is_group_home() {
+	if ( bp_is_single_item() && bp_is_groups_component() && ( ! bp_current_action() || bp_is_current_action( 'home' ) ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Is the current page part of the group creation process?
+ *
+ * @return bool True if the current page is part of the group creation process.
+ */
+function bp_is_group_create() {
+	return (bool) ( bp_is_groups_component() && bp_is_current_action( 'create' ) );
+}
+
+/**
+ * Is the current page part of a single group's admin screens?
+ *
+ * Eg http://example.com/groups/mygroup/admin/settings/.
+ *
+ * @return bool True if the current page is part of a single group's admin.
+ */
+function bp_is_group_admin_page() {
+	return (bool) ( bp_is_single_item() && bp_is_groups_component() && bp_is_current_action( 'admin' ) );
+}
+
+/**
+ * Is the current page a group's forum page?
+ *
+ * Only applies to legacy bbPress forums.
+ *
+ * @return bool True if the current page is a group forum page.
+ */
+function bp_is_group_forum() {
+	$retval = false;
+
+	// At a forum URL
+	if ( bp_is_single_item() && bp_is_groups_component() && bp_is_current_action( 'forum' ) ) {
+		$retval = true;
+
+		// If at a forum URL, set back to false if forums are inactive, or not
+		// installed correctly.
+		if ( ! bp_is_active( 'forums' ) || ! bp_forums_is_installed_correctly() ) {
+			$retval = false;
+		}
+	}
+
+	return $retval;
+}
+
+/**
+ * Is the current page a group's activity page?
+ *
+ * @return True if the current page is a group's activity page.
+ */
+function bp_is_group_activity() {
+	$retval = false;
+
+	if ( bp_is_single_item() && bp_is_groups_component() && bp_is_current_action( 'activity' ) ) {
+		$retval = true;
+	}
+
+	if ( bp_is_group_home() && bp_is_active( 'activity' ) && ! bp_is_group_custom_front() ) {
+		$retval = true;
+	}
+
+	return $retval;
+}
+
+/**
+ * Is the current page a group forum topic?
+ *
+ * Only applies to legacy bbPress (1.x) forums.
+ *
+ * @return bool True if the current page is part of a group forum topic.
+ */
+function bp_is_group_forum_topic() {
+	return (bool) ( bp_is_single_item() && bp_is_groups_component() && bp_is_current_action( 'forum' ) && bp_is_action_variable( 'topic', 0 ) );
+}
+
+/**
+ * Is the current page a group forum topic edit page?
+ *
+ * Only applies to legacy bbPress (1.x) forums.
+ *
+ * @return bool True if the current page is part of a group forum topic edit page.
+ */
+function bp_is_group_forum_topic_edit() {
+	return (bool) ( bp_is_single_item() && bp_is_groups_component() && bp_is_current_action( 'forum' ) && bp_is_action_variable( 'topic', 0 ) && bp_is_action_variable( 'edit', 2 ) );
+}
+
+/**
+ * Is the current page a group's Members page?
+ *
+ * Eg http://example.com/groups/mygroup/members/.
+ *
+ * @return bool True if the current page is part of a group's Members page.
+ */
+function bp_is_group_members() {
+	$retval = false;
+
+	if ( bp_is_single_item() && bp_is_groups_component() && bp_is_current_action( 'members' ) ) {
+		$retval = true;
+	}
+
+	if ( bp_is_group_home() && ! bp_is_active( 'activity' ) && ! bp_is_group_custom_front() ) {
+		$retval = true;
+	}
+
+	return $retval;
+}
+
+/**
+ * Is the current page a group's Invites page?
+ *
+ * Eg http://example.com/groups/mygroup/send-invites/.
+ *
+ * @return bool True if the current page is a group's Send Invites page.
+ */
+function bp_is_group_invites() {
+	return (bool) ( bp_is_groups_component() && bp_is_current_action( 'send-invites' ) );
+}
+
+/**
+ * Is the current page a group's Request Membership page?
+ *
+ * Eg http://example.com/groups/mygroup/request-membership/.
+ *
+ * @return bool True if the current page is a group's Request Membership page.
+ */
+function bp_is_group_membership_request() {
+	return (bool) ( bp_is_groups_component() && bp_is_current_action( 'request-membership' ) );
+}
+
+/**
+ * Is the current page a leave group attempt?
+ *
+ * @return bool True if the current page is a Leave Group attempt.
+ */
+function bp_is_group_leave() {
+	return (bool) ( bp_is_groups_component() && bp_is_single_item() && bp_is_current_action( 'leave-group' ) );
+}
+
+/**
+ * Is the current page part of a single group?
+ *
+ * Not currently used by BuddyPress.
+ *
+ * @todo How is this functionally different from bp_is_group()?
+ *
+ * @return bool True if the current page is part of a single group.
+ */
+function bp_is_group_single() {
+	return (bool) ( bp_is_groups_component() && bp_is_single_item() );
+}
+
+/**
+ * Is the current group page a custom front?
+ *
+ * @since 2.4.0
+ *
+ * @return bool True if the current group page is a custom front.
+ */
+function bp_is_group_custom_front() {
+	$bp = buddypress();
+	return (bool) bp_is_group_home() && ! empty( $bp->groups->current_group->front_template );
+}
+
+/**
+ * Is the current page the Create a Blog page?
+ *
+ * Eg http://example.com/sites/create/.
+ *
+ * @return bool True if the current page is the Create a Blog page.
+ */
+function bp_is_create_blog() {
+	return (bool) ( bp_is_blogs_component() && bp_is_current_action( 'create' ) );
+}
+
+/**
+ * Is the current page the blogs directory ?
+ *
+ * @since 2.0.0
+ *
+ * @return True if the current page is the blogs directory.
+ */
+function bp_is_blogs_directory() {
+	if ( is_multisite() && bp_is_blogs_component() && ! bp_current_action() ) {
+		return true;
+	}
+
+	return false;
+}
+
+/** Messages ******************************************************************/
+
+/**
+ * Is the current page part of a user's Messages pages?
+ *
+ * Eg http://example.com/members/joe/messages/ (or a subpage thereof).
+ *
+ * @return bool True if the current page is part of a user's Messages pages.
+ */
+function bp_is_user_messages() {
+	return (bool) ( bp_is_user() && bp_is_messages_component() );
+}
+
+/**
+ * Is the current page a user's Messages Inbox?
+ *
+ * Eg http://example.com/members/joe/messages/inbox/.
+ *
+ * @return bool True if the current page is a user's Messages Inbox.
+ */
+function bp_is_messages_inbox() {
+	if ( bp_is_user_messages() && ( ! bp_current_action() || bp_is_current_action( 'inbox' ) ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Is the current page a user's Messages Sentbox?
+ *
+ * Eg http://example.com/members/joe/messages/sentbox/.
+ *
+ * @return bool True if the current page is a user's Messages Sentbox.
+ */
+function bp_is_messages_sentbox() {
+	return (bool) ( bp_is_user_messages() && bp_is_current_action( 'sentbox' ) );
+}
+
+/**
+ * Is the current page a user's Messages Compose screen??
+ *
+ * Eg http://example.com/members/joe/messages/compose/.
+ *
+ * @return bool True if the current page is a user's Messages Compose screen.
+ */
+function bp_is_messages_compose_screen() {
+	return (bool) ( bp_is_user_messages() && bp_is_current_action( 'compose' ) );
+}
+
+/**
+ * Is the current page the Notices screen?
+ *
+ * Eg http://example.com/members/joe/messages/notices/.
+ *
+ * @return bool True if the current page is the Notices screen.
+ */
+function bp_is_notices() {
+	return (bool) ( bp_is_user_messages() && bp_is_current_action( 'notices' ) );
+}
+
+/**
+ * Is the current page a single Messages conversation thread?
+ *
+ * @return bool True if the current page a single Messages conversation thread?
+ */
+function bp_is_messages_conversation() {
+	return (bool) ( bp_is_user_messages() && ( bp_is_current_action( 'view' ) ) );
+}
+
+/**
+ * Not currently used by BuddyPress.
+ *
+ * @param string $component Current component to check for.
+ * @param string $callback  Callback to invoke.
+ *
+ * @return bool
+ */
+function bp_is_single( $component, $callback ) {
+	return (bool) ( bp_is_current_component( $component ) && ( true === call_user_func( $callback ) ) );
+}
+
+/** Registration **************************************************************/
+
+/**
+ * Is the current page the Activate page?
+ *
+ * Eg http://example.com/activate/.
+ *
+ * @return bool True if the current page is the Activate page.
+ */
+function bp_is_activation_page() {
+	return (bool) bp_is_current_component( 'activate' );
+}
+
+/**
+ * Is the current page the Register page?
+ *
+ * Eg http://example.com/register/.
+ *
+ * @return bool True if the current page is the Register page.
+ */
+function bp_is_register_page() {
+	return (bool) bp_is_current_component( 'register' );
+}
+
+/**
+ * Get the title parts of the BuddyPress displayed page
+ *
+ * @since 2.4.3
+ *
+ * @param string $seplocation
+ * @return array the title parts
+ */
+function bp_get_title_parts( $seplocation = 'right' ) {
+	$bp = buddypress();
+
+	// Defaults to an empty array
+	$bp_title_parts = array();
+
+	// If this is not a BP page, return the empty array.
+	if ( bp_is_blog_page() ) {
+		return $bp_title_parts;
+	}
+
+	// If this is a 404, return the empty array.
+	if ( is_404() ) {
+		return $bp_title_parts;
+	}
+
+	// If this is the front page of the site, return the empty array.
+	if ( is_front_page() || is_home() ) {
+		return $bp_title_parts;
+	}
+
+	// Return the empty array if not a BuddyPress page.
+	if ( ! is_buddypress() ) {
+		return $bp_title_parts;
+	}
+
+	// Now we can build the BP Title Parts
+	// Is there a displayed user, and do they have a name?
+	$displayed_user_name = bp_get_displayed_user_fullname();
+
+	// Displayed user.
+	if ( ! empty( $displayed_user_name ) && ! is_404() ) {
+
+		// Get the component's ID to try and get its name.
+		$component_id = $component_name = bp_current_component();
+
+		// Set empty subnav name.
+		$component_subnav_name = '';
+
+		// Use the component nav name.
+		if ( ! empty( $bp->bp_nav[$component_id] ) ) {
+			$component_name = _bp_strip_spans_from_title( $bp->bp_nav[ $component_id ]['name'] );
+
+		// Fall back on the component ID.
+		} elseif ( ! empty( $bp->{$component_id}->id ) ) {
+			$component_name = ucwords( $bp->{$component_id}->id );
+		}
+
+		// Append action name if we're on a member component sub-page.
+		if ( ! empty( $bp->bp_options_nav[ $component_id ] ) && ! empty( $bp->canonical_stack['action'] ) ) {
+			$component_subnav_name = wp_filter_object_list( $bp->bp_options_nav[ $component_id ], array( 'slug' => bp_current_action() ), 'and', 'name' );
+
+			if ( ! empty( $component_subnav_name ) ) {
+				$component_subnav_name = array_shift( $component_subnav_name );
+			}
+		}
+
+		// If on the user profile's landing page, just use the fullname.
+		if ( bp_is_current_component( $bp->default_component ) && ( bp_get_requested_url() === bp_displayed_user_domain() ) ) {
+			$bp_title_parts[] = $displayed_user_name;
+
+		// Use component name on member pages.
+		} else {
+			$bp_title_parts = array_merge( $bp_title_parts, array_map( 'strip_tags', array(
+				$displayed_user_name,
+				$component_name,
+			) ) );
+
+			// If we have a subnav name, add it separately for localization.
+			if ( ! empty( $component_subnav_name ) ) {
+				$bp_title_parts[] = strip_tags( $component_subnav_name );
+			}
+		}
+
+	// A single group.
+	} elseif ( bp_is_active( 'groups' ) && ! empty( $bp->groups->current_group ) && ! empty( $bp->bp_options_nav[ $bp->groups->current_group->slug ] ) ) {
+		$subnav      = isset( $bp->bp_options_nav[ $bp->groups->current_group->slug ][ bp_current_action() ]['name'] ) ? $bp->bp_options_nav[ $bp->groups->current_group->slug ][ bp_current_action() ]['name'] : '';
+		$bp_title_parts = array( $bp->bp_options_title, $subnav );
+
+	// A single item from a component other than groups.
+	} elseif ( bp_is_single_item() ) {
+		$bp_title_parts = array( $bp->bp_options_title, $bp->bp_options_nav[ bp_current_item() ][ bp_current_action() ]['name'] );
+
+	// An index or directory.
+	} elseif ( bp_is_directory() ) {
+		$current_component = bp_current_component();
+
+		// No current component (when does this happen?).
+		$bp_title_parts = array( _x( 'Directory', 'component directory title', 'buddypress' ) );
+
+		if ( ! empty( $current_component ) ) {
+			$bp_title_parts = array( bp_get_directory_title( $current_component ) );
+		}
+
+	// Sign up page.
+	} elseif ( bp_is_register_page() ) {
+		$bp_title_parts = array( __( 'Create an Account', 'buddypress' ) );
+
+	// Activation page.
+	} elseif ( bp_is_activation_page() ) {
+		$bp_title_parts = array( __( 'Activate Your Account', 'buddypress' ) );
+
+	// Group creation page.
+	} elseif ( bp_is_group_create() ) {
+		$bp_title_parts = array( __( 'Create a Group', 'buddypress' ) );
+
+	// Blog creation page.
+	} elseif ( bp_is_create_blog() ) {
+		$bp_title_parts = array( __( 'Create a Site', 'buddypress' ) );
+	}
+
+	// Strip spans.
+	$bp_title_parts = array_map( '_bp_strip_spans_from_title', $bp_title_parts );
+
+	// Sep on right, so reverse the order.
+	if ( 'right' === $seplocation ) {
+		$bp_title_parts = array_reverse( $bp_title_parts );
+	}
+
+	/**
+	 * Filter BuddyPress title parts before joining.
+	 *
+	 * @since 2.4.3
+	 *
+	 * @param  array $bp_title_parts Current BuddyPress title parts
+	 * @return array
+	 */
+	return (array) apply_filters( 'bp_get_title_parts', $bp_title_parts );
+}
+
+/**
+ * Customize the body class, according to the currently displayed BP content.
+ */
+function bp_the_body_class() {
+	echo bp_get_the_body_class();
+}
+	/**
+	 * Customize the body class, according to the currently displayed BP content.
+	 *
+	 * Uses the above is_() functions to output a body class for each scenario.
+	 *
+	 * @param array      $wp_classes     The body classes coming from WP.
+	 * @param array|bool $custom_classes Classes that were passed to get_body_class().
+	 *
+	 * @return array $classes The BP-adjusted body classes.
+	 */
+	function bp_get_the_body_class( $wp_classes = array(), $custom_classes = false ) {
+
+		$bp_classes = array();
+
+		/** Pages *************************************************************/
+
+		if ( is_front_page() ) {
+			$bp_classes[] = 'home-page';
+		}
+
+		if ( bp_is_directory() ) {
+			$bp_classes[] = 'directory';
+		}
+
+		if ( bp_is_single_item() ) {
+			$bp_classes[] = 'single-item';
+		}
+
+		/** Components ********************************************************/
+
+		if ( ! bp_is_blog_page() ) {
+			if ( bp_is_user_profile() )  {
+				$bp_classes[] = 'xprofile';
+			}
+
+			if ( bp_is_activity_component() ) {
+				$bp_classes[] = 'activity';
+			}
+
+			if ( bp_is_blogs_component() ) {
+				$bp_classes[] = 'blogs';
+			}
+
+			if ( bp_is_messages_component() ) {
+				$bp_classes[] = 'messages';
+			}
+
+			if ( bp_is_friends_component() ) {
+				$bp_classes[] = 'friends';
+			}
+
+			if ( bp_is_groups_component() ) {
+				$bp_classes[] = 'groups';
+			}
+
+			if ( bp_is_settings_component()  ) {
+				$bp_classes[] = 'settings';
+			}
+		}
+
+		/** User **************************************************************/
+
+		if ( bp_is_user() ) {
+			$bp_classes[] = 'bp-user';
+		}
+
+		if ( ! bp_is_directory() ) {
+			if ( bp_is_user_blogs() ) {
+				$bp_classes[] = 'my-blogs';
+			}
+
+			if ( bp_is_user_groups() ) {
+				$bp_classes[] = 'my-groups';
+			}
+
+			if ( bp_is_user_activity() ) {
+				$bp_classes[] = 'my-activity';
+			}
+		}
+
+		if ( bp_is_my_profile() ) {
+			$bp_classes[] = 'my-account';
+		}
+
+		if ( bp_is_user_profile() ) {
+			$bp_classes[] = 'my-profile';
+		}
+
+		if ( bp_is_user_friends() ) {
+			$bp_classes[] = 'my-friends';
+		}
+
+		if ( bp_is_user_messages() ) {
+			$bp_classes[] = 'my-messages';
+		}
+
+		if ( bp_is_user_recent_commments() ) {
+			$bp_classes[] = 'recent-comments';
+		}
+
+		if ( bp_is_user_recent_posts() ) {
+			$bp_classes[] = 'recent-posts';
+		}
+
+		if ( bp_is_user_change_avatar() ) {
+			$bp_classes[] = 'change-avatar';
+		}
+
+		if ( bp_is_user_profile_edit() ) {
+			$bp_classes[] = 'profile-edit';
+		}
+
+		if ( bp_is_user_friends_activity() ) {
+			$bp_classes[] = 'friends-activity';
+		}
+
+		if ( bp_is_user_groups_activity() ) {
+			$bp_classes[] = 'groups-activity';
+		}
+
+		/** Messages **********************************************************/
+
+		if ( bp_is_messages_inbox() ) {
+			$bp_classes[] = 'inbox';
+		}
+
+		if ( bp_is_messages_sentbox() ) {
+			$bp_classes[] = 'sentbox';
+		}
+
+		if ( bp_is_messages_compose_screen() ) {
+			$bp_classes[] = 'compose';
+		}
+
+		if ( bp_is_notices() ) {
+			$bp_classes[] = 'notices';
+		}
+
+		if ( bp_is_user_friend_requests() ) {
+			$bp_classes[] = 'friend-requests';
+		}
+
+		if ( bp_is_create_blog() ) {
+			$bp_classes[] = 'create-blog';
+		}
+
+		/** Groups ************************************************************/
+
+		if ( bp_is_group() ) {
+			$bp_classes[] = 'group-' . groups_get_current_group()->slug;
+		}
+
+		if ( bp_is_group_leave() ) {
+			$bp_classes[] = 'leave-group';
+		}
+
+		if ( bp_is_group_invites() ) {
+			$bp_classes[] = 'group-invites';
+		}
+
+		if ( bp_is_group_members() ) {
+			$bp_classes[] = 'group-members';
+		}
+
+		if ( bp_is_group_forum_topic() ) {
+			$bp_classes[] = 'group-forum-topic';
+		}
+
+		if ( bp_is_group_forum_topic_edit() ) {
+			$bp_classes[] = 'group-forum-topic-edit';
+		}
+
+		if ( bp_is_group_forum() ) {
+			$bp_classes[] = 'group-forum';
+		}
+
+		if ( bp_is_group_admin_page() ) {
+			$bp_classes[] = 'group-admin';
+			$bp_classes[] = bp_get_group_current_admin_tab();
+		}
+
+		if ( bp_is_group_create() ) {
+			$bp_classes[] = 'group-create';
+			$bp_classes[] = bp_get_groups_current_create_step();
+		}
+
+		if ( bp_is_group_home() ) {
+			$bp_classes[] = 'group-home';
+		}
+
+		if ( bp_is_single_activity() ) {
+			$bp_classes[] = 'activity-permalink';
+		}
+
+		/** Registration ******************************************************/
+
+		if ( bp_is_register_page() ) {
+			$bp_classes[] = 'registration';
+		}
+
+		if ( bp_is_activation_page() ) {
+			$bp_classes[] = 'activation';
+		}
+
+		/** Current Component & Action ****************************************/
+
+		if ( ! bp_is_blog_page() ) {
+			$bp_classes[] = bp_current_component();
+			$bp_classes[] = bp_current_action();
+		}
+
+		/** Clean up ***********************************************************/
+
+		// Add BuddyPress class if we are within a BuddyPress page
+		if ( ! bp_is_blog_page() ) {
+			$bp_classes[] = 'buddypress';
+		}
+
+		// Merge WP classes with BuddyPress classes and remove any duplicates
+		$classes = array_unique( array_merge( (array) $bp_classes, (array) $wp_classes ) );
+
+		/**
+		 * Filters the BuddyPress classes to be added to body_class()
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param array $classes        Array of body classes to add.
+		 * @param array $bp_classes     Array of BuddyPress-based classes.
+		 * @param array $wp_classes     Array of WordPress-based classes.
+		 * @param array $custom_classes Array of classes that were passed to get_body_class().
+		 */
+		return apply_filters( 'bp_get_the_body_class', $classes, $bp_classes, $wp_classes, $custom_classes );
+	}
+	add_filter( 'body_class', 'bp_get_the_body_class', 10, 2 );
+
+/**
+ * Customizes the post CSS class according to BuddyPress content.
+ *
+ * Hooked to the 'post_class' filter.
+ *
+ * @since 2.1.0
+ *
+ * @param array $wp_classes The post classes coming from WordPress.
+ *
+ * @return array
+ */
+function bp_get_the_post_class( $wp_classes = array() ) {
+	// don't do anything if we're not on a BP page
+	if ( ! is_buddypress() ) {
+		return $wp_classes;
+	}
+
+	$bp_classes = array();
+
+	if ( bp_is_user() || bp_is_single_activity() ) {
+		$bp_classes[] = 'bp_members';
+
+	} elseif ( bp_is_group() ) {
+		$bp_classes[] = 'bp_group';
+
+	} elseif ( bp_is_activity_component() ) {
+		$bp_classes[] = 'bp_activity';
+
+	} elseif ( bp_is_blogs_component() ) {
+		$bp_classes[] = 'bp_blogs';
+
+	} elseif ( bp_is_register_page() ) {
+		$bp_classes[] = 'bp_register';
+
+	} elseif ( bp_is_activation_page() ) {
+		$bp_classes[] = 'bp_activate';
+
+	} elseif ( bp_is_forums_component() && bp_is_directory() ) {
+		$bp_classes[] = 'bp_forum';
+	}
+
+	if ( empty( $bp_classes ) ) {
+		return $wp_classes;
+	}
+
+	// emulate post type css class
+	foreach ( $bp_classes as $bp_class ) {
+		$bp_classes[] = "type-{$bp_class}";
+	}
+
+	// okay let's merge!
+	return array_unique( array_merge( $bp_classes, $wp_classes ) );
+}
+add_filter( 'post_class', 'bp_get_the_post_class' );
+
+/**
+ * Sort BuddyPress nav menu items by their position property.
+ *
+ * This is an internal convenience function and it will probably be removed in
+ * a later release. Do not use.
+ *
+ * @access private
+ * @since 1.7.0
+ *
+ * @param array $a First item.
+ * @param array $b Second item.
+ *
+ * @return int Returns an integer less than, equal to, or greater than zero if
+ *             the first argument is considered to be respectively less than,
+ *             equal to, or greater than the second.
+ */
+function _bp_nav_menu_sort( $a, $b ) {
+	if ( $a['position'] == $b['position'] ) {
+		return 0;
+	} elseif ( $a['position'] < $b['position'] ) {
+		return -1;
+	} else {
+		return 1;
+	}
+}
+
+/**
+ * Get the items registered in the primary and secondary BuddyPress navigation menus.
+ *
+ * @since 1.7.0
+ *
+ * @return array A multidimensional array of all navigation items.
+ */
+function bp_get_nav_menu_items() {
+	$menus = $selected_menus = array();
+
+	// Get the second level menus
+	foreach ( (array) buddypress()->bp_options_nav as $parent_menu => $sub_menus ) {
+
+		// The root menu's ID is "xprofile", but the Profile submenus are using "profile". See BP_Core::setup_nav().
+		if ( 'profile' === $parent_menu ) {
+			$parent_menu = 'xprofile';
+		}
+
+		// Sort the items in this menu's navigation by their position property
+		$second_level_menus = (array) $sub_menus;
+		usort( $second_level_menus, '_bp_nav_menu_sort' );
+
+		// Iterate through the second level menus
+		foreach( $second_level_menus as $sub_nav ) {
+
+			// Skip items we don't have access to
+			if ( empty( $sub_nav['user_has_access'] ) ) {
+				continue;
+			}
+
+			// Add this menu
+			$menu         = new stdClass;
+			$menu->class  = array( 'menu-child' );
+			$menu->css_id = $sub_nav['css_id'];
+			$menu->link   = $sub_nav['link'];
+			$menu->name   = $sub_nav['name'];
+			$menu->parent = $parent_menu;  // Associate this sub nav with a top-level menu
+
+			// If we're viewing this item's screen, record that we need to mark its parent menu to be selected
+			if ( $sub_nav['slug'] == bp_current_action() ) {
+				$menu->class[]    = 'current-menu-item';
+				$selected_menus[] = $parent_menu;
+			}
+
+			$menus[] = $menu;
+		}
+	}
+
+	// Get the top-level menu parts (Friends, Groups, etc) and sort by their position property
+	$top_level_menus = (array) buddypress()->bp_nav;
+	usort( $top_level_menus, '_bp_nav_menu_sort' );
+
+	// Iterate through the top-level menus
+	foreach ( $top_level_menus as $nav ) {
+
+		// Skip items marked as user-specific if you're not on your own profile
+		if ( empty( $nav['show_for_displayed_user'] ) && ! bp_core_can_edit_settings()  ) {
+			continue;
+		}
+
+		// Get the correct menu link. See https://buddypress.trac.wordpress.org/ticket/4624
+		$link = bp_loggedin_user_domain() ? str_replace( bp_loggedin_user_domain(), bp_displayed_user_domain(), $nav['link'] ) : trailingslashit( bp_displayed_user_domain() . $nav['link'] );
+
+		// Add this menu
+		$menu         = new stdClass;
+		$menu->class  = array( 'menu-parent' );
+		$menu->css_id = $nav['css_id'];
+		$menu->link   = $link;
+		$menu->name   = $nav['name'];
+		$menu->parent = 0;
+
+		// Check if we need to mark this menu as selected
+		if ( in_array( $nav['css_id'], $selected_menus ) ) {
+			$menu->class[] = 'current-menu-parent';
+		}
+
+		$menus[] = $menu;
+	}
+
+	/**
+	 * Filters the items registered in the primary and secondary BuddyPress navigation menus.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $menus Array of items registered in the primary and secondary BuddyPress navigation.
+	 */
+	return apply_filters( 'bp_get_nav_menu_items', $menus );
+}
+
+/**
+ * Display a navigation menu.
+ *
+ * @since 1.7.0
+ *
+ * @param string|array $args {
+ *     An array of optional arguments.
+ *
+ *     @type string $after           Text after the link text. Default: ''.
+ *     @type string $before          Text before the link text. Default: ''.
+ *     @type string $container       The name of the element to wrap the navigation
+ *                                   with. 'div' or 'nav'. Default: 'div'.
+ *     @type string $container_class The class that is applied to the container.
+ *                                   Default: 'menu-bp-container'.
+ *     @type string $container_id    The ID that is applied to the container.
+ *                                   Default: ''.
+ *     @type int    $depth           How many levels of the hierarchy are to be included.
+ *                                   0 means all. Default: 0.
+ *     @type bool   $echo            True to echo the menu, false to return it.
+ *                                   Default: true.
+ *     @type bool   $fallback_cb     If the menu doesn't exist, should a callback
+ *                                   function be fired? Default: false (no fallback).
+ *     @type string $items_wrap      How the list items should be wrapped. Should be
+ *                                   in the form of a printf()-friendly string, using numbered
+ *                                   placeholders. Default: '<ul id="%1$s" class="%2$s">%3$s</ul>'.
+ *     @type string $link_after      Text after the link. Default: ''.
+ *     @type string $link_before     Text before the link. Default: ''.
+ *     @type string $menu_class      CSS class to use for the <ul> element which
+ *                                   forms the menu. Default: 'menu'.
+ *     @type string $menu_id         The ID that is applied to the <ul> element which
+ *                                   forms the menu. Default: 'menu-bp', incremented.
+ *     @type string $walker          Allows a custom walker class to be specified.
+ *                                   Default: 'BP_Walker_Nav_Menu'.
+ * }
+ *
+ * @return string|null If $echo is false, returns a string containing the nav
+ *                     menu markup.
+ */
+function bp_nav_menu( $args = array() ) {
+	static $menu_id_slugs = array();
+
+	$defaults = array(
+		'after'           => '',
+		'before'          => '',
+		'container'       => 'div',
+		'container_class' => '',
+		'container_id'    => '',
+		'depth'           => 0,
+		'echo'            => true,
+		'fallback_cb'     => false,
+		'items_wrap'      => '<ul id="%1$s" class="%2$s">%3$s</ul>',
+		'link_after'      => '',
+		'link_before'     => '',
+		'menu_class'      => 'menu',
+		'menu_id'         => '',
+		'walker'          => '',
+	);
+	$args = wp_parse_args( $args, $defaults );
+
+	/**
+	 * Filters the parsed bp_nav_menu arguments.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $args Array of parsed arguments.
+	 */
+	$args = apply_filters( 'bp_nav_menu_args', $args );
+	$args = (object) $args;
+
+	$items = $nav_menu = '';
+	$show_container = false;
+
+	// Create custom walker if one wasn't set
+	if ( empty( $args->walker ) ) {
+		$args->walker = new BP_Walker_Nav_Menu;
+	}
+
+	// Sanitise values for class and ID
+	$args->container_class = sanitize_html_class( $args->container_class );
+	$args->container_id    = sanitize_html_class( $args->container_id );
+
+	// Whether to wrap the ul, and what to wrap it with
+	if ( $args->container ) {
+
+		/**
+		 * Filters the allowed tags for the wp_nav_menu_container.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param array $value Array of allowed tags. Default 'div' and 'nav'.
+		 */
+		$allowed_tags = apply_filters( 'wp_nav_menu_container_allowedtags', array( 'div', 'nav', ) );
+
+		if ( in_array( $args->container, $allowed_tags ) ) {
+			$show_container = true;
+
+			$class     = $args->container_class ? ' class="' . esc_attr( $args->container_class ) . '"' : ' class="menu-bp-container"';
+			$id        = $args->container_id    ? ' id="' . esc_attr( $args->container_id ) . '"'       : '';
+			$nav_menu .= '<' . $args->container . $id . $class . '>';
+		}
+	}
+
+	/**
+	 * Filters the BuddyPress menu objects.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $value Array of nav menu objects.
+	 * @param array $args  Array of arguments for the menu.
+	 */
+	$menu_items = apply_filters( 'bp_nav_menu_objects', bp_get_nav_menu_items(), $args );
+	$items      = walk_nav_menu_tree( $menu_items, $args->depth, $args );
+	unset( $menu_items );
+
+	// Set the ID that is applied to the ul element which forms the menu.
+	if ( ! empty( $args->menu_id ) ) {
+		$wrap_id = $args->menu_id;
+
+	} else {
+		$wrap_id = 'menu-bp';
+
+		// If a specific ID wasn't requested, and there are multiple menus on the same screen, make sure the autogenerated ID is unique
+		while ( in_array( $wrap_id, $menu_id_slugs ) ) {
+			if ( preg_match( '#-(\d+)$#', $wrap_id, $matches ) ) {
+				$wrap_id = preg_replace('#-(\d+)$#', '-' . ++$matches[1], $wrap_id );
+			} else {
+				$wrap_id = $wrap_id . '-1';
+			}
+		}
+	}
+	$menu_id_slugs[] = $wrap_id;
+
+	/**
+	 * Filters the BuddyPress menu items.
+	 *
+	 * Allow plugins to hook into the menu to add their own <li>'s
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $items Array of nav menu items.
+	 * @param array $args  Array of arguments for the menu.
+	 */
+	$items = apply_filters( 'bp_nav_menu_items', $items, $args );
+
+	// Build the output
+	$wrap_class  = $args->menu_class ? $args->menu_class : '';
+	$nav_menu   .= sprintf( $args->items_wrap, esc_attr( $wrap_id ), esc_attr( $wrap_class ), $items );
+	unset( $items );
+
+	// If we've wrapped the ul, close it
+	if ( ! empty( $show_container ) ) {
+		$nav_menu .= '</' . $args->container . '>';
+	}
+
+	/**
+	 * Filters the final BuddyPress menu output.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $nav_menu Final nav menu output.
+	 * @param array  $args     Array of arguments for the menu.
+	 */
+	$nav_menu = apply_filters( 'bp_nav_menu', $nav_menu, $args );
+
+	if ( ! empty( $args->echo ) ) {
+		echo $nav_menu;
+	} else {
+		return $nav_menu;
+	}
+}
Index: /tags/2.4.3/src/bp-core/bp-core-theme-compatibility.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-theme-compatibility.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-theme-compatibility.php	(revision 10410)
@@ -0,0 +1,1135 @@
+<?php
+/**
+ * BuddyPress Core Theme Compatibility.
+ *
+ * @package BuddyPress
+ * @subpackage ThemeCompatibility
+ */
+
+// 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 BuddyPress content.
+ *
+ * To do this, BuddyPress 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 Theme Packs to use. {@link BP_Legacy} is a good example of
+ * extending this class.
+ *
+ * @since 1.7.0
+ *
+ * @todo We should probably do something similar to BP_Component::start().
+ * @todo If this is only intended to be extended, it should be abstract.
+ *
+ * @param array $properties {
+ *     An array of properties describing the theme compat package.
+ *     @type string $id      ID of the package. Must be unique.
+ *     @type string $name    Name of the theme. This should match the name given
+ *                           in style.css.
+ *     @type string $version Theme version. Used for busting script and style
+ *                           browser caches.
+ *     @type string $dir     Filesystem path of the theme.
+ *     @type string $url     Base URL of the theme.
+ * }
+ */
+class BP_Theme_Compat {
+
+	/**
+	 * Template package properties, as passed to the constructor.
+	 *
+	 * @var array
+	 */
+	protected $_data = array();
+
+	/**
+	 * Pass the $properties to the object on creation.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $properties Array of properties for BP_Theme_Compat.
+	 */
+    	public function __construct( Array $properties = array() ) {
+		$this->_data = $properties;
+	}
+
+	/**
+	 * Set up the BuddyPress-specific theme compat methods.
+	 *
+	 * Themes should use this method in their constructor.
+	 *
+	 * @since 1.7.0
+	 */
+	protected function start() {
+		// Sanity check
+		if ( ! bp_use_theme_compat_with_current_theme() ) {
+			return;
+		}
+
+		// Setup methods
+		$this->setup_globals();
+		$this->setup_actions();
+	}
+
+	/**
+	 * Set up global data for your template package.
+	 *
+	 * Meant to be overridden in your class. See
+	 * {@link BP_Legacy::setup_globals()} for an example.
+	 *
+	 * @since 1.7.0
+	 */
+	protected function setup_globals() {}
+
+	/**
+	 * Set up theme hooks for your template package.
+	 *
+	 * Meant to be overridden in your class. See
+	 * {@link BP_Legacy::setup_actions()} for an example.
+	 *
+	 * @since 1.7.0
+	 */
+	protected function setup_actions() {}
+
+	/**
+	 * Set a theme's property.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $property Property name.
+	 * @param mixed  $value    Property value.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function __set( $property, $value ) {
+		return $this->_data[$property] = $value;
+	}
+
+	/**
+	 * Get a theme's property.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $property Property name.
+	 *
+	 * @return mixed The value of the property if it exists, otherwise an
+	 *               empty string.
+	 */
+	public function __get( $property ) {
+		return array_key_exists( $property, $this->_data ) ? $this->_data[$property] : '';
+	}
+}
+
+/** Functions *****************************************************************/
+
+/**
+ * Set up the default theme compat theme.
+ *
+ * @since 1.7.0
+ *
+ * @param string $theme Optional. The unique ID identifier of a theme package.
+ */
+function bp_setup_theme_compat( $theme = '' ) {
+	$bp = buddypress();
+
+	// Make sure theme package is available, set to default if not
+	if ( ! isset( $bp->theme_compat->packages[$theme] ) || ! is_a( $bp->theme_compat->packages[$theme], 'BP_Theme_Compat' ) ) {
+		$theme = 'legacy';
+	}
+
+	// Set the active theme compat theme
+	$bp->theme_compat->theme = $bp->theme_compat->packages[$theme];
+}
+
+/**
+ * Get the ID of the theme package being used.
+ *
+ * This can be filtered or set manually. Tricky theme authors can override the
+ * default and include their own BuddyPress compatibility layers for their themes.
+ *
+ * @since 1.7.0
+ *
+ * @uses apply_filters()
+ *
+ * @return string ID of the theme package in use.
+ */
+function bp_get_theme_compat_id() {
+
+	/**
+	 * Filters the ID of the theme package being used.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $id ID of the theme package in use.
+	 */
+	return apply_filters( 'bp_get_theme_compat_id', buddypress()->theme_compat->theme->id );
+}
+
+/**
+ * Get the name of the theme package being used.
+ *
+ * This can be filtered or set manually. Tricky theme authors can override the
+ * default and include their own BuddyPress compatibility layers for their themes.
+ *
+ * @since 1.7.0
+ *
+ * @uses apply_filters()
+ *
+ * @return string Name of the theme package currently in use.
+ */
+function bp_get_theme_compat_name() {
+
+	/**
+	 * Filters the name of the theme package being used.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $name Name of the theme package in use.
+	 */
+	return apply_filters( 'bp_get_theme_compat_name', buddypress()->theme_compat->theme->name );
+}
+
+/**
+ * Get the version of the theme package being used.
+ *
+ * This can be filtered or set manually. Tricky theme authors can override the
+ * default and include their own BuddyPress compatibility layers for their themes.
+ *
+ * @since 1.7.0
+ *
+ * @uses apply_filters()
+ *
+ * @return string The version string of the theme package currently in use.
+ */
+function bp_get_theme_compat_version() {
+
+	/**
+	 * Filters the version of the theme package being used.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $version The version string of the theme package in use.
+	 */
+	return apply_filters( 'bp_get_theme_compat_version', buddypress()->theme_compat->theme->version );
+}
+
+/**
+ * Get the absolute path of the theme package being used.
+ *
+ * or set manually. Tricky theme authors can override the default and include
+ * their own BuddyPress compatibility layers for their themes.
+ *
+ * @since 1.7.0
+ *
+ * @uses apply_filters()
+ *
+ * @return string The absolute path of the theme package currently in use.
+ */
+function bp_get_theme_compat_dir() {
+
+	/**
+	 * Filters the absolute path of the theme package being used.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $dir The absolute path of the theme package in use.
+	 */
+	return apply_filters( 'bp_get_theme_compat_dir', buddypress()->theme_compat->theme->dir );
+}
+
+/**
+ * Get the URL of the theme package being used.
+ *
+ * This can be filtered, or set manually. Tricky theme authors can override
+ * the default and include their own BuddyPress compatibility layers for their
+ * themes.
+ *
+ * @since 1.7.0
+ *
+ * @uses apply_filters()
+ *
+ * @return string URL of the theme package currently in use.
+ */
+function bp_get_theme_compat_url() {
+
+	/**
+	 * Filters the URL of the theme package being used.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $url URL of the theme package in use.
+	 */
+	return apply_filters( 'bp_get_theme_compat_url', buddypress()->theme_compat->theme->url );
+}
+
+/**
+ * Should we use theme compat for this theme?
+ *
+ * If the current theme's need for theme compat hasn't yet been detected, we
+ * do so using bp_detect_theme_compat_with_current_theme().
+ *
+ * @since 1.9.0
+ *
+ * @uses bp_detect_theme_compat_with_current_theme()
+ *
+ * @return bool True if the current theme needs theme compatibility.
+ */
+function bp_use_theme_compat_with_current_theme() {
+	if ( ! isset( buddypress()->theme_compat->use_with_current_theme ) ) {
+		bp_detect_theme_compat_with_current_theme();
+	}
+
+	/**
+	 * Filters whether or not to use theme compat for the active theme.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param bool $use_with_current_theme True if the current theme needs theme compatibility.
+	 */
+	return apply_filters( 'bp_use_theme_compat_with_current_theme', buddypress()->theme_compat->use_with_current_theme );
+}
+
+/**
+ * Set our flag to determine whether theme compat should be enabled.
+ *
+ * Theme compat is disabled when a theme meets one of the following criteria:
+ * 1) It declares BP support with add_theme_support( 'buddypress' )
+ * 2) It is bp-default, or a child theme of bp-default
+ * 3) A legacy template is found at members/members-loop.php. This is a
+ *    fallback check for themes that were derived from bp-default, and have
+ *    not been updated for BP 1.7+; we make the assumption that any theme in
+ *    this category will have the members-loop.php template, and so use its
+ *    presence as an indicator that theme compatibility is not required
+ *
+ * @since 1.9.0
+ *
+ * @return bool True if the current theme needs theme compatibility.
+ */
+function bp_detect_theme_compat_with_current_theme() {
+	if ( isset( buddypress()->theme_compat->use_with_current_theme ) ) {
+		return buddypress()->theme_compat->use_with_current_theme;
+	}
+
+	// theme compat enabled by default
+	$theme_compat = true;
+
+	// If the theme supports 'buddypress', bail.
+	if ( current_theme_supports( 'buddypress' ) ) {
+		$theme_compat = false;
+
+	// If the theme doesn't support BP, do some additional checks
+	} else {
+		// Bail if theme is a derivative of bp-default
+		if ( in_array( 'bp-default', array( get_template(), get_stylesheet() ) ) ) {
+			$theme_compat = false;
+
+		// Brute-force check for a BP template
+		// Examples are clones of bp-default
+		} elseif ( locate_template( 'members/members-loop.php', false, false ) ) {
+			$theme_compat = false;
+		}
+	}
+
+	// set a flag in the buddypress() singleton so we don't have to run this again
+	buddypress()->theme_compat->use_with_current_theme = $theme_compat;
+
+	return $theme_compat;
+}
+
+/**
+ * Is the current page using theme compatibility?
+ *
+ * @since 1.7.0
+ *
+ * @return bool True if the current page uses theme compatibility.
+ */
+function bp_is_theme_compat_active() {
+	$bp = buddypress();
+
+	if ( empty( $bp->theme_compat->active ) ) {
+		return false;
+	}
+
+	return $bp->theme_compat->active;
+}
+
+/**
+ * Set the flag that tells whether the current page is using theme compatibility.
+ *
+ * @since 1.7.0
+ *
+ * @param bool $set True to set the flag to true, false to set it to false.
+ *
+ * @return bool Returns the value of $set.
+ */
+function bp_set_theme_compat_active( $set = true ) {
+	buddypress()->theme_compat->active = $set;
+
+	return (bool) buddypress()->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 1.7.0
+ *
+ * @param array $templates The template stack.
+ *
+ * @return array The template stack (value of $templates).
+ */
+function bp_set_theme_compat_templates( $templates = array() ) {
+	buddypress()->theme_compat->templates = $templates;
+
+	return buddypress()->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 1.7.0
+ *
+ * @param string $template The template currently in use.
+ *
+ * @return string The template currently in use (value of $template).
+ */
+function bp_set_theme_compat_template( $template = '' ) {
+	buddypress()->theme_compat->template = $template;
+
+	return buddypress()->theme_compat->template;
+}
+
+/**
+ * Set the theme compat original_template global.
+ *
+ * Stash the original template file for the current query. Useful for checking
+ * if BuddyPress was able to find a more appropriate template.
+ *
+ * @since 1.7.0
+ *
+ * @param string $template The template originally selected by WP.
+ *
+ * @return string The template originally selected by WP (value of $template).
+ */
+function bp_set_theme_compat_original_template( $template = '' ) {
+	buddypress()->theme_compat->original_template = $template;
+
+	return buddypress()->theme_compat->original_template;
+}
+
+/**
+ * Set a theme compat feature
+ *
+ * @since 2.4.0
+ *
+ * @param  string $theme_id the theme id (eg: legacy)
+ * @param  array  $feature  an associative array (eg: array( name => 'feature_name', 'settings' => array() ))
+ */
+function bp_set_theme_compat_feature( $theme_id, $feature = array() ) {
+	if ( empty( $theme_id ) || empty( $feature['name'] ) ) {
+		return;
+	}
+
+	// Get BuddyPress instance
+	$bp = buddypress();
+
+	// Get current theme compat theme
+	$theme_compat_theme = $bp->theme_compat->theme;
+
+	// Bail if the Theme Compat theme is not in use
+	if ( $theme_id !== bp_get_theme_compat_id() ) {
+		return;
+	}
+
+	$features = $theme_compat_theme->__get( 'features' );
+	if ( empty( $features ) ) {
+		$features = array();
+	}
+
+	// Bail if the feature is already registered or no settings were provided
+	if ( isset( $features[ $feature['name'] ] ) || empty( $feature['settings'] ) ) {
+		return;
+	}
+
+	// Add the feature
+	$features[ $feature['name'] ] = (object) $feature['settings'];
+
+	// The feature is attached to components
+	if ( isset( $features[ $feature['name'] ]->components ) ) {
+		// Set the feature for each concerned component
+		foreach ( (array) $features[ $feature['name'] ]->components as $component ) {
+			// The xProfile component is specific
+			if ( 'xprofile' === $component ) {
+				$component = 'profile';
+			}
+
+			if ( isset( $bp->{$component} ) ) {
+				if ( isset( $bp->{$component}->features ) ) {
+					$bp->{$component}->features[] = $feature['name'];
+				} else {
+					$bp->{$component}->features = array( $feature['name'] );
+				}
+			}
+		}
+	}
+
+	// Finally update the theme compat features
+	$theme_compat_theme->__set( 'features', $features );
+}
+
+/**
+ * Get a theme compat feature
+ *
+ * @since 2.4.0
+ *
+ * @param  string $feature the feature (eg: cover_image)
+ * @return object          the feature settings.
+ */
+function bp_get_theme_compat_feature( $feature = '' ) {
+	// Get current theme compat theme
+	$theme_compat_theme = buddypress()->theme_compat->theme;
+
+	// Get features
+	$features = $theme_compat_theme->__get( 'features' );
+
+	if ( ! isset( $features[ $feature ] ) ) {
+		return false;
+	}
+
+	return $features[ $feature ];
+}
+
+/**
+ * Setup the theme's features
+ *
+ * Note: BP Legacy's buddypress-functions.php is not loaded in WP Administration
+ * as it's loaded using bp_locate_template(). That's why this function is here.
+ *
+ * @since 2.4.0
+ *
+ * @global $content_width the content width of the theme
+ */
+function bp_register_theme_compat_default_features() {
+	global $content_width;
+
+	// Do not set up default features on deactivation
+	if ( bp_is_deactivation() ) {
+		return;
+	}
+
+	// If the current theme doesn't need theme compat, bail at this point.
+	if ( ! bp_use_theme_compat_with_current_theme() ) {
+		return;
+	}
+
+	// Make sure BP Legacy is the Theme Compat in use.
+	if ( 'legacy' !== bp_get_theme_compat_id() ) {
+		return;
+	}
+
+	// Get the theme
+	$current_theme = wp_get_theme();
+	$theme_handle  = $current_theme->get_stylesheet();
+	$parent        = $current_theme->parent();
+
+	if ( $parent ) {
+		$theme_handle = $parent->get_stylesheet();
+	}
+
+	/**
+	 * Since Companion stylesheets, the $content_width is smaller
+	 * than the width used by BuddyPress, so we need to manually set the
+	 * content width for the concerned themes.
+	 *
+	 * array( stylesheet => content width used by BuddyPress )
+	 */
+	$bp_content_widths = array(
+		'twentyfifteen'  => 1300,
+		'twentyfourteen' => 955,
+		'twentythirteen' => 890,
+	);
+
+	// Default values
+	$bp_content_width = (int) $content_width;
+	$bp_handle        = 'bp-legacy-css';
+
+	// Specific to themes having companion stylesheets
+	if ( isset( $bp_content_widths[ $theme_handle ] ) ) {
+		$bp_content_width = $bp_content_widths[ $theme_handle ];
+		$bp_handle        = 'bp-' . $theme_handle;
+	}
+
+	if ( is_rtl() ) {
+		$bp_handle .= '-rtl';
+	}
+
+	$top_offset    = 150;
+	$avatar_height = apply_filters( 'bp_core_avatar_full_height', $top_offset );
+
+	if ( $avatar_height > $top_offset ) {
+		$top_offset = $avatar_height;
+	}
+
+	bp_set_theme_compat_feature( 'legacy', array(
+		'name'     => 'cover_image',
+		'settings' => array(
+			'components'   => array( 'xprofile', 'groups' ),
+			'width'        => $bp_content_width,
+			'height'       => $top_offset + round( $avatar_height / 2 ),
+			'callback'     => 'bp_legacy_theme_cover_image',
+			'theme_handle' => $bp_handle,
+		),
+	) );
+}
+
+/**
+ * Check whether a given template is the one that WP originally selected to display current page.
+ *
+ * @since 1.7.0
+ *
+ * @param string $template The template name to check.
+ *
+ * @return bool True if the value of $template is the same as the
+ *              "original_template" originally selected by WP. Otherwise false.
+ */
+function bp_is_theme_compat_original_template( $template = '' ) {
+	$bp = buddypress();
+
+	if ( empty( $bp->theme_compat->original_template ) ) {
+		return false;
+	}
+
+	return (bool) ( $bp->theme_compat->original_template == $template );
+}
+
+/**
+ * Register a new BuddyPress theme package in the active theme packages array.
+ *
+ * For an example of how this function is used, see:
+ * {@link BuddyPress::register_theme_packages()}.
+ *
+ * @since 1.7.0
+ *
+ * @see BP_Theme_Compat for a description of the $theme parameter arguments.
+ *
+ * @param array $theme    See {@link BP_Theme_Compat}.
+ * @param bool  $override If true, overrides whatever package is currently set.
+ *                        Default: true.
+ */
+function bp_register_theme_package( $theme = array(), $override = true ) {
+
+	// Create new BP_Theme_Compat object from the $theme array
+	if ( is_array( $theme ) ) {
+		$theme = new BP_Theme_Compat( $theme );
+	}
+
+	// Bail if $theme isn't a proper object
+	if ( ! is_a( $theme, 'BP_Theme_Compat' ) ) {
+		return;
+	}
+
+	// Load up BuddyPress
+	$bp = buddypress();
+
+	// Only set if the theme package was not previously registered or if the
+	// override flag is set
+	if ( empty( $bp->theme_compat->packages[$theme->id] ) || ( true === $override ) ) {
+		$bp->theme_compat->packages[$theme->id] = $theme;
+	}
+}
+
+/**
+ * Populate various WordPress globals with dummy data to prevent errors.
+ *
+ * This dummy data is necessary because theme compatibility essentially fakes
+ * WordPress into thinking that there is content where, in fact, there is none
+ * (at least, no WordPress post content). By providing dummy data, we ensure
+ * that template functions - things like is_page() - don't throw errors.
+ *
+ * @since 1.7.0
+ *
+ * @global WP_Query $wp_query WordPress database access object.
+ * @global object $post Current post object.
+ *
+ * @param array $args Array of optional arguments. Arguments parallel the properties
+ *                    of {@link WP_Post}; see that class for more details.
+ */
+function bp_theme_compat_reset_post( $args = array() ) {
+	global $wp_query, $post;
+
+	// Switch defaults if post is set
+	if ( isset( $wp_query->post ) ) {
+		$dummy = wp_parse_args( $args, 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 {
+		$dummy = wp_parse_args( $args, array(
+			'ID'                    => -9999,
+			'post_status'           => 'public',
+			'post_author'           => 0,
+			'post_parent'           => 0,
+			'post_type'             => 'page',
+			'post_date'             => 0,
+			'post_date_gmt'         => 0,
+			'post_modified'         => 0,
+			'post_modified_gmt'     => 0,
+			'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,
+		) );
+	}
+
+	// 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'];
+
+	// Clean up the dummy post
+	unset( $dummy );
+
+	/**
+	 * Force the header back to 200 status if not a deliberate 404
+	 *
+	 * @see https://bbpress.trac.wordpress.org/ticket/1973
+	 */
+	if ( ! $wp_query->is_404() ) {
+		status_header( 200 );
+	}
+
+	// If we are resetting a post, we are in theme compat
+	bp_set_theme_compat_active( true );
+
+	// If we are in theme compat, we don't need the 'Edit' post link
+	add_filter( 'get_edit_post_link', 'bp_core_filter_edit_post_link', 10, 2 );
+}
+
+/**
+ * Reset main query vars and filter 'the_content' to output a BuddyPress template part as needed.
+ *
+ * @since 1.7.0
+ *
+ * @uses bp_is_single_user() To check if page is single user.
+ * @uses bp_get_single_user_template() To get user template.
+ * @uses bp_is_single_user_edit() To check if page is single user edit.
+ * @uses bp_get_single_user_edit_template() To get user edit template.
+ * @uses bp_is_single_view() To check if page is single view.
+ * @uses bp_get_single_view_template() To get view template.
+ * @uses bp_is_forum_edit() To check if page is forum edit.
+ * @uses bp_get_forum_edit_template() To get forum edit template.
+ * @uses bp_is_topic_merge() To check if page is topic merge.
+ * @uses bp_get_topic_merge_template() To get topic merge template.
+ * @uses bp_is_topic_split() To check if page is topic split.
+ * @uses bp_get_topic_split_template() To get topic split template.
+ * @uses bp_is_topic_edit() To check if page is topic edit.
+ * @uses bp_get_topic_edit_template() To get topic edit template.
+ * @uses bp_is_reply_edit() To check if page is reply edit.
+ * @uses bp_get_reply_edit_template() To get reply edit template.
+ * @uses bp_set_theme_compat_template() To set the global theme compat template.
+ *
+ * @param string $template Template name.
+ *
+ * @return string $template Template name.
+ */
+function bp_template_include_theme_compat( $template = '' ) {
+
+	// If the current theme doesn't need theme compat, bail at this point.
+	if ( ! bp_use_theme_compat_with_current_theme() ) {
+		return $template;
+	}
+
+	/**
+	 * Fires when resetting main query vars and filtering 'the_content' to output BuddyPress template parts.
+	 *
+	 * Use this action to execute code that will communicate to BuddyPress's
+	 * theme compatibility layer whether or not we're replacing the_content()
+	 * with some other template part.
+	 *
+	 * @since 1.7.0
+	 */
+	do_action( 'bp_template_include_reset_dummy_post_data' );
+
+	// Bail if the template already matches a BuddyPress template
+	if ( ! empty( buddypress()->theme_compat->found_template ) ) {
+		return $template;
+	}
+
+	/**
+	 * If we are relying on BuddyPress's built in theme compatibility 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 bp_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 'bp_get_buddypress_template' to override the array of
+	 * possible templates, or 'bp_buddypress_template' to override the result.
+	 */
+	if ( bp_is_theme_compat_active() ) {
+		$template = bp_get_theme_compat_templates();
+
+		add_filter( 'the_content', 'bp_replace_the_content' );
+
+		// Add BuddyPress's head action to wp_head
+		if ( ! has_action( 'wp_head', 'bp_head' ) ) {
+			add_action( 'wp_head', 'bp_head' );
+		}
+	}
+
+	/**
+	 * Filters the template name to include.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $template Template name.
+	 */
+	return apply_filters( 'bp_template_include_theme_compat', $template );
+}
+
+/**
+ * Conditionally replace 'the_content'.
+ *
+ * Replaces the_content() if the post_type being displayed is one that would
+ * normally be handled by BuddyPress, but proper single page templates do not
+ * exist in the currently active theme.
+ *
+ * @since 1.7.0
+ *
+ * @param string $content Original post content.
+ *
+ * @return string $content Post content, potentially modified.
+ */
+function bp_replace_the_content( $content = '' ) {
+
+	// Bail if not the main loop where theme compat is happening
+	if ( ! bp_do_theme_compat() ) {
+		return $content;
+	}
+
+	// Set theme compat to false early, to avoid recursion from nested calls to
+	// the_content() that execute before theme compat has unhooked itself.
+	bp_set_theme_compat_active( false );
+
+	/**
+	 * Filters the content to replace in the post.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $content Original post content.
+	 */
+	$new_content = apply_filters( 'bp_replace_the_content', $content );
+
+	// Juggle the content around and try to prevent unsightly comments
+	if ( ! empty( $new_content ) && ( $new_content !== $content ) ) {
+
+		// Set the content to be the new content
+		$content = $new_content;
+
+		// Clean up after ourselves
+		unset( $new_content );
+
+		// Reset the $post global
+		wp_reset_postdata();
+	}
+
+	// Return possibly hi-jacked content
+	return $content;
+}
+
+/**
+ * Are we currently replacing the_content?
+ *
+ * @since 1.8.0
+ *
+ * @return bool True if the_content is currently in the process of being
+ *              filtered and replaced.
+ */
+function bp_do_theme_compat() {
+	return (bool) ( ! bp_is_template_included() && in_the_loop() && bp_is_theme_compat_active() );
+}
+
+/** Filters *******************************************************************/
+
+/**
+ * Remove all filters from a WordPress filter hook.
+ *
+ * Removed filters are stashed in the $bp global, in case they need to be
+ * restored later.
+ *
+ * @since 1.7.0
+ *
+ * @global WP_filter $wp_filter
+ * @global array $merged_filters
+ *
+ * @param string   $tag      The filter tag to remove filters from.
+ * @param int|bool $priority Optional. If present, only those callbacks attached
+ *                           at a given priority will be removed. Otherwise, all callbacks
+ *                           attached to the tag will be removed, regardless of priority.
+ *
+ * @return bool True on success.
+ */
+function bp_remove_all_filters( $tag, $priority = false ) {
+	global $wp_filter, $merged_filters;
+
+	$bp = buddypress();
+
+	// 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
+			$bp->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
+			$bp->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
+		$bp->filters->merged_filters[$tag] = $merged_filters[$tag];
+
+		// Unset the filters
+		unset( $merged_filters[$tag] );
+	}
+
+	return true;
+}
+
+/**
+ * Restore filters that were removed using bp_remove_all_filters().
+ *
+ * @since 1.7.0
+ *
+ * @global WP_filter $wp_filter
+ * @global array $merged_filters
+ *
+ * @param string   $tag      The tag to which filters should be restored.
+ * @param int|bool $priority Optional. If present, only those filters that were originally
+ *                           attached to the tag with $priority will be restored. Otherwise,
+ *                           all available filters will be restored, regardless of priority.
+ *
+ * @return bool True on success.
+ */
+function bp_restore_all_filters( $tag, $priority = false ) {
+	global $wp_filter, $merged_filters;
+
+	$bp = buddypress();
+
+	// Filters exist
+	if ( isset( $bp->filters->wp_filter[$tag] ) ) {
+
+		// Filters exist in this priority
+		if ( ! empty( $priority ) && isset( $bp->filters->wp_filter[$tag][$priority] ) ) {
+
+			// Store filters in a backup
+			$wp_filter[$tag][$priority] = $bp->filters->wp_filter[$tag][$priority];
+
+			// Unset the filters
+			unset( $bp->filters->wp_filter[$tag][$priority] );
+
+		// Priority is empty
+		} else {
+
+			// Store filters in a backup
+			$wp_filter[$tag] = $bp->filters->wp_filter[$tag];
+
+			// Unset the filters
+			unset( $bp->filters->wp_filter[$tag] );
+		}
+	}
+
+	// Check merged filters
+	if ( isset( $bp->filters->merged_filters[$tag] ) ) {
+
+		// Store filters in a backup
+		$merged_filters[$tag] = $bp->filters->merged_filters[$tag];
+
+		// Unset the filters
+		unset( $bp->filters->merged_filters[$tag] );
+	}
+
+	return true;
+}
+
+/**
+ * Force comments_status to 'closed' for BuddyPress post types.
+ *
+ * @since 1.7.0
+ *
+ * @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 bp_comments_open( $open, $post_id = 0 ) {
+
+	$retval = is_buddypress() ? false : $open;
+
+	/**
+	 * Filters whether or not to force comments_status to closed for BuddyPress post types.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param bool $retval  Whether or not we are on a BuddyPress post type.
+	 * @param bool $open    True if comments open, false if closed.
+	 * @param int  $post_id Post ID for the checked post.
+	 */
+	return apply_filters( 'bp_force_comment_status', $retval, $open, $post_id );
+}
+
+/**
+ * Do not allow {@link comments_template()} to render during theme compatibility.
+ *
+ * When theme compatibility sets the 'is_page' flag to true via
+ * {@link bp_theme_compat_reset_post()}, themes that use comments_template()
+ * in their page template will run.
+ *
+ * To prevent comments_template() from rendering, we set the 'is_page' and
+ * 'is_single' flags to false since that function looks at these conditionals
+ * before querying the database for comments and loading the comments template.
+ *
+ * This is done during the output buffer as late as possible to prevent any
+ * wonkiness.
+ *
+ * @since 1.9.2
+ *
+ * @param string $retval The current post content.
+ *
+ * @return string $retval
+ */
+function bp_theme_compat_toggle_is_page( $retval = '' ) {
+	global $wp_query;
+
+	$wp_query->is_page = false;
+
+	// Set a switch so we know that we've toggled these WP_Query properties
+	buddypress()->theme_compat->is_page_toggled = true;
+
+	return $retval;
+}
+add_filter( 'bp_replace_the_content', 'bp_theme_compat_toggle_is_page', 9999 );
+
+/**
+ * Restores the 'is_single' and 'is_page' flags if toggled by BuddyPress.
+ *
+ * @since 1.9.2
+ *
+ * @see bp_theme_compat_toggle_is_page()
+ * @param object $query The WP_Query object.
+ */
+function bp_theme_compat_loop_end( $query ) {
+
+	// Get BuddyPress
+	$bp = buddypress();
+
+	// Bail if page is not toggled
+	if ( ! isset( $bp->theme_compat->is_page_toggled ) ) {
+		return;
+	}
+
+	// Revert our toggled WP_Query properties
+	$query->is_page = true;
+
+	// Unset our switch
+	unset( $bp->theme_compat->is_page_toggled );
+}
+add_action( 'loop_end', 'bp_theme_compat_loop_end' );
Index: /tags/2.4.3/src/bp-core/bp-core-update.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-update.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-update.php	(revision 10410)
@@ -0,0 +1,692 @@
+<?php
+/**
+ * BuddyPress Updater.
+ *
+ * @package BuddyPress
+ * @subpackage Updater
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Is this a fresh installation of BuddyPress?
+ *
+ * If there is no raw DB version, we infer that this is the first installation.
+ *
+ * @since 1.7.0
+ *
+ * @uses get_option()
+ * @uses bp_get_db_version() To get BuddyPress's database version.
+ *
+ * @return bool True if this is a fresh BP install, otherwise false.
+ */
+function bp_is_install() {
+    return ! bp_get_db_version_raw();
+}
+
+/**
+ * Is this a BuddyPress update?
+ *
+ * Determined by comparing the registered BuddyPress version to the version
+ * number stored in the database. If the registered version is greater, it's
+ * an update.
+ *
+ * @since 1.6.0
+ *
+ * @uses get_option()
+ * @uses bp_get_db_version() To get BuddyPress's database version.
+ *
+ * @return bool True if update, otherwise false.
+ */
+function bp_is_update() {
+
+    // Current DB version of this site (per site in a multisite network)
+    $current_db   = bp_get_option( '_bp_db_version' );
+    $current_live = bp_get_db_version();
+
+    // Compare versions (cast as int and bool to be safe)
+    $is_update = (bool) ( (int) $current_db < (int) $current_live );
+
+    // Return the product of version comparison
+    return $is_update;
+}
+
+/**
+ * Determine whether BuddyPress is in the process of being activated.
+ *
+ * @since 1.6.0
+ *
+ * @uses buddypress()
+ *
+ * @param string $basename BuddyPress basename.
+ *
+ * @return bool True if activating BuddyPress, false if not.
+ */
+function bp_is_activation( $basename = '' ) {
+    $bp     = buddypress();
+    $action = 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' ) ) ) {
+        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( $bp->basename ) ) {
+        $basename = $bp->basename;
+    }
+
+    // Bail if no basename
+    if ( empty( $basename ) ) {
+        return false;
+    }
+
+    // Is BuddyPress being activated?
+    return in_array( $basename, $plugins );
+}
+
+/**
+ * Determine whether BuddyPress is in the process of being deactivated.
+ *
+ * @since 1.6.0
+ *
+ * @uses buddypress()
+ *
+ * @param string $basename BuddyPress basename.
+ *
+ * @return bool True if deactivating BuddyPress, false if not.
+ */
+function bp_is_deactivation( $basename = '' ) {
+    $bp     = buddypress();
+    $action = 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' ) ) ) {
+        return false;
+    }
+
+    // The plugin(s) being deactivated
+    if ( 'deactivate' == $action ) {
+        $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( $bp->basename ) ) {
+        $basename = $bp->basename;
+    }
+
+    // Bail if no basename
+    if ( empty( $basename ) ) {
+        return false;
+    }
+
+    // Is bbPress being deactivated?
+    return in_array( $basename, $plugins );
+}
+
+/**
+ * Update the BP version stored in the database to the current version.
+ *
+ * @since 1.6.0
+ *
+ * @uses bp_get_db_version() To get BuddyPress's database version.
+ * @uses bp_update_option() To update BuddyPress's database version.
+ */
+function bp_version_bump() {
+    bp_update_option( '_bp_db_version', bp_get_db_version() );
+}
+
+/**
+ * Set up the BuddyPress updater.
+ *
+ * @since 1.6.0
+ */
+function bp_setup_updater() {
+
+    // Are we running an outdated version of BuddyPress?
+    if ( ! bp_is_update() ) {
+        return;
+    }
+
+    bp_version_updater();
+}
+
+/**
+ * Initialize an update or installation of BuddyPress.
+ *
+ * BuddyPress's version updater looks at what the current database version is,
+ * and runs whatever other code is needed - either the "update" or "install"
+ * code.
+ *
+ * This is most often used when the data schema changes, but should also be used
+ * to correct issues with BuddyPress metadata silently on software update.
+ *
+ * @since 1.7.0
+ */
+function bp_version_updater() {
+
+    // Get the raw database version
+    $raw_db_version = (int) bp_get_db_version_raw();
+
+    /**
+     * Filters the default components to activate for a new install.
+     *
+     * @since 1.7.0
+     *
+     * @param array $value Array of default components to activate.
+     */
+    $default_components = apply_filters( 'bp_new_install_default_components', array(
+        'activity'      => 1,
+        'members'       => 1,
+        'settings'      => 1,
+        'xprofile'      => 1,
+        'notifications' => 1,
+    ) );
+
+    require_once( buddypress()->plugin_dir . '/bp-core/admin/bp-core-admin-schema.php' );
+
+    // Install BP schema and activate only Activity and XProfile
+    if ( bp_is_install() ) {
+
+        // Apply schema and set Activity and XProfile components as active
+        bp_core_install( $default_components );
+        bp_update_option( 'bp-active-components', $default_components );
+        bp_core_add_page_mappings( $default_components, 'delete' );
+
+    // Upgrades
+    } else {
+
+        // Run the schema install to update tables
+        bp_core_install();
+
+        // 1.5.0
+        if ( $raw_db_version < 1801 ) {
+            bp_update_to_1_5();
+            bp_core_add_page_mappings( $default_components, 'delete' );
+        }
+
+        // 1.6.0
+        if ( $raw_db_version < 6067 ) {
+            bp_update_to_1_6();
+        }
+
+        // 1.9.0
+        if ( $raw_db_version < 7553 ) {
+            bp_update_to_1_9();
+        }
+
+        // 1.9.2
+        if ( $raw_db_version < 7731 ) {
+            bp_update_to_1_9_2();
+        }
+
+        // 2.0.0
+        if ( $raw_db_version < 7892 ) {
+            bp_update_to_2_0();
+        }
+
+        // 2.0.1
+        if ( $raw_db_version < 8311 ) {
+            bp_update_to_2_0_1();
+        }
+
+        // 2.2.0
+        if ( $raw_db_version < 9181 ) {
+            bp_update_to_2_2();
+        }
+
+        // 2.3.0
+        if ( $raw_db_version < 9615 ) {
+            bp_update_to_2_3();
+        }
+    }
+
+    /** All done! *************************************************************/
+
+    // Bump the version
+    bp_version_bump();
+}
+
+/**
+ * Perform database operations that must take place before the general schema upgrades.
+ *
+ * `dbDelta()` cannot handle certain operations - like changing indexes - so we do it here instead.
+ *
+ * @since 2.3.0
+ */
+function bp_pre_schema_upgrade() {
+    global $wpdb;
+
+    $raw_db_version = (int) bp_get_db_version_raw();
+    $bp_prefix      = bp_core_get_table_prefix();
+
+    // 2.3.0: Change index lengths to account for utf8mb4.
+    if ( $raw_db_version < 9695 ) {
+        // table_name => columns.
+        $tables = array(
+            $bp_prefix . 'bp_activity_meta'       => array( 'meta_key' ),
+            $bp_prefix . 'bp_groups_groupmeta'    => array( 'meta_key' ),
+            $bp_prefix . 'bp_messages_meta'       => array( 'meta_key' ),
+            $bp_prefix . 'bp_notifications_meta'  => array( 'meta_key' ),
+            $bp_prefix . 'bp_user_blogs_blogmeta' => array( 'meta_key' ),
+            $bp_prefix . 'bp_xprofile_meta'       => array( 'meta_key' ),
+        );
+
+        foreach ( $tables as $table_name => $indexes ) {
+            foreach ( $indexes as $index ) {
+                if ( $wpdb->query( $wpdb->prepare( "SHOW TABLES LIKE %s", bp_esc_like( $table_name ) ) ) ) {
+                    $wpdb->query( "ALTER TABLE {$table_name} DROP INDEX {$index}" );
+                }
+            }
+        }
+    }
+}
+
+/** Upgrade Routines **********************************************************/
+
+/**
+ * Remove unused metadata from database when upgrading from < 1.5.
+ *
+ * Database update methods based on version numbers.
+ *
+ * @since 1.7.0
+ */
+function bp_update_to_1_5() {
+
+    // Delete old database version options
+    delete_site_option( 'bp-activity-db-version' );
+    delete_site_option( 'bp-blogs-db-version'    );
+    delete_site_option( 'bp-friends-db-version'  );
+    delete_site_option( 'bp-groups-db-version'   );
+    delete_site_option( 'bp-messages-db-version' );
+    delete_site_option( 'bp-xprofile-db-version' );
+}
+
+/**
+ * Remove unused metadata from database when upgrading from < 1.6.
+ *
+ * Database update methods based on version numbers.
+ *
+ * @since 1.7.0
+ */
+function bp_update_to_1_6() {
+
+    // Delete possible site options
+    delete_site_option( 'bp-db-version'       );
+    delete_site_option( '_bp_db_version'      );
+    delete_site_option( 'bp-core-db-version'  );
+    delete_site_option( '_bp-core-db-version' );
+
+    // Delete possible blog options
+    delete_blog_option( bp_get_root_blog_id(), 'bp-db-version'       );
+    delete_blog_option( bp_get_root_blog_id(), 'bp-core-db-version'  );
+    delete_site_option( bp_get_root_blog_id(), '_bp-core-db-version' );
+    delete_site_option( bp_get_root_blog_id(), '_bp_db_version'      );
+}
+
+/**
+ * Add the notifications component to active components.
+ *
+ * Notifications was added in 1.9.0, and previous installations will already
+ * have the core notifications API active. We need to add the new Notifications
+ * component to the active components option to retain existing functionality.
+ *
+ * @since 1.9.0
+ */
+function bp_update_to_1_9() {
+
+    // Setup hardcoded keys
+    $active_components_key      = 'bp-active-components';
+    $notifications_component_id = 'notifications';
+
+    // Get the active components
+    $active_components          = bp_get_option( $active_components_key );
+
+    // Add notifications
+    if ( ! in_array( $notifications_component_id, $active_components ) ) {
+        $active_components[ $notifications_component_id ] = 1;
+    }
+
+    // Update the active components option
+    bp_update_option( $active_components_key, $active_components );
+}
+
+/**
+ * Perform database updates for BP 1.9.2
+ *
+ * In 1.9, BuddyPress stopped registering its theme directory when it detected
+ * that bp-default (or a child theme) was not currently being used, in effect
+ * deprecating bp-default. However, this ended up causing problems when site
+ * admins using bp-default would switch away from the theme temporarily:
+ * bp-default would no longer be available, with no obvious way (outside of
+ * a manual filter) to restore it. In 1.9.2, we add an option that flags
+ * whether bp-default or a child theme is active at the time of upgrade; if so,
+ * the theme directory will continue to be registered even if the theme is
+ * deactivated temporarily. Thus, new installations will not see bp-default,
+ * but legacy installations using the theme will continue to see it.
+ *
+ * @since 1.9.2
+ */
+function bp_update_to_1_9_2() {
+    if ( 'bp-default' === get_stylesheet() || 'bp-default' === get_template() ) {
+        update_site_option( '_bp_retain_bp_default', 1 );
+    }
+}
+
+/**
+ * 2.0 update routine.
+ *
+ * - Ensure that the activity tables are installed, for last_activity storage.
+ * - Migrate last_activity data from usermeta to activity table.
+ * - Add values for all BuddyPress options to the options table.
+ *
+ * @since 2.0.0
+ */
+function bp_update_to_2_0() {
+
+    /** Install activity tables for 'last_activity' ***************************/
+
+    bp_core_install_activity_streams();
+
+    /** Migrate 'last_activity' data ******************************************/
+
+    bp_last_activity_migrate();
+
+    /** Migrate signups data **************************************************/
+
+    if ( ! is_multisite() ) {
+
+        // Maybe install the signups table
+        bp_core_maybe_install_signups();
+
+        // Run the migration script
+        bp_members_migrate_signups();
+    }
+
+    /** Add BP options to the options table ***********************************/
+
+    bp_add_options();
+}
+
+/**
+ * 2.0.1 database upgrade routine.
+ *
+ * @since 2.0.1
+ *
+ * @return void
+ */
+function bp_update_to_2_0_1() {
+
+    // We purposely call this during both the 2.0 upgrade and the 2.0.1 upgrade.
+    // Don't worry; it won't break anything, and safely handles all cases.
+    bp_core_maybe_install_signups();
+}
+
+/**
+ * 2.2.0 update routine.
+ *
+ * - Add messages meta table.
+ * - Update the component field of the 'new members' activity type.
+ * - Clean up hidden friendship activities.
+ *
+ * @since 2.2.0
+ */
+function bp_update_to_2_2() {
+
+    // Also handled by `bp_core_install()`
+    if ( bp_is_active( 'messages' ) ) {
+        bp_core_install_private_messaging();
+    }
+
+    if ( bp_is_active( 'activity' ) ) {
+        bp_migrate_new_member_activity_component();
+
+        if ( bp_is_active( 'friends' ) ) {
+            bp_cleanup_friendship_activities();
+        }
+    }
+}
+
+/**
+ * 2.3.0 update routine.
+ *
+ * - Add notifications meta table.
+ *
+ * @since 2.3.0
+ */
+function bp_update_to_2_3() {
+
+    // Also handled by `bp_core_install()`
+    if ( bp_is_active( 'notifications' ) ) {
+        bp_core_install_notifications();
+    }
+}
+
+/**
+ * Updates the component field for new_members type.
+ *
+ * @since 2.2.0
+ *
+ * @global $wpdb
+ * @uses   buddypress()
+ *
+ */
+function bp_migrate_new_member_activity_component() {
+    global $wpdb;
+    $bp = buddypress();
+
+    // Update the component for the new_member type
+    $wpdb->update(
+        // Activity table
+        $bp->members->table_name_last_activity,
+        array(
+            'component' => $bp->members->id,
+        ),
+        array(
+            'component' => 'xprofile',
+            'type'      => 'new_member',
+        ),
+        // Data sanitization format
+        array(
+            '%s',
+        ),
+        // WHERE sanitization format
+        array(
+            '%s',
+            '%s'
+        )
+    );
+}
+
+/**
+ * Remove all hidden friendship activities.
+ *
+ * @since 2.2.0
+ *
+ * @uses bp_activity_delete() to delete the corresponding friendship activities.
+ */
+function bp_cleanup_friendship_activities() {
+    bp_activity_delete( array(
+        'component'     => buddypress()->friends->id,
+        'type'          => 'friendship_created',
+        'hide_sitewide' => true,
+    ) );
+ }
+
+/**
+ * Redirect user to BP's What's New page on first page load after activation.
+ *
+ * @since 1.7.0
+ *
+ * @internal Used internally to redirect BuddyPress to the about page on activation.
+ *
+ * @uses set_transient() To drop the activation transient for 30 seconds.
+ */
+function bp_add_activation_redirect() {
+
+    // Bail if activating from network, or bulk
+    if ( isset( $_GET['activate-multi'] ) ) {
+        return;
+    }
+
+    // Record that this is a new installation, so we show the right
+    // welcome message
+    if ( bp_is_install() ) {
+        set_transient( '_bp_is_new_install', true, 30 );
+    }
+
+    // Add the transient to redirect
+    set_transient( '_bp_activation_redirect', true, 30 );
+}
+
+/** Signups *******************************************************************/
+
+/**
+ * Check if the signups table needs to be created or upgraded.
+ *
+ * @since 2.0.0
+ *
+ * @global WPDB $wpdb
+ *
+ * @return bool|null If signups table exists.
+ */
+function bp_core_maybe_install_signups() {
+    global $wpdb;
+
+    // The table to run queries against
+    $signups_table = $wpdb->base_prefix . 'signups';
+
+    // Suppress errors because users shouldn't see what happens next
+    $old_suppress  = $wpdb->suppress_errors();
+
+    // Never use bp_core_get_table_prefix() for any global users tables
+    $table_exists  = (bool) $wpdb->get_results( "DESCRIBE {$signups_table};" );
+
+    // Table already exists, so maybe upgrade instead?
+    if ( true === $table_exists ) {
+
+        // Look for the 'signup_id' column
+        $column_exists = $wpdb->query( "SHOW COLUMNS FROM {$signups_table} LIKE 'signup_id'" );
+
+        // 'signup_id' column doesn't exist, so run the upgrade
+        if ( empty( $column_exists ) ) {
+            bp_core_upgrade_signups();
+        }
+
+    // Table does not exist, and we are a single site, so install the multisite
+    // signups table using WordPress core's database schema.
+    } elseif ( ! is_multisite() ) {
+        bp_core_install_signups();
+    }
+
+    // Restore previous error suppression setting
+    $wpdb->suppress_errors( $old_suppress );
+}
+
+/** Activation Actions ********************************************************/
+
+/**
+ * Fire activation hooks and events.
+ *
+ * Runs on BuddyPress activation.
+ *
+ * @since 1.6.0
+ *
+ * @uses do_action() Calls 'bp_activation' hook.
+ */
+function bp_activation() {
+
+    // Force refresh theme roots.
+    delete_site_transient( 'theme_roots' );
+
+    // Add options
+    bp_add_options();
+
+    /**
+     * Fires during the activation of BuddyPress.
+     *
+     * Use as of (1.6.0)
+     *
+     * @since 1.6.0
+     */
+    do_action( 'bp_activation' );
+
+    // @deprecated as of (1.6)
+    do_action( 'bp_loader_activate' );
+}
+
+/**
+ * Fire deactivation hooks and events.
+ *
+ * Runs on BuddyPress deactivation.
+ *
+ * @since 1.6.0
+ *
+ * @uses do_action() Calls 'bp_deactivation' hook.
+ */
+function bp_deactivation() {
+
+    // Force refresh theme roots.
+    delete_site_transient( 'theme_roots' );
+
+    // Switch to WordPress's default theme if current parent or child theme
+    // depend on bp-default. This is to prevent white screens of doom.
+    if ( in_array( 'bp-default', array( get_template(), get_stylesheet() ) ) ) {
+        switch_theme( WP_DEFAULT_THEME, WP_DEFAULT_THEME );
+        update_option( 'template_root',   get_raw_theme_root( WP_DEFAULT_THEME, true ) );
+        update_option( 'stylesheet_root', get_raw_theme_root( WP_DEFAULT_THEME, true ) );
+    }
+
+    /**
+     * Fires during the deactivation of BuddyPress.
+     *
+     * Use as of (1.6.0)
+     *
+     * @since 1.6.0
+     */
+    do_action( 'bp_deactivation' );
+
+    // @deprecated as of (1.6)
+    do_action( 'bp_loader_deactivate' );
+}
+
+/**
+ * Fire uninstall hook.
+ *
+ * Runs when uninstalling BuddyPress.
+ *
+ * @since 1.6.0
+ *
+ * @uses do_action() Calls 'bp_uninstall' hook.
+ */
+function bp_uninstall() {
+
+    /**
+     * Fires during the uninstallation of BuddyPress.
+     *
+     * @since 1.6.0
+     */
+    do_action( 'bp_uninstall' );
+}
Index: /tags/2.4.3/src/bp-core/bp-core-widgets.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-widgets.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-widgets.php	(revision 10410)
@@ -0,0 +1,188 @@
+<?php
+/**
+ * BuddyPress Core Component Widgets.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register bp-core widgets.
+ *
+ * @since 1.0.0
+ */
+function bp_core_register_widgets() {
+	add_action('widgets_init', create_function('', 'return register_widget("BP_Core_Login_Widget");') );
+}
+add_action( 'bp_register_widgets', 'bp_core_register_widgets' );
+
+/**
+ * BuddyPress Login Widget.
+ *
+ * @since 1.9.0
+ */
+class BP_Core_Login_Widget extends WP_Widget {
+
+	/**
+	 * Constructor method.
+	 */
+	public function __construct() {
+		parent::__construct(
+			false,
+			_x( '(BuddyPress) Log In', 'Title of the login widget', 'buddypress' ),
+			array(
+				'description' => __( 'Show a Log In form to logged-out visitors, and a Log Out link to those who are logged in.', 'buddypress' ),
+				'classname' => 'widget_bp_core_login_widget buddypress widget',
+			)
+		);
+	}
+
+	/**
+	 * Display the login widget.
+	 *
+	 * @see WP_Widget::widget() for description of parameters.
+	 *
+	 * @param array $args     Widget arguments.
+	 * @param array $instance Widget settings, as saved by the user.
+	 */
+	public function widget( $args, $instance ) {
+		$title = isset( $instance['title'] ) ? $instance['title'] : '';
+
+		/**
+		 * Filters the title of the Login widget.
+		 *
+		 * @since 1.9.0
+		 * @since 2.3.0 Added 'instance' and 'id_base' to arguments passed to filter.
+		 *
+		 * @param string $title    The widget title.
+		 * @param array  $instance The settings for the particular instance of the widget.
+		 * @param string $id_base  Root ID for all widgets of this type.
+		 */
+		$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
+
+		echo $args['before_widget'];
+
+		echo $args['before_title'] . esc_html( $title ) . $args['after_title']; ?>
+
+		<?php if ( is_user_logged_in() ) : ?>
+
+			<?php
+			/**
+			 * Fires before the display of widget content if logged in.
+			 *
+			 * @since 1.9.0
+			 */
+			do_action( 'bp_before_login_widget_loggedin' ); ?>
+
+			<div class="bp-login-widget-user-avatar">
+				<a href="<?php echo bp_loggedin_user_domain(); ?>">
+					<?php bp_loggedin_user_avatar( 'type=thumb&width=50&height=50' ); ?>
+				</a>
+			</div>
+
+			<div class="bp-login-widget-user-links">
+				<div class="bp-login-widget-user-link"><?php echo bp_core_get_userlink( bp_loggedin_user_id() ); ?></div>
+				<div class="bp-login-widget-user-logout"><a class="logout" href="<?php echo wp_logout_url( bp_get_requested_url() ); ?>"><?php _e( 'Log Out', 'buddypress' ); ?></a></div>
+			</div>
+
+			<?php
+
+			/**
+			 * Fires after the display of widget content if logged in.
+			 *
+			 * @since 1.9.0
+			 */
+			do_action( 'bp_after_login_widget_loggedin' ); ?>
+
+		<?php else : ?>
+
+			<?php
+
+			/**
+			 * Fires before the display of widget content if logged out.
+			 *
+			 * @since 1.9.0
+			 */
+			do_action( 'bp_before_login_widget_loggedout' ); ?>
+
+			<form name="bp-login-form" id="bp-login-widget-form" class="standard-form" action="<?php echo esc_url( site_url( 'wp-login.php', 'login_post' ) ); ?>" method="post">
+				<label for="bp-login-widget-user-login"><?php _e( 'Username', 'buddypress' ); ?></label>
+				<input type="text" name="log" id="bp-login-widget-user-login" class="input" value="" />
+
+				<label for="bp-login-widget-user-pass"><?php _e( 'Password', 'buddypress' ); ?></label>
+				<input type="password" name="pwd" id="bp-login-widget-user-pass" class="input" value="" <?php bp_form_field_attributes( 'password' ) ?> />
+
+				<div class="forgetmenot"><label for="bp-login-widget-rememberme"><input name="rememberme" type="checkbox" id="bp-login-widget-rememberme" value="forever" /> <?php _e( 'Remember Me', 'buddypress' ); ?></label></div>
+
+				<input type="submit" name="wp-submit" id="bp-login-widget-submit" value="<?php esc_attr_e( 'Log In', 'buddypress' ); ?>" />
+
+				<?php if ( bp_get_signup_allowed() ) : ?>
+
+					<span class="bp-login-widget-register-link"><?php printf( __( '<a href="%s" title="Register for a new account">Register</a>', 'buddypress' ), bp_get_signup_page() ); ?></span>
+
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires inside the display of the login widget form.
+				 *
+				 * @since 2.4.0
+				 */
+				do_action( 'bp_login_widget_form' ); ?>
+
+			</form>
+
+			<?php
+
+			/**
+			 * Fires after the display of widget content if logged out.
+			 *
+			 * @since 1.9.0
+			 */
+			do_action( 'bp_after_login_widget_loggedout' ); ?>
+
+		<?php endif;
+
+		echo $args['after_widget'];
+	}
+
+	/**
+	 * Update the login widget options.
+	 *
+	 * @param array $new_instance The new instance options.
+	 * @param array $old_instance The old instance options.
+	 *
+	 * @return array $instance The parsed options to be saved.
+	 */
+	public function update( $new_instance, $old_instance ) {
+		$instance             = $old_instance;
+		$instance['title']    = isset( $new_instance['title'] ) ? strip_tags( $new_instance['title'] ) : '';
+
+		return $instance;
+	}
+
+	/**
+	 * Output the login widget options form.
+	 *
+	 * @param array $instance Settings for this widget.
+	 *
+	 * @return void
+	 */
+	public function form( $instance = array() ) {
+
+		$settings = wp_parse_args( $instance, array(
+			'title' => '',
+		) ); ?>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:', 'buddypress' ); ?>
+			<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
+	}
+}
Index: /tags/2.4.3/src/bp-core/bp-core-wpabstraction.php
===================================================================
--- /tags/2.4.3/src/bp-core/bp-core-wpabstraction.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/bp-core-wpabstraction.php	(revision 10410)
@@ -0,0 +1,226 @@
+<?php
+/**
+ * WordPress Abstraction.
+ *
+ * @package BuddyPress
+ *
+ * The functions within this file will detect the version of WordPress you are
+ * running and will alter the environment so BuddyPress can run regardless.
+ *
+ * The code below mostly contains function mappings. This file is subject to
+ * change at any time.
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Parse the WordPress core version number into the major release.
+ *
+ * @since 1.5.2
+ *
+ * @global string $wp_version
+ *
+ * @return string $wp_version
+ */
+function bp_get_major_wp_version() {
+	global $wp_version;
+
+	return (float) $wp_version;
+}
+
+/**
+ * Only add MS-specific abstraction functions if WordPress is not in multisite mode.
+ */
+if ( !is_multisite() ) {
+	global $wpdb;
+
+	$wpdb->base_prefix = $wpdb->prefix;
+	$wpdb->blogid      = BP_ROOT_BLOG;
+
+	if ( !function_exists( 'get_blog_option' ) ) {
+		/**
+		 * @see get_blog_option()
+		 */
+		function get_blog_option( $blog_id, $option_name, $default = false ) {
+			return get_option( $option_name, $default );
+		}
+	}
+
+	if ( ! function_exists( 'add_blog_option' ) ) {
+		/**
+		 * @see add_blog_option()
+		 */
+		function add_blog_option( $blog_id, $option_name, $option_value ) {
+			return add_option( $option_name, $option_value );
+		}
+	}
+
+	if ( !function_exists( 'update_blog_option' ) ) {
+		/**
+		 * @see update_blog_option()
+		 */
+		function update_blog_option( $blog_id, $option_name, $value ) {
+			return update_option( $option_name, $value );
+		}
+	}
+
+	if ( !function_exists( 'delete_blog_option' ) ) {
+		/**
+		 * @see delete_blog_option()
+		 */
+		function delete_blog_option( $blog_id, $option_name ) {
+			return delete_option( $option_name );
+		}
+	}
+
+	if ( !function_exists( 'switch_to_blog' ) ) {
+		/**
+		 * @see switch_to_blog()
+		 */
+		function switch_to_blog( $new_blog, $deprecated = null ) {
+			return bp_get_root_blog_id();
+		}
+	}
+
+	if ( !function_exists( 'restore_current_blog' ) ) {
+		/**
+		 * @see restore_current_blog()
+		 */
+		function restore_current_blog() {
+			return bp_get_root_blog_id();
+		}
+	}
+
+	if ( !function_exists( 'get_blogs_of_user' ) ) {
+		/**
+		 * @see get_blogs_of_user()
+		 */
+		function get_blogs_of_user( $user_id, $all = false ) {
+			return false;
+		}
+	}
+
+	if ( !function_exists( 'update_blog_status' ) ) {
+		/**
+		 * @see update_blog_status()
+		 */
+		function update_blog_status( $blog_id, $pref, $value, $deprecated = null ) {
+			return true;
+		}
+	}
+
+	if ( !function_exists( 'is_subdomain_install' ) ) {
+		/**
+		 * @see is_subdomain_install()
+		 */
+		function is_subdomain_install() {
+			if ( ( defined( 'VHOST' ) && 'yes' == VHOST ) || ( defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL ) )
+				return true;
+
+			return false;
+		}
+	}
+}
+
+/**
+ * Get SQL chunk for filtering spam users from member queries.
+ *
+ * @internal
+ * @todo Why is this function defined in this file?
+ *
+ * @param string|bool $prefix Global table prefix.
+ *
+ * @return string SQL chunk.
+ */
+function bp_core_get_status_sql( $prefix = false ) {
+	if ( !is_multisite() )
+		return "{$prefix}user_status = 0";
+	else
+		return "{$prefix}spam = 0 AND {$prefix}deleted = 0 AND {$prefix}user_status = 0";
+}
+
+/**
+ * Multibyte encoding fallback functions.
+ *
+ * The PHP multibyte encoding extension is not enabled by default. In cases where it is not enabled,
+ * these functions provide a fallback.
+ *
+ * Borrowed from MediaWiki, under the GPLv2. Thanks!
+ */
+if ( !function_exists( 'mb_strlen' ) ) {
+	/**
+	 * Fallback implementation of mb_strlen(), hardcoded to UTF-8.
+	 *
+	 * @param string $str String to be measured.
+	 * @param string $enc Optional. Encoding type. Ignored.
+	 *
+	 * @return int String length.
+	 */
+	function mb_strlen( $str, $enc = '' ) {
+		$counts = count_chars( $str );
+		$total = 0;
+
+		// Count ASCII bytes
+		for( $i = 0; $i < 0x80; $i++ ) {
+			$total += $counts[$i];
+		}
+
+		// Count multibyte sequence heads
+		for( $i = 0xc0; $i < 0xff; $i++ ) {
+			$total += $counts[$i];
+		}
+		return $total;
+	}
+}
+
+if ( !function_exists( 'mb_strpos' ) ) {
+	/**
+	 * Fallback implementation of mb_strpos(), hardcoded to UTF-8.
+	 *
+	 * @param string $haystack String to search in.
+	 * @param string $needle String to search for.
+	 * @param int    $offset Optional. Start position for the search. Default: 0.
+	 * @param string $encoding Optional. Encoding type. Ignored.
+	 *
+	 * @return int|bool Position of needle in haystack if found, else false.
+	 */
+	function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
+		$needle = preg_quote( $needle, '/' );
+
+		$ar = array();
+		preg_match( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
+
+		if( isset( $ar[0][1] ) ) {
+			return $ar[0][1];
+		} else {
+			return false;
+		}
+	}
+}
+
+if ( !function_exists( 'mb_strrpos' ) ) {
+	/**
+	 * Fallback implementation of mb_strrpos(), hardcoded to UTF-8.
+	 *
+	 * @param string $haystack String to search in.
+	 * @param string $needle String to search for.
+	 * @param int    $offset Optional. Start position for the search. Default: 0.
+	 * @param string $encoding Optional. Encoding type. Ignored.
+	 *
+	 * @return int Position of last needle in haystack if found, else false.
+	 */
+	function mb_strrpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
+		$needle = preg_quote( $needle, '/' );
+
+		$ar = array();
+		preg_match_all( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
+
+		if( isset( $ar[0] ) && count( $ar[0] ) > 0 &&
+			isset( $ar[0][count( $ar[0] ) - 1][1] ) ) {
+			return $ar[0][count( $ar[0] ) - 1][1];
+		} else {
+			return false;
+		}
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-attachment-avatar.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-attachment-avatar.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-attachment-avatar.php	(revision 10410)
@@ -0,0 +1,445 @@
+<?php
+/**
+ * Core Avatars attachment class.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BP Attachment Avatar class.
+ *
+ * Extends BP Attachment to manage the avatar uploads.
+ *
+ * @since 2.3.0
+ */
+class BP_Attachment_Avatar extends BP_Attachment {
+
+	/**
+	 * Construct Upload parameters.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @see  BP_Attachment::__construct() for list of parameters
+	 * @uses bp_core_avatar_original_max_filesize()
+	 * @uses BP_Attachment::__construct()
+	 */
+	public function __construct() {
+		// Allowed avatar types
+		$allowed_types = bp_core_get_allowed_avatar_types();
+
+		parent::__construct( array(
+			'action'                => 'bp_avatar_upload',
+			'file_input'            => 'file',
+			'original_max_filesize' => bp_core_avatar_original_max_filesize(),
+
+			// Specific errors for avatars
+			'upload_error_strings'  => array(
+				9  => sprintf( __( 'That photo is too big. Please upload one smaller than %s', 'buddypress' ), size_format( bp_core_avatar_original_max_filesize() ) ),
+				10 => sprintf( _n( 'Please upload only this file type: %s.', 'Please upload only these file types: %s.', count( $allowed_types ), 'buddypress' ), self::get_avatar_types( $allowed_types ) ),
+			),
+		) );
+	}
+
+	/**
+	 * Gets the available avatar types.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param array $allowed_types Array of allowed avatar types.
+	 *
+	 * @return string comma separated list of allowed avatar types.
+	 */
+	public static function get_avatar_types( $allowed_types = array() ) {
+		$types = array_map( 'strtoupper', $allowed_types );
+		$comma = _x( ',', 'avatar types separator', 'buddypress' );
+		return join( $comma . ' ', $types );
+	}
+
+	/**
+	 * Set Upload Dir data for avatars.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @uses bp_core_avatar_upload_path()
+	 * @uses bp_core_avatar_url()
+	 * @uses bp_upload_dir()
+	 * @uses BP_Attachment::set_upload_dir()
+	 */
+	public function set_upload_dir() {
+		if ( bp_core_avatar_upload_path() && bp_core_avatar_url() ) {
+			$this->upload_path = bp_core_avatar_upload_path();
+			$this->url         = bp_core_avatar_url();
+			$this->upload_dir  = bp_upload_dir();
+		} else {
+			parent::set_upload_dir();
+		}
+	}
+
+	/**
+	 * Avatar specific rules.
+	 *
+	 * Adds an error if the avatar size or type don't match BuddyPress needs.
+	 * The error code is the index of $upload_error_strings.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @uses   bp_core_check_avatar_size()
+	 * @uses   bp_core_check_avatar_type()
+	 *
+	 * @param  array $file the temporary file attributes (before it has been moved).
+	 *
+	 * @return array the file with extra errors if needed.
+	 */
+	public function validate_upload( $file = array() ) {
+		// Bail if already an error
+		if ( ! empty( $file['error'] ) ) {
+			return $file;
+		}
+
+		// File size is too big
+		if ( ! bp_core_check_avatar_size( array( 'file' => $file ) ) ) {
+			$file['error'] = 9;
+
+		// File is of invalid type
+		} elseif ( ! bp_core_check_avatar_type( array( 'file' => $file ) ) ) {
+			$file['error'] = 10;
+		}
+
+		// Return with error code attached
+		return $file;
+	}
+
+	/**
+	 * Maybe shrink the attachment to fit maximum allowed width.
+	 *
+	 * @since 2.3.0
+	 * @since 2.4.0 Add the $ui_available_width parameter, to inform about the Avatar UI width.
+	 *
+	 * @uses  bp_core_avatar_original_max_width()
+	 *
+	 * @param string $file the absolute path to the file.
+	 *
+	 * @return mixed
+	 */
+	public static function shrink( $file = '', $ui_available_width = 0 ) {
+		// Get image size
+		$avatar_data = parent::get_image_data( $file );
+
+		// Init the edit args
+		$edit_args = array();
+
+		// Defaults to the Avatar original max width constant.
+		$original_max_width = bp_core_avatar_original_max_width();
+
+		// The ui_available_width is defined and it's smaller than the Avatar original max width
+		if ( ! empty( $ui_available_width ) && $ui_available_width < $original_max_width ) {
+			/**
+			 * In this case, to make sure the content of the image will be fully displayed
+			 * during the cropping step, let's use the Avatar UI Available width.
+			 */
+			$original_max_width = $ui_available_width;
+
+			// $original_max_width has to be larger than the avatar's full width
+			if ( $original_max_width < bp_core_avatar_full_width() ) {
+				$original_max_width = bp_core_avatar_full_width();
+			}
+		}
+
+		// Do we need to resize the image ?
+		if ( isset( $avatar_data['width'] ) && $avatar_data['width'] > $original_max_width ) {
+			$edit_args = array(
+				'max_w' => $original_max_width,
+				'max_h' => $original_max_width,
+			);
+		}
+
+		// Do we need to rotate the image ?
+		$angles = array(
+			3 => 180,
+			6 => -90,
+			8 =>  90,
+		);
+
+		if ( isset( $avatar_data['meta']['orientation'] ) && isset( $angles[ $avatar_data['meta']['orientation'] ] ) ) {
+			$edit_args['rotate'] = $angles[ $avatar_data['meta']['orientation'] ];
+		}
+
+		// No need to edit the avatar, original file will be used
+		if ( empty( $edit_args ) ) {
+			return false;
+
+		// Add the file to the edit arguments
+		} else {
+			$edit_args['file'] = $file;
+		}
+
+		return parent::edit_image( 'avatar', $edit_args );
+	}
+
+	/**
+	 * Check if the image dimensions are smaller than full avatar dimensions.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @uses  bp_core_avatar_full_width()
+	 * @uses  bp_core_avatar_full_height()
+	 *
+	 * @param string $file the absolute path to the file.
+	 *
+	 * @return boolean
+	 */
+	public static function is_too_small( $file = '' ) {
+		$uploaded_image = @getimagesize( $file );
+		$full_width     = bp_core_avatar_full_width();
+		$full_height    = bp_core_avatar_full_height();
+
+		if ( isset( $uploaded_image[0] ) && $uploaded_image[0] < $full_width || $uploaded_image[1] < $full_height ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Crop the avatar.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @see  BP_Attachment::crop for the list of parameters
+	 * @uses bp_core_fetch_avatar()
+	 * @uses bp_core_delete_existing_avatar()
+	 * @uses bp_core_avatar_full_width()
+	 * @uses bp_core_avatar_full_height()
+	 * @uses bp_core_avatar_dimension()
+	 * @uses BP_Attachment::crop
+	 *
+	 * @param array $args
+	 *
+	 * @return array The cropped avatars (full and thumb).
+	 */
+	public function crop( $args = array() ) {
+		// Bail if the original file is missing
+		if ( empty( $args['original_file'] ) ) {
+			return false;
+		}
+
+		/**
+		 * Original file is a relative path to the image
+		 * eg: /avatars/1/avatar.jpg
+		 */
+		$relative_path = $args['original_file'];
+		$absolute_path = $this->upload_path . $relative_path;
+
+		// Bail if the avatar is not available
+		if ( ! file_exists( $absolute_path ) )  {
+			return false;
+		}
+
+		if ( empty( $args['item_id'] ) ) {
+
+			/** This filter is documented in bp-core/bp-core-avatars.php */
+			$avatar_folder_dir = apply_filters( 'bp_core_avatar_folder_dir', dirname( $absolute_path ), $args['item_id'], $args['object'], $args['avatar_dir'] );
+		} else {
+
+			/** This filter is documented in bp-core/bp-core-avatars.php */
+			$avatar_folder_dir = apply_filters( 'bp_core_avatar_folder_dir', $this->upload_path . '/' . $args['avatar_dir'] . '/' . $args['item_id'], $args['item_id'], $args['object'], $args['avatar_dir'] );
+		}
+
+		// Bail if the avatar folder is missing for this item_id
+		if ( ! file_exists( $avatar_folder_dir ) ) {
+			return false;
+		}
+
+		// Delete the existing avatar files for the object
+		$existing_avatar = bp_core_fetch_avatar( array(
+			'object'  => $args['object'],
+			'item_id' => $args['item_id'],
+			'html' => false,
+		) );
+
+		/**
+		 * Check that the new avatar doesn't have the same name as the
+		 * old one before deleting
+		 */
+		if ( ! empty( $existing_avatar ) && $existing_avatar !== $this->url . $relative_path ) {
+			bp_core_delete_existing_avatar( array( 'object' => $args['object'], 'item_id' => $args['item_id'], 'avatar_path' => $avatar_folder_dir ) );
+		}
+
+		// Make sure we at least have minimal data for cropping
+		if ( empty( $args['crop_w'] ) ) {
+			$args['crop_w'] = bp_core_avatar_full_width();
+		}
+
+		if ( empty( $args['crop_h'] ) ) {
+			$args['crop_h'] = bp_core_avatar_full_height();
+		}
+
+		// Get the file extension
+		$data = @getimagesize( $absolute_path );
+		$ext  = $data['mime'] == 'image/png' ? 'png' : 'jpg';
+
+		$args['original_file'] = $absolute_path;
+		$args['src_abs']       = false;
+		$avatar_types = array( 'full' => '', 'thumb' => '' );
+
+		foreach ( $avatar_types as $key_type => $type ) {
+			if ( 'thumb' === $key_type ) {
+				$args['dst_w'] = bp_core_avatar_thumb_width();
+				$args['dst_h'] = bp_core_avatar_thumb_height();
+			} else {
+				$args['dst_w'] = bp_core_avatar_full_width();
+				$args['dst_h'] = bp_core_avatar_full_height();
+			}
+
+			$args['dst_file'] = $avatar_folder_dir . '/' . wp_hash( $absolute_path . time() ) . '-bp' . $key_type . '.' . $ext;
+
+			$avatar_types[ $key_type ] = parent::crop( $args );
+		}
+
+		// Remove the original
+		@unlink( $absolute_path );
+
+		// Return the full and thumb cropped avatars
+		return $avatar_types;
+	}
+
+	/**
+	 * Get the user id to set its avatar.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @return integer The user ID.
+	 */
+	private function get_user_id() {
+		$bp = buddypress();
+		$user_id = 0;
+
+		if ( bp_is_user() ) {
+			$user_id = bp_displayed_user_id();
+		}
+
+		if ( ! empty( $bp->members->admin->user_id ) ) {
+			$user_id = $bp->members->admin->user_id;
+		}
+
+		return $user_id;
+	}
+
+	/**
+	 * Get the group id to set its avatar.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @return integer The group ID.
+	 */
+	private function get_group_id() {
+		$group_id = 0;
+
+		if ( bp_is_group() ) {
+			$group_id = bp_get_current_group_id();
+		}
+
+		return $group_id;
+	}
+
+	/**
+	 * Build script datas for the Uploader UI.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @return array The javascript localization data.
+	 */
+	public function script_data() {
+		// Get default script data
+		$script_data = parent::script_data();
+
+		// Defaults to Avatar Backbone script
+		$js_scripts = array( 'bp-avatar' );
+
+		// Default object
+		$object = '';
+
+		// Get the possible item ids
+		$user_id  = $this->get_user_id();
+		$group_id = $this->get_group_id();
+
+		if ( ! empty( $user_id ) ) {
+			// Should we load the the Webcam Avatar javascript file
+			if ( bp_avatar_use_webcam() ) {
+				$js_scripts = array( 'bp-webcam' );
+			}
+
+			$script_data['bp_params'] = array(
+				'object'     => 'user',
+				'item_id'    => $user_id,
+				'has_avatar' => bp_get_user_has_avatar( $user_id ),
+				'nonces'  => array(
+					'set'    => wp_create_nonce( 'bp_avatar_cropstore' ),
+					'remove' => wp_create_nonce( 'bp_delete_avatar_link' ),
+				),
+			);
+
+			// Set feedback messages
+			$script_data['feedback_messages'] = array(
+				1 => __( 'There was a problem cropping your profile photo.', 'buddypress' ),
+				2 => __( 'Your new profile photo was uploaded successfully.', 'buddypress' ),
+				3 => __( 'There was a problem deleting your profile photo. Please try again.', 'buddypress' ),
+				4 => __( 'Your profile photo was deleted successfully!', 'buddypress' ),
+			);
+		} elseif ( ! empty( $group_id ) ) {
+			$script_data['bp_params'] = array(
+				'object'     => 'group',
+				'item_id'    => $group_id,
+				'has_avatar' => bp_get_group_has_avatar( $group_id ),
+				'nonces'     => array(
+					'set'    => wp_create_nonce( 'bp_avatar_cropstore' ),
+					'remove' => wp_create_nonce( 'bp_group_avatar_delete' ),
+				),
+			);
+
+			// Set feedback messages
+			$script_data['feedback_messages'] = array(
+				1 => __( 'There was a problem cropping the group profile photo.', 'buddypress' ),
+				2 => __( 'The group profile photo was uploaded successfully.', 'buddypress' ),
+				3 => __( 'There was a problem deleting the group profile photo. Please try again.', 'buddypress' ),
+				4 => __( 'The group profile photo was deleted successfully!', 'buddypress' ),
+			);
+		} else {
+
+			/**
+			 * Use this filter to include specific BuddyPress params for your object.
+			 * e.g. Blavatar.
+			 *
+			 * @since 2.3.0
+			 *
+			 * @param array $value The avatar specific BuddyPress parameters.
+			 */
+			$script_data['bp_params'] = apply_filters( 'bp_attachment_avatar_params', array() );
+		}
+
+		// Include the specific css
+		$script_data['extra_css'] = array( 'bp-avatar' );
+
+		// Include the specific css
+		$script_data['extra_js']  = $js_scripts;
+
+		// Set the object to contextualize the filter
+		if ( isset( $script_data['bp_params']['object'] ) ) {
+			$object = $script_data['bp_params']['object'];
+		}
+
+		/**
+		 * Use this filter to override/extend the avatar script data.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $script_data The avatar script data.
+		 * @param string $object      The object the avatar belongs to (eg: user or group).
+		 */
+		return apply_filters( 'bp_attachment_avatar_script_data', $script_data, $object );
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-attachment-cover-image.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-attachment-cover-image.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-attachment-cover-image.php	(revision 10410)
@@ -0,0 +1,284 @@
+<?php
+/**
+ * Core Cover Image attachment class.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BP Attachment Cover Image class.
+ *
+ * Extends BP Attachment to manage the cover images uploads.
+ *
+ * @since 2.4.0
+ */
+class BP_Attachment_Cover_Image extends BP_Attachment {
+	/**
+	 * The constuctor
+	 *
+	 * @since 2.4.0
+	 */
+	public function __construct() {
+		// Allowed cover image types & upload size
+		$allowed_types        = bp_attachments_get_allowed_types( 'cover_image' );
+		$max_upload_file_size = bp_attachments_get_max_upload_file_size( 'cover_image' );
+
+		parent::__construct( array(
+			'action'                => 'bp_cover_image_upload',
+			'file_input'            => 'file',
+			'original_max_filesize' => $max_upload_file_size,
+			'base_dir'              => bp_attachments_uploads_dir_get( 'dir' ),
+			'required_wp_files'     => array( 'file', 'image' ),
+
+			// Specific errors for cover images
+			'upload_error_strings'  => array(
+				11  => sprintf( __( 'That image is too big. Please upload one smaller than %s', 'buddypress' ), size_format( $max_upload_file_size ) ),
+				12  => sprintf( _n( 'Please upload only this file type: %s.', 'Please upload only these file types: %s.', count( $allowed_types ), 'buddypress' ), self::get_cover_image_types( $allowed_types ) ),
+			),
+		) );
+	}
+
+	/**
+	 * Gets the available cover image types.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param array $allowed_types Array of allowed cover image types.
+	 *
+	 * @return string comma separated list of allowed cover image types.
+	 */
+	public static function get_cover_image_types( $allowed_types = array() ) {
+		$types = array_map( 'strtoupper', $allowed_types );
+		$comma = _x( ',', 'cover image types separator', 'buddypress' );
+		return join( $comma . ' ', $types );
+	}
+
+	/**
+	 * Cover image specific rules.
+	 *
+	 * Adds an error if the cover image size or type don't match BuddyPress needs.
+	 * The error code is the index of $upload_error_strings.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param  array $file the temporary file attributes (before it has been moved).
+	 *
+	 * @return array the file with extra errors if needed.
+	 */
+	public function validate_upload( $file = array() ) {
+		// Bail if already an error
+		if ( ! empty( $file['error'] ) ) {
+			return $file;
+		}
+
+		// File size is too big
+		if ( $file['size'] > $this->original_max_filesize ) {
+			$file['error'] = 11;
+
+		// File is of invalid type
+		} elseif ( ! bp_attachments_check_filetype( $file['tmp_name'], $file['name'], bp_attachments_get_allowed_mimes( 'cover_image' ) ) ) {
+			$file['error'] = 12;
+		}
+
+		// Return with error code attached
+		return $file;
+	}
+
+	/**
+	 * Set the directory when uploading a file
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param  array $upload_dir The original Uploads dir.
+	 * @return array upload data (path, url, basedir...)
+	 */
+	public function upload_dir_filter( $upload_dir = array() ) {
+		// Default values are for profiles
+		$object_id = bp_displayed_user_id();
+
+		if ( empty( $object_id ) ) {
+			$object_id = bp_loggedin_user_id();
+		}
+
+		$object_directory = 'members';
+
+		// We're in a group, edit default values
+		if ( bp_is_group() || bp_is_group_create() ) {
+			$object_id        = bp_get_current_group_id();
+			$object_directory = 'groups';
+		}
+
+		// Set the subdir
+		$subdir  = '/' . $object_directory . '/' . $object_id . '/cover-image';
+
+		/**
+		 * Filters the cover image upload directory.
+		 *
+		 * @since 2.4.0
+		 *
+		 * @param array $value       Array containing the path, URL, and other helpful settings.
+		 * @param array $upload_dir The original Uploads dir.
+		 */
+		return apply_filters( 'bp_attachments_cover_image_upload_dir', array(
+			'path'    => $this->upload_path . $subdir,
+			'url'     => $this->url . $subdir,
+			'subdir'  => $subdir,
+			'basedir' => $this->upload_path,
+			'baseurl' => $this->url,
+			'error'   => false
+		), $upload_dir );
+	}
+
+	/**
+	 * Adjust the cover image to fit with advised width & height.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string $file the absolute path to the file.
+	 * @return mixed
+	 */
+	public function fit( $file = '', $dimensions = array() ) {
+		if ( empty( $dimensions['width'] ) || empty( $dimensions['height'] ) ) {
+			return false;
+		}
+
+		// Get image size
+		$cover_data = parent::get_image_data( $file );
+
+		// Init the edit args
+		$edit_args = array();
+
+		// Do we need to resize the image ?
+		if ( ( isset( $cover_data['width'] ) && $cover_data['width'] > $dimensions['width'] ) ||
+		( isset( $cover_data['height'] ) && $cover_data['height'] > $dimensions['height'] ) ) {
+			$edit_args = array(
+				'max_w' => $dimensions['width'],
+				'max_h' => $dimensions['height'],
+				'crop'  => true,
+			);
+		}
+
+		// Do we need to rotate the image ?
+		$angles = array(
+			3 => 180,
+			6 => -90,
+			8 =>  90,
+		);
+
+		if ( isset( $cover_data['meta']['orientation'] ) && isset( $angles[ $cover_data['meta']['orientation'] ] ) ) {
+			$edit_args['rotate'] = $angles[ $cover_data['meta']['orientation'] ];
+		}
+
+		// No need to edit the avatar, original file will be used
+		if ( empty( $edit_args ) ) {
+			return false;
+
+		// Add the file to the edit arguments
+		} else {
+			$edit_args = array_merge( $edit_args, array( 'file' => $file, 'save' => false ) );
+		}
+
+		// Get the editor so that we can use a specific save method
+		$editor = parent::edit_image( 'cover_image', $edit_args );
+
+		if ( is_wp_error( $editor ) )  {
+			return $editor;
+		} elseif ( ! is_a( $editor, 'WP_Image_Editor' ) ) {
+			return false;
+		}
+
+		// Save the new image file
+		return $editor->save( $this->generate_filename( $file ) );
+	}
+
+	/**
+	 * Generate a filename for the cover image
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param  string $file the absolute path to the file.
+	 * @return string       the absolute path to the new file name
+	 */
+	public function generate_filename( $file = '' ) {
+		if ( empty( $file ) || ! file_exists( $file ) ) {
+			return false;
+		}
+
+		$info    = pathinfo( $file );
+		$dir     = $info['dirname'];
+		$ext     = strtolower( $info['extension'] );
+		$name    = wp_hash( $file . time() ) . '-bp-cover-image';
+
+		return trailingslashit( $dir ) . "{$name}.{$ext}";
+	}
+
+	/**
+	 * Build script datas for the Uploader UI
+	 *
+	 * @since 2.4.0
+	 *
+	 * @return array the javascript localization data
+	 */
+	public function script_data() {
+		// Get default script data
+		$script_data = parent::script_data();
+
+		if ( bp_is_user() ) {
+			$item_id = bp_displayed_user_id();
+
+			$script_data['bp_params'] = array(
+				'object'          => 'user',
+				'item_id'         => $item_id,
+				'has_cover_image' => bp_attachments_get_user_has_cover_image( $item_id ),
+				'nonces'  => array(
+					'remove' => wp_create_nonce( 'bp_delete_cover_image' ),
+				),
+			);
+
+			// Set feedback messages
+			$script_data['feedback_messages'] = array(
+				1 => __( 'Your new cover image was uploaded successfully.', 'buddypress' ),
+				2 => __( 'There was a problem deleting your cover image. Please try again.', 'buddypress' ),
+				3 => __( 'Your cover image was deleted successfully!', 'buddypress' ),
+			);
+		} elseif ( bp_is_group() ) {
+			$item_id = bp_get_current_group_id();
+
+			$script_data['bp_params'] = array(
+				'object'          => 'group',
+				'item_id'         => bp_get_current_group_id(),
+				'has_cover_image' => bp_attachments_get_group_has_cover_image( $item_id ),
+				'nonces'  => array(
+					'remove' => wp_create_nonce( 'bp_delete_cover_image' ),
+				),
+			);
+
+			// Set feedback messages
+			$script_data['feedback_messages'] = array(
+				1 => __( 'The group cover image was uploaded successfully.', 'buddypress' ),
+				2 => __( 'There was a problem deleting the group cover image. Please try again.', 'buddypress' ),
+				3 => __( 'The group cover image was deleted successfully!', 'buddypress' ),
+			);
+		} else {
+			/**
+			 * Use this filter to include specific BuddyPress params for your object.
+			 * e.g. Cover image for blogs single item.
+			 *
+			 * @since 2.4.0
+			 *
+			 * @param array $value The cover image specific BuddyPress parameters.
+			 */
+			$script_data['bp_params'] = apply_filters( 'bp_attachment_cover_image_params', array() );
+		}
+
+		// Include our specific js & css
+		$script_data['extra_js']  = array( 'bp-cover-image' );
+		$script_data['extra_css'] = array( 'bp-avatar' );
+
+		return apply_filters( 'bp_attachments_cover_image_script_data', $script_data );
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-attachment.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-attachment.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-attachment.php	(revision 10410)
@@ -0,0 +1,674 @@
+<?php
+/**
+ * Core attachment class.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BP Attachment class.
+ *
+ * Extend it to manage your component's uploads.
+ *
+ * @since 2.3.0
+ */
+abstract class BP_Attachment {
+
+	/** Upload properties *****************************************************/
+
+	/**
+	 * The file being uploaded.
+	 *
+	 * @var array
+	 */
+	public $attachment = array();
+
+	/**
+	 * The default args to be merged with the
+	 * ones passed by the child class.
+	 *
+	 * @var array
+	 */
+	protected $default_args = array(
+		'original_max_filesize'  => 0,
+		'allowed_mime_types'     => array(),
+		'base_dir'               => '',
+		'action'                 => '',
+		'file_input'             => '',
+		'upload_error_strings'   => array(),
+		'required_wp_files'      => array( 'file' ),
+		'upload_dir_filter_args' => 0,
+	);
+
+	/**
+	 * Construct Upload parameters.
+	 *
+	 * @since 2.3.0
+	 * @since 2.4.0 Add the $upload_dir_filter_args argument to the $arguments array
+	 *
+	 * @param array|string $args {
+	 *     @type int    $original_max_filesize  Maximum file size in kilobytes. Defaults to php.ini settings.
+	 *     @type array  $allowed_mime_types     List of allowed file extensions (eg: array( 'jpg', 'gif', 'png' ) ).
+	 *                                          Defaults to WordPress allowed mime types.
+	 *     @type string $base_dir               Component's upload base directory. Defaults to WordPress 'uploads'.
+	 *     @type string $action                 The upload action used when uploading a file, $_POST['action'] must be set
+	 *                                          and its value must equal $action {@link wp_handle_upload()} (required).
+	 *     @type string $file_input             The name attribute used in the file input. (required).
+	 *     @type array  $upload_error_strings   A list of specific error messages (optional).
+	 *     @type array  $required_wp_files      The list of required WordPress core files. Default: array( 'file' ).
+	 *     @type int    $upload_dir_filter_args 1 to receive the original Upload dir array in the Upload dir filter, 0 otherwise.
+	 *                                          Defaults to 0 (optional).
+	 * }
+	 * @uses  sanitize_key()
+	 * @uses  wp_max_upload_size()
+	 * @uses  bp_parse_args()
+	 * @uses  BP_Attachment->set_upload_error_strings()
+	 * @uses  BP_Attachment->set_upload_dir()
+	 */
+	public function __construct( $args = '' ) {
+		// Upload action and the file input name are required parameters
+		if ( empty( $args['action'] ) || empty( $args['file_input'] ) ) {
+			return false;
+		}
+
+		// Sanitize the action ID and the file input name
+		$this->action     = sanitize_key( $args['action'] );
+		$this->file_input = sanitize_key( $args['file_input'] );
+
+		/**
+		 * Max file size defaults to php ini settings or, in the case of
+		 * a multisite config, the root site fileupload_maxk option
+		 */
+		$this->default_args['original_max_filesize'] = (int) wp_max_upload_size();
+
+		$params = bp_parse_args( $args, $this->default_args, $this->action . '_upload_params' );
+
+		foreach ( $params as $key => $param ) {
+			if ( 'upload_error_strings' === $key ) {
+				$this->{$key} = $this->set_upload_error_strings( $param );
+
+			// Sanitize the base dir
+			} elseif ( 'base_dir' === $key ) {
+				$this->{$key} = sanitize_title( $param );
+
+			// Sanitize the upload dir filter arg to pass
+			} elseif ( 'upload_dir_filter_args' === $key ) {
+				$this->{$key} = (int) $param;
+
+			// Action & File input are already set and sanitized
+			} elseif ( 'action' !== $key && 'file_input' !== $key ) {
+				$this->{$key} = $param;
+			}
+		}
+
+		// Set the path/url and base dir for uploads
+		$this->set_upload_dir();
+	}
+
+	/**
+	 * Set upload path and url for the component.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @uses bp_upload_dir()
+	 */
+	public function set_upload_dir() {
+		// Set the directory, path, & url variables
+		$this->upload_dir  = bp_upload_dir();
+
+		if ( empty( $this->upload_dir ) ) {
+			return false;
+		}
+
+		$this->upload_path = $this->upload_dir['basedir'];
+		$this->url         = $this->upload_dir['baseurl'];
+
+		// Ensure URL is https if SSL is set/forced
+		if ( is_ssl() ) {
+			$this->url = str_replace( 'http://', 'https://', $this->url );
+		}
+
+		/**
+		 * Custom base dir.
+		 *
+		 * If the component set this property, set the specific path, url and create the dir
+		 */
+		if ( ! empty( $this->base_dir ) ) {
+			$this->upload_path = trailingslashit( $this->upload_path ) . $this->base_dir;
+			$this->url         = trailingslashit( $this->url  ) . $this->base_dir;
+
+			// Finally create the base dir
+			$this->create_dir();
+		}
+	}
+
+	/**
+	 * Set Upload error messages.
+	 *
+	 * Used into the $overrides argument of BP_Attachment->upload()
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param array $param A list of error messages to add to BuddyPress core ones.
+	 *
+	 * @return array the list of upload errors
+	 */
+	public function set_upload_error_strings( $param = array() ) {
+		/**
+		 * Index of the array is the error code
+		 * Custom errors will start at 9 code
+		 */
+		$upload_errors = array(
+			0 => __( 'The file was uploaded successfully', 'buddypress' ),
+			1 => __( 'The uploaded file exceeds the maximum allowed file size for this site', 'buddypress' ),
+			2 => sprintf( __( 'The uploaded file exceeds the maximum allowed file size of: %s', 'buddypress' ), size_format( $this->original_max_filesize ) ),
+			3 => __( 'The uploaded file was only partially uploaded.', 'buddypress' ),
+			4 => __( 'No file was uploaded.', 'buddypress' ),
+			5 => '',
+			6 => __( 'Missing a temporary folder.', 'buddypress' ),
+			7 => __( 'Failed to write file to disk.', 'buddypress' ),
+			8 => __( 'File upload stopped by extension.', 'buddypress' ),
+		);
+
+		if ( ! array_intersect_key( $upload_errors, (array) $param ) ) {
+			foreach ( $param as $key_error => $error_message ) {
+				$upload_errors[ $key_error ] = $error_message;
+			}
+		}
+
+		return $upload_errors;
+	}
+
+	/**
+	 * Include the WordPress core needed files.
+	 *
+	 * @since 2.3.0
+	 */
+	public function includes() {
+		foreach ( array_unique( $this->required_wp_files ) as $wp_file ) {
+			if ( ! file_exists( ABSPATH . "/wp-admin/includes/{$wp_file}.php" ) ) {
+				continue;
+			}
+
+			require_once( ABSPATH . "/wp-admin/includes/{$wp_file}.php" );
+		}
+	}
+
+	/**
+	 * Upload the attachment.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param  array       $file              The appropriate entry the from $_FILES superglobal.
+	 * @param  string      $upload_dir_filter A specific filter to be applied to 'upload_dir' (optional).
+	 * @param  string|null $time              Optional. Time formatted in 'yyyy/mm'. Default null.
+	 *
+	 * @uses   wp_handle_upload()        To upload the file
+	 * @uses   add_filter()              To temporarly overrides WordPress uploads data
+	 * @uses   remove_filter()           To stop overriding WordPress uploads data
+	 * @uses   apply_filters()           Call 'bp_attachment_upload_overrides' to include specific upload overrides
+	 *
+	 * @return array                     On success, returns an associative array of file attributes.
+	 *                                   On failure, returns an array containing the error message
+	 *                                   (eg: array( 'error' => $message ) )
+	 */
+	public function upload( $file, $upload_dir_filter = '', $time = null ) {
+		/**
+		 * Upload action and the file input name are required parameters
+		 * @see BP_Attachment:__construct()
+		 */
+		if ( empty( $this->action ) || empty( $this->file_input ) ) {
+			return false;
+		}
+
+		/**
+		 * Add custom rules before enabling the file upload
+		 */
+		add_filter( "{$this->action}_prefilter", array( $this, 'validate_upload' ), 10, 1 );
+
+		/**
+		 * The above dynamic filter was introduced in WordPress 4.0, as we support WordPress
+		 * back to 3.6, we need to also use the pre 4.0 static filter and remove it after
+		 * the upload was processed.
+		 */
+		add_filter( 'wp_handle_upload_prefilter', array( $this, 'validate_upload' ), 10, 1 );
+
+		// Set Default overrides
+		$overrides = array(
+			'action'               => $this->action,
+			'upload_error_strings' => $this->upload_error_strings,
+		);
+
+		/**
+		 * Add a mime override if needed
+		 * Used to restrict uploads by extensions
+		 */
+		if ( ! empty( $this->allowed_mime_types ) ) {
+			$mime_types = $this->validate_mime_types();
+
+			if ( ! empty( $mime_types ) ) {
+				$overrides['mimes'] = $mime_types;
+			}
+		}
+
+		/**
+		 * If you need to add some overrides we haven't thought of.
+		 *
+		 * @param array $overrides The wp_handle_upload overrides
+		 */
+		$overrides = apply_filters( 'bp_attachment_upload_overrides', $overrides );
+
+		$this->includes();
+
+		/**
+		 * If the $base_dir was set when constructing the class,
+		 * and no specific filter has been requested, use a default
+		 * filter to create the specific $base dir
+		 * @see  BP_Attachment->upload_dir_filter()
+		 */
+		if ( empty( $upload_dir_filter ) && ! empty( $this->base_dir ) ) {
+			$upload_dir_filter = array( $this, 'upload_dir_filter' );
+		}
+
+		// Make sure the file will be uploaded in the attachment directory
+		if ( ! empty( $upload_dir_filter ) ) {
+			add_filter( 'upload_dir', $upload_dir_filter, 10, $this->upload_dir_filter_args );
+		}
+
+		// Upload the attachment
+		$this->attachment = wp_handle_upload( $file[ $this->file_input ], $overrides, $time );
+
+		// Restore WordPress Uploads data
+		if ( ! empty( $upload_dir_filter ) ) {
+			remove_filter( 'upload_dir', $upload_dir_filter, 10, $this->upload_dir_filter_args );
+		}
+
+		// Remove the pre WordPress 4.0 static filter
+		remove_filter( 'wp_handle_upload_prefilter', array( $this, 'validate_upload' ), 10, 1 );
+
+		// Finally return the uploaded file or the error
+		return $this->attachment;
+	}
+
+	/**
+	 * Validate the allowed mime types using WordPress allowed mime types.
+	 *
+	 * In case of a multisite, the mime types are already restricted by
+	 * the 'upload_filetypes' setting. BuddyPress will respect this setting.
+	 * @see check_upload_mimes()
+	 *
+	 * @since 2.3.0
+	 *
+	 * @uses get_allowed_mime_types()
+	 */
+	protected function validate_mime_types() {
+		$wp_mimes = get_allowed_mime_types();
+		$valid_mimes = array();
+
+		// Set the allowed mimes for the upload
+		foreach ( (array) $this->allowed_mime_types as $ext ) {
+			foreach ( $wp_mimes as $ext_pattern => $mime ) {
+				if ( $ext !== '' && strpos( $ext_pattern, $ext ) !== false ) {
+					$valid_mimes[$ext_pattern] = $mime;
+				}
+			}
+		}
+		return $valid_mimes;
+	}
+
+	/**
+	 * Specific upload rules.
+	 *
+	 * Override this function from your child class to build your specific rules
+	 * By default, if an original_max_filesize is provided, a check will be done
+	 * on the file size.
+	 *
+	 * @see BP_Attachment_Avatar->validate_upload() for an example of use
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param  array $file The temporary file attributes (before it has been moved).
+	 *
+	 * @return array The file.
+	 */
+	public function validate_upload( $file = array() ) {
+		// Bail if already an error
+		if ( ! empty( $file['error'] ) ) {
+			return $file;
+		}
+
+		if ( ! empty( $this->original_max_filesize ) && $file['size'] > $this->original_max_filesize ) {
+			$file['error'] = 2;
+		}
+
+		// Return the file
+		return $file;
+	}
+
+	/**
+	 * Default filter to save the attachments.
+	 *
+	 * @since 2.3.0
+	 * @since 2.4.0 Add the $upload_dir parameter to the method
+	 *
+	 * @uses apply_filters() call 'bp_attachment_upload_dir' to eventually override the upload location
+	 *       regarding to context
+	 *
+	 * @param  array $upload_dir The original Uploads dir.
+	 * @return array The upload directory data.
+	 */
+	public function upload_dir_filter( $upload_dir = array() ) {
+
+		/**
+		 * Filters the component's upload directory.
+		 *
+		 * @since 2.3.0
+		 * @since 2.4.0 Include the original Upload directory as the second parameter of the filter.
+		 *
+		 * @param array $value          Array containing the path, URL, and other helpful settings.
+		 * @param array $upload_dir     The original Uploads dir.
+		 */
+		return apply_filters( 'bp_attachment_upload_dir', array(
+			'path'    => $this->upload_path,
+			'url'     => $this->url,
+			'subdir'  => false,
+			'basedir' => $this->upload_path,
+			'baseurl' => $this->url,
+			'error'   => false
+		), $upload_dir );
+	}
+
+	/**
+	 * Create the custom base directory for the component uploads.
+	 *
+	 * Override this function in your child class to run specific actions.
+	 * (eg: add an .htaccess file)
+	 *
+	 * @since 2.3.0
+	 *
+	 * @uses wp_mkdir_p()
+	 */
+	public function create_dir() {
+		// Bail if no specific base dir is set
+		if ( empty( $this->base_dir ) ) {
+			return false;
+		}
+
+		// Check if upload path already exists
+		if ( ! is_dir( $this->upload_path ) ) {
+
+			// If path does not exist, attempt to create it
+			if ( ! wp_mkdir_p( $this->upload_path ) ) {
+				return false;
+			}
+		}
+
+		// Directory exists
+		return true;
+	}
+
+	/**
+	 * Crop an image file.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param array $args {
+	 *     @type string $original_file The source file (absolute path) for the Attachment.
+	 *     @type int    $crop_x        The start x position to crop from.
+	 *     @type int    $crop_y        The start y position to crop from.
+	 *     @type int    $crop_w        The width to crop.
+	 *     @type int    $crop_h        The height to crop.
+	 *     @type int    $dst_w         The destination width.
+	 *     @type int    $dst_h         The destination height.
+	 *     @type int    $src_abs       Optional. If the source crop points are absolute.
+	 *     @type string $dst_file      Optional. The destination file to write to.
+	 * }
+	 * @uses wp_crop_image()
+	 *
+	 * @return string|WP_Error New filepath on success, WP_Error on failure.
+	 */
+	public function crop( $args = array() ) {
+		$wp_error = new WP_Error();
+
+		$r = bp_parse_args( $args, array(
+			'original_file' => '',
+			'crop_x'        => 0,
+			'crop_y'        => 0,
+			'crop_w'        => 0,
+			'crop_h'        => 0,
+			'dst_w'         => 0,
+			'dst_h'         => 0,
+			'src_abs'       => false,
+			'dst_file'      => false,
+		), 'bp_attachment_crop_args' );
+
+		if ( empty( $r['original_file'] ) || ! file_exists( $r['original_file'] ) ) {
+			$wp_error->add( 'crop_error', __( 'Cropping the file failed: missing source file.', 'buddypress' ) );
+			return $wp_error;
+		}
+
+		// Check image file pathes
+		$path_error = __( 'Cropping the file failed: the file path is not allowed.', 'buddypress' );
+
+		// Make sure it's coming from an uploaded file
+		if ( false === strpos( $r['original_file'], $this->upload_path ) ) {
+			$wp_error->add( 'crop_error', $path_error );
+			return $wp_error;
+		}
+
+		/**
+		 * If no destination file is provided, WordPress will use a default name
+		 * and will write the file in the source file's folder.
+		 * If a destination file is provided, we need to make sure it's going into uploads
+		 */
+		if ( ! empty( $r['dst_file'] ) && false === strpos( $r['dst_file'], $this->upload_path ) ) {
+			$wp_error->add( 'crop_error', $path_error );
+			return $wp_error;
+		}
+
+		// Check image file types
+		$check_types = array( 'src_file' => array( 'file' => $r['original_file'], 'error' => _x( 'source file', 'Attachment source file', 'buddypress' ) ) );
+		if ( ! empty( $r['dst_file'] ) ) {
+			$check_types['dst_file'] = array( 'file' => $r['dst_file'], 'error' => _x( 'destination file', 'Attachment destination file', 'buddypress' ) );
+		}
+
+		/**
+		 * WordPress image supported types
+		 * @see wp_attachment_is()
+		 */
+		$supported_image_types = array(
+			'jpg'  => 1,
+			'jpeg' => 1,
+			'jpe'  => 1,
+			'gif'  => 1,
+			'png'  => 1,
+		);
+
+		foreach ( $check_types as $file ) {
+			$is_image      = wp_check_filetype( $file['file'] );
+			$ext           = $is_image['ext'];
+
+			if ( empty( $ext ) || empty( $supported_image_types[ $ext ] ) ) {
+				$wp_error->add( 'crop_error', sprintf( __( 'Cropping the file failed: %s is not a supported image file.', 'buddypress' ), $file['error'] ) );
+				return $wp_error;
+			}
+		}
+
+		// Add the image.php to the required WordPress files, if it's not already the case
+		$required_files = array_flip( $this->required_wp_files );
+		if ( ! isset( $required_files['image'] ) ) {
+			$this->required_wp_files[] = 'image';
+		}
+
+		// Load the files
+		$this->includes();
+
+		// Finally crop the image
+		return wp_crop_image( $r['original_file'], (int) $r['crop_x'], (int) $r['crop_y'], (int) $r['crop_w'], (int) $r['crop_h'], (int) $r['dst_w'], (int) $r['dst_h'], $r['src_abs'], $r['dst_file'] );
+	}
+
+	/**
+	 * Build script datas for the Uploader UI.
+	 *
+	 * Override this method from your child class to build the script datas.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @return array The javascript localization data.
+	 */
+	public function script_data() {
+		$script_data = array(
+			'action'            => $this->action,
+			'file_data_name'    => $this->file_input,
+			'max_file_size'     => $this->original_max_filesize,
+			'feedback_messages' => array(
+				1 => __( 'Sorry, uploading the file failed.', 'buddypress' ),
+				2 => __( 'File successfully uploaded.', 'buddypress' ),
+			),
+		);
+
+		return $script_data;
+	}
+
+	/**
+	 * Get full data for an image
+	 *
+	 * @since  2.4.0
+	 *
+	 * @param  string $file Absolute path to the uploaded image.
+	 * @return bool|array   An associate array containing the width, height and metadatas.
+	 *                      False in case an important image attribute is missing.
+	 */
+	public static function get_image_data( $file ) {
+		// Try to get image basic data
+		list( $width, $height, $sourceImageType ) = @getimagesize( $file );
+
+		// No need to carry on if we couldn't get image's basic data.
+		if ( is_null( $width ) || is_null( $height ) || is_null( $sourceImageType ) ) {
+			return false;
+		}
+
+		// Initialize the image data
+		$image_data = array(
+			'width'  => $width,
+			'height' => $height,
+		);
+
+		/**
+		 * Make sure the wp_read_image_metadata function is reachable for the old Avatar UI
+		 * or if WordPress < 3.9 (New Avatar UI is not available in this case)
+		 */
+		if ( ! function_exists( 'wp_read_image_metadata' ) ) {
+			require_once( ABSPATH . 'wp-admin/includes/image.php' );
+		}
+
+		// Now try to get image's meta data
+		$meta = wp_read_image_metadata( $file );
+
+		if ( ! empty( $meta ) ) {
+			// Before 4.0 the Orientation wasn't included
+			if ( ! isset( $meta['orientation'] ) &&
+				is_callable( 'exif_read_data' ) &&
+				in_array( $sourceImageType, apply_filters( 'wp_read_image_metadata_types', array( IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM ) ) )
+			) {
+				$exif = exif_read_data( $file );
+
+				if ( ! empty( $exif['Orientation'] ) ) {
+					$meta['orientation'] = $exif['Orientation'];
+				}
+			}
+
+			// Now add the metas to image data
+			$image_data['meta'] = $meta;
+		}
+
+		/**
+		 * Filter here to add/remove/edit data to the image full data
+		 *
+		 * @since  2.4.0
+		 *
+		 * @param  array $image_data An associate array containing the width, height and metadatas.
+		 */
+		return apply_filters( 'bp_attachments_get_image_data', $image_data );
+	}
+
+	/**
+	 * Edit an image file to resize it or rotate it
+	 *
+	 * @since  2.4.0
+	 *
+	 * @param  string $attachment_type The attachment type (eg: avatar or cover_image). Required.
+	 * @param  array  array $args {
+	 *     @type string $file     Absolute path to the image file (required).
+	 *     @type int    $max_w    Max width attribute for the editor's resize method (optional).
+	 *     @type int    $max_h    Max height attribute for the editor's resize method (optional).
+	 *     @type bool   $crop     Crop attribute for the editor's resize method (optional).
+	 *     @type float  $rotate   Angle for the editor's rotate method (optional).
+	 *     @type int    $quality  Compression quality on a 1-100% scale (optional).
+	 *     @type bool   $save     Whether to use the editor's save method or not (optional).
+	 * }
+	 *
+	 * @return string|WP_Image_Editor|WP_Error The edited image path or the WP_Image_Editor object in case of success,
+	 *                                         an WP_Error object otherwise.
+	 */
+	public static function edit_image( $attachment_type, $args = array() ) {
+		if ( empty( $attachment_type ) ) {
+			return new WP_Error( 'missing_parameter' );
+		}
+
+		$r = bp_parse_args( $args, array(
+			'file'   => '',
+			'max_w'   => 0,
+			'max_h'   => 0,
+			'crop'    => false,
+			'rotate'  => 0,
+			'quality' => 90,
+			'save'    => true,
+		), 'attachment_' . $attachment_type . '_edit_image' );
+
+		// Make sure we have to edit the image.
+		if ( empty( $r['max_w'] ) && empty( $r['max_h'] ) && empty( $r['rotate'] ) && empty( $r['file'] ) ) {
+			return new WP_Error( 'missing_parameter' );
+		}
+
+		// Get the image editor
+		$editor = wp_get_image_editor( $r['file'] );
+
+		if ( is_wp_error( $editor ) ) {
+			return $editor;
+		}
+
+		$editor->set_quality( $r['quality'] );
+
+		if ( ! empty( $r['rotate'] ) ) {
+			$rotated = $editor->rotate( $r['rotate'] );
+
+			// Stop in case of error
+			if ( is_wp_error( $rotated ) ) {
+				return $rotated;
+			}
+		}
+
+		if ( ! empty( $r['max_w'] ) || ! empty( $r['max_h'] ) ) {
+			$resized = $editor->resize( $r['max_w'], $r['max_h'], $r['crop'] );
+
+			// Stop in case of error
+			if ( is_wp_error( $resized ) ) {
+				return $resized;
+			}
+		}
+
+		// Use the editor save method to get a path to the edited image
+		if ( true === $r['save'] ) {
+			return $editor->save( $editor->generate_filename() );
+
+		// Need to do some other edit actions or use a specific method to save file
+		} else {
+			return $editor;
+		}
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-button.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-button.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-button.php	(revision 10410)
@@ -0,0 +1,270 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * API to create BuddyPress buttons.
+ *
+ * @since 1.2.6
+ *
+ * @param array $args {
+ *     Array of arguments.
+ *
+ *     @type string      $id                String describing the button type.
+ *     @type string      $component         The name of the component the button belongs to.
+ *                                          Default: 'core'.
+ *     @type bool        $must_be_logged_in Optional. Does the user need to be logged
+ *                                          in to see this button? Default: true.
+ *     @type bool        $block_self        Optional. True if the button should be hidden
+ *                                          when a user is viewing his own profile.
+ *                                          Default: true.
+ *     @type string|bool $wrapper           Optional. HTML element type that should wrap
+ *                                          the button: 'div', 'span', 'p', or 'li'.
+ *                                          False for no wrapper at all. Default: 'div'.
+ *     @type string      $wrapper_id        Optional. DOM ID of the button wrapper element.
+ *                                          Default: ''.
+ *     @type string      $wrapper_class     Optional. DOM class of the button wrapper
+ *                                          element. Default: ''.
+ *     @type string      $link_href         Optional. Destination link of the button.
+ *                                          Default: ''.
+ *     @type string      $link_class        Optional. DOM class of the button. Default: ''.
+ *     @type string      $link_id           Optional. DOM ID of the button. Default: ''.
+ *     @type string      $link_rel          Optional. DOM 'rel' attribute of the button.
+ *                                          Default: ''.
+ *     @type string      $link_title        Optional. Title attribute of the button.
+ *                                          Default: ''.
+ *     @type string      $link_text         Optional. Text to appear on the button.
+ *                                          Default: ''.
+ * }
+ */
+class BP_Button {
+
+	/** Button properties *****************************************************/
+
+	/**
+	 * The button ID.
+	 *
+	 * @var string
+	 */
+	public $id = '';
+
+	/**
+	 * The name of the component that the button belongs to.
+	 *
+	 * @var string
+	 */
+	public $component = 'core';
+
+	/**
+	 * Does the user need to be logged in to see this button?
+	 *
+	 * @var bool
+	 */
+	public $must_be_logged_in = true;
+
+	/**
+	 * Whether the button should be hidden when viewing your own profile.
+	 *
+	 * @var bool
+	 */
+	public $block_self = true;
+
+	/** Wrapper ***************************************************************/
+
+	/**
+	 * The type of DOM element to use for a wrapper.
+	 *
+	 * @var string|bool 'div', 'span', 'p', 'li', or false for no wrapper.
+	 */
+	public $wrapper = 'div';
+
+	/**
+	 * The DOM class of the button wrapper.
+	 *
+	 * @var string
+	 */
+	public $wrapper_class = '';
+
+	/**
+	 * The DOM ID of the button wrapper.
+	 *
+	 * @var string
+	 */
+	public $wrapper_id = '';
+
+	/** Button ****************************************************************/
+
+	/**
+	 * The destination link of the button.
+	 *
+	 * @var string
+	 */
+	public $link_href = '';
+
+	/**
+	 * The DOM class of the button link.
+	 *
+	 * @var string
+	 */
+	public $link_class = '';
+
+	/**
+	 * The DOM ID of the button link.
+	 *
+	 * @var string
+	 */
+	public $link_id = '';
+
+	/**
+	 * The DOM rel value of the button link.
+	 *
+	 * @var string
+	 */
+	public $link_rel = '';
+
+	/**
+	 * Title of the button link.
+	 *
+	 * @var string
+	 */
+	public $link_title = '';
+
+	/**
+	 * The contents of the button link.
+	 *
+	 * @var string
+	 */
+	public $link_text = '';
+
+	/** HTML result ***********************************************************/
+
+	public $contents = '';
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * Builds the button based on class parameters.
+	 *
+	 * @since 1.2.6
+	 *
+	 * @param array|string $args See {@BP_Button}.
+	 */
+	public function __construct( $args = '' ) {
+
+		$r = wp_parse_args( $args, get_class_vars( __CLASS__ ) );
+
+		// Required button properties
+		$this->id                = $r['id'];
+		$this->component         = $r['component'];
+		$this->must_be_logged_in = (bool) $r['must_be_logged_in'];
+		$this->block_self        = (bool) $r['block_self'];
+		$this->wrapper           = $r['wrapper'];
+
+		// $id and $component are required
+		if ( empty( $r['id'] ) || empty( $r['component'] ) )
+			return false;
+
+		// No button if component is not active
+		if ( ! bp_is_active( $this->component ) )
+			return false;
+
+		// No button for guests if must be logged in
+		if ( true == $this->must_be_logged_in && ! is_user_logged_in() )
+			return false;
+
+		// block_self
+		if ( true == $this->block_self ) {
+			// No button if you are the current user in a members loop
+			// This condition takes precedence, because members loops
+			// can be found on user profiles
+			if ( bp_get_member_user_id() ) {
+				if ( is_user_logged_in() && bp_loggedin_user_id() == bp_get_member_user_id() ) {
+					return false;
+				}
+
+			// No button if viewing your own profile (and not in
+			// a members loop)
+			} elseif ( bp_is_my_profile() ) {
+				return false;
+			}
+		}
+
+		// Wrapper properties
+		if ( false !== $this->wrapper ) {
+
+			// Wrapper ID
+			if ( !empty( $r['wrapper_id'] ) ) {
+				$this->wrapper_id    = ' id="' . $r['wrapper_id'] . '"';
+			}
+
+			// Wrapper class
+			if ( !empty( $r['wrapper_class'] ) ) {
+				$this->wrapper_class = ' class="generic-button ' . $r['wrapper_class'] . '"';
+			} else {
+				$this->wrapper_class = ' class="generic-button"';
+			}
+
+			// Set before and after
+			$before = '<' . $r['wrapper'] . $this->wrapper_class . $this->wrapper_id . '>';
+			$after  = '</' . $r['wrapper'] . '>';
+
+		// No wrapper
+		} else {
+			$before = $after = '';
+		}
+
+		// Link properties
+		if ( !empty( $r['link_id']    ) ) $this->link_id    = ' id="' .    $r['link_id']    . '"';
+		if ( !empty( $r['link_href']  ) ) $this->link_href  = ' href="' .  $r['link_href']  . '"';
+		if ( !empty( $r['link_title'] ) ) $this->link_title = ' title="' . $r['link_title'] . '"';
+		if ( !empty( $r['link_rel']   ) ) $this->link_rel   = ' rel="' .   $r['link_rel']   . '"';
+		if ( !empty( $r['link_class'] ) ) $this->link_class = ' class="' . $r['link_class'] . '"';
+		if ( !empty( $r['link_text']  ) ) $this->link_text  =              $r['link_text'];
+
+		// Build the button
+		$this->contents = $before . '<a'. $this->link_href . $this->link_title . $this->link_id . $this->link_rel . $this->link_class . '>' . $this->link_text . '</a>' . $after;
+
+		/**
+		 * Filters the button based on class parameters.
+		 *
+		 * This filter is a dynamic filter based on component and component ID and
+		 * allows button to be manipulated externally.
+		 *
+		 * @since 1.2.6
+		 *
+		 * @param string    $contents HTML being used for the button.
+		 * @param BP_Button $this     Current BP_Button instance.
+		 * @param string    $before   HTML appended before the actual button.
+		 * @param string    $after    HTML appended after the actual button.
+		 */
+		$this->contents = apply_filters( 'bp_button_' . $this->component . '_' . $this->id, $this->contents, $this, $before, $after );
+	}
+
+	/**
+	 * Return the markup for the generated button.
+	 *
+	 * @since 1.2.6
+	 *
+	 * @return string Button markup.
+	 */
+	public function contents() {
+		return $this->contents;
+	}
+
+	/**
+	 * Output the markup of button.
+	 *
+	 * @since 1.2.6
+	 */
+	public function display() {
+		if ( !empty( $this->contents ) )
+			echo $this->contents;
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-core-notification.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-core-notification.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-core-notification.php	(revision 10410)
@@ -0,0 +1,284 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BP_Core_Notification is deprecated.
+ *
+ * Use BP_Notifications_Notification instead.
+ *
+ * @package BuddyPress Core
+ * @deprecated since 1.9.0
+ */
+class BP_Core_Notification {
+
+	/**
+	 * The notification id.
+	 *
+	 * @var int
+	 */
+	public $id;
+
+	/**
+	 * The ID to which the notification relates to within the component.
+	 *
+	 * @var int
+	 */
+	public $item_id;
+
+	/**
+	 * The secondary ID to which the notification relates to within the component.
+	 *
+	 * @var int
+	 */
+	public $secondary_item_id = null;
+
+	/**
+	 * The user ID for who the notification is for.
+	 *
+	 * @var int
+	 */
+	public $user_id;
+
+	/**
+	 * The name of the component that the notification is for.
+	 *
+	 * @var string
+	 */
+	public $component_name;
+
+	/**
+	 * The action within the component which the notification is related to.
+	 *
+	 * @var string
+	 */
+	public $component_action;
+
+	/**
+	 * The date the notification was created.
+	 *
+	 * @var string
+	 */
+	public $date_notified;
+
+	/**
+	 * Is the notification new or has it already been read.
+	 *
+	 * @var boolean
+	 */
+	public $is_new;
+
+	/** Public Methods ********************************************************/
+
+	/**
+	 * Constructor
+	 *
+	 * @param int $id
+	 */
+	public function __construct( $id = 0 ) {
+		if ( !empty( $id ) ) {
+			$this->id = $id;
+			$this->populate();
+		}
+	}
+
+	/**
+	 * Update or insert notification details into the database.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @return bool Success or failure.
+	 */
+	public function save() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		// Update
+		if ( !empty( $this->id ) ) {
+			$sql = $wpdb->prepare( "UPDATE {$bp->core->table_name_notifications} SET item_id = %d, secondary_item_id = %d, user_id = %d, component_name = %s, component_action = %d, date_notified = %s, is_new = %d ) WHERE id = %d", $this->item_id, $this->secondary_item_id, $this->user_id, $this->component_name, $this->component_action, $this->date_notified, $this->is_new, $this->id );
+
+		// Save
+		} else {
+			$sql = $wpdb->prepare( "INSERT INTO {$bp->core->table_name_notifications} ( item_id, secondary_item_id, user_id, component_name, component_action, date_notified, is_new ) VALUES ( %d, %d, %d, %s, %s, %s, %d )", $this->item_id, $this->secondary_item_id, $this->user_id, $this->component_name, $this->component_action, $this->date_notified, $this->is_new );
+		}
+
+		if ( !$result = $wpdb->query( $sql ) )
+			return false;
+
+		$this->id = $wpdb->insert_id;
+
+		return true;
+	}
+
+	/** Private Methods *******************************************************/
+
+	/**
+	 * Fetches the notification data from the database.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 */
+	public function populate() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( $notification = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->core->table_name_notifications} WHERE id = %d", $this->id ) ) ) {
+			$this->item_id = $notification->item_id;
+			$this->secondary_item_id = $notification->secondary_item_id;
+			$this->user_id           = $notification->user_id;
+			$this->component_name    = $notification->component_name;
+			$this->component_action  = $notification->component_action;
+			$this->date_notified     = $notification->date_notified;
+			$this->is_new            = $notification->is_new;
+		}
+	}
+
+	/** Static Methods ********************************************************/
+
+	/**
+	 * Check the access for a user.
+	 *
+	 * @param int $user_id         ID to check access for.
+	 * @param int $notification_id Notification ID to check for.
+	 *
+	 * @return string
+	 */
+	public static function check_access( $user_id, $notification_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->core->table_name_notifications} WHERE id = %d AND user_id = %d", $notification_id, $user_id ) );
+	}
+
+	/**
+	 * Fetches all the notifications in the database for a specific user.
+	 *
+	 * @global wpdb $wpdb WordPress database object
+	 *
+	 * @param int    $user_id User ID.
+	 * @param string $status 'is_new' or 'all'.
+	 *
+	 * @return array Associative array
+	 * @static
+	 */
+	public static function get_all_for_user( $user_id, $status = 'is_new' ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$is_new = ( 'is_new' === $status )
+			? ' AND is_new = 1 '
+			: '';
+
+ 		return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$bp->core->table_name_notifications} WHERE user_id = %d {$is_new}", $user_id ) );
+	}
+
+	/**
+	 * Delete all the notifications for a user based on the component name and action.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param int    $user_id
+	 * @param string $component_name
+	 * @param string $component_action
+	 *
+	 * @static
+	 *
+	 * @return mixed
+	 */
+	public static function delete_for_user_by_type( $user_id, $component_name, $component_action ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE user_id = %d AND component_name = %s AND component_action = %s", $user_id, $component_name, $component_action ) );
+	}
+
+	/**
+	 * Delete all the notifications that have a specific item id, component name and action.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param int      $user_id           The ID of the user who the notifications are for.
+	 * @param int      $item_id           The item ID of the notifications we wish to delete.
+	 * @param string   $component_name    The name of the component that the notifications we wish to delete.
+	 * @param string   $component_action  The action of the component that the notifications we wish to delete.
+	 * @param int|bool $secondary_item_id (optional) The secondary item id of the notifications that we wish to
+	 *                                    use to delete.
+	 * @static
+	 *
+	 * @return mixed
+	 */
+	public static function delete_for_user_by_item_id( $user_id, $item_id, $component_name, $component_action, $secondary_item_id = false ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$secondary_item_sql = !empty( $secondary_item_id )
+			? $wpdb->prepare( " AND secondary_item_id = %d", $secondary_item_id )
+			: '';
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE user_id = %d AND item_id = %d AND component_name = %s AND component_action = %s{$secondary_item_sql}", $user_id, $item_id, $component_name, $component_action ) );
+	}
+
+	/**
+	 * Deletes all the notifications sent by a specific user, by component and action.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param int    $user_id          The ID of the user whose sent notifications we wish to delete.
+	 * @param string $component_name   The name of the component the notification was sent from.
+	 * @param string $component_action The action of the component the notification was sent from.
+	 * @static
+	 *
+	 * @return mixed
+	 */
+	public static function delete_from_user_by_type( $user_id, $component_name, $component_action ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE item_id = %d AND component_name = %s AND component_action = %s", $user_id, $component_name, $component_action ) );
+	}
+
+	/**
+	 * Deletes all the notifications for all users by item id, and optional secondary item id,
+	 * and component name and action.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param string $item_id           The item id that they notifications are to be for.
+	 * @param string $component_name    The component that the notifications are to be from.
+	 * @param string $component_action  The action that the notifications are to be from.
+	 * @param string $secondary_item_id Optional secondary item id that the notifications are to have.
+	 * @static
+	 *
+	 * @return mixed
+	 */
+	public static function delete_all_by_type( $item_id, $component_name, $component_action, $secondary_item_id ) {
+		global $wpdb;
+
+		if ( $component_action )
+			$component_action_sql = $wpdb->prepare( "AND component_action = %s", $component_action );
+		else
+			$component_action_sql = '';
+
+		if ( $secondary_item_id )
+			$secondary_item_sql = $wpdb->prepare( "AND secondary_item_id = %d", $secondary_item_id );
+		else
+			$secondary_item_sql = '';
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->core->table_name_notifications} WHERE item_id = %d AND component_name = %s {$component_action_sql} {$secondary_item_sql}", $item_id, $component_name ) );
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-core-user.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-core-user.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-core-user.php	(revision 10410)
@@ -0,0 +1,966 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Fetch data about a BuddyPress user.
+ *
+ * BP_Core_User class can be used by any component. It will fetch useful
+ * details for any user when provided with a user_id.
+ *
+ * Example:
+ *    $user = new BP_Core_User( $user_id );
+ *    $user_avatar = $user->avatar;
+ *	  $user_email = $user->email;
+ *    $user_status = $user->status;
+ *    etc.
+ */
+class BP_Core_User {
+
+	/**
+	 * ID of the user which the object relates to.
+	 *
+	 * @var integer
+	 */
+	public $id;
+
+	/**
+	 * The URL to the full size of the avatar for the user.
+	 *
+	 * @var string
+	 */
+	public $avatar;
+
+	/**
+	 * The URL to the thumb size of the avatar for the user.
+	 *
+	 * @var string
+	 */
+	public $avatar_thumb;
+
+	/**
+	 * The URL to the mini size of the avatar for the user.
+	 *
+	 * @var string
+	 */
+	public $avatar_mini;
+
+	/**
+	 * The full name of the user.
+	 *
+	 * @var string
+	 */
+	public $fullname;
+
+	/**
+	 * The email for the user.
+	 *
+	 * @var string
+	 */
+	public $email;
+
+	/**
+	 * The absolute url for the user's profile.
+	 *
+	 * @var string
+	 */
+	public $user_url;
+
+	/**
+	 * The HTML for the user link, with the link text being the user's full name.
+	 *
+	 * @var string
+	 */
+	public $user_link;
+
+	/**
+	 * Contains a formatted string when the last time the user was active.
+	 *
+	 * Example: "active 2 hours and 50 minutes ago"
+	 *
+	 * @var string
+	 */
+	public $last_active;
+
+	/* Extras */
+
+	/**
+	 * The total number of "Friends" the user has on site.
+	 *
+	 * @var integer
+	 */
+	public $total_friends;
+
+	/**
+	 * The total number of blog posts posted by the user.
+	 *
+	 * @var integer
+	 * @deprecated No longer used
+	 */
+	public $total_blogs;
+
+	/**
+	 * The total number of groups the user is a part of.
+	 *
+	 * Example: "1 group", "2 groups"
+	 *
+	 * @var string
+	 */
+	public $total_groups;
+
+	/**
+	 * Profile information for the specific user.
+	 *
+	 * @since 1.2.0
+	 * @var array
+	 */
+	public $profile_data;
+
+	/** Public Methods *******************************************************/
+
+	/**
+	 * Class constructor.
+	 *
+	 * @param integer $user_id         The ID for the user being queried.
+	 * @param bool    $populate_extras Whether to fetch extra information such as
+	 *                                 group/friendship counts or not. Default: false.
+	 */
+	public function __construct( $user_id, $populate_extras = false ) {
+		if ( !empty( $user_id ) ) {
+			$this->id = $user_id;
+			$this->populate();
+
+			if ( !empty( $populate_extras ) ) {
+				$this->populate_extras();
+			}
+		}
+	}
+
+	/**
+	 * Populate the instantiated class with data based on the User ID provided.
+	 *
+	 * @uses bp_core_get_userurl() Returns the URL with no HTML markup for
+	 *       a user based on their user id.
+	 * @uses bp_core_get_userlink() Returns a HTML formatted link for a
+	 *       user with the user's full name as the link text.
+	 * @uses bp_core_get_user_email() Returns the email address for the
+	 *       user based on user ID.
+	 * @uses bp_get_user_meta() BP function returns the value of passed
+	 *       usermeta name from usermeta table.
+	 * @uses bp_core_fetch_avatar() Returns HTML formatted avatar for a user
+	 * @uses bp_profile_last_updated_date() Returns the last updated date
+	 *       for a user.
+	 */
+	public function populate() {
+
+		if ( bp_is_active( 'xprofile' ) )
+			$this->profile_data = $this->get_profile_data();
+
+		if ( !empty( $this->profile_data ) ) {
+			$full_name_field_name = bp_xprofile_fullname_field_name();
+
+			$this->user_url  = bp_core_get_user_domain( $this->id, $this->profile_data['user_nicename'], $this->profile_data['user_login'] );
+			$this->fullname  = esc_attr( $this->profile_data[$full_name_field_name]['field_data'] );
+			$this->user_link = "<a href='{$this->user_url}' title='{$this->fullname}'>{$this->fullname}</a>";
+			$this->email     = esc_attr( $this->profile_data['user_email'] );
+		} else {
+			$this->user_url  = bp_core_get_user_domain( $this->id );
+			$this->user_link = bp_core_get_userlink( $this->id );
+			$this->fullname  = esc_attr( bp_core_get_user_displayname( $this->id ) );
+			$this->email     = esc_attr( bp_core_get_user_email( $this->id ) );
+		}
+
+		// Cache a few things that are fetched often
+		wp_cache_set( 'bp_user_fullname_' . $this->id, $this->fullname, 'bp' );
+		wp_cache_set( 'bp_user_email_' . $this->id, $this->email, 'bp' );
+		wp_cache_set( 'bp_user_url_' . $this->id, $this->user_url, 'bp' );
+
+		$this->avatar       = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'full', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->fullname ) ) );
+		$this->avatar_thumb = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->fullname ) ) );
+		$this->avatar_mini  = bp_core_fetch_avatar( array( 'item_id' => $this->id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->fullname ), 'width' => 30, 'height' => 30 ) );
+		$this->last_active  = bp_core_get_last_activity( bp_get_user_last_activity( $this->id ), __( 'active %s', 'buddypress' ) );
+	}
+
+	/**
+	 * Populates extra fields such as group and friendship counts.
+	 */
+	public function populate_extras() {
+
+		if ( bp_is_active( 'friends' ) ) {
+			$this->total_friends = BP_Friends_Friendship::total_friend_count( $this->id );
+		}
+
+		if ( bp_is_active( 'groups' ) ) {
+			$this->total_groups = BP_Groups_Member::total_group_count( $this->id );
+			$this->total_groups = sprintf( _n( '%d group', '%d groups', $this->total_groups, 'buddypress' ), $this->total_groups );
+		}
+	}
+
+	/**
+	 * Fetch xprofile data for the current user.
+	 *
+	 * @see BP_XProfile_ProfileData::get_all_for_user() for description of
+	 *      return value.
+	 *
+	 * @return array See {@link BP_XProfile_Profile_Data::get_all_for_user()}.
+	 */
+	public function get_profile_data() {
+		return BP_XProfile_ProfileData::get_all_for_user( $this->id );
+	}
+
+	/** Static Methods ********************************************************/
+
+	/**
+	 * Get a list of users that match the query parameters.
+	 *
+	 * Since BuddyPress 1.7, use {@link BP_User_Query} instead.
+	 *
+	 * @deprecated 1.7.0 Use {@link BP_User_Query}.
+	 *
+	 * @see BP_User_Query for a description of parameters, most of which
+	 *      are used there in the same way.
+	 *
+	 * @param string      $type            See {@link BP_User_Query}.
+	 * @param int         $limit           See {@link BP_User_Query}. Default: 0.
+	 * @param int         $page            See {@link BP_User_Query}. Default: 1.
+	 * @param int         $user_id         See {@link BP_User_Query}. Default: 0.
+	 * @param mixed       $include         See {@link BP_User_Query}. Default: false.
+	 * @param string|bool $search_terms    See {@link BP_User_Query}.
+	 *                                     Default: false.
+	 * @param bool        $populate_extras See {@link BP_User_Query}.
+	 *                                     Default: true.
+	 * @param mixed       $exclude         See {@link BP_User_Query}. Default: false.
+	 * @param string|bool $meta_key        See {@link BP_User_Query}.
+	 *                                     Default: false.
+	 * @param string|bool $meta_value      See {@link BP_User_Query}.
+	 *                                     Default: false.
+	 * @return array {
+	 *     @type int   $total_users Total number of users matched by query
+	 *                              params.
+	 *     @type array $paged_users The current page of users matched by
+	 *                              query params.
+	 * }
+	 */
+	public static function get_users( $type, $limit = 0, $page = 1, $user_id = 0, $include = false, $search_terms = false, $populate_extras = true, $exclude = false, $meta_key = false, $meta_value = false ) {
+		global $wpdb;
+
+		_deprecated_function( __METHOD__, '1.7', 'BP_User_Query' );
+
+		$bp = buddypress();
+
+		$sql = array();
+
+		$sql['select_main'] = "SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.display_name, u.user_email";
+
+		if ( 'active' == $type || 'online' == $type || 'newest' == $type  ) {
+			$sql['select_active'] = ", um.meta_value as last_activity";
+		}
+
+		if ( 'popular' == $type ) {
+			$sql['select_popular'] = ", um.meta_value as total_friend_count";
+		}
+
+		if ( 'alphabetical' == $type ) {
+			$sql['select_alpha'] = ", pd.value as fullname";
+		}
+
+		if ( $meta_key ) {
+			$sql['select_meta'] = ", umm.meta_key";
+
+			if ( $meta_value ) {
+				$sql['select_meta'] .= ", umm.meta_value";
+			}
+		}
+
+		$sql['from'] = "FROM {$wpdb->users} u LEFT JOIN {$wpdb->usermeta} um ON um.user_id = u.ID";
+
+		// We search against xprofile fields, so we must join the table
+		if ( $search_terms && bp_is_active( 'xprofile' ) ) {
+			$sql['join_profiledata_search'] = "LEFT JOIN {$bp->profile->table_name_data} spd ON u.ID = spd.user_id";
+		}
+
+		// Alphabetical sorting is done by the xprofile Full Name field
+		if ( 'alphabetical' == $type ) {
+			$sql['join_profiledata_alpha'] = "LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id";
+		}
+
+		if ( $meta_key ) {
+			$sql['join_meta'] = "LEFT JOIN {$wpdb->usermeta} umm ON umm.user_id = u.ID";
+		}
+
+		$sql['where'] = 'WHERE ' . bp_core_get_status_sql( 'u.' );
+
+		if ( 'active' == $type || 'online' == $type || 'newest' == $type ) {
+			$sql['where_active'] = $wpdb->prepare( "AND um.meta_key = %s", bp_get_user_meta_key( 'last_activity' ) );
+		}
+
+		if ( 'popular' == $type ) {
+			$sql['where_popular'] = $wpdb->prepare( "AND um.meta_key = %s", bp_get_user_meta_key( 'total_friend_count' ) );
+		}
+
+		if ( 'online' == $type ) {
+			$sql['where_online'] = "AND DATE_ADD( um.meta_value, INTERVAL 5 MINUTE ) >= UTC_TIMESTAMP()";
+		}
+
+		if ( 'alphabetical' == $type ) {
+			$sql['where_alpha'] = "AND pd.field_id = 1";
+		}
+
+		if ( !empty( $exclude ) ) {
+			$exclude              = implode( ',', wp_parse_id_list( $exclude ) );
+			$sql['where_exclude'] = "AND u.ID NOT IN ({$exclude})";
+		}
+
+		// Passing an $include value of 0 or '0' will necessarily result in an empty set
+		// returned. The default value of false will hit the 'else' clause.
+		if ( 0 === $include || '0' === $include ) {
+			$sql['where_users'] = "AND 0 = 1";
+		} else {
+			if ( !empty( $include ) ) {
+				$include = implode( ',',  wp_parse_id_list( $include ) );
+				$sql['where_users'] = "AND u.ID IN ({$include})";
+			} elseif ( !empty( $user_id ) && bp_is_active( 'friends' ) ) {
+				$friend_ids = friends_get_friend_user_ids( $user_id );
+
+				if ( !empty( $friend_ids ) ) {
+					$friend_ids = implode( ',', wp_parse_id_list( $friend_ids ) );
+					$sql['where_friends'] = "AND u.ID IN ({$friend_ids})";
+
+				// User has no friends, return false since there will be no users to fetch.
+				} else {
+					return false;
+				}
+			}
+		}
+
+		if ( !empty( $search_terms ) && bp_is_active( 'xprofile' ) ) {
+			$search_terms_like        = '%' . bp_esc_like( $search_terms ) . '%';
+			$sql['where_searchterms'] = $wpdb->prepare( "AND spd.value LIKE %s", $search_terms_like );
+		}
+
+		if ( !empty( $meta_key ) ) {
+			$sql['where_meta'] = $wpdb->prepare( " AND umm.meta_key = %s", $meta_key );
+
+			// If a meta value is provided, match it
+			if ( $meta_value ) {
+				$sql['where_meta'] .= $wpdb->prepare( " AND umm.meta_value = %s", $meta_value );
+			}
+		}
+
+		switch ( $type ) {
+			case 'active': case 'online': default:
+				$sql[] = "ORDER BY um.meta_value DESC";
+				break;
+			case 'newest':
+				$sql[] = "ORDER BY u.ID DESC";
+				break;
+			case 'alphabetical':
+				$sql[] = "ORDER BY pd.value ASC";
+				break;
+			case 'random':
+				$sql[] = "ORDER BY rand()";
+				break;
+			case 'popular':
+				$sql[] = "ORDER BY CONVERT(um.meta_value, SIGNED) DESC";
+				break;
+		}
+
+		if ( !empty( $limit ) && !empty( $page ) ) {
+			$sql['pagination'] = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+		}
+
+		/**
+		 * Filters the SQL used to query for paged users.
+		 *
+		 * @since 1.2.6
+		 *
+		 * @param string $value Concatenated SQL statement for the query.
+		 * @param array  $sql   Array of SQL statement parts for the query.
+		 */
+		$paged_users_sql = apply_filters( 'bp_core_get_paged_users_sql', join( ' ', (array) $sql ), $sql );
+		$paged_users     = $wpdb->get_results( $paged_users_sql );
+
+		// Re-jig the SQL so we can get the total user count
+		unset( $sql['select_main'] );
+
+		if ( !empty( $sql['select_active'] ) ) {
+			unset( $sql['select_active'] );
+		}
+
+		if ( !empty( $sql['select_popular'] ) ) {
+			unset( $sql['select_popular'] );
+		}
+
+		if ( !empty( $sql['select_alpha'] ) ) {
+			unset( $sql['select_alpha'] );
+		}
+
+		if ( !empty( $sql['pagination'] ) ) {
+			unset( $sql['pagination'] );
+		}
+
+		array_unshift( $sql, "SELECT COUNT(u.ID)" );
+
+		/**
+		 * Filters the SQL used to query for total users.
+		 *
+		 * @since 1.2.6
+		 *
+		 * @param string $value Concatenated SQL statement for the query.
+		 * @param array  $sql   Array of SQL statement parts for the query.
+		 */
+		$total_users_sql = apply_filters( 'bp_core_get_total_users_sql', join( ' ', (array) $sql ), $sql );
+		$total_users     = $wpdb->get_var( $total_users_sql );
+
+		/***
+		 * Lets fetch some other useful data in a separate queries, this will be faster than querying the data for every user in a list.
+		 * We can't add these to the main query above since only users who have this information will be returned (since the much of the data is in usermeta and won't support any type of directional join)
+		 */
+		if ( !empty( $populate_extras ) ) {
+			$user_ids = array();
+
+			foreach ( (array) $paged_users as $user ) {
+				$user_ids[] = $user->id;
+			}
+
+			// Add additional data to the returned results
+			$paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids, $type );
+		}
+
+		return array( 'users' => $paged_users, 'total' => $total_users );
+	}
+
+
+	/**
+	 * Fetch the details for all users whose usernames start with the given letter.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param string $letter          The letter the users names are to start with.
+	 * @param int    $limit           The number of users we wish to retrive.
+	 * @param int    $page            The page number we are currently on, used in conjunction
+	 *                                with $limit to get the start position for the limit.
+	 * @param bool   $populate_extras Populate extra user fields?
+	 * @param string $exclude         Comma-separated IDs of users whose results
+	 *                                aren't to be fetched.
+	 *
+	 * @return mixed False on error, otherwise associative array of results.
+	 */
+	public static function get_users_by_letter( $letter, $limit = null, $page = 1, $populate_extras = true, $exclude = '' ) {
+		global $wpdb;
+
+		$pag_sql = '';
+		if ( $limit && $page ) {
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+		}
+
+		// Multibyte compliance
+		if ( function_exists( 'mb_strlen' ) ) {
+			if ( mb_strlen( $letter, 'UTF-8' ) > 1 || is_numeric( $letter ) || !$letter ) {
+				return false;
+			}
+		} else {
+			if ( strlen( $letter ) > 1 || is_numeric( $letter ) || !$letter ) {
+				return false;
+			}
+		}
+
+		$bp = buddypress();
+
+		$letter_like = bp_esc_like( $letter ) . '%';
+		$status_sql  = bp_core_get_status_sql( 'u.' );
+
+		if ( !empty( $exclude ) ) {
+			$exclude     = implode( ',', wp_parse_id_list( $exclude ) );
+			$exclude_sql = " AND u.id NOT IN ({$exclude})";
+		} else {
+			$exclude_sql = '';
+		}
+
+		/**
+		 * Filters the SQL used to query for total user count by first letter.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value SQL prepared statement for the user count query.
+		 */
+		$total_users_sql = apply_filters( 'bp_core_users_by_letter_count_sql', $wpdb->prepare( "SELECT COUNT(DISTINCT u.ID) FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id LEFT JOIN {$bp->profile->table_name_fields} pf ON pd.field_id = pf.id WHERE {$status_sql} AND pf.name = %s {$exclude_sql} AND pd.value LIKE %s ORDER BY pd.value ASC", bp_xprofile_fullname_field_name(), $letter_like ) );
+
+		/**
+		 * Filters the SQL used to query for users by first letter.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value SQL prepared statement for the user query.
+		 */
+		$paged_users_sql = apply_filters( 'bp_core_users_by_letter_sql',       $wpdb->prepare( "SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.user_email FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id LEFT JOIN {$bp->profile->table_name_fields} pf ON pd.field_id = pf.id WHERE {$status_sql} AND pf.name = %s {$exclude_sql} AND pd.value LIKE %s ORDER BY pd.value ASC{$pag_sql}", bp_xprofile_fullname_field_name(), $letter_like ) );
+
+		$total_users = $wpdb->get_var( $total_users_sql );
+		$paged_users = $wpdb->get_results( $paged_users_sql );
+
+		/***
+		 * Lets fetch some other useful data in a separate queries, this will be
+		 * faster than querying the data for every user in a list. We can't add
+		 * these to the main query above since only users who have this
+		 * information will be returned (since the much of the data is in
+		 * usermeta and won't support any type of directional join)
+		 */
+		$user_ids = array();
+		foreach ( (array) $paged_users as $user )
+			$user_ids[] = (int) $user->id;
+
+		// Add additional data to the returned results
+		if ( $populate_extras ) {
+			$paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids );
+		}
+
+		return array( 'users' => $paged_users, 'total' => $total_users );
+	}
+
+	/**
+	 * Get details of specific users from the database.
+	 *
+	 * Use {@link BP_User_Query} with the 'user_ids' param instead.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param array $user_ids        The user IDs of the users who we wish to
+	 *                               fetch information on.
+	 * @param int   $limit           The limit of results we want.
+	 * @param int   $page            The page we are on for pagination.
+	 * @param bool  $populate_extras Populate extra user fields?
+	 *
+	 * @return array Associative array.
+	 */
+	public static function get_specific_users( $user_ids, $limit = null, $page = 1, $populate_extras = true ) {
+		global $wpdb;
+
+		$pag_sql = '';
+		if ( $limit && $page )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		$user_ids   = implode( ',', wp_parse_id_list( $user_ids ) );
+		$status_sql = bp_core_get_status_sql();
+
+		/**
+		 * Filter the SQL string used for querying specific user count.
+		 *
+		 * This same filter name is used for the paged user SQL, and so should be avoided.
+		 * Use 'bp_core_user_get_specific_users_count_sql' instead.
+		 *
+		 * @deprecated 2.3.0
+		 *
+		 * @param string $sql SQL string.
+		 */
+		$total_users_sql = apply_filters( 'bp_core_get_specific_users_count_sql', "SELECT COUNT(ID) FROM {$wpdb->users} WHERE {$status_sql} AND ID IN ({$user_ids})" );
+
+		/**
+		 * Filter the SQL string used for querying specific user count results.
+		 *
+		 * Use this instead of the deprecated 'bp_core_get_specific_users_count_sql'.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param string   $sql             SQL string.
+		 * @param array    $user_ids        Array of IDs of specific users to fetch.
+		 * @param int|null $limit           Max number of records to return. Null for no limit.
+		 * @param int      $page            The page we're on for pagination.
+		 * @param bool     $populate_extras Whether to populate extra user fields.
+		 */
+		$total_users_sql = apply_filters( 'bp_core_user_get_specific_users_count_sql', $total_users_sql, $user_ids, $limit, $page, $populate_extras );
+
+		/**
+		 * Filter the SQL string used for querying specific user paged results.
+		 *
+		 * This same filter name is used for the user count SQL, and so should be avoided.
+		 * Use 'bp_core_user_get_specific_users_paged_sql' instead.
+		 *
+		 * @deprecated 2.3.0
+		 *
+		 * @param string $sql SQL string.
+		 */
+		$paged_users_sql = apply_filters( 'bp_core_get_specific_users_count_sql', "SELECT ID as id, user_registered, user_nicename, user_login, user_email FROM {$wpdb->users} WHERE {$status_sql} AND ID IN ({$user_ids}) {$pag_sql}" );
+
+		/**
+		 * Filter the SQL string used for querying specific user paged results.
+		 *
+		 * Use this instead of the deprecated 'bp_core_get_specific_users_count_sql'.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param string   $sql             SQL string.
+		 * @param array    $user_ids        Array of IDs of specific users to fetch.
+		 * @param int|null $limit           Max number of records to return. Null for no limit.
+		 * @param int      $page            The page we're on for pagination.
+		 * @param bool     $populate_extras Whether to populate extra user fields.
+		 */
+		$paged_users_sql = apply_filters( 'bp_core_user_get_specific_users_paged_sql', $paged_users_sql, $user_ids, $limit, $page, $populate_extras );
+
+		$total_users = $wpdb->get_var( $total_users_sql );
+		$paged_users = $wpdb->get_results( $paged_users_sql );
+
+		/***
+		 * Lets fetch some other useful data in a separate queries, this will be
+		 * faster than querying the data for every user in a list. We can't add
+		 * these to the main query above since only users who have this
+		 * information will be returned (since the much of the data is in
+		 * usermeta and won't support any type of directional join)
+		 */
+
+		// Add additional data to the returned results
+		if ( !empty( $populate_extras ) ) {
+			$paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids );
+		}
+
+		return array( 'users' => $paged_users, 'total' => $total_users );
+	}
+
+	/**
+	 * Find users who match on the value of an xprofile data.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param string  $search_terms    The terms to search the profile table
+	 *                                 value column for.
+	 * @param int     $limit           The limit of results we want.
+	 * @param int     $page            The page we are on for pagination.
+	 * @param boolean $populate_extras Populate extra user fields?
+	 *
+	 * @return array Associative array.
+	 */
+	public static function search_users( $search_terms, $limit = null, $page = 1, $populate_extras = true ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$user_ids = array();
+		$pag_sql  = $limit && $page ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * intval( $limit ) ), intval( $limit ) ) : '';
+
+		$search_terms_like = '%' . bp_esc_like( $search_terms ) . '%';
+		$status_sql        = bp_core_get_status_sql( 'u.' );
+
+		/**
+		 * Filters the SQL used to query for searched users count.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value SQL statement for the searched users count query.
+		 */
+		$total_users_sql = apply_filters( 'bp_core_search_users_count_sql', $wpdb->prepare( "SELECT COUNT(DISTINCT u.ID) as id FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id WHERE {$status_sql} AND pd.value LIKE %s ORDER BY pd.value ASC", $search_terms_like ), $search_terms );
+
+		/**
+		 * Filters the SQL used to query for searched users.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value SQL statement for the searched users query.
+		 */
+		$paged_users_sql = apply_filters( 'bp_core_search_users_sql',       $wpdb->prepare( "SELECT DISTINCT u.ID as id, u.user_registered, u.user_nicename, u.user_login, u.user_email FROM {$wpdb->users} u LEFT JOIN {$bp->profile->table_name_data} pd ON u.ID = pd.user_id WHERE {$status_sql} AND pd.value LIKE %s ORDER BY pd.value ASC{$pag_sql}", $search_terms_like ), $search_terms, $pag_sql );
+
+		$total_users = $wpdb->get_var( $total_users_sql );
+		$paged_users = $wpdb->get_results( $paged_users_sql );
+
+		/***
+		 * Lets fetch some other useful data in a separate queries, this will be faster than querying the data for every user in a list.
+		 * We can't add these to the main query above since only users who have this information will be returned (since the much of the data is in usermeta and won't support any type of directional join)
+		 */
+		foreach ( (array) $paged_users as $user )
+			$user_ids[] = $user->id;
+
+		// Add additional data to the returned results
+		if ( $populate_extras )
+			$paged_users = BP_Core_User::get_user_extras( $paged_users, $user_ids );
+
+		return array( 'users' => $paged_users, 'total' => $total_users );
+	}
+
+	/**
+	 * Fetch extra user information, such as friend count and last profile update message.
+	 *
+	 * Accepts multiple user IDs to fetch data for.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param array       $paged_users An array of stdClass containing the users.
+	 * @param string      $user_ids    The user ids to select information about.
+	 * @param string|bool $type        The type of fields we wish to get.
+	 *
+	 * @return mixed False on error, otherwise associative array of results.
+	 */
+	public static function get_user_extras( &$paged_users, &$user_ids, $type = false ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( empty( $user_ids ) )
+			return $paged_users;
+
+		// Sanitize user IDs
+		$user_ids = implode( ',', wp_parse_id_list( $user_ids ) );
+
+		// Fetch the user's full name
+		if ( bp_is_active( 'xprofile' ) && 'alphabetical' != $type ) {
+			$names = $wpdb->get_results( $wpdb->prepare( "SELECT pd.user_id as id, pd.value as fullname FROM {$bp->profile->table_name_fields} pf, {$bp->profile->table_name_data} pd WHERE pf.id = pd.field_id AND pf.name = %s AND pd.user_id IN ( {$user_ids} )", bp_xprofile_fullname_field_name() ) );
+			for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
+				foreach ( (array) $names as $name ) {
+					if ( $name->id == $paged_users[$i]->id )
+						$paged_users[$i]->fullname = $name->fullname;
+				}
+			}
+		}
+
+		// Fetch the user's total friend count
+		if ( 'popular' != $type ) {
+			$friend_count = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as total_friend_count FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'total_friend_count' ) ) );
+			for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
+				foreach ( (array) $friend_count as $fcount ) {
+					if ( $fcount->id == $paged_users[$i]->id )
+						$paged_users[$i]->total_friend_count = (int) $fcount->total_friend_count;
+				}
+			}
+		}
+
+		// Fetch whether or not the user is a friend
+		if ( bp_is_active( 'friends' ) ) {
+			$friend_status = $wpdb->get_results( $wpdb->prepare( "SELECT initiator_user_id, friend_user_id, is_confirmed FROM {$bp->friends->table_name} WHERE (initiator_user_id = %d AND friend_user_id IN ( {$user_ids} ) ) OR (initiator_user_id IN ( {$user_ids} ) AND friend_user_id = %d )", bp_loggedin_user_id(), bp_loggedin_user_id() ) );
+			for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
+				foreach ( (array) $friend_status as $status ) {
+					if ( $status->initiator_user_id == $paged_users[$i]->id || $status->friend_user_id == $paged_users[$i]->id )
+						$paged_users[$i]->is_friend = $status->is_confirmed;
+				}
+			}
+		}
+
+		if ( 'active' != $type ) {
+			$user_activity = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as last_activity FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'last_activity' ) ) );
+			for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
+				foreach ( (array) $user_activity as $activity ) {
+					if ( $activity->id == $paged_users[$i]->id )
+						$paged_users[$i]->last_activity = $activity->last_activity;
+				}
+			}
+		}
+
+		// Fetch the user's last_activity
+		if ( 'active' != $type ) {
+			$user_activity = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as last_activity FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'last_activity' ) ) );
+			for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
+				foreach ( (array) $user_activity as $activity ) {
+					if ( $activity->id == $paged_users[$i]->id )
+						$paged_users[$i]->last_activity = $activity->last_activity;
+				}
+			}
+		}
+
+		// Fetch the user's latest update
+		$user_update = $wpdb->get_results( $wpdb->prepare( "SELECT user_id as id, meta_value as latest_update FROM {$wpdb->usermeta} WHERE meta_key = %s AND user_id IN ( {$user_ids} )", bp_get_user_meta_key( 'bp_latest_update' ) ) );
+		for ( $i = 0, $count = count( $paged_users ); $i < $count; ++$i ) {
+			foreach ( (array) $user_update as $update ) {
+				if ( $update->id == $paged_users[$i]->id )
+					$paged_users[$i]->latest_update = $update->latest_update;
+			}
+		}
+
+		return $paged_users;
+	}
+
+	/**
+	 * Get WordPress user details for a specified user.
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @param int $user_id User ID.
+	 *
+	 * @return array Associative array.
+	 */
+	public static function get_core_userdata( $user_id ) {
+		global $wpdb;
+
+		if ( !$user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->users} WHERE ID = %d LIMIT 1", $user_id ) ) )
+			return false;
+
+		return $user;
+	}
+
+	/**
+	 * Get last activity data for a user or set of users.
+	 *
+	 * @param int|array User IDs or multiple user IDs.
+	 *
+	 * @return array
+	 */
+	public static function get_last_activity( $user_id ) {
+		global $wpdb;
+
+		// Sanitize and remove empty values
+		$user_ids = array_filter( wp_parse_id_list( $user_id ) );
+
+		if ( empty( $user_ids ) ) {
+			return false;
+		}
+
+		$uncached_user_ids = bp_get_non_cached_ids( $user_ids, 'bp_last_activity' );
+		if ( ! empty( $uncached_user_ids ) ) {
+			$bp = buddypress();
+
+			$user_ids_sql = implode( ',', $uncached_user_ids );
+			$user_count   = count( $uncached_user_ids );
+
+			$last_activities = $wpdb->get_results( $wpdb->prepare( "SELECT id, user_id, date_recorded FROM {$bp->members->table_name_last_activity} WHERE component = %s AND type = 'last_activity' AND user_id IN ({$user_ids_sql}) LIMIT {$user_count}", $bp->members->id ) );
+
+			foreach ( $last_activities as $last_activity ) {
+				wp_cache_set( $last_activity->user_id, array(
+					'user_id'       => $last_activity->user_id,
+					'date_recorded' => $last_activity->date_recorded,
+					'activity_id'   => $last_activity->id,
+				), 'bp_last_activity' );
+			}
+		}
+
+		// Fetch all user data from the cache
+		$retval = array();
+		foreach ( $user_ids as $user_id ) {
+			$retval[ $user_id ] = wp_cache_get( $user_id, 'bp_last_activity' );
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * Set a user's last_activity value.
+	 *
+	 * Will create a new entry if it does not exist. Otherwise updates the
+	 * existing entry.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param int    $user_id ID of the user whose last_activity you are updating.
+	 * @param string $time    MySQL-formatted time string.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public static function update_last_activity( $user_id, $time ) {
+		global $wpdb;
+
+		$table_name = buddypress()->members->table_name_last_activity;
+
+		$activity = self::get_last_activity( $user_id );
+
+		if ( ! empty( $activity[ $user_id ] ) ) {
+			$updated = $wpdb->update(
+				$table_name,
+
+				// Data to update
+				array(
+					'date_recorded' => $time,
+				),
+
+				// WHERE
+				array(
+					'id' => $activity[ $user_id ]['activity_id'],
+				),
+
+				// Data sanitization format
+				array(
+					'%s',
+				),
+
+				// WHERE sanitization format
+				array(
+					'%d',
+				)
+			);
+
+			// add new date to existing activity entry for caching
+			$activity[ $user_id ]['date_recorded'] = $time;
+
+		} else {
+			$updated = $wpdb->insert(
+				$table_name,
+
+				// Data
+				array(
+					'user_id'       => $user_id,
+					'component'     => buddypress()->members->id,
+					'type'          => 'last_activity',
+					'action'        => '',
+					'content'       => '',
+					'primary_link'  => '',
+					'item_id'       => 0,
+					'date_recorded' => $time,
+				),
+
+				// Data sanitization format
+				array(
+					'%d',
+					'%s',
+					'%s',
+					'%s',
+					'%s',
+					'%s',
+					'%d',
+					'%s',
+				)
+			);
+
+			// set up activity array for caching
+			// view the foreach loop in the get_last_activity() method for format
+			$activity = array();
+			$activity[ $user_id ] = array(
+				'user_id'       => $user_id,
+				'date_recorded' => $time,
+				'activity_id'   => $wpdb->insert_id,
+			);
+		}
+
+		// set cache
+		wp_cache_set( $user_id, $activity[ $user_id ], 'bp_last_activity' );
+
+		return $updated;
+	}
+
+	/**
+	 * Delete a user's last_activity value.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param int $user_id
+	 *
+	 * @return bool True on success, false on failure or if no last_activity
+	 *              is found for the user.
+	 */
+	public static function delete_last_activity( $user_id ) {
+		global $wpdb;
+
+		$existing = self::get_last_activity( $user_id );
+
+		if ( empty( $existing ) ) {
+			return false;
+		}
+
+		$deleted = $wpdb->delete(
+			buddypress()->members->table_name_last_activity,
+
+			// WHERE
+			array(
+				'id' => $existing[ $user_id ]['activity_id'],
+			),
+
+			// WHERE sanitization format
+			array(
+				'%s',
+			)
+		);
+
+		wp_cache_delete( $user_id, 'bp_last_activity' );
+
+		return $deleted;
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-date-query.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-date-query.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-date-query.php	(revision 10410)
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+if ( class_exists( 'WP_Date_Query' ) ) :
+/**
+ * BuddyPress date query class.
+ *
+ * Extends the {@link WP_Date_Query} class for use with BuddyPress.
+ *
+ * @since 2.1.0
+ *
+ * @param array $date_query {
+ *     Date query arguments.  See first parameter of {@link WP_Date_Query::__construct()}.
+ * }
+ * @param string $column The DB column to query against.
+ */
+class BP_Date_Query extends WP_Date_Query {
+	/**
+	 * The column to query against. Can be changed via the query arguments.
+	 *
+	 * @var string
+	 */
+	public $column;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param array  $date_query
+	 * @param string $column
+	 *
+	 * @see WP_Date_Query::__construct()
+	 */
+	public function __construct( $date_query, $column = '' ) {
+		if ( ! empty( $column ) ) {
+			$this->column = $column;
+			add_filter( 'date_query_valid_columns', array( $this, 'register_date_column' ) );
+		}
+
+		parent::__construct( $date_query, $column );
+	}
+
+	/**
+	 * Destructor.
+	 */
+	public function __destruct() {
+		remove_filter( 'date_query_valid_columns', array( $this, 'register_date_column' ) );
+	}
+
+	/**
+	 * Registers our date column with WP Date Query to pass validation.
+	 *
+	 * @param array $retval Current DB columns.
+	 *
+	 * @return array
+	 */
+	public function register_date_column( $retval = array() ) {
+		$retval[] = $this->column;
+		return $retval;
+	}
+}
+endif;
Index: /tags/2.4.3/src/bp-core/classes/class-bp-embed.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-embed.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-embed.php	(revision 10410)
@@ -0,0 +1,250 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Enable oEmbeds in BuddyPress contexts.
+ *
+ * Extends WP_Embed class for use with BuddyPress.
+ *
+ * @since 1.5.0
+ *
+ * @see WP_Embed
+ */
+class BP_Embed extends WP_Embed {
+
+	/**
+	 * Constructor
+	 *
+	 * @global WP_Embed $wp_embed
+	 */
+	public function __construct() {
+		global $wp_embed;
+
+		// Make sure we populate the WP_Embed handlers array.
+		// These are providers that use a regex callback on the URL in question.
+		// Do not confuse with oEmbed providers, which require an external ping.
+		// Used in WP_Embed::shortcode()
+		$this->handlers = $wp_embed->handlers;
+
+		if ( bp_use_embed_in_activity() ) {
+			add_filter( 'bp_get_activity_content_body', array( &$this, 'autoembed' ), 8 );
+			add_filter( 'bp_get_activity_content_body', array( &$this, 'run_shortcode' ), 7 );
+		}
+
+		if ( bp_use_embed_in_activity_replies() ) {
+			add_filter( 'bp_get_activity_content', array( &$this, 'autoembed' ), 8 );
+			add_filter( 'bp_get_activity_content', array( &$this, 'run_shortcode' ), 7 );
+		}
+
+		if ( bp_use_embed_in_forum_posts() ) {
+			add_filter( 'bp_get_the_topic_post_content', array( &$this, 'autoembed' ), 8 );
+			add_filter( 'bp_get_the_topic_post_content', array( &$this, 'run_shortcode' ), 7 );
+		}
+
+		if ( bp_use_embed_in_private_messages() ) {
+			add_filter( 'bp_get_the_thread_message_content', array( &$this, 'autoembed' ), 8 );
+			add_filter( 'bp_get_the_thread_message_content', array( &$this, 'run_shortcode' ), 7 );
+		}
+
+		/**
+		 * Filters the BuddyPress Core oEmbed setup.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param BP_Embed $this Current instance of the BP_Embed. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_core_setup_oembed', array( &$this ) );
+	}
+
+	/**
+	 * The {@link do_shortcode()} callback function.
+	 *
+	 * Attempts to convert a URL into embed HTML. Starts by checking the
+	 * URL against the regex of the registered embed handlers. Next, checks
+	 * the URL against the regex of registered {@link WP_oEmbed} providers
+	 * if oEmbed discovery is false. If none of the regex matches and it's
+	 * enabled, then the URL will be passed to {@link BP_Embed::parse_oembed()}
+	 * for oEmbed parsing.
+	 *
+	 * @uses wp_parse_args()
+	 * @uses wp_embed_defaults()
+	 * @uses current_user_can()
+	 * @uses _wp_oembed_get_object()
+	 * @uses WP_Embed::maybe_make_link()
+	 *
+	 * @param array  $attr Shortcode attributes.
+	 * @param string $url  The URL attempting to be embeded.
+	 *
+	 * @return string The embed HTML on success, otherwise the original URL.
+	 */
+	public function shortcode( $attr, $url = '' ) {
+		if ( empty( $url ) )
+			return '';
+
+		$rawattr = $attr;
+		$attr = wp_parse_args( $attr, wp_embed_defaults() );
+
+		// kses converts & into &amp; and we need to undo this
+		// See https://core.trac.wordpress.org/ticket/11311
+		$url = str_replace( '&amp;', '&', $url );
+
+		// Look for known internal handlers
+		ksort( $this->handlers );
+		foreach ( $this->handlers as $priority => $handlers ) {
+			foreach ( $handlers as $hid => $handler ) {
+				if ( preg_match( $handler['regex'], $url, $matches ) && is_callable( $handler['callback'] ) ) {
+					if ( false !== $return = call_user_func( $handler['callback'], $matches, $attr, $url, $rawattr ) ) {
+
+						/**
+						 * Filters the oEmbed handler result for the provided URL.
+						 *
+						 * @since 1.5.0
+						 *
+						 * @param string $return Handler callback for the oEmbed.
+						 * @param string $url    URL attempting to be embedded.
+						 * @param array  $attr   Shortcode attributes.
+						 */
+						return apply_filters( 'embed_handler_html', $return, $url, $attr );
+					}
+				}
+			}
+		}
+
+		/**
+		 * Filters the embed object ID.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int $value Value of zero.
+		 */
+		$id = apply_filters( 'embed_post_id', 0 );
+
+		/**
+		 * Filters whether or not oEmbed discovery is on.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param bool $value Current status of oEmbed discovery.
+		 */
+		$attr['discover'] = ( apply_filters( 'bp_embed_oembed_discover', false ) && current_user_can( 'unfiltered_html' ) );
+
+		// Set up a new WP oEmbed object to check URL with registered oEmbed providers
+		require_once( ABSPATH . WPINC . '/class-oembed.php' );
+		$oembed_obj = _wp_oembed_get_object();
+
+		// If oEmbed discovery is true, skip oEmbed provider check
+		$is_oembed_link = false;
+		if ( !$attr['discover'] ) {
+			foreach ( (array) $oembed_obj->providers as $provider_matchmask => $provider ) {
+				$regex = ( $is_regex = $provider[1] ) ? $provider_matchmask : '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $provider_matchmask ), '#' ) ) . '#i';
+
+				if ( preg_match( $regex, $url ) )
+					$is_oembed_link = true;
+			}
+
+			// If url doesn't match a WP oEmbed provider, stop parsing
+			if ( !$is_oembed_link )
+				return $this->maybe_make_link( $url );
+		}
+
+		return $this->parse_oembed( $id, $url, $attr, $rawattr );
+	}
+
+	/**
+	 * Base function so BP components/plugins can parse links to be embedded.
+	 *
+	 * View an example to add support in {@link bp_activity_embed()}.
+	 *
+	 * @uses apply_filters() Filters cache.
+	 * @uses do_action() To save cache.
+	 * @uses wp_oembed_get() Connects to oEmbed provider and returns HTML
+	 *       on success.
+	 * @uses WP_Embed::maybe_make_link() Process URL for hyperlinking on
+	 *       oEmbed failure.
+	 *
+	 * @param int    $id      ID to do the caching for.
+	 * @param string $url     The URL attempting to be embedded.
+	 * @param array  $attr    Shortcode attributes from {@link WP_Embed::shortcode()}.
+	 * @param array  $rawattr Untouched shortcode attributes from
+	 *                        {@link WP_Embed::shortcode()}.
+	 *
+	 * @return string The embed HTML on success, otherwise the original URL.
+	 */
+	public function parse_oembed( $id, $url, $attr, $rawattr ) {
+		$id = intval( $id );
+
+		if ( $id ) {
+			// Setup the cachekey
+			$cachekey = '_oembed_' . md5( $url . serialize( $attr ) );
+
+			// Let components / plugins grab their cache
+			$cache = '';
+
+			/**
+			 * Filters the cache value to be used in the oEmbed, if exists.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param string $cache    Empty initial cache value.
+			 * @param int    $id       ID that the caching is for.
+			 * @param string $cachekey Key to use for the caching in the database.
+			 * @param string $url      The URL attempting to be embedded.
+			 * @param array  $attr     Parsed shortcode attributes.
+			 * @param array  $rawattr  Unparsed shortcode attributes.
+			 */
+			$cache = apply_filters( 'bp_embed_get_cache', $cache, $id, $cachekey, $url, $attr, $rawattr );
+
+			// Grab cache and return it if available
+			if ( !empty( $cache ) ) {
+
+				/**
+				 * Filters the found cache for the provided URL.
+				 *
+				 * @since 1.5.0
+				 *
+				 * @param string $cache   Cached HTML markup for embed.
+				 * @param string $url     The URL being embedded.
+				 * @param array  $attr    Parsed shortcode attributes.
+				 * @param array  $rawattr Unparased shortcode attributes.
+				 */
+				return apply_filters( 'bp_embed_oembed_html', $cache, $url, $attr, $rawattr );
+
+			// If no cache, ping the oEmbed provider and cache the result
+			} else {
+				$html = wp_oembed_get( $url, $attr );
+				$cache = ( $html ) ? $html : $url;
+
+				/**
+				 * Fires if there is no existing cache and triggers cache setting.
+				 *
+				 * Lets components / plugins save their cache.
+				 *
+				 * @since 1.5.0
+				 *
+				 * @param string $cache    Newly cached HTML markup for embed.
+				 * @param string $cachekey Key to use for the caching in the database.
+				 * @param int    $id       ID to do the caching for.
+				 */
+				do_action( 'bp_embed_update_cache', $cache, $cachekey, $id );
+
+				// If there was a result, return it
+				if ( $html ) {
+
+					/** This filter is documented in bp-core/classes/class-bp-embed.php */
+					return apply_filters( 'bp_embed_oembed_html', $html, $url, $attr, $rawattr );
+				}
+			}
+		}
+
+		// Still unknown
+		return $this->maybe_make_link( $url );
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-media-extractor.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-media-extractor.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-media-extractor.php	(revision 10410)
@@ -0,0 +1,938 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Extracts media from text. Use {@link extract()}.
+ *
+ * @since 2.3.0
+ *
+ * The supported types are links, mentions, images, shortcodes, embeds, audio, video, and "all".
+ * This is what each type extracts:
+ *
+ * Links:      <a href="http://example.com">
+ * Mentions:   @name
+ *             If the Activity component is enabled, we use it to parse out any at-names. A consequence
+ *             to note is that the "name" mentioned must match a real user account. If it's a made-up
+ *             at-name, then it isn't extracted.
+ *             If the Activity component is disabled, any at-name is extracted (both those matching
+ *             real accounts, and those made-up).
+ * Images:     <img src="image.gif">, [gallery], [gallery ids="2,3"], featured images (Post thumbnails).
+ *             If an extracted image is in the Media Library, then its resolution will be included.
+ * Shortcodes: Extract information about any (registered) shortcodes.
+ *             This includes any shortcodes indirectly covered by any of the other media extraction types.
+ *             For example, [gallery].
+ * Embeds:     Extract any URL matching a registered oEmbed handler.
+ * Audio:      <a href="*.mp3"">, [audio]
+ *             See wp_get_audio_extensions() for supported audio formats.
+ * Video:      [video]
+ *             See wp_get_video_extensions() for supported video formats.
+ *
+ * @see BP_Media_Extractor::extract() Use this to extract media.
+ */
+class BP_Media_Extractor {
+	/**
+	 * Media type.
+	 *
+	 * @since 2.3.0
+	 * @var int
+	 */
+	const ALL        = 255;
+	const LINKS      = 1;
+	const MENTIONS   = 2;
+	const IMAGES     = 4;
+	const SHORTCODES = 8;
+	const EMBEDS     = 16;
+	const AUDIO      = 32;
+	const VIDEOS     = 64;
+
+
+	/**
+	 * Extract media from text.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string|WP_Post $richtext        Content to parse.
+	 * @param int            $what_to_extract Media type to extract (optional).
+	 * @param array          $extra_args      Bespoke data for a particular extractor (optional).
+	 *
+	 * @return array {
+	 *     @type array $has Extracted media counts. {
+	 *         @type int $audio
+	 *         @type int $embeds
+	 *         @type int $images
+	 *         @type int $links
+	 *         @type int $mentions
+	 *         @type int $shortcodes
+	 *         @type int $video
+	 *     }
+	 *     @type array $audio Extracted audio. {
+	 *         Array of extracted media.
+	 *
+	 *         @type string $source Media source. Either "html" or "shortcodes".
+	 *         @type string $url    Link to audio.
+	 *     }
+	 *     @type array $embeds Extracted oEmbeds. {
+	 *         Array of extracted media.
+	 *
+	 *         @type string $url oEmbed link.
+	 *     }
+	 *     @type array $images Extracted images. {
+	 *         Array of extracted media.
+	 *
+	 *         @type int    $gallery_id Gallery ID. Optional, not always set.
+	 *         @type int    $height     Width of image. If unknown, set to 0.
+	 *         @type string $source     Media source. Either "html" or "galleries".
+	 *         @type string $url        Link to image.
+	 *         @type int    $width      Width of image. If unknown, set to 0.
+	 *     }
+	 *     @type array $links Extracted URLs. {
+	 *         Array of extracted media.
+	 *
+	 *         @type string $url Link.
+	 *     }
+	 *     @type array $mentions Extracted mentions. {
+	 *         Array of extracted media.
+	 *
+	 *         @type string $name    @mention.
+	 *         @type string $user_id User ID. Optional, only set if Activity component enabled.
+	 *     }
+	 *     @type array $shortcodes Extracted shortcodes. {
+	 *         Array of extracted media.
+	 *
+	 *         @type array  $attributes Key/value pairs of the shortcodes attributes (if any).
+	 *         @type string $content    Text wrapped by the shortcode.
+	 *         @type string $type       Shortcode type.
+	 *         @type string $original   The entire shortcode.
+	 *     }
+	 *     @type array $videos Extracted video. {
+	 *         Array of extracted media.
+	 *
+	 *         @type string $source Media source. Currently only "shortcodes".
+	 *         @type string $url    Link to audio.
+	 *     }
+	 * }
+	 */
+	public function extract( $richtext, $what_to_extract = self::ALL, $extra_args = array() ) {
+		$media = array();
+
+		// Support passing a WordPress Post for the $richtext parameter.
+		if ( is_a( $richtext, 'WP_Post' ) ) {
+			$extra_args['post'] = $richtext;
+			$richtext           = $extra_args['post']->post_content;
+		}
+
+		$plaintext = $this->strip_markup( $richtext );
+
+
+		// Extract links.
+		if ( self::LINKS & $what_to_extract ) {
+			$media = array_merge_recursive( $media, $this->extract_links( $richtext, $plaintext, $extra_args ) );
+		}
+
+		// Extract mentions.
+		if ( self::MENTIONS & $what_to_extract ) {
+			$media = array_merge_recursive( $media, $this->extract_mentions( $richtext, $plaintext, $extra_args ) );
+		}
+
+		// Extract images.
+		if ( self::IMAGES & $what_to_extract ) {
+			$media = array_merge_recursive( $media, $this->extract_images( $richtext, $plaintext, $extra_args ) );
+		}
+
+		// Extract shortcodes.
+		if ( self::SHORTCODES & $what_to_extract ) {
+			$media = array_merge_recursive( $media, $this->extract_shortcodes( $richtext, $plaintext, $extra_args ) );
+		}
+
+		// Extract oEmbeds.
+		if ( self::EMBEDS & $what_to_extract ) {
+			$media = array_merge_recursive( $media, $this->extract_embeds( $richtext, $plaintext, $extra_args ) );
+		}
+
+		// Extract audio.
+		if ( self::AUDIO & $what_to_extract ) {
+			$media = array_merge_recursive( $media, $this->extract_audio( $richtext, $plaintext, $extra_args ) );
+		}
+
+		// Extract video.
+		if ( self::VIDEOS & $what_to_extract ) {
+			$media = array_merge_recursive( $media, $this->extract_video( $richtext, $plaintext, $extra_args ) );
+		}
+
+		/**
+		 * Filters media extracted from text.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $media           Extracted media. See {@link BP_Media_Extractor::extract()} for format.
+		 * @param string $richtext        Content to parse.
+		 * @param int    $what_to_extract Media type to extract.
+		 * @param array  $extra_args      Bespoke data for a particular extractor.
+		 * @param string $plaintext       Copy of $richtext without any markup.
+		 */
+		return apply_filters( 'bp_media_extractor_extract', $media, $richtext, $what_to_extract, $extra_args, $plaintext );
+	}
+
+
+	/**
+	 * Content type specific extraction methods.
+	 *
+	 * You shouldn't need to use these directly; just use {@link BP_Media_Extractor::extract()}.
+	 */
+
+	/**
+	 * Extract `<a href>` tags from text.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $richtext   Content to parse.
+	 * @param string $plaintext  Sanitized version of the content.
+	 * @param array  $extra_args Bespoke data for a particular extractor (optional).
+	 *
+	 * @return array {
+	 *     @type array $has Extracted media counts. {
+	 *         @type int $links
+	 *     }
+	 *     @type array $links Extracted URLs. {
+	 *         Array of extracted media.
+	 *
+	 *         @type string $url Link.
+	 *     }
+	 * }
+	 */
+	protected function extract_links( $richtext, $plaintext, $extra_args = array() ) {
+		$data = array( 'has' => array( 'links' => 0 ), 'links' => array() );
+
+		// Matches: href="text" and href='text'
+		if ( stripos( $richtext, 'href=' ) !== false ) {
+			preg_match_all( '#href=(["\'])([^"\']+)\1#i', $richtext, $matches );
+
+			if ( ! empty( $matches[2] ) ) {
+				$matches[2] = array_unique( $matches[2] );
+
+				foreach ( $matches[2] as $link_src ) {
+					$link_src = esc_url_raw( $link_src );
+
+					if ( $link_src ) {
+						$data['links'][] = array( 'url' => $link_src );
+					}
+				}
+			}
+		}
+
+		$data['has']['links'] = count( $data['links'] );
+
+		/**
+		 * Filters links extracted from text.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $data       Extracted links. See {@link BP_Media_Extractor::extract_links()} for format.
+		 * @param string $richtext   Content to parse.
+		 * @param string $plaintext  Copy of $richtext without any markup.
+		 * @param array  $extra_args Bespoke data for a particular extractor.
+		 */
+		return apply_filters( 'bp_media_extractor_links', $data, $richtext, $plaintext, $extra_args );
+	}
+
+	/**
+	 * Extract @mentions tags from text.
+	 *
+	 * If the Activity component is enabled, it is used to parse @mentions.
+	 * The mentioned "name" must match a user account, otherwise it is discarded.
+	 *
+	 * If the Activity component is disabled, any @mentions are extracted.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $richtext   Content to parse.
+	 * @param string $plaintext  Sanitized version of the content.
+	 * @param array  $extra_args Bespoke data for a particular extractor.
+	 *
+	 * @return array {
+	 *     @type array $has Extracted media counts. {
+	 *         @type int $mentions
+	 *     }
+	 *     @type array $mentions Extracted mentions. {
+	 *         Array of extracted media.
+	 *
+	 *         @type string $name    @mention.
+	 *         @type string $user_id User ID. Optional, only set if Activity component enabled.
+	 *     }
+	 * }
+	 */
+	protected function extract_mentions( $richtext, $plaintext, $extra_args = array() ) {
+		$data     = array( 'has' => array( 'mentions' => 0 ), 'mentions' => array() );
+		$mentions = array();
+
+		// If the Activity component is active, use it to parse @mentions.
+		if ( bp_is_active( 'activity' ) ) {
+			$mentions = bp_activity_find_mentions( $plaintext );
+			if ( ! $mentions ) {
+				$mentions = array();
+			}
+
+		// If the Activity component is disabled, instead do a basic parse.
+		} else {
+			if ( strpos( $plaintext, '@' ) !== false ) {
+				preg_match_all( '/[@]+([A-Za-z0-9-_\.@]+)\b/', $plaintext, $matches );
+
+				if ( ! empty( $matches[1] ) ) {
+					$mentions = array_unique( array_map( 'strtolower', $matches[1] ) );
+				}
+			}
+		}
+
+		// Build results
+		foreach ( $mentions as $user_id => $mention_name ) {
+			$mention = array( 'name' => strtolower( $mention_name ) );
+
+			// If the Activity component is active, store the User ID, too.
+			if ( bp_is_active( 'activity' ) ) {
+				$mention['user_id'] = (int) $user_id;
+			}
+
+			$data['mentions'][] = $mention;
+		}
+
+		$data['has']['mentions'] = count( $data['mentions'] );
+
+		/**
+		 * Filters @mentions extracted from text.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $data       Extracted @mentions. See {@link BP_Media_Extractor::extract_mentions()} for format.
+		 * @param string $richtext   Content to parse.
+		 * @param string $plaintext  Copy of $richtext without any markup.
+		 * @param array  $extra_args Bespoke data for a particular extractor (optional).
+		 */
+		return apply_filters( 'bp_media_extractor_mentions', $data, $richtext, $plaintext, $extra_args );
+	}
+
+	/**
+	 * Extract images from `<img src>` tags, [galleries], and featured images from a Post.
+	 *
+	 * If an image is in the Media Library, then its resolution is included in the results.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $richtext   Content to parse.
+	 * @param string $plaintext  Sanitized version of the content.
+	 * @param array  $extra_args Bespoke data for a particular extractor (optional).
+	 *
+	 * @return array {
+	 *     @type array $has Extracted media counts. {
+	 *         @type int $images
+	 *     }
+	 *     @type array $images Extracted images. {
+	 *         Array of extracted media.
+	 *
+	 *         @type int    $gallery_id Gallery ID. Optional, not always set.
+	 *         @type int    $height     Width of image. If unknown, set to 0.
+	 *         @type string $source     Media source. Either "html" or "galleries".
+	 *         @type string $url        Link to image.
+	 *         @type int    $width      Width of image. If unknown, set to 0.
+	 *     }
+	 * }
+	 */
+	protected function extract_images( $richtext, $plaintext, $extra_args = array() ) {
+		$media = array( 'has' => array( 'images' => 0 ), 'images' => array() );
+
+		$featured_image = $this->extract_images_from_featured_images( $richtext, $plaintext, $extra_args );
+		$galleries      = $this->extract_images_from_galleries( $richtext, $plaintext, $extra_args );
+
+
+		// `<img src>` tags.
+		if ( stripos( $richtext, 'src=' ) !== false ) {
+			preg_match_all( '#src=(["\'])([^"\']+)\1#i', $richtext, $img_srcs );  // matches src="text" and src='text'
+
+			// <img>.
+			if ( ! empty( $img_srcs[2] ) ) {
+				$img_srcs[2] = array_unique( $img_srcs[2] );
+
+				foreach ( $img_srcs[2] as $image_src ) {
+					// Skip data URIs.
+					if ( strtolower( substr( $image_src, 0, 5 ) ) === 'data:' ) {
+						continue;
+					}
+
+					$image_src = esc_url_raw( $image_src );
+					if ( ! $image_src ) {
+						continue;
+					}
+
+					$media['images'][] = array(
+						'source' => 'html',
+						'url'    => $image_src,
+
+						// The image resolution isn't available, but we need to set the keys anyway.
+						'height' => 0,
+						'width'  => 0,
+					);
+				}
+			}
+		}
+
+		// Galleries.
+		if ( ! empty( $galleries ) ) {
+			foreach ( $galleries as $gallery ) {
+				foreach ( $gallery as $image ) {
+					$image_url = esc_url_raw( $image['url'] );
+					if ( ! $image_url ) {
+						continue;
+					}
+
+					$media['images'][] = array(
+						'gallery_id' => $image['gallery_id'],
+						'source'     => 'galleries',
+						'url'        => $image_url,
+						'width'      => $image['width'],
+						'height'     => $image['height'],
+					);
+				}
+			}
+
+			$media['has']['galleries'] = count( $galleries );
+		}
+
+		// Featured images (aka thumbnails).
+		if ( ! empty( $featured_image ) ) {
+			$image_url = esc_url_raw( $featured_image[0] );
+
+			if ( $image_url ) {
+				$media['images'][] = array(
+					'source' => 'featured_images',
+					'url'    => $image_url,
+					'width'  => $featured_image[1],
+					'height' => $featured_image[2],
+				);
+
+				$media['has']['featured_images'] = 1;
+			}
+		}
+
+		// Update image count.
+		$media['has']['images'] = count( $media['images'] );
+
+
+		/**
+		 * Filters images extracted from text.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $media      Extracted images. See {@link BP_Media_Extractor::extract_images()} for format.
+		 * @param string $richtext   Content to parse.
+		 * @param string $plaintext  Copy of $richtext without any markup.
+		 * @param array  $extra_args Bespoke data for a particular extractor.
+		 */
+		return apply_filters( 'bp_media_extractor_images', $media, $richtext, $plaintext, $extra_args );
+	}
+
+	/**
+	 * Extract shortcodes from text.
+	 *
+	 * This includes any shortcodes indirectly used by other media extraction types.
+	 * For example, [gallery] and [audio].
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $richtext   Content to parse.
+	 * @param string $plaintext  Sanitized version of the content.
+	 * @param array  $extra_args Bespoke data for a particular extractor (optional).
+	 *
+	 * @return array {
+	 *     @type array $has Extracted media counts. {
+	 *         @type int $shortcodes
+	 *     }
+	 *     @type array $shortcodes Extracted shortcodes. {
+	 *         Array of extracted media.
+	 *
+	 *         @type array  $attributes Key/value pairs of the shortcodes attributes (if any).
+	 *         @type string $content    Text wrapped by the shortcode.
+	 *         @type string $type       Shortcode type.
+	 *         @type string $original   The entire shortcode.
+	 *     }
+	 * }
+	 */
+	protected function extract_shortcodes( $richtext, $plaintext, $extra_args = array() ) {
+		$data = array( 'has' => array( 'shortcodes' => 0 ), 'shortcodes' => array() );
+
+		// Match any registered WordPress shortcodes.
+		if ( strpos( $richtext, '[' ) !== false ) {
+			preg_match_all( '/' . get_shortcode_regex() . '/s', $richtext, $matches );
+
+			if ( ! empty( $matches[2] ) ) {
+				foreach ( $matches[2] as $i => $shortcode_name ) {
+					$attrs = shortcode_parse_atts( $matches[3][ $i ] );
+					$attrs = ( ! $attrs ) ? array() : $attrs;
+
+					$shortcode               = array();
+					$shortcode['attributes'] = $attrs;             // Attributes
+					$shortcode['content']    = $matches[5][ $i ];  // Content
+					$shortcode['type']       = $shortcode_name;    // Shortcode
+					$shortcode['original']   = $matches[0][ $i ];  // Entire shortcode
+
+					$data['shortcodes'][] = $shortcode;
+				}
+			}
+		}
+
+		$data['has']['shortcodes'] = count( $data['shortcodes'] );
+
+		/**
+		 * Filters shortcodes extracted from text.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $data       Extracted shortcodes.
+		 *                           See {@link BP_Media_Extractor::extract_shortcodes()} for format.
+		 * @param string $richtext   Content to parse.
+		 * @param string $plaintext  Copy of $richtext without any markup.
+		 * @param array  $extra_args Bespoke data for a particular extractor.
+		 */
+		return apply_filters( 'bp_media_extractor_shortcodes', $data, $richtext, $plaintext, $extra_args );
+	}
+
+	/**
+	 * Extract any URL, matching a registered oEmbed endpoint, from text.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $richtext   Content to parse.
+	 * @param string $plaintext  Sanitized version of the content.
+	 * @param array  $extra_args Bespoke data for a particular extractor (optional).
+	 *
+	 * @return array {
+	 *     @type array $has Extracted media counts. {
+	 *         @type int $embeds
+	 *     }
+	 *     @type array $embeds Extracted oEmbeds. {
+	 *         Array of extracted media.
+	 *
+	 *         @type string $url oEmbed link.
+	 *     }
+	 * }
+	 */
+	protected function extract_embeds( $richtext, $plaintext, $extra_args = array() ) {
+		$data   = array( 'has' => array( 'embeds' => 0 ), 'embeds' => array() );
+		$embeds = array();
+
+		if ( ! function_exists( '_wp_oembed_get_object' ) ) {
+			require( ABSPATH . WPINC . '/class-oembed.php' );
+		}
+
+
+		// Matches any links on their own lines. They may be oEmbeds.
+		if ( stripos( $richtext, 'http' ) !== false ) {
+			preg_match_all( '#^\s*(https?://[^\s"]+)\s*$#im', $richtext, $matches );
+
+			if ( ! empty( $matches[1] ) ) {
+				$matches[1] = array_unique( $matches[1] );
+				$oembed     = _wp_oembed_get_object();
+
+				foreach ( $matches[1] as $link ) {
+					// Skip data URIs.
+					if ( strtolower( substr( $link, 0, 5 ) ) === 'data:' ) {
+						continue;
+					}
+
+					foreach ( $oembed->providers as $matchmask => $oembed_data ) {
+						list( , $is_regex ) = $oembed_data;
+
+						// Turn asterisk-type provider URLs into regexs.
+						if ( ! $is_regex ) {
+							$matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i';
+							$matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask );
+						}
+
+						// Check whether this "link" is really an oEmbed.
+						if ( preg_match( $matchmask, $link ) ) {
+							$data['embeds'][] = array( 'url' => $link );
+
+							break;
+						}
+					}
+				}
+			}
+		}
+
+		$data['has']['embeds'] = count( $data['embeds'] );
+
+		/**
+		 * Filters embeds extracted from text.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $data       Extracted embeds. See {@link BP_Media_Extractor::extract_embeds()} for format.
+		 * @param string $richtext   Content to parse.
+		 * @param string $plaintext  Copy of $richtext without any markup.
+		 * @param array  $extra_args Bespoke data for a particular extractor.
+		 */
+		return apply_filters( 'bp_media_extractor_embeds', $data, $richtext, $plaintext, $extra_args );
+	}
+
+	/**
+	 * Extract [audio] shortcodes and `<a href="*.mp3">` tags, from text.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @see wp_get_audio_extensions() for supported audio formats.
+	 *
+	 * @param string $richtext   Content to parse.
+	 * @param string $plaintext  Sanitized version of the content.
+	 * @param array  $extra_args Bespoke data for a particular extractor (optional).
+	 *
+	 * @return array {
+	 *     @type array $has Extracted media counts. {
+	 *         @type int $audio
+	 *     }
+	 *     @type array $audio Extracted audio. {
+	 *         Array of extracted media.
+	 *
+	 *         @type string $original The entire shortcode.
+	 *         @type string $source   Media source. Either "html" or "shortcodes".
+	 *         @type string $url      Link to audio.
+	 *     }
+	 * }
+	 */
+	protected function extract_audio( $richtext, $plaintext, $extra_args = array() ) {
+		$data   = array( 'has' => array( 'audio' => 0 ), 'audio' => array() );
+		$audios = $this->extract_shortcodes( $richtext, $plaintext, $extra_args );
+		$links  = $this->extract_links( $richtext, $plaintext, $extra_args );
+
+		$audio_types = wp_get_audio_extensions();
+
+
+		// [audio]
+		$audios = wp_list_filter( $audios['shortcodes'], array( 'type' => 'audio' ) );
+		foreach ( $audios as $audio ) {
+
+			// Media URL can appear as the first parameter inside the shortcode brackets.
+			if ( isset( $audio['attributes']['src'] ) ) {
+				$src_param = 'src';
+			} elseif ( isset( $audio['attributes'][0] ) ) {
+				$src_param = 0;
+			} else {
+				continue;
+			}
+
+			$path = untrailingslashit( parse_url( $audio['attributes'][ $src_param ], PHP_URL_PATH ) );
+
+			foreach ( $audio_types as $extension ) {
+				$extension = '.' . $extension;
+
+				// Check this URL's file extension matches that of an accepted audio format.
+				if ( ! $path || substr( $path, -4 ) !== $extension ) {
+					continue;
+				}
+
+				$data['audio'][] = array(
+					'original' => '[audio src="' . esc_url_raw( $audio['attributes'][ $src_param ] ) . '"]',
+					'source'   => 'shortcodes',
+					'url'      => esc_url_raw( $audio['attributes'][ $src_param ] ),
+				);
+			}
+		}
+
+		// <a href="*.mp3"> tags
+		foreach ( $audio_types as $extension ) {
+			$extension = '.' . $extension;
+
+			foreach ( $links['links'] as $link ) {
+				$path = untrailingslashit( parse_url( $link['url'], PHP_URL_PATH ) );
+
+				// Check this URL's file extension matches that of an accepted audio format.
+				if ( ! $path || substr( $path, -4 ) !== $extension ) {
+					continue;
+				}
+
+				$data['audio'][] = array(
+					'original' => '[audio src="' . esc_url_raw( $link['url'] ) . '"]',  // Build an audio shortcode.
+					'source'   => 'html',
+					'url'      => esc_url_raw( $link['url'] ),
+				);
+			}
+		}
+
+		$data['has']['audio'] = count( $data['audio'] );
+
+		/**
+		 * Filters audio extracted from text.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $data       Extracted audio. See {@link BP_Media_Extractor::extract_audio()} for format.
+		 * @param string $richtext   Content to parse.
+		 * @param string $plaintext  Copy of $richtext without any markup.
+		 * @param array  $extra_args Bespoke data for a particular extractor.
+		 */
+		return apply_filters( 'bp_media_extractor_audio', $data, $richtext, $plaintext, $extra_args );
+	}
+
+	/**
+	 * Extract [video] shortcodes from text.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @see wp_get_video_extensions() for supported video formats.
+	 *
+	 * @param string $richtext   Content to parse.
+	 * @param string $plaintext  Sanitized version of the content.
+	 * @param array  $extra_args Bespoke data for a particular extractor (optional).
+	 *
+	 * @return array {
+	 *     @type array $has Extracted media counts. {
+	 *         @type int $video
+	 *     }
+	 *     @type array $videos Extracted video. {
+	 *         Array of extracted media.
+	 *
+	 *         @type string $source Media source. Currently only "shortcodes".
+	 *         @type string $url    Link to audio.
+	 *     }
+	 * }
+	 */
+	protected function extract_video( $richtext, $plaintext, $extra_args = array() ) {
+		$data   = array( 'has' => array( 'videos' => 0 ), 'videos' => array() );
+		$videos = $this->extract_shortcodes( $richtext, $plaintext, $extra_args );
+
+		$video_types = wp_get_video_extensions();
+
+
+		// [video]
+		$videos = wp_list_filter( $videos['shortcodes'], array( 'type' => 'video' ) );
+		foreach ( $videos as $video ) {
+
+			// Media URL can appear as the first parameter inside the shortcode brackets.
+			if ( isset( $video['attributes']['src'] ) ) {
+				$src_param = 'src';
+			} elseif ( isset( $video['attributes'][0] ) ) {
+				$src_param = 0;
+			} else {
+				continue;
+			}
+
+			$path = untrailingslashit( parse_url( $video['attributes'][ $src_param ], PHP_URL_PATH ) );
+
+			foreach ( $video_types as $extension ) {
+				$extension = '.' . $extension;
+
+				// Check this URL's file extension matches that of an accepted video format (-5 for webm).
+				if ( ! $path || ( substr( $path, -4 ) !== $extension && substr( $path, -5 ) !== $extension ) ) {
+					continue;
+				}
+
+				$data['videos'][] = array(
+					'original' => $video['original'],  // Entire shortcode.
+					'source'   => 'shortcodes',
+					'url'      => esc_url_raw( $video['attributes'][ $src_param ] ),
+				);
+			}
+		}
+
+		$data['has']['videos'] = count( $data['videos'] );
+
+		/**
+		 * Filters videos extracted from text.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $data       Extracted videos. See {@link BP_Media_Extractor::extract_videos()} for format.
+		 * @param string $richtext   Content to parse.
+		 * @param string $plaintext  Copy of $richtext without any markup.
+		 * @param array  $extra_args Bespoke data for a particular extractor.
+		 */
+		return apply_filters( 'bp_media_extractor_videos', $data, $richtext, $plaintext, $extra_args );
+	}
+
+
+	/**
+	 * Helpers and utility methods.
+	 */
+
+	/**
+	 * Extract images in [galleries] shortcodes from text.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $richtext   Content to parse.
+	 * @param string $plaintext  Sanitized version of the content.
+	 * @param array  $extra_args Bespoke data for a particular extractor (optional).
+	 *
+	 * @return array
+	 */
+	protected function extract_images_from_galleries( $richtext, $plaintext, $extra_args = array() ) {
+		if ( ! isset( $extra_args['post'] ) || ! is_a( $extra_args['post'], 'WP_Post' ) ) {
+			$post = new WP_Post( (object) array( 'post_content' => $richtext ) );
+		} else {
+			$post = $extra_args['post'];
+		}
+
+		// We're not using get_post_galleries_images() because it returns thumbnails; we want the original image.
+		$galleries      = get_post_galleries( $post, false );
+		$galleries_data = array();
+
+		if ( ! empty( $galleries ) ) {
+			// Validate the size of the images requested.
+			if ( isset( $extra_args['width'] ) ) {
+
+				// A width was specified but not a height, so calculate it assuming a 4:3 ratio.
+				if ( ! isset( $extra_args['height'] ) && ctype_digit( $extra_args['width'] ) ) {
+					$extra_args['height'] = round( ( $extra_args['width'] / 4 ) * 3 );
+				}
+
+				if ( ctype_digit( $extra_args['width'] ) ) {
+					$image_size = array( $extra_args['width'], $extra_args['height'] );
+				} else {
+					$image_size = $extra_args['width'];  // e.g. "thumb", "medium".
+				}
+
+			} else {
+				$image_size = 'full';
+			}
+
+			/**
+			 * There are two variants of gallery shortcode.
+			 *
+			 * One kind specifies the image (post) IDs via an `ids` parameter.
+			 * The other gets the image IDs from post_type=attachment and post_parent=get_the_ID().
+			 */
+
+			foreach ( $galleries as $gallery_id => $gallery ) {
+				$data   = array();
+				$images = array();
+
+				// Gallery ids= variant.
+				if ( isset( $gallery['ids'] ) ) {
+					$images = wp_parse_id_list( $gallery['ids'] );
+
+				// Gallery post_parent variant.
+				} elseif ( isset( $extra_args['post'] ) ) {
+					$images = wp_parse_id_list(
+						get_children( array(
+							'fields'         => 'ids',
+							'order'          => 'ASC',
+							'orderby'        => 'menu_order ID',
+							'post_mime_type' => 'image',
+							'post_parent'    => $extra_args['post']->ID,
+							'post_status'    => 'inherit',
+							'post_type'      => 'attachment',
+						) )
+					);
+				}
+
+				// Extract the data we need from each image in this gallery.
+				foreach ( $images as $image_id ) {
+					$image  = wp_get_attachment_image_src( $image_id, $image_size );
+					$data[] = array(
+						'url'    => $image[0],
+						'width'  => $image[1],
+						'height' => $image[2],
+
+						'gallery_id' => 1 + $gallery_id,
+					);
+				}
+
+				$galleries_data[] = $data;
+			}
+		}
+
+		/**
+		 * Filters image galleries extracted from text.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $galleries_data Galleries. See {@link BP_Media_Extractor::extract_images_from_galleries()}.
+		 * @param string $richtext       Content to parse.
+		 * @param string $plaintext      Copy of $richtext without any markup.
+		 * @param array  $extra_args     Bespoke data for a particular extractor.
+		 */
+		return apply_filters( 'bp_media_extractor_galleries', $galleries_data, $richtext, $plaintext, $extra_args );
+	}
+
+	/**
+	 * Extract the featured image from a Post.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $richtext   Content to parse.
+	 * @param string $plaintext  Sanitized version of the content.
+	 * @param array  $extra_args Contains data that an implementation might need beyond the defaults.
+	 *
+	 * @return array
+	 */
+	protected function extract_images_from_featured_images( $richtext, $plaintext, $extra_args ) {
+		$image = array();
+		$thumb = 0;
+
+		if ( isset( $extra_args['post'] ) ) {
+			$thumb = (int) get_post_thumbnail_id( $extra_args['post']->ID );
+		}
+
+		if ( $thumb ) {
+			// Validate the size of the images requested.
+			if ( isset( $extra_args['width'] ) ) {
+				if ( ! isset( $extra_args['height'] ) && ctype_digit( $extra_args['width'] ) ) {
+					// A width was specified but not a height, so calculate it assuming a 4:3 ratio.
+					$extra_args['height'] = round( ( $extra_args['width'] / 4 ) * 3 );
+				}
+
+				if ( ctype_digit( $extra_args['width'] ) ) {
+					$image_size = array( $extra_args['width'], $extra_args['height'] );
+				} else {
+					$image_size = $extra_args['width'];  // e.g. "thumb", "medium".
+				}
+			} else {
+				$image_size = 'full';
+			}
+
+			$image = wp_get_attachment_image_src( $thumb, $image_size );
+		}
+
+		/**
+		 * Filters featured images extracted from a WordPress Post.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $image      Extracted images. See {@link BP_Media_Extractor_Post::extract_images()} for format.
+		 * @param string $richtext   Content to parse.
+		 * @param string $plaintext  Copy of $richtext without any markup.
+		 * @param array  $extra_args Bespoke data for a particular extractor.
+		 */
+		return apply_filters( 'bp_media_extractor_featured_images', $image, $richtext, $plaintext, $extra_args );
+	}
+
+	/**
+	 * Sanitize and format raw content to prepare for content extraction.
+	 *
+	 * HTML tags and shortcodes are removed, and HTML entities are decoded.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $richtext
+	 *
+	 * @return string
+	 */
+	protected function strip_markup( $richtext ) {
+		$plaintext = strip_shortcodes( html_entity_decode( strip_tags( $richtext ) ) );
+
+		/**
+		 * Filters the generated plain text version of the content passed to the extractor.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $plaintext Generated plain text.
+		 * @param string $richtext  Original content.
+		 */
+		return apply_filters( 'bp_media_extractor_strip_markup', $plaintext, $richtext );
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-members-suggestions.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-members-suggestions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-members-suggestions.php	(revision 10410)
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Adds support for user at-mentions to the Suggestions API.
+ *
+ * This class is in the Core component because it's required by a class in the Groups component,
+ * and Groups is loaded before Members (alphabetical order).
+ *
+ * @since 2.1.0
+ */
+class BP_Members_Suggestions extends BP_Suggestions {
+
+	/**
+	 * Default arguments for this suggestions service.
+	 *
+	 * @since 2.1.0
+	 * @var array $args {
+	 *     @type int    $limit        Maximum number of results to display. Default: 16.
+	 *     @type bool   $only_friends If true, only match the current user's friends. Default: false.
+	 *     @type string $term         The suggestion service will try to find results that contain this string.
+	 *                                Mandatory.
+	 * }
+	 */
+	protected $default_args = array(
+		'limit'        => 10,
+		'only_friends' => false,
+		'term'         => '',
+		'type'         => '',
+	);
+
+
+	/**
+	 * Validate and sanitise the parameters for the suggestion service query.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
+	 */
+	public function validate() {
+		$this->args['only_friends'] = (bool) $this->args['only_friends'];
+
+		/**
+		 * Filters the members suggestions args for the current user.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param array                  $args Array of arguments for the member suggestions.
+		 * @param BP_Members_Suggestions $this Current BP_Members_Suggestions instance.
+		 */
+		$this->args                 = apply_filters( 'bp_members_suggestions_args', $this->args, $this );
+
+		// Check for invalid or missing mandatory parameters.
+		if ( $this->args['only_friends'] && ( ! bp_is_active( 'friends' ) || ! is_user_logged_in() ) ) {
+			return new WP_Error( 'missing_requirement' );
+		}
+
+		/**
+		 * Filters the validation status for the suggestion service query.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param bool|WP_Error          $value Results of validation check.
+		 * @param BP_Members_Suggestions $this  Current BP_Members_Suggestions instance.
+		 */
+		return apply_filters( 'bp_members_suggestions_validate_args', parent::validate(), $this );
+	}
+
+	/**
+	 * Find and return a list of username suggestions that match the query.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
+	 */
+	public function get_suggestions() {
+		$user_query = array(
+			'count_total'     => '',  // Prevents total count
+			'populate_extras' => false,
+			'type'            => 'alphabetical',
+
+			'page'            => 1,
+			'per_page'        => $this->args['limit'],
+			'search_terms'    => $this->args['term'],
+			'search_wildcard' => 'right',
+		);
+
+		// Only return matches of friends of this user.
+		if ( $this->args['only_friends'] && is_user_logged_in() ) {
+			$user_query['user_id'] = get_current_user_id();
+		}
+
+		/**
+		 * Filters the members suggestions query args.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param array                  $user_query Array of query arguments.
+		 * @param BP_Members_Suggestions $this       Current BP_Members_Suggestions instance.
+		 */
+		$user_query = apply_filters( 'bp_members_suggestions_query_args', $user_query, $this );
+		if ( is_wp_error( $user_query ) ) {
+			return $user_query;
+		}
+
+
+		$user_query = new BP_User_Query( $user_query );
+		$results    = array();
+
+		foreach ( $user_query->results as $user ) {
+			$result        = new stdClass();
+			$result->ID    = $user->user_nicename;
+			$result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
+			$result->name  = bp_core_get_user_displayname( $user->ID );
+
+			$results[] = $result;
+		}
+
+		/**
+		 * Filters the members suggestions results.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param array                  $results Array of users to suggest.
+		 * @param BP_Members_Suggestions $this    Current BP_Members_Suggestions instance.
+		 */
+		return apply_filters( 'bp_members_suggestions_get_suggestions', $results, $this );
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-recursive-query.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-recursive-query.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-recursive-query.php	(revision 10410)
@@ -0,0 +1,229 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Base class for creating query classes that generate SQL fragments for filtering results based on recursive query params.
+ *
+ * @since 2.2.0
+ */
+abstract class BP_Recursive_Query {
+
+	/**
+	 * Query arguments passed to the constructor.
+	 *
+	 * @since 2.2.0
+	 * @var array
+	 */
+	public $queries = array();
+
+	/**
+	 * Generate SQL clauses to be appended to a main query.
+	 *
+	 * Extending classes should call this method from within a publicly
+	 * accessible get_sql() method, and manipulate the SQL as necessary.
+	 * For example, {@link BP_XProfile_Query::get_sql()} is merely a wrapper for
+	 * get_sql_clauses(), while {@link BP_Activity_Query::get_sql()} discards
+	 * the empty 'join' clause, and only passes the 'where' clause.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @return array
+	 */
+	protected function get_sql_clauses() {
+		$sql = $this->get_sql_for_query( $this->queries );
+
+		if ( ! empty( $sql['where'] ) ) {
+			$sql['where'] = ' AND ' . "\n" . $sql['where'] . "\n";
+		}
+
+		return $sql;
+	}
+
+	/**
+	 * Generate SQL clauses for a single query array.
+	 *
+	 * If nested subqueries are found, this method recurses the tree to
+	 * produce the properly nested SQL.
+	 *
+	 * Subclasses generally do not need to call this method. It is invoked
+	 * automatically from get_sql_clauses().
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param  array $query Query to parse.
+	 * @param  int   $depth Optional. Number of tree levels deep we
+	 *                      currently are. Used to calculate indentation.
+	 * @return array
+	 */
+	protected function get_sql_for_query( $query, $depth = 0 ) {
+		$sql_chunks = array(
+			'join'  => array(),
+			'where' => array(),
+		);
+
+		$sql = array(
+			'join'  => '',
+			'where' => '',
+		);
+
+		$indent = '';
+		for ( $i = 0; $i < $depth; $i++ ) {
+			$indent .= "\t";
+		}
+
+		foreach ( $query as $key => $clause ) {
+			if ( 'relation' === $key ) {
+				$relation = $query['relation'];
+			} elseif ( is_array( $clause ) ) {
+				// This is a first-order clause
+				if ( $this->is_first_order_clause( $clause ) ) {
+					$clause_sql = $this->get_sql_for_clause( $clause, $query );
+
+					$where_count = count( $clause_sql['where'] );
+					if ( ! $where_count ) {
+						$sql_chunks['where'][] = '';
+					} elseif ( 1 === $where_count ) {
+						$sql_chunks['where'][] = $clause_sql['where'][0];
+					} else {
+						$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
+					}
+
+					$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
+				// This is a subquery
+				} else {
+					$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
+
+					$sql_chunks['where'][] = $clause_sql['where'];
+					$sql_chunks['join'][]  = $clause_sql['join'];
+				}
+			}
+		}
+
+		// Filter empties
+		$sql_chunks['join']  = array_filter( $sql_chunks['join'] );
+		$sql_chunks['where'] = array_filter( $sql_chunks['where'] );
+
+		if ( empty( $relation ) ) {
+			$relation = 'AND';
+		}
+
+		if ( ! empty( $sql_chunks['join'] ) ) {
+			$sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
+		}
+
+		if ( ! empty( $sql_chunks['where'] ) ) {
+			$sql['where'] = '( ' . "\n\t" . $indent . implode( ' ' . "\n\t" . $indent . $relation . ' ' . "\n\t" . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')' . "\n";
+		}
+
+		return $sql;
+	}
+
+	/**
+	 * Recursive-friendly query sanitizer.
+	 *
+	 * Ensures that each query-level clause has a 'relation' key, and that
+	 * each first-order clause contains all the necessary keys from
+	 * $defaults.
+	 *
+	 * Extend this method if your class uses different sanitizing logic.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $queries Array of query clauses.
+	 *
+	 * @return array Sanitized array of query clauses.
+	 */
+	protected function sanitize_query( $queries ) {
+		$clean_queries = array();
+
+		if ( ! is_array( $queries ) ) {
+			return $clean_queries;
+		}
+
+		foreach ( $queries as $key => $query ) {
+			if ( 'relation' === $key ) {
+				$relation = $query;
+
+			} elseif ( ! is_array( $query ) ) {
+				continue;
+
+			// First-order clause.
+			} elseif ( $this->is_first_order_clause( $query ) ) {
+				if ( isset( $query['value'] ) && array() === $query['value'] ) {
+					unset( $query['value'] );
+				}
+
+				$clean_queries[] = $query;
+
+			// Otherwise, it's a nested query, so we recurse.
+			} else {
+				$cleaned_query = $this->sanitize_query( $query );
+
+				if ( ! empty( $cleaned_query ) ) {
+					$clean_queries[] = $cleaned_query;
+				}
+			}
+		}
+
+		if ( empty( $clean_queries ) ) {
+			return $clean_queries;
+		}
+
+		// Sanitize the 'relation' key provided in the query.
+		if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
+			$clean_queries['relation'] = 'OR';
+
+		/*
+		 * If there is only a single clause, call the relation 'OR'.
+		 * This value will not actually be used to join clauses, but it
+		 * simplifies the logic around combining key-only queries.
+		 */
+		} elseif ( 1 === count( $clean_queries ) ) {
+			$clean_queries['relation'] = 'OR';
+
+		// Default to AND.
+		} else {
+			$clean_queries['relation'] = 'AND';
+		}
+
+		return $clean_queries;
+	}
+
+	/**
+	 * Generate JOIN and WHERE clauses for a first-order clause.
+	 *
+	 * Must be overridden in a subclass.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $clause       Array of arguments belonging to the clause.
+	 * @param array $parent_query Parent query to which the clause belongs.
+	 *
+	 * @return array {
+	 *     @type array $join  Array of subclauses for the JOIN statement.
+	 *     @type array $where Array of subclauses for the WHERE statement.
+	 * }
+	 */
+	abstract protected function get_sql_for_clause( $clause, $parent_query );
+
+	/**
+	 * Determine whether a clause is first-order.
+	 *
+	 * Must be overridden in a subclass.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $query Clause to check.
+	 *
+	 * @return bool
+	 */
+	abstract protected function is_first_order_clause( $query );
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-suggestions.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-suggestions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-suggestions.php	(revision 10410)
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Base class for the BuddyPress Suggestions API.
+ *
+ * Originally built to power BuddyPress' at-mentions suggestions, it's flexible enough to be used
+ * for similar kinds of future core requirements, or those desired by third-party developers.
+ *
+ * To implement a new suggestions service, create a new class that extends this one, and update
+ * the list of default services in {@link bp_core_get_suggestions()}. If you're building a plugin,
+ * it's recommend that you use the `bp_suggestions_services` filter to do this. :)
+ *
+ * While the implementation of the query logic is left to you, it should be as quick and efficient
+ * as possible. When implementing the abstract methods in this class, pay close attention to the
+ * recommendations provided in the phpDoc blocks, particularly the expected return types.
+ *
+ * @since 2.1.0
+ */
+abstract class BP_Suggestions {
+
+	/**
+	 * Default arguments common to all suggestions services.
+	 *
+	 * If your custom service requires further defaults, add them here.
+	 *
+	 * @since 2.1.0
+	 * @var array
+	 */
+	protected $default_args = array(
+		'limit' => 16,
+		'term'  => '',
+		'type'  => '',
+	);
+
+	/**
+	 * Holds the arguments for the query (about to made to the suggestions service).
+	 *
+	 * This includes `$default_args`, as well as the user-supplied values.
+	 *
+	 * @since 2.1.0
+	 * @var array
+	 */
+	protected $args = array(
+	);
+
+
+	/**
+	 * Constructor.
+	 *
+	 * @param array $args Optional. If set, used as the parameters for the suggestions service query.
+	 * @since 2.1.0
+	 */
+	public function __construct( array $args = array() ) {
+		if ( ! empty( $args ) ) {
+			$this->set_query( $args );
+		}
+	}
+
+	/**
+	 * Set the parameters for the suggestions service query.
+	 *
+	 * @param array $args {
+	 *     @type int    $limit Maximum number of results to display. Optional, default: 16.
+	 *     @type string $type  The name of the suggestion service to use for the request. Mandatory.
+	 *     @type string $term  The suggestion service will try to find results that contain this string.
+	 *                         Mandatory.
+	 * }
+	 * @since 2.1.0
+	 */
+	public function set_query( array $args = array() ) {
+		$this->args = wp_parse_args( $args, $this->default_args );
+	}
+
+	/**
+	 * Validate and sanitise the parameters for the suggestion service query.
+	 *
+	 * Be sure to call this class' version of this method when implementing it in your own service.
+	 * If validation fails, you must return a WP_Error object.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
+	 */
+	public function validate() {
+		$this->args['limit'] = absint( $this->args['limit'] );
+		$this->args['term']  = trim( sanitize_text_field( $this->args['term'] ) );
+
+		/**
+		 * Filters the arguments to be validated for the BP_Suggestions query.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param BP_Suggestions $value Arguments to be validated.
+		 * @param BP_Suggestions $this  Current BP_Suggestions instance.
+		 */
+		$this->args          = apply_filters( 'bp_suggestions_args', $this->args, $this );
+
+		// Check for invalid or missing mandatory parameters.
+		if ( ! $this->args['limit'] || ! $this->args['term'] ) {
+			return new WP_Error( 'missing_parameter' );
+		}
+
+		// Check for blocked users (e.g. deleted accounts, or spammers).
+		if ( is_user_logged_in() && ! bp_is_user_active( get_current_user_id() ) ) {
+			return new WP_Error( 'invalid_user' );
+		}
+
+		/**
+		 * Filters the status of validation for the BP_Suggestions query.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param bool           $value Whether or not the values are valid.
+		 * @param BP_Suggestions $this  Current BP_Suggestions instance.
+		 */
+		return apply_filters( 'bp_suggestions_validate_args', true, $this );
+	}
+
+	/**
+	 * Find and return a list of suggestions that match the query.
+	 *
+	 * The return type is important. If no matches are found, an empty array must be returned.
+	 * Matches must be returned as objects in an array.
+	 *
+	 * The object format for each match must be: { 'ID': string, 'image': string, 'name': string }
+	 * For example: { 'ID': 'admin', 'image': 'http://example.com/logo.png', 'name': 'Name Surname' }
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
+	 */
+	abstract public function get_suggestions();
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-user-query.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-user-query.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-user-query.php	(revision 10410)
@@ -0,0 +1,822 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BuddyPress User Query class.
+ *
+ * Used for querying users in a BuddyPress context, in situations where WP_User_Query won't do the trick:
+ * Member directories, the Friends component, etc.
+ *
+ * @since 1.7.0
+ *
+ * @param array $query {
+ *     Query arguments. All items are optional.
+ *     @type string            $type                Determines sort order. Select from 'newest', 'active', 'online',
+ *                                                  'random', 'popular', 'alphabetical'. Default: 'newest'.
+ *     @type int               $per_page            Number of results to return. Default: 0 (no limit).
+ *     @type int               $page                Page offset (together with $per_page). Default: 1.
+ *     @type int               $user_id             ID of a user. If present, and if the friends component is activated,
+ *                                                  results will be limited to the friends of that user. Default: 0.
+ *     @type string|bool       $search_terms        Terms to search by. Search happens across xprofile fields. Requires
+ *                                                  XProfile component. Default: false.
+ *     @type string            $search_wildcard     When searching with $search_terms, set where wildcards around the
+ *                                                  term should be positioned. Accepts 'both', 'left', 'right'.
+ *                                                  Default: 'both'.
+ *     @type array|string|bool $include             An array or comma-separated list of user IDs to which query should
+ *                                                  be limited. Default: false.
+ *     @type array|string|bool $exclude             An array or comma-separated list of user IDs that will be excluded
+ *                                                  from query results. Default: false.
+ *     @type array|string|bool $user_ids            An array or comma-separated list of IDs corresponding to the users
+ *                                                  that should be returned. When this parameter is passed, it will
+ *                                                  override all others; BP User objects will be constructed using these
+ *                                                  IDs only. Default: false.
+ *     @type array|string      $member_type         Array or comma-separated list of member types to limit results to.
+ *     @type array|string      $member_type__in     Array or comma-separated list of member types to limit results to.
+ *     @type array|string      $member_type__not_in Array or comma-separated list of member types that will be
+ *			                                             excluded from results.
+ *     @type string|bool       $meta_key            Limit results to users that have usermeta associated with this meta_key.
+ *                                                  Usually used with $meta_value. Default: false.
+ *     @type string|bool       $meta_value          When used with $meta_key, limits results to users whose usermeta value
+ *                                                  associated with $meta_key matches $meta_value. Default: false.
+ *     @type array             $xprofile_query      Filter results by xprofile data. Requires the xprofile component.
+ *                                                  See {@see BP_XProfile_Query} for details.
+ *     @type bool              $populate_extras     True if you want to fetch extra metadata
+ *                                                  about returned users, such as total group and friend counts.
+ *     @type string            $count_total         Determines how BP_User_Query will do a count of total users matching
+ *                                                  the other filter criteria. Default value is 'count_query', which
+ *                                                  does a separate SELECT COUNT query to determine the total.
+ *                                                  'sql_count_found_rows' uses SQL_COUNT_FOUND_ROWS and
+ *                                                  SELECT FOUND_ROWS(). Pass an empty string to skip the total user
+ *                                                  count query.
+ * }
+ */
+class BP_User_Query {
+
+	/** Variables *************************************************************/
+
+	/**
+	 * Unaltered params as passed to the constructor.
+	 *
+	 * @since 1.8.0
+	 * @var array
+	 */
+	public $query_vars_raw = array();
+
+	/**
+	 * Array of variables to query with.
+	 *
+	 * @since 1.7.0
+	 * @var array
+	 */
+	public $query_vars = array();
+
+	/**
+	 * List of found users and their respective data.
+	 *
+	 * @since 1.7.0
+	 * @var array
+	 */
+	public $results = array();
+
+	/**
+	 * Total number of found users for the current query.
+	 *
+	 * @since 1.7.0
+	 * @var int
+	 */
+	public $total_users = 0;
+
+	/**
+	 * List of found user IDs.
+	 *
+	 * @since 1.7.0
+	 * @var array
+	 */
+	public $user_ids = array();
+
+	/**
+	 * SQL clauses for the user ID query.
+	 *
+	 * @since 1.7.0
+	 * @var array
+	 */
+	public $uid_clauses = array();
+
+	/**
+	 * SQL table where the user ID is being fetched from.
+	 *
+	 * @since 2.2.0
+	 * @var string
+	 */
+	public $uid_table = '';
+
+	/**
+	 * SQL database column name to order by.
+	 *
+	 * @since 1.7.0
+	 * @var string
+	 */
+	public $uid_name = '';
+
+	/**
+	 * Standard response when the query should not return any rows.
+	 *
+	 * @since 1.7.0
+	 * @var string
+	 */
+	protected $no_results = array( 'join' => '', 'where' => '0 = 1' );
+
+
+	/** Methods ***************************************************************/
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string|array $query See {@link BP_User_Query}.
+	 */
+	public function __construct( $query = null ) {
+
+		// Store the raw query vars for later access
+		$this->query_vars_raw = $query;
+
+		// Allow extending classes to register action/filter hooks
+		$this->setup_hooks();
+
+		if ( ! empty( $this->query_vars_raw ) ) {
+			$this->query_vars = wp_parse_args( $this->query_vars_raw, array(
+				'type'                => 'newest',
+				'per_page'            => 0,
+				'page'                => 1,
+				'user_id'             => 0,
+				'search_terms'        => false,
+				'search_wildcard'     => 'both',
+				'include'             => false,
+				'exclude'             => false,
+				'user_ids'            => false,
+				'member_type'         => '',
+				'member_type__in'     => '',
+				'member_type__not_in' => '',
+				'meta_key'            => false,
+				'meta_value'          => false,
+				'xprofile_query'      => false,
+				'populate_extras'     => true,
+				'count_total'         => 'count_query'
+			) );
+
+			/**
+			 * Fires before the construction of the BP_User_Query query.
+			 *
+			 * @since 1.7.0
+			 *
+			 * @param BP_User_Query $this Current instance of the BP_User_Query. Passed by reference.
+			 */
+			do_action_ref_array( 'bp_pre_user_query_construct', array( &$this ) );
+
+			// Get user ids
+			// If the user_ids param is present, we skip the query
+			if ( false !== $this->query_vars['user_ids'] ) {
+				$this->user_ids = wp_parse_id_list( $this->query_vars['user_ids'] );
+			} else {
+				$this->prepare_user_ids_query();
+				$this->do_user_ids_query();
+			}
+		}
+
+		// Bail if no user IDs were found
+		if ( empty( $this->user_ids ) ) {
+			return;
+		}
+
+		// Fetch additional data. First, using WP_User_Query
+		$this->do_wp_user_query();
+
+		// Get BuddyPress specific user data
+		$this->populate_extras();
+	}
+
+	/**
+	 * Allow extending classes to set up action/filter hooks.
+	 *
+	 * When extending BP_User_Query, you may need to use some of its
+	 * internal hooks to modify the output. It's not convenient to call
+	 * add_action() or add_filter() in your class constructor, because
+	 * BP_User_Query::__construct() contains a fair amount of logic that
+	 * you may not want to override in your class. Define this method in
+	 * your own class if you need a place where your extending class can
+	 * add its hooks early in the query-building process. See
+	 * {@link BP_Group_Member_Query::setup_hooks()} for an example.
+	 *
+	 * @since 1.8.0
+	 */
+	public function setup_hooks() {}
+
+	/**
+	 * Prepare the query for user_ids.
+	 *
+	 * @since 1.7.0
+	 */
+	public function prepare_user_ids_query() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		// Default query variables used here
+		$type         = '';
+		$per_page     = 0;
+		$page         = 1;
+		$user_id      = 0;
+		$include      = false;
+		$search_terms = false;
+		$exclude      = false;
+		$meta_key     = false;
+		$meta_value   = false;
+
+		extract( $this->query_vars );
+
+		// Setup the main SQL query container
+		$sql = array(
+			'select'  => '',
+			'where'   => array(),
+			'orderby' => '',
+			'order'   => '',
+			'limit'   => ''
+		);
+
+		/** TYPE **************************************************************/
+
+		// Determines the sort order, which means it also determines where the
+		// user IDs are drawn from (the SELECT and WHERE statements)
+		switch ( $type ) {
+
+			// 'online' query happens against the last_activity usermeta key
+			// Filter 'bp_user_query_online_interval' to modify the
+			// number of minutes used as an interval
+			case 'online' :
+				$this->uid_name = 'user_id';
+				$this->uid_table = $bp->members->table_name_last_activity;
+				$sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
+				$sql['where'][] = $wpdb->prepare( "u.component = %s AND u.type = 'last_activity'", buddypress()->members->id );
+
+				/**
+				 * Filters the threshold for activity timestamp minutes since to indicate online status.
+				 *
+				 * @since 1.8.0
+				 *
+				 * @param int $value Amount of minutes for threshold. Default 15.
+				 */
+				$sql['where'][] = $wpdb->prepare( "u.date_recorded >= DATE_SUB( UTC_TIMESTAMP(), INTERVAL %d MINUTE )", apply_filters( 'bp_user_query_online_interval', 15 ) );
+				$sql['orderby'] = "ORDER BY u.date_recorded";
+				$sql['order']   = "DESC";
+
+				break;
+
+			// 'active', 'newest', and 'random' queries
+			// all happen against the last_activity usermeta key
+			case 'active' :
+			case 'newest' :
+			case 'random' :
+				$this->uid_name = 'user_id';
+				$this->uid_table = $bp->members->table_name_last_activity;
+				$sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
+				$sql['where'][] = $wpdb->prepare( "u.component = %s AND u.type = 'last_activity'", buddypress()->members->id );
+
+				if ( 'newest' == $type ) {
+					$sql['orderby'] = "ORDER BY u.user_id";
+					$sql['order'] = "DESC";
+				} elseif ( 'random' == $type ) {
+					$sql['orderby'] = "ORDER BY rand()";
+				} else {
+					$sql['orderby'] = "ORDER BY u.date_recorded";
+					$sql['order'] = "DESC";
+				}
+
+				break;
+
+			// 'popular' sorts by the 'total_friend_count' usermeta
+			case 'popular' :
+				$this->uid_name = 'user_id';
+				$this->uid_table = $wpdb->usermeta;
+				$sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
+				$sql['where'][] = $wpdb->prepare( "u.meta_key = %s", bp_get_user_meta_key( 'total_friend_count' ) );
+				$sql['orderby'] = "ORDER BY CONVERT(u.meta_value, SIGNED)";
+				$sql['order']   = "DESC";
+
+				break;
+
+			// 'alphabetical' sorts depend on the xprofile setup
+			case 'alphabetical' :
+
+				// We prefer to do alphabetical sorts against the display_name field
+				// of wp_users, because the table is smaller and better indexed. We
+				// can do so if xprofile sync is enabled, or if xprofile is inactive.
+				//
+				// @todo remove need for bp_is_active() check
+				if ( ! bp_disable_profile_sync() || ! bp_is_active( 'xprofile' ) ) {
+					$this->uid_name = 'ID';
+					$this->uid_table = $wpdb->users;
+					$sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
+					$sql['orderby'] = "ORDER BY u.display_name";
+					$sql['order']   = "ASC";
+
+				// When profile sync is disabled, alphabetical sorts must happen against
+				// the xprofile table
+				} else {
+					$this->uid_name = 'user_id';
+					$this->uid_table = $bp->profile->table_name_data;
+					$sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
+					$sql['where'][] = $wpdb->prepare( "u.field_id = %d", bp_xprofile_fullname_field_id() );
+					$sql['orderby'] = "ORDER BY u.value";
+					$sql['order']   = "ASC";
+				}
+
+				// Alphabetical queries ignore last_activity, while BP uses last_activity
+				// to infer spam/deleted/non-activated users. To ensure that these users
+				// are filtered out, we add an appropriate sub-query.
+				$sql['where'][] = "u.{$this->uid_name} IN ( SELECT ID FROM {$wpdb->users} WHERE " . bp_core_get_status_sql( '' ) . " )";
+
+				break;
+
+			// Any other 'type' falls through
+			default :
+				$this->uid_name = 'ID';
+				$this->uid_table = $wpdb->users;
+				$sql['select']  = "SELECT u.{$this->uid_name} as id FROM {$this->uid_table} u";
+
+				// In this case, we assume that a plugin is
+				// handling order, so we leave those clauses
+				// blank
+
+				break;
+		}
+
+		/** WHERE *************************************************************/
+
+		// 'include' - User ids to include in the results
+		$include     = false !== $include ? wp_parse_id_list( $include ) : array();
+		$include_ids = $this->get_include_ids( $include );
+		if ( ! empty( $include_ids ) ) {
+			$include_ids    = implode( ',', wp_parse_id_list( $include_ids ) );
+			$sql['where'][] = "u.{$this->uid_name} IN ({$include_ids})";
+		}
+
+		// 'exclude' - User ids to exclude from the results
+		if ( false !== $exclude ) {
+			$exclude_ids    = implode( ',', wp_parse_id_list( $exclude ) );
+			$sql['where'][] = "u.{$this->uid_name} NOT IN ({$exclude_ids})";
+		}
+
+		// 'user_id' - When a user id is passed, limit to the friends of the user
+		// @todo remove need for bp_is_active() check
+		if ( ! empty( $user_id ) && bp_is_active( 'friends' ) ) {
+			$friend_ids = friends_get_friend_user_ids( $user_id );
+			$friend_ids = implode( ',', wp_parse_id_list( $friend_ids ) );
+
+			if ( ! empty( $friend_ids ) ) {
+				$sql['where'][] = "u.{$this->uid_name} IN ({$friend_ids})";
+
+			// If the user has no friends, the query should always
+			// return no users
+			} else {
+				$sql['where'][] = $this->no_results['where'];
+			}
+		}
+
+		/** Search Terms ******************************************************/
+
+		// 'search_terms' searches user_login and user_nicename
+		// xprofile field matches happen in bp_xprofile_bp_user_query_search()
+		if ( false !== $search_terms ) {
+			$search_terms = bp_esc_like( wp_kses_normalize_entities( $search_terms ) );
+
+			if ( $search_wildcard === 'left' ) {
+				$search_terms_nospace = '%' . $search_terms;
+				$search_terms_space   = '%' . $search_terms . ' %';
+			} elseif ( $search_wildcard === 'right' ) {
+				$search_terms_nospace =        $search_terms . '%';
+				$search_terms_space   = '% ' . $search_terms . '%';
+			} else {
+				$search_terms_nospace = '%' . $search_terms . '%';
+				$search_terms_space   = '%' . $search_terms . '%';
+			}
+
+			$sql['where']['search'] = $wpdb->prepare(
+				"u.{$this->uid_name} IN ( SELECT ID FROM {$wpdb->users} WHERE ( user_login LIKE %s OR user_login LIKE %s OR user_nicename LIKE %s OR user_nicename LIKE %s ) )",
+				$search_terms_nospace,
+				$search_terms_space,
+				$search_terms_nospace,
+				$search_terms_space
+			);
+		}
+
+		// Only use $member_type__in if $member_type is not set.
+		if ( empty( $member_type ) && ! empty( $member_type__in ) ) {
+			$member_type = $member_type__in;
+		}
+
+		// Member types to exclude. Note that this takes precedence over inclusions.
+		if ( ! empty( $member_type__not_in ) ) {
+			$member_type_clause = $this->get_sql_clause_for_member_types( $member_type__not_in, 'NOT IN' );
+
+		// Member types to include.
+		} elseif ( ! empty( $member_type ) ) {
+			$member_type_clause = $this->get_sql_clause_for_member_types( $member_type, 'IN' );
+		}
+
+		if ( ! empty( $member_type_clause ) ) {
+			$sql['where']['member_type'] = $member_type_clause;
+		}
+
+		// 'meta_key', 'meta_value' allow usermeta search
+		// To avoid global joins, do a separate query
+		if ( false !== $meta_key ) {
+			$meta_sql = $wpdb->prepare( "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = %s", $meta_key );
+
+			if ( false !== $meta_value ) {
+				$meta_sql .= $wpdb->prepare( " AND meta_value = %s", $meta_value );
+			}
+
+			$found_user_ids = $wpdb->get_col( $meta_sql );
+
+			if ( ! empty( $found_user_ids ) ) {
+				$sql['where'][] = "u.{$this->uid_name} IN (" . implode( ',', wp_parse_id_list( $found_user_ids ) ) . ")";
+			} else {
+				$sql['where'][] = '1 = 0';
+			}
+		}
+
+		// 'per_page', 'page' - handles LIMIT
+		if ( !empty( $per_page ) && !empty( $page ) ) {
+			$sql['limit'] = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $per_page ), intval( $per_page ) );
+		} else {
+			$sql['limit'] = '';
+		}
+
+		/**
+		 * Filters the clauses for the user query.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array         $sql  Array of SQL clauses to be used in the query.
+		 * @param BP_User_Query $this Current BP_User_Query instance.
+		 */
+		$sql = apply_filters_ref_array( 'bp_user_query_uid_clauses', array( $sql, &$this ) );
+
+		// Assemble the query chunks
+		$this->uid_clauses['select']  = $sql['select'];
+		$this->uid_clauses['where']   = ! empty( $sql['where'] ) ? 'WHERE ' . implode( ' AND ', $sql['where'] ) : '';
+		$this->uid_clauses['orderby'] = $sql['orderby'];
+		$this->uid_clauses['order']   = $sql['order'];
+		$this->uid_clauses['limit']   = $sql['limit'];
+
+		/**
+		 * Fires before the BP_User_Query query is made.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param BP_User_Query $this Current BP_User_Query instance. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_pre_user_query', array( &$this ) );
+	}
+
+	/**
+	 * Query for IDs of users that match the query parameters.
+	 *
+	 * Perform a database query to specifically get only user IDs, using
+	 * existing query variables set previously in the constructor.
+	 *
+	 * Also used to quickly perform user total counts.
+	 *
+	 * @since 1.7.0
+	 */
+	public function do_user_ids_query() {
+		global $wpdb;
+
+		// If counting using SQL_CALC_FOUND_ROWS, set it up here
+		if ( 'sql_calc_found_rows' == $this->query_vars['count_total'] ) {
+			$this->uid_clauses['select'] = str_replace( 'SELECT', 'SELECT SQL_CALC_FOUND_ROWS', $this->uid_clauses['select'] );
+		}
+
+		// Get the specific user ids
+		$this->user_ids = $wpdb->get_col( "{$this->uid_clauses['select']} {$this->uid_clauses['where']} {$this->uid_clauses['orderby']} {$this->uid_clauses['order']} {$this->uid_clauses['limit']}" );
+
+		// Get the total user count
+		if ( 'sql_calc_found_rows' == $this->query_vars['count_total'] ) {
+
+			/**
+			 * Filters the found user SQL statements before query.
+			 *
+			 * If "sql_calc_found_rows" is the provided count_total query var
+			 * then the value will be "SELECT FOUND_ROWS()". Otherwise it will
+			 * use a "SELECT COUNT()" query statement.
+			 *
+			 * @since 1.7.0
+			 *
+			 * @param string        $value SQL statement to select FOUND_ROWS().
+			 * @param BP_User_Query $this  Current BP_User_Query instance.
+			 */
+			$this->total_users = $wpdb->get_var( apply_filters( 'bp_found_user_query', "SELECT FOUND_ROWS()", $this ) );
+		} elseif ( 'count_query' == $this->query_vars['count_total'] ) {
+			$count_select      = preg_replace( '/^SELECT.*?FROM (\S+) u/', "SELECT COUNT(u.{$this->uid_name}) FROM $1 u", $this->uid_clauses['select'] );
+
+			/** This filter is documented in bp-core/classes/class-bp-user-query.php */
+			$this->total_users = $wpdb->get_var( apply_filters( 'bp_found_user_query', "{$count_select} {$this->uid_clauses['where']}", $this ) );
+		}
+	}
+
+	/**
+	 * Use WP_User_Query() to pull data for the user IDs retrieved in the main query.
+	 *
+	 * @since 1.7.0
+	 */
+	public function do_wp_user_query() {
+		$fields = array( 'ID', 'user_login', 'user_pass', 'user_nicename', 'user_email', 'user_url', 'user_registered', 'user_activation_key', 'user_status', 'display_name' );
+
+		if ( is_multisite() ) {
+			$fields[] = 'spam';
+			$fields[] = 'deleted';
+		}
+
+		/**
+		 * Filters the WP User Query arguments before passing into the class.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param array         $value Array of arguments for the user query.
+		 * @param BP_User_Query $this  Current BP_User_Query instance.
+		 */
+		$wp_user_query = new WP_User_Query( apply_filters( 'bp_wp_user_query_args', array(
+
+			// Relevant
+			'fields'      => $fields,
+			'include'     => $this->user_ids,
+
+			// Overrides
+			'blog_id'     => 0,    // BP does not require blog roles
+			'count_total' => false // We already have a count
+
+		), $this ) );
+
+		// WP_User_Query doesn't cache the data it pulls from wp_users,
+		// and it does not give us a way to save queries by fetching
+		// only uncached users. However, BP does cache this data, so
+		// we set it here.
+		foreach ( $wp_user_query->results as $u ) {
+			wp_cache_set( 'bp_core_userdata_' . $u->ID, $u, 'bp' );
+		}
+
+		// We calculate total_users using a standalone query, except
+		// when a whitelist of user_ids is passed to the constructor.
+		// This clause covers the latter situation, and ensures that
+		// pagination works when querying by $user_ids.
+		if ( empty( $this->total_users ) ) {
+			$this->total_users = count( $wp_user_query->results );
+		}
+
+		// Reindex for easier matching
+		$r = array();
+		foreach ( $wp_user_query->results as $u ) {
+			$r[ $u->ID ] = $u;
+		}
+
+		// Match up to the user ids from the main query
+		foreach ( $this->user_ids as $key => $uid ) {
+			if ( isset( $r[ $uid ] ) ) {
+				$this->results[ $uid ] = $r[ $uid ];
+
+				// The BP template functions expect an 'id'
+				// (as opposed to 'ID') property
+				$this->results[ $uid ]->id = $uid;
+
+			// remove user ID from original user_ids property
+			} else {
+				unset( $this->user_ids[ $key ] );
+			}
+		}
+	}
+
+	/**
+	 * Fetch the IDs of users to put in the IN clause of the main query.
+	 *
+	 * By default, returns the value passed to it
+	 * ($this->query_vars['include']). Having this abstracted into a
+	 * standalone method means that extending classes can override the
+	 * logic, parsing together their own user_id limits with the 'include'
+	 * ids passed to the class constructor. See {@link BP_Group_Member_Query}
+	 * for an example.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param array $include Sanitized array of user IDs, as passed to the 'include'
+	 *                       parameter of the class constructor.
+	 *
+	 * @return array The list of users to which the main query should be
+	 *               limited.
+	 */
+	public function get_include_ids( $include = array() ) {
+		return $include;
+	}
+
+	/**
+	 * Perform a database query to populate any extra metadata we might need.
+	 *
+	 * Different components will hook into the 'bp_user_query_populate_extras'
+	 * action to loop in the things they want.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @global WPDB $wpdb Global WordPress database access object.
+	 */
+	public function populate_extras() {
+		global $wpdb;
+
+		// Bail if no users
+		if ( empty( $this->user_ids ) || empty( $this->results ) ) {
+			return;
+		}
+
+		// Bail if the populate_extras flag is set to false
+		// In the case of the 'popular' sort type, we force
+		// populate_extras to true, because we need the friend counts
+		if ( 'popular' == $this->query_vars['type'] ) {
+			$this->query_vars['populate_extras'] = 1;
+		}
+
+		if ( ! (bool) $this->query_vars['populate_extras'] ) {
+			return;
+		}
+
+		// Turn user ID's into a query-usable, comma separated value
+		$user_ids_sql = implode( ',', wp_parse_id_list( $this->user_ids ) );
+
+		/**
+		 * Allows users to independently populate custom extras.
+		 *
+		 * Note that anything you add here should query using $user_ids_sql, to
+		 * avoid running multiple queries per user in the loop.
+		 *
+		 * Two BuddyPress components currently do this:
+		 * - XProfile: To override display names.
+		 * - Friends:  To set whether or not a user is the current users friend.
+		 *
+		 * @see bp_xprofile_filter_user_query_populate_extras()
+		 * @see bp_friends_filter_user_query_populate_extras()
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param BP_User_Query $this         Current BP_User_Query instance.
+		 * @param string        $user_ids_sql Comma-separated string of user IDs.
+		 */
+		do_action_ref_array( 'bp_user_query_populate_extras', array( $this, $user_ids_sql ) );
+
+		// Fetch last_active data from the activity table
+		$last_activities = BP_Core_User::get_last_activity( $this->user_ids );
+
+		// Set a last_activity value for each user, even if it's empty
+		foreach ( $this->results as $user_id => $user ) {
+			$user_last_activity = isset( $last_activities[ $user_id ] ) ? $last_activities[ $user_id ]['date_recorded'] : '';
+			$this->results[ $user_id ]->last_activity = $user_last_activity;
+		}
+
+		// Fetch usermeta data
+		// We want the three following pieces of info from usermeta:
+		// - friend count
+		// - latest update
+		$total_friend_count_key = bp_get_user_meta_key( 'total_friend_count' );
+		$bp_latest_update_key   = bp_get_user_meta_key( 'bp_latest_update'   );
+
+		// total_friend_count must be set for each user, even if its
+		// value is 0
+		foreach ( $this->results as $uindex => $user ) {
+			$this->results[$uindex]->total_friend_count = 0;
+		}
+
+		// Create, prepare, and run the separate usermeta query
+		$user_metas = $wpdb->get_results( $wpdb->prepare( "SELECT user_id, meta_key, meta_value FROM {$wpdb->usermeta} WHERE meta_key IN (%s,%s) AND user_id IN ({$user_ids_sql})", $total_friend_count_key, $bp_latest_update_key ) );
+
+		// The $members_template global expects the index key to be different
+		// from the meta_key in some cases, so we rejig things here.
+		foreach ( $user_metas as $user_meta ) {
+			switch ( $user_meta->meta_key ) {
+				case $total_friend_count_key :
+					$key = 'total_friend_count';
+					break;
+
+				case $bp_latest_update_key :
+					$key = 'latest_update';
+					break;
+			}
+
+			if ( isset( $this->results[ $user_meta->user_id ] ) ) {
+				$this->results[ $user_meta->user_id ]->{$key} = $user_meta->meta_value;
+			}
+		}
+
+		// When meta_key or meta_value have been passed to the query,
+		// fetch the resulting values for use in the template functions
+		if ( ! empty( $this->query_vars['meta_key'] ) ) {
+			$meta_sql = array(
+				'select' => "SELECT user_id, meta_key, meta_value",
+				'from'   => "FROM $wpdb->usermeta",
+				'where'  => $wpdb->prepare( "WHERE meta_key = %s", $this->query_vars['meta_key'] )
+			);
+
+			if ( false !== $this->query_vars['meta_value'] ) {
+				$meta_sql['where'] .= $wpdb->prepare( " AND meta_value = %s", $this->query_vars['meta_value'] );
+			}
+
+			$metas = $wpdb->get_results( "{$meta_sql['select']} {$meta_sql['from']} {$meta_sql['where']}" );
+
+			if ( ! empty( $metas ) ) {
+				foreach ( $metas as $meta ) {
+					if ( isset( $this->results[ $meta->user_id ] ) ) {
+						$this->results[ $meta->user_id ]->meta_key = $meta->meta_key;
+
+						if ( ! empty( $meta->meta_value ) ) {
+							$this->results[ $meta->user_id ]->meta_value = $meta->meta_value;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Get a SQL clause representing member_type include/exclusion.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string|array $member_types Array or comma-separated list of member types.
+	 * @param string       $operator     'IN' or 'NOT IN'.
+	 *
+	 * @return string
+	 */
+	protected function get_sql_clause_for_member_types( $member_types, $operator ) {
+		global $wpdb;
+
+		// Sanitize.
+		if ( 'NOT IN' !== $operator ) {
+			$operator = 'IN';
+		}
+
+		// Parse and sanitize types.
+		if ( ! is_array( $member_types ) ) {
+			$member_types = preg_split( '/[,\s+]/', $member_types );
+		}
+
+		$types = array();
+		foreach ( $member_types as $mt ) {
+			if ( bp_get_member_type_object( $mt ) ) {
+				$types[] = $mt;
+			}
+		}
+
+		$tax_query = new WP_Tax_Query( array(
+			array(
+				'taxonomy' => 'bp_member_type',
+				'field'    => 'name',
+				'operator' => $operator,
+				'terms'    => $types,
+			),
+		) );
+
+		// Switch to the root blog, where member type taxonomies live.
+		$switched = false;
+		if ( ! bp_is_root_blog() ) {
+			switch_to_blog( bp_get_root_blog_id() );
+			$switched = true;
+		}
+
+		$sql_clauses = $tax_query->get_sql( 'u', $this->uid_name );
+
+		if ( $switched ) {
+			restore_current_blog();
+		}
+
+		$clause = '';
+
+		// no_results clauses are the same between IN and NOT IN.
+		if ( false !== strpos( $sql_clauses['where'], '0 = 1' ) ) {
+			$clause = $this->no_results['where'];
+
+		// The tax_query clause generated for NOT IN can be used almost as-is. We just trim the leading 'AND'.
+		} elseif ( 'NOT IN' === $operator ) {
+			$clause = preg_replace( '/^\s*AND\s*/', '', $sql_clauses['where'] );
+
+		// IN clauses must be converted to a subquery.
+		} elseif ( preg_match( '/' . $wpdb->term_relationships . '\.term_taxonomy_id IN \([0-9, ]+\)/', $sql_clauses['where'], $matches ) ) {
+			$clause = "u.{$this->uid_name} IN ( SELECT object_id FROM $wpdb->term_relationships WHERE {$matches[0]} )";
+		}
+
+		return $clause;
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-walker-nav-menu-checklist.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-walker-nav-menu-checklist.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-walker-nav-menu-checklist.php	(revision 10410)
@@ -0,0 +1,117 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Create a set of BuddyPress-specific links for use in the Menus admin UI.
+ *
+ * Borrowed heavily from {@link Walker_Nav_Menu_Checklist}, but modified so as not
+ * to require an actual post type or taxonomy, and to force certain CSS classes.
+ *
+ * @since 1.9.0
+ */
+class BP_Walker_Nav_Menu_Checklist extends Walker_Nav_Menu {
+
+	/**
+	 * Constructor.
+	 *
+	 * @see Walker_Nav_Menu::__construct() for a description of parameters.
+	 *
+	 * @param array|bool $fields See {@link Walker_Nav_Menu::__construct()}.
+	 */
+	public function __construct( $fields = false ) {
+		if ( $fields ) {
+			$this->db_fields = $fields;
+		}
+	}
+
+	/**
+	 * Create the markup to start a tree level.
+	 *
+	 * @see Walker_Nav_Menu::start_lvl() for description of parameters.
+	 *
+	 * @param string $output See {@Walker_Nav_Menu::start_lvl()}.
+	 * @param int    $depth  See {@Walker_Nav_Menu::start_lvl()}.
+	 * @param array  $args   See {@Walker_Nav_Menu::start_lvl()}.
+	 */
+	public function start_lvl( &$output, $depth = 0, $args = array() ) {
+		$indent = str_repeat( "\t", $depth );
+		$output .= "\n$indent<ul class='children'>\n";
+	}
+
+	/**
+	 * Create the markup to end a tree level.
+	 *
+	 * @see Walker_Nav_Menu::end_lvl() for description of parameters.
+	 *
+	 * @param string $output See {@Walker_Nav_Menu::end_lvl()}.
+	 * @param int    $depth  See {@Walker_Nav_Menu::end_lvl()}.
+	 * @param array  $args   See {@Walker_Nav_Menu::end_lvl()}.
+	 */
+	public function end_lvl( &$output, $depth = 0, $args = array() ) {
+		$indent = str_repeat( "\t", $depth );
+		$output .= "\n$indent</ul>";
+	}
+
+	/**
+	 * Create the markup to start an element.
+	 *
+	 * @see Walker::start_el() for description of parameters.
+	 *
+	 * @param string       $output Passed by reference. Used to append additional
+	 *                             content.
+	 * @param object       $item   Menu item data object.
+	 * @param int          $depth  Depth of menu item. Used for padding.
+	 * @param object|array $args   See {@Walker::start_el()}.
+	 * @param int          $id     See {@Walker::start_el()}.
+	 */
+	function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
+		global $_nav_menu_placeholder;
+
+		$_nav_menu_placeholder = ( 0 > $_nav_menu_placeholder ) ? intval($_nav_menu_placeholder) - 1 : -1;
+		$possible_object_id = isset( $item->post_type ) && 'nav_menu_item' == $item->post_type ? $item->object_id : $_nav_menu_placeholder;
+		$possible_db_id = ( ! empty( $item->ID ) ) && ( 0 < $possible_object_id ) ? (int) $item->ID : 0;
+
+		$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
+
+		$output .= $indent . '<li>';
+		$output .= '<label class="menu-item-title">';
+		$output .= '<input type="checkbox" class="menu-item-checkbox';
+
+		if ( property_exists( $item, 'label' ) ) {
+			$title = $item->label;
+		}
+
+		$output .= '" name="menu-item[' . $possible_object_id . '][menu-item-object-id]" value="'. esc_attr( $item->object_id ) .'" /> ';
+		$output .= isset( $title ) ? esc_html( $title ) : esc_html( $item->title );
+		$output .= '</label>';
+
+		if ( empty( $item->url ) ) {
+			$item->url = $item->guid;
+		}
+
+		if ( ! in_array( array( 'bp-menu', 'bp-'. $item->post_excerpt .'-nav' ), $item->classes ) ) {
+			$item->classes[] = 'bp-menu';
+			$item->classes[] = 'bp-'. $item->post_excerpt .'-nav';
+		}
+
+		// Menu item hidden fields
+		$output .= '<input type="hidden" class="menu-item-db-id" name="menu-item[' . $possible_object_id . '][menu-item-db-id]" value="' . $possible_db_id . '" />';
+		$output .= '<input type="hidden" class="menu-item-object" name="menu-item[' . $possible_object_id . '][menu-item-object]" value="'. esc_attr( $item->object ) .'" />';
+		$output .= '<input type="hidden" class="menu-item-parent-id" name="menu-item[' . $possible_object_id . '][menu-item-parent-id]" value="'. esc_attr( $item->menu_item_parent ) .'" />';
+		$output .= '<input type="hidden" class="menu-item-type" name="menu-item[' . $possible_object_id . '][menu-item-type]" value="custom" />';
+		$output .= '<input type="hidden" class="menu-item-title" name="menu-item[' . $possible_object_id . '][menu-item-title]" value="'. esc_attr( $item->title ) .'" />';
+		$output .= '<input type="hidden" class="menu-item-url" name="menu-item[' . $possible_object_id . '][menu-item-url]" value="'. esc_attr( $item->url ) .'" />';
+		$output .= '<input type="hidden" class="menu-item-target" name="menu-item[' . $possible_object_id . '][menu-item-target]" value="'. esc_attr( $item->target ) .'" />';
+		$output .= '<input type="hidden" class="menu-item-attr_title" name="menu-item[' . $possible_object_id . '][menu-item-attr_title]" value="'. esc_attr( $item->attr_title ) .'" />';
+		$output .= '<input type="hidden" class="menu-item-classes" name="menu-item[' . $possible_object_id . '][menu-item-classes]" value="'. esc_attr( implode( ' ', $item->classes ) ) .'" />';
+		$output .= '<input type="hidden" class="menu-item-xfn" name="menu-item[' . $possible_object_id . '][menu-item-xfn]" value="'. esc_attr( $item->xfn ) .'" />';
+	}
+}
Index: /tags/2.4.3/src/bp-core/classes/class-bp-walker-nav-menu.php
===================================================================
--- /tags/2.4.3/src/bp-core/classes/class-bp-walker-nav-menu.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/classes/class-bp-walker-nav-menu.php	(revision 10410)
@@ -0,0 +1,215 @@
+<?php
+/**
+ * Core component classes.
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Create HTML list of BP nav items.
+ *
+ * @since 1.7.0
+ */
+class BP_Walker_Nav_Menu extends Walker_Nav_Menu {
+
+	/**
+	 * Description of fields indexes for building markup.
+	 *
+	 * @since 1.7.0
+	 * @var array
+	 */
+	var $db_fields = array( 'id' => 'css_id', 'parent' => 'parent' );
+
+	/**
+	 * Tree type.
+	 *
+	 * @since 1.7.0
+	 * @var string
+	 */
+	var $tree_type = array();
+
+	/**
+	 * Display array of elements hierarchically.
+	 *
+	 * This method is almost identical to the version in {@link Walker::walk()}.
+	 * The only change is on one line which has been commented. An IF was
+	 * comparing 0 to a non-empty string which was preventing child elements
+	 * being grouped under their parent menu element.
+	 *
+	 * This caused a problem for BuddyPress because our primary/secondary
+	 * navigations don't have a unique numerical ID that describes a
+	 * hierarchy (we use a slug). Obviously, WordPress Menus use Posts, and
+	 * those have ID/post_parent.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @see Walker::walk()
+	 *
+	 * @param array $elements  See {@link Walker::walk()}.
+	 * @param int   $max_depth See {@link Walker::walk()}.
+	 *
+	 * @return string See {@link Walker::walk()}.
+	 */
+	public function walk( $elements, $max_depth ) {
+		$func_args = func_get_args();
+
+		$args   = array_slice( $func_args, 2 );
+		$output = '';
+
+		if ( $max_depth < -1 ) // invalid parameter
+			return $output;
+
+		if ( empty( $elements ) ) // nothing to walk
+			return $output;
+
+		$parent_field = $this->db_fields['parent'];
+
+		// flat display
+		if ( -1 == $max_depth ) {
+
+			$empty_array = array();
+			foreach ( $elements as $e )
+				$this->display_element( $e, $empty_array, 1, 0, $args, $output );
+
+			return $output;
+		}
+
+		/*
+		 * Need to display in hierarchical order
+		 * separate elements into two buckets: top level and children elements
+		 * children_elements is two dimensional array, eg.
+		 * children_elements[10][] contains all sub-elements whose parent is 10.
+		 */
+		$top_level_elements = array();
+		$children_elements  = array();
+
+		foreach ( $elements as $e ) {
+			// BuddyPress: changed '==' to '==='. This is the only change from version in Walker::walk().
+			if ( 0 === $e->$parent_field )
+				$top_level_elements[] = $e;
+			else
+				$children_elements[$e->$parent_field][] = $e;
+		}
+
+		/*
+		 * When none of the elements is top level
+		 * assume the first one must be root of the sub elements.
+		 */
+		if ( empty( $top_level_elements ) ) {
+
+			$first              = array_slice( $elements, 0, 1 );
+			$root               = $first[0];
+			$top_level_elements = array();
+			$children_elements  = array();
+
+			foreach ( $elements as $e ) {
+				if ( $root->$parent_field == $e->$parent_field )
+					$top_level_elements[] = $e;
+				else
+					$children_elements[$e->$parent_field][] = $e;
+			}
+		}
+
+		foreach ( $top_level_elements as $e )
+			$this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
+
+		/*
+		 * if we are displaying all levels, and remaining children_elements is not empty,
+		 * then we got orphans, which should be displayed regardless
+		 */
+		if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
+			$empty_array = array();
+
+			foreach ( $children_elements as $orphans )
+				foreach ( $orphans as $op )
+					$this->display_element( $op, $empty_array, 1, 0, $args, $output );
+		 }
+
+		 return $output;
+	}
+
+	/**
+	 * Display the current <li> that we are on.
+	 *
+	 * @see Walker::start_el() for complete description of parameters.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $output Passed by reference. Used to append
+	 *                       additional content.
+	 * @param object $item   Menu item data object.
+	 * @param int    $depth  Depth of menu item. Used for padding. Optional,
+	 *                       defaults to 0.
+	 * @param array  $args   Optional. See {@link Walker::start_el()}.
+	 * @param int    $id     Menu item ID. Optional.
+	 */
+	public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
+		// If we're someway down the tree, indent the HTML with the appropriate number of tabs
+		$indent = $depth ? str_repeat( "\t", $depth ) : '';
+
+		/**
+		 * Filters the classes to be added to the nav menu markup.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param array  $value Array of classes to be added.
+		 * @param object $item  Menu item data object.
+		 * @param array  $args  Array of arguments for the item.
+		 */
+		$class_names = join( ' ', apply_filters( 'bp_nav_menu_css_class', array_filter( $item->class ), $item, $args ) );
+		$class_names = ! empty( $class_names ) ? ' class="' . esc_attr( $class_names ) . '"' : '';
+
+		// Add HTML ID
+		$id = sanitize_html_class( $item->css_id . '-personal-li' );  // Backpat with BP pre-1.7
+
+		/**
+		 * Filters the value to be used for the nav menu ID attribute.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $id   ID attribute to be added to the menu item.
+		 * @param object $item Menu item data object.
+		 * @param array  $args Array of arguments for the item.
+		 */
+		$id = apply_filters( 'bp_nav_menu_item_id', $id, $item, $args );
+		$id = ! empty( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';
+
+		// Opening tag; closing tag is handled in Walker_Nav_Menu::end_el().
+		$output .= $indent . '<li' . $id . $class_names . '>';
+
+		// Add href attribute
+		$attributes = ! empty( $item->link ) ? ' href="' . esc_url( $item->link ) . '"' : '';
+
+		// Construct the link
+		$item_output = $args->before;
+		$item_output .= '<a' . $attributes . '>';
+
+		/**
+		 * Filters the link text to be added to the item output.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $name  Item text to be applied.
+		 * @param int    $value Post ID the title is for.
+		 */
+		$item_output .= $args->link_before . apply_filters( 'the_title', $item->name, 0 ) . $args->link_after;
+		$item_output .= '</a>';
+		$item_output .= $args->after;
+
+		/**
+		 * Filters the final result for the menu item.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $item_output Constructed output for the menu item to append to output.
+		 * @param object $item        Menu item data object.
+		 * @param int    $depth       Depth of menu item. Used for padding.
+		 * @param array  $args        Array of arguments for the item.
+		 */
+		$output .= apply_filters( 'bp_walker_nav_menu_start_el', $item_output, $item, $depth, $args );
+	}
+}
Index: /tags/2.4.3/src/bp-core/css/admin-bar-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-core/css/admin-bar-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-core/css/admin-bar-rtl.css	(revision 10410)
@@ -0,0 +1,58 @@
+/* Displayed User */
+#wpadminbar .quicklinks li#wp-admin-bar-user-admin-with-avatar > a img,
+#wpadminbar .quicklinks li#wp-admin-bar-group-admin-with-avatar > a img {
+	width: 16px;
+	height: 16px;
+	display: inline;
+	border: 1px solid #999;
+	vertical-align: middle;
+	margin: -2px -5px 0 10px;
+	padding: 0;
+	background: #eee;
+	float: none;
+}
+
+/* Displayed Group */
+#wpadminbar .quicklinks li#wp-admin-bar-group-admin-with-avatar ul {
+	right: 0;
+}
+#wpadminbar .quicklinks li#wp-admin-bar-group-admin-with-avatar ul ul {
+ 	right: 0;
+}
+
+/* Notifications */
+#wpadminbar .quicklinks li#wp-admin-bar-my-account a span.count,
+#wpadminbar .quicklinks li#wp-admin-bar-my-account-with-avatar a span.count,
+#wpadminbar .quicklinks li#wp-admin-bar-bp-notifications #ab-pending-notifications {
+	background: #21759B;
+	color: #fff;
+	text-shadow: none;
+	display: inline;
+	padding: 2px 5px;
+	font-size: 10px;
+	font-weight: bold;
+	-moz-border-radius: 10px;
+	-khtml-border-radius: 10px;
+	-webkit-border-radius: 10px;
+	border-radius: 10px;
+}
+
+#wpadminbar .quicklinks li#wp-admin-bar-bp-notifications #ab-pending-notifications {
+	background: #ddd;
+	color: #333;
+	margin: 0
+}
+
+#wpadminbar .quicklinks li#wp-admin-bar-bp-notifications #ab-pending-notifications.alert {
+	background-color: #1fb3dd;
+	color: #fff;
+}
+
+#wpadminbar .quicklinks li#wp-admin-bar-bp-notifications > a {
+	padding: 0 .5em;
+}
+
+#wp-admin-bar-user-info img.avatar {
+	height: 64px;
+	width: 64px;
+}
Index: /tags/2.4.3/src/bp-core/css/admin-bar.css
===================================================================
--- /tags/2.4.3/src/bp-core/css/admin-bar.css	(revision 10410)
+++ /tags/2.4.3/src/bp-core/css/admin-bar.css	(revision 10410)
@@ -0,0 +1,58 @@
+/* Displayed User */
+#wpadminbar .quicklinks li#wp-admin-bar-user-admin-with-avatar > a img,
+#wpadminbar .quicklinks li#wp-admin-bar-group-admin-with-avatar > a img {
+	width: 16px;
+	height: 16px;
+	display: inline;
+	border: 1px solid #999;
+	vertical-align: middle;
+	margin: -2px 10px 0 -5px;
+	padding: 0;
+	background: #eee;
+	float: none;
+}
+
+/* Displayed Group */
+#wpadminbar .quicklinks li#wp-admin-bar-group-admin-with-avatar ul {
+	left: 0;
+}
+#wpadminbar .quicklinks li#wp-admin-bar-group-admin-with-avatar ul ul {
+ 	left: 0;
+}
+
+/* Notifications */
+#wpadminbar .quicklinks li#wp-admin-bar-my-account a span.count,
+#wpadminbar .quicklinks li#wp-admin-bar-my-account-with-avatar a span.count,
+#wpadminbar .quicklinks li#wp-admin-bar-bp-notifications #ab-pending-notifications {
+	background: #21759B;
+	color: #fff;
+	text-shadow: none;
+	display: inline;
+	padding: 2px 5px;
+	font-size: 10px;
+	font-weight: bold;
+	-moz-border-radius: 10px;
+	-khtml-border-radius: 10px;
+	-webkit-border-radius: 10px;
+	border-radius: 10px;
+}
+
+#wpadminbar .quicklinks li#wp-admin-bar-bp-notifications #ab-pending-notifications {
+	background: #ddd;
+	color: #333;
+	margin: 0
+}
+
+#wpadminbar .quicklinks li#wp-admin-bar-bp-notifications #ab-pending-notifications.alert {
+	background-color: #1fb3dd;
+	color: #fff;
+}
+
+#wpadminbar .quicklinks li#wp-admin-bar-bp-notifications > a {
+	padding: 0 .5em;
+}
+
+#wp-admin-bar-user-info img.avatar {
+	height: 64px;
+	width: 64px;
+}
Index: /tags/2.4.3/src/bp-core/css/avatar-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-core/css/avatar-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-core/css/avatar-rtl.css	(revision 10410)
@@ -0,0 +1,397 @@
+div.bp-avatar-status,
+div.bp-cover-image-status {
+	clear: both;
+	margin: 1em 0;
+}
+
+div.bp-avatar-status p.updated,
+div.bp-cover-image-status p.updated {
+	display: block;
+	padding: 10px 15px;
+}
+
+div.bp-avatar-status p.success,
+div.bp-cover-image-status p.success {
+	background-color: #efc;
+	border: 1px solid #591;
+	color: #250;
+}
+
+div.bp-avatar-status p.error,
+div.bp-cover-image-status p.error {
+	background-color: #fdc;
+	border: 1px solid #a00;
+	color: #800;
+}
+
+div.bp-avatar-status .bp-progress,
+div.bp-cover-image-status .bp-progress {
+	background: none;
+	border: 1px solid #d1d1d1;
+	float: left;
+	height: 22px;
+	line-height: 2em;
+	margin: 6px 0 0 10px;
+	margin-bottom: 2px;
+	padding: 0;
+	overflow: hidden;
+	width: 200px;
+}
+
+div.bp-avatar-status .bp-bar,
+div.bp-cover-image-status .bp-bar {
+	background-color: #c3ff88;
+	width: 0;
+	height: 100%;
+	z-index: 9;
+}
+
+.bp-uploader-progress div.error {
+	background-color: #fdc;
+	border: 1px solid #a00;
+	color: #800;
+	display: block;
+	font-size: 90%;
+	padding: 10px 15px;
+}
+
+#buddypress p.warning,
+body.users_page_bp-profile-edit.modal-open #TB_ajaxContent p.warning,
+body.profile_page_bp-profile-edit.modal-open #TB_ajaxContent p.warning {
+	background-color: #ffd;
+	border: 1px solid #cb2;
+	color: #440;
+	display: block;
+	font-size: 90%;
+	margin: 1em 0;
+	padding: 10px 15px;
+}
+
+div.bp-avatar-nav {
+	background: transparent;
+	clear: both;
+	margin: 10px 0 10px;
+	overflow: hidden;
+}
+
+.avatar-nav-items {
+	margin: 0;
+	padding: 0;
+}
+
+.bp-avatar-nav .avatar-nav-items li.avatar-nav-item {
+	float: right !important;
+	margin: 0;
+	list-style: none;
+}
+
+.avatar-nav-items li a {
+	display: block;
+	padding: 5px 10px;
+	text-decoration: none;
+}
+
+.bp-avatar-nav	ul:before,
+.bp-avatar-nav	ul:after {
+	content: " ";
+	display: table;
+}
+
+.bp-avatar-nav	ul:after {
+	clear: both;
+}
+
+.bp-avatar-nav	ul {
+	border-bottom: 1px solid #ccc;
+	margin-bottom: 10px;
+}
+
+.bp-avatar-nav	ul.avatar-nav-items li.current {
+	border: 1px solid #ccc;
+	border-bottom-color: #fff;
+	border-top-right-radius: 4px;
+	border-top-left-radius: 4px;
+	margin-bottom: -1px;
+}
+
+.bp-avatar-nav	li.current a {
+	background: none;
+	color: inherit;
+	font-weight: bold;
+	opacity: 0.8;
+	outline: 0;
+}
+
+#drag-drop-area {
+	border: 4px dashed #bbb;
+	height: 200px;
+}
+
+.drag-drop.drag-over #drag-drop-area {
+	border-color: #83b4d8;
+}
+
+.drag-drop-inside p {
+	display: none;
+}
+
+.drag-drop-inside p.drag-drop-buttons {
+	margin-top: 80px;
+	text-align: center;
+}
+
+.drag-drop .drag-drop-inside p.drag-drop-buttons {
+	margin: auto;
+	text-align: auto;
+}
+
+.drag-drop #drag-drop-area {
+	display: table;
+	height: 100%;
+	width: 100%;
+}
+
+.drag-drop .drag-drop-inside {
+	display: table-cell;
+	padding: 40px 0;
+	text-align: center;
+	vertical-align: middle;
+}
+
+.drag-drop .drag-drop-inside p,
+.drag-drop-inside p.drag-drop-buttons {
+	display: block;
+}
+
+.drag-drop .drag-drop-inside p {
+	color: #aaa;
+	font-size: 110%;
+	margin: 5px 0;
+	text-align: center;
+}
+
+.drag-drop-inside p.drag-drop-info {margin-top: 0;}
+
+#avatar-to-crop {
+	margin: 0 auto 20px ;
+	text-align: right;
+}
+
+#bp-webcam-avatar #avatar-to-crop {
+	float: right;
+	margin: 0 0 20px;
+}
+
+#avatar-to-crop .jcrop-holder {
+	margin: 0 auto;
+}
+
+.avatar-crop-management {
+	clear: right;
+	overflow: hidden;
+	padding-top: 20px;
+	text-align: center;
+}
+
+#bp-webcam-avatar .avatar-crop-management {
+	clear: none;
+	float: none;
+	overflow: visible;
+	padding-top: 0;
+	width: auto;
+}
+
+#avatar-crop-pane {
+	margin: 0 auto;
+	overflow: hidden;
+}
+
+#bp-webcam-avatar #avatar-to-crop {
+	border: 1px solid #eee;
+	max-width: 100%;
+	width: 100%;
+}
+
+@media screen and (min-width: 801px) {
+	#bp-webcam-avatar #avatar-to-crop {
+		max-width: 64%;
+		width: 64%;
+	}
+}
+
+#avatar-crop-actions a {
+	display: block;
+}
+
+#bp-webcam-avatar #avatar-crop-actions {
+	float: right;
+	margin: 0 0 20px 0;
+	width: 50%;
+}
+
+#avatar-crop-actions a.button {
+	margin-top: 10px;
+}
+
+#bp-webcam-avatar #avatar-crop-actions a.button {
+	display: block;
+	margin: 0 0 5px;
+	padding: 4px 0;
+	width: 100%;
+}
+
+#avatar-to-crop img,
+#avatar-crop-pane img,
+#avatar-crop-pane canvas,
+#avatar-upload-form img,
+#create-group-form img,
+#group-settings-form img {
+	border: none !important;
+	max-width: none !important;
+}
+
+#bp-webcam-avatar video {
+	float: right;
+	margin-bottom: 0;
+	max-width: 100%;
+	width: 100%;
+	-webkit-transform: scaleX(-1);
+	transform: scaleX(-1);
+}
+
+#bp-webcam-avatar #avatar-crop-pane {
+	border: 2px dashed #bbb;
+	clear: right;
+	float: left;
+	margin: 0  0 10px 40px;
+	overflow: hidden;
+}
+
+/**
+*  Users can define avatar constants with new dimension values
+*  If they do, we'll ensure the layouts preserved by enforcing a max width
+*  that matches bp default settings.
+*/
+#bp-webcam-avatar .avatar-crop-management #avatar-crop-pane {
+	max-width: 150px;
+	max-height: 150px;
+}
+
+#avatar-crop-pane canvas {
+	height: auto;
+	width: 100%;
+	max-width: 100%;
+}
+
+.group-avatar .bp-avatar .avatar-crop-management {
+	margin-right: 0;
+	padding-top: 0;
+	width: auto;
+}
+
+.bp-avatar .item {
+	overflow:hidden;
+}
+
+.bp-avatar .avatar-crop-management.adjust {
+	float: right;
+	clear: none;
+	padding-top: 0;
+}
+
+.bp-avatar #avatar-to-crop.adjust {
+	float: right;
+	margin-left: 20px;
+}
+
+@media screen and (max-width: 480px) {
+
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-actions,
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-pane {
+		float: none;
+	}
+
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-pane {
+		margin: 0 auto 10px;
+	}
+
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-actions  {
+		width: auto;
+	}
+
+}
+
+@media screen and (min-width: 801px) {
+
+	#bp-webcam-avatar .avatar-crop-management {
+		clear: none;
+		float: left;
+	}
+
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-pane {
+		float: none;
+		margin: 0 auto 10px;
+	}
+
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-actions {
+		float: right;
+		width: 100%;
+	}
+
+}
+
+/** Admin Profile **/
+body.users_page_bp-profile-edit.modal-open #TB_ajaxContent,
+body.profile_page_bp-profile-edit.modal-open #TB_ajaxContent {
+	height: 95% !important;
+	width: 95% !important;
+}
+
+body.users_page_bp-profile-edit.modal-open #TB_ajaxContent p.updated,
+body.users_page_bp-profile-edit.modal-open #TB_ajaxContent p.warning,
+body.profile_page_bp-profile-edit.modal-open #TB_ajaxContent p.updated,
+body.profile_page_bp-profile-edit.modal-open #TB_ajaxContent p.warning {
+	display: block;
+	padding: 10px 15px;
+}
+
+.wp-admin #TB_window .bp-avatar #avatar-to-crop {
+	float: right;
+	margin: 0;
+}
+.wp-admin #TB_window .bp-avatar #bp-webcam-avatar #avatar-to-crop {
+	margin-bottom: 20px;
+}
+@media screen and (min-width: 783px) {
+
+	.wp-admin #TB_window .bp-avatar .avatar-crop-management {
+		clear: none;
+		float: right;
+		margin-right: 20px;
+		padding-top: 0;
+		text-align: center;
+
+	}
+
+}
+
+.wp-admin #TB_window .bp-avatar #avatar-to-crop video {
+		width: 100%;
+	}
+
+.wp-admin #TB_window .bp-avatar .avatar-crop-management a.button {
+	height: auto;
+	line-height: inherit;
+}
+
+@media screen and (min-width: 810px) {
+	.wp-admin #TB_window .bp-avatar #bp-webcam-avatar #avatar-to-crop {
+		max-width: none;
+		width: 76%;
+	}
+
+	.wp-admin #TB_window #bp-webcam-avatar .avatar-crop-management {
+		max-width: none;
+		width: auto;
+	}
+}
Index: /tags/2.4.3/src/bp-core/css/avatar.css
===================================================================
--- /tags/2.4.3/src/bp-core/css/avatar.css	(revision 10410)
+++ /tags/2.4.3/src/bp-core/css/avatar.css	(revision 10410)
@@ -0,0 +1,397 @@
+div.bp-avatar-status,
+div.bp-cover-image-status {
+	clear: both;
+	margin: 1em 0;
+}
+
+div.bp-avatar-status p.updated,
+div.bp-cover-image-status p.updated {
+	display: block;
+	padding: 10px 15px;
+}
+
+div.bp-avatar-status p.success,
+div.bp-cover-image-status p.success {
+	background-color: #efc;
+	border: 1px solid #591;
+	color: #250;
+}
+
+div.bp-avatar-status p.error,
+div.bp-cover-image-status p.error {
+	background-color: #fdc;
+	border: 1px solid #a00;
+	color: #800;
+}
+
+div.bp-avatar-status .bp-progress,
+div.bp-cover-image-status .bp-progress {
+	background: none;
+	border: 1px solid #d1d1d1;
+	float: right;
+	height: 22px;
+	line-height: 2em;
+	margin: 6px 10px 0 0;
+	margin-bottom: 2px;
+	padding: 0;
+	overflow: hidden;
+	width: 200px;
+}
+
+div.bp-avatar-status .bp-bar,
+div.bp-cover-image-status .bp-bar {
+	background-color: #c3ff88;
+	width: 0;
+	height: 100%;
+	z-index: 9;
+}
+
+.bp-uploader-progress div.error {
+	background-color: #fdc;
+	border: 1px solid #a00;
+	color: #800;
+	display: block;
+	font-size: 90%;
+	padding: 10px 15px;
+}
+
+#buddypress p.warning,
+body.users_page_bp-profile-edit.modal-open #TB_ajaxContent p.warning,
+body.profile_page_bp-profile-edit.modal-open #TB_ajaxContent p.warning {
+	background-color: #ffd;
+	border: 1px solid #cb2;
+	color: #440;
+	display: block;
+	font-size: 90%;
+	margin: 1em 0;
+	padding: 10px 15px;
+}
+
+div.bp-avatar-nav {
+	background: transparent;
+	clear: both;
+	margin: 10px 0 10px;
+	overflow: hidden;
+}
+
+.avatar-nav-items {
+	margin: 0;
+	padding: 0;
+}
+
+.bp-avatar-nav .avatar-nav-items li.avatar-nav-item {
+	float: left !important;
+	margin: 0;
+	list-style: none;
+}
+
+.avatar-nav-items li a {
+	display: block;
+	padding: 5px 10px;
+	text-decoration: none;
+}
+
+.bp-avatar-nav	ul:before,
+.bp-avatar-nav	ul:after {
+	content: " ";
+	display: table;
+}
+
+.bp-avatar-nav	ul:after {
+	clear: both;
+}
+
+.bp-avatar-nav	ul {
+	border-bottom: 1px solid #ccc;
+	margin-bottom: 10px;
+}
+
+.bp-avatar-nav	ul.avatar-nav-items li.current {
+	border: 1px solid #ccc;
+	border-bottom-color: #fff;
+	border-top-left-radius: 4px;
+	border-top-right-radius: 4px;
+	margin-bottom: -1px;
+}
+
+.bp-avatar-nav	li.current a {
+	background: none;
+	color: inherit;
+	font-weight: bold;
+	opacity: 0.8;
+	outline: 0;
+}
+
+#drag-drop-area {
+	border: 4px dashed #bbb;
+	height: 200px;
+}
+
+.drag-drop.drag-over #drag-drop-area {
+	border-color: #83b4d8;
+}
+
+.drag-drop-inside p {
+	display: none;
+}
+
+.drag-drop-inside p.drag-drop-buttons {
+	margin-top: 80px;
+	text-align: center;
+}
+
+.drag-drop .drag-drop-inside p.drag-drop-buttons {
+	margin: auto;
+	text-align: auto;
+}
+
+.drag-drop #drag-drop-area {
+	display: table;
+	height: 100%;
+	width: 100%;
+}
+
+.drag-drop .drag-drop-inside {
+	display: table-cell;
+	padding: 40px 0;
+	text-align: center;
+	vertical-align: middle;
+}
+
+.drag-drop .drag-drop-inside p,
+.drag-drop-inside p.drag-drop-buttons {
+	display: block;
+}
+
+.drag-drop .drag-drop-inside p {
+	color: #aaa;
+	font-size: 110%;
+	margin: 5px 0;
+	text-align: center;
+}
+
+.drag-drop-inside p.drag-drop-info {margin-top: 0;}
+
+#avatar-to-crop {
+	margin: 0 auto 20px ;
+	text-align: left;
+}
+
+#bp-webcam-avatar #avatar-to-crop {
+	float: left;
+	margin: 0 0 20px;
+}
+
+#avatar-to-crop .jcrop-holder {
+	margin: 0 auto;
+}
+
+.avatar-crop-management {
+	clear: left;
+	overflow: hidden;
+	padding-top: 20px;
+	text-align: center;
+}
+
+#bp-webcam-avatar .avatar-crop-management {
+	clear: none;
+	float: none;
+	overflow: visible;
+	padding-top: 0;
+	width: auto;
+}
+
+#avatar-crop-pane {
+	margin: 0 auto;
+	overflow: hidden;
+}
+
+#bp-webcam-avatar #avatar-to-crop {
+	border: 1px solid #eee;
+	max-width: 100%;
+	width: 100%;
+}
+
+@media screen and (min-width: 801px) {
+	#bp-webcam-avatar #avatar-to-crop {
+		max-width: 64%;
+		width: 64%;
+	}
+}
+
+#avatar-crop-actions a {
+	display: block;
+}
+
+#bp-webcam-avatar #avatar-crop-actions {
+	float: left;
+	margin: 0 0 20px 0;
+	width: 50%;
+}
+
+#avatar-crop-actions a.button {
+	margin-top: 10px;
+}
+
+#bp-webcam-avatar #avatar-crop-actions a.button {
+	display: block;
+	margin: 0 0 5px;
+	padding: 4px 0;
+	width: 100%;
+}
+
+#avatar-to-crop img,
+#avatar-crop-pane img,
+#avatar-crop-pane canvas,
+#avatar-upload-form img,
+#create-group-form img,
+#group-settings-form img {
+	border: none !important;
+	max-width: none !important;
+}
+
+#bp-webcam-avatar video {
+	float: left;
+	margin-bottom: 0;
+	max-width: 100%;
+	width: 100%;
+	-webkit-transform: scaleX(-1);
+	transform: scaleX(-1);
+}
+
+#bp-webcam-avatar #avatar-crop-pane {
+	border: 2px dashed #bbb;
+	clear: left;
+	float: right;
+	margin: 0  40px 10px 0;
+	overflow: hidden;
+}
+
+/**
+*  Users can define avatar constants with new dimension values
+*  If they do, we'll ensure the layouts preserved by enforcing a max width
+*  that matches bp default settings.
+*/
+#bp-webcam-avatar .avatar-crop-management #avatar-crop-pane {
+	max-width: 150px;
+	max-height: 150px;
+}
+
+#avatar-crop-pane canvas {
+	height: auto;
+	width: 100%;
+	max-width: 100%;
+}
+
+.group-avatar .bp-avatar .avatar-crop-management {
+	margin-left: 0;
+	padding-top: 0;
+	width: auto;
+}
+
+.bp-avatar .item {
+	overflow:hidden;
+}
+
+.bp-avatar .avatar-crop-management.adjust {
+	float: left;
+	clear: none;
+	padding-top: 0;
+}
+
+.bp-avatar #avatar-to-crop.adjust {
+	float: left;
+	margin-right: 20px;
+}
+
+@media screen and (max-width: 480px) {
+
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-actions,
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-pane {
+		float: none;
+	}
+
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-pane {
+		margin: 0 auto 10px;
+	}
+
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-actions  {
+		width: auto;
+	}
+
+}
+
+@media screen and (min-width: 801px) {
+
+	#bp-webcam-avatar .avatar-crop-management {
+		clear: none;
+		float: right;
+	}
+
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-pane {
+		float: none;
+		margin: 0 auto 10px;
+	}
+
+	#bp-webcam-avatar .avatar-crop-management #avatar-crop-actions {
+		float: left;
+		width: 100%;
+	}
+
+}
+
+/** Admin Profile **/
+body.users_page_bp-profile-edit.modal-open #TB_ajaxContent,
+body.profile_page_bp-profile-edit.modal-open #TB_ajaxContent {
+	height: 95% !important;
+	width: 95% !important;
+}
+
+body.users_page_bp-profile-edit.modal-open #TB_ajaxContent p.updated,
+body.users_page_bp-profile-edit.modal-open #TB_ajaxContent p.warning,
+body.profile_page_bp-profile-edit.modal-open #TB_ajaxContent p.updated,
+body.profile_page_bp-profile-edit.modal-open #TB_ajaxContent p.warning {
+	display: block;
+	padding: 10px 15px;
+}
+
+.wp-admin #TB_window .bp-avatar #avatar-to-crop {
+	float: left;
+	margin: 0;
+}
+.wp-admin #TB_window .bp-avatar #bp-webcam-avatar #avatar-to-crop {
+	margin-bottom: 20px;
+}
+@media screen and (min-width: 783px) {
+
+	.wp-admin #TB_window .bp-avatar .avatar-crop-management {
+		clear: none;
+		float: left;
+		margin-left: 20px;
+		padding-top: 0;
+		text-align: center;
+
+	}
+
+}
+
+.wp-admin #TB_window .bp-avatar #avatar-to-crop video {
+		width: 100%;
+	}
+
+.wp-admin #TB_window .bp-avatar .avatar-crop-management a.button {
+	height: auto;
+	line-height: inherit;
+}
+
+@media screen and (min-width: 810px) {
+	.wp-admin #TB_window .bp-avatar #bp-webcam-avatar #avatar-to-crop {
+		max-width: none;
+		width: 76%;
+	}
+
+	.wp-admin #TB_window #bp-webcam-avatar .avatar-crop-management {
+		max-width: none;
+		width: auto;
+	}
+}
Index: /tags/2.4.3/src/bp-core/css/buddybar-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-core/css/buddybar-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-core/css/buddybar-rtl.css	(revision 10410)
@@ -0,0 +1,203 @@
+body:not(.wp-admin) {
+	padding-top: 25px !important;
+}
+#wp-admin-bar {
+	position: fixed;
+	top: 0;
+	right: 0;
+	height: 25px;
+	font-size: 11px;
+	width: 100%;
+	z-index: 9999;
+}
+#wp-admin-bar .padder {
+	position: relative;
+	padding: 0;
+	width: 100%;
+	margin: 0 auto;
+	background: url('../images/60pc_black.png');
+	height: 25px;
+}
+body#bp-default #wp-admin-bar .padder {
+	max-width: 1250px;
+}
+#wp-admin-bar * {
+	z-index: 999;
+}
+#wp-admin-bar div#admin-bar-logo {
+	position: absolute;
+	top: 5px;
+	right: 10px;
+}
+#wp-admin-bar a img {
+	border: none;
+}
+#wp-admin-bar li {
+	list-style: none;
+	margin: 0;
+	padding: 0;
+	line-height: 100%;
+	text-align: right;
+}
+#wp-admin-bar li a {
+	padding: 7px 15px 7px 15px;
+	color: #eee;
+	text-decoration: none;
+	font-size: 11px;
+}
+#wp-admin-bar li.alt {
+	border: none;
+}
+#wp-admin-bar li.no-arrow a {
+	padding-left: 15px;
+}
+#wp-admin-bar ul li ul li a span {
+	display: none;
+}
+#wp-admin-bar li:hover,
+#wp-admin-bar li.hover {
+	position: static;
+}
+#admin-bar-logo {
+	float: right;
+	font-weight: bold;
+	font-size: 11px;
+	padding: 5px 8px;
+	margin: 0;
+	text-decoration: none;
+	color: #fff;
+}
+body#bp-default #admin-bar-logo {
+	padding: 2px 8px;
+}
+
+/* all lists */
+#wp-admin-bar ul {
+	margin: 0;
+	list-style: none;
+	line-height: 1;
+	cursor: pointer;
+	height: auto;
+	padding: 0;
+}
+
+/* all list items */
+#wp-admin-bar ul li {
+	padding: 0;
+	float: right;
+	position: relative;
+	background: url('../images/admin-menu-arrow.gif') 12% 53% no-repeat;
+	padding-left: 11px;
+}
+#wp-admin-bar ul li.no-arrow {
+	background: none;
+	padding-left: 0;
+}
+#wp-admin-bar ul li ul li {
+	background-image: none;
+}
+#wp-admin-bar ul li.align-right {
+	position: absolute;
+	left: 0;
+}
+#wp-admin-bar ul li a {
+	display: block;
+}
+#wp-admin-bar ul.main-nav li:hover,
+#wp-admin-bar ul.main-nav li.sfhover,
+#wp-admin-bar ul.main-nav li ul li.sfhover {
+	background-color: #333;
+}
+
+/* second-level lists */
+#wp-admin-bar ul li ul {
+	position: absolute;
+	width: 185px;
+	right: -999em;
+	margin-right: 0;
+	background: #333;
+	border: 1px solid #222;
+	-moz-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+	-webkit-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+	-moz-border-radius: 3px;
+	-webkit-border-radius: 3px;
+	-moz-border-radius-topleft: 0;
+	-webkit-border-top-right-radius: 0;
+	-moz-border-radius-topright: 0;
+	-webkit-border-top-left-radius: 0;
+}
+#wp-admin-bar ul li > ul {
+	border-top: none;
+}
+#wp-admin-bar ul li ul a {
+	color: #eee;
+}
+#wp-admin-bar ul li ul li {
+	float: right;
+	width: 174px;
+	margin: 0;
+}
+#wp-admin-bar ul li ul li:hover a {
+	color: #fff;
+}
+#wp-admin-bar ul li div.admin-bar-clear {
+	clear: both;
+}
+#wp-admin-bar ul.main-nav li ul li:hover,
+#wp-admin-bar ul.main-nav li ul li.sfhover,
+#wp-admin-bar ul.main-nav li ul li.sfhover {
+	background-color: #222;
+}
+
+/* third-and-above-level lists */
+#wp-admin-bar ul li ul ul {
+	margin: -25px 184px 0 0;
+	-moz-border-radius: 3px;
+	-webkit-border-radius: 3px;
+}
+#wp-admin-bar ul li ul li:hover ul li a {
+	color: #eee;
+}
+#wp-admin-bar ul li ul li ul li:hover a {
+	color: #fff;
+}
+#wp-admin-bar ul li:hover ul,
+#wp-admin-bar ul li ul li:hover ul,
+#wp-admin-bar ul li.sfhover ul,
+#wp-admin-bar ul li ul li.sfhover ul {
+	right: auto;
+}
+#wp-admin-bar ul li.align-right:hover ul {
+	left: 0;
+}
+#wp-admin-bar ul li:hover ul ul,
+#wp-admin-bar li.sfhover ul li ul {
+	right: -999em;
+}
+
+/* Menu item css */
+#wp-admin-bar img.avatar {
+	float: right;
+	margin-left: 8px;
+}
+#wp-admin-bar span.activity {
+	display: block;
+	margin-right: 34px;
+	padding: 0;
+}
+#wp-admin-bar ul.author-list li a {
+	height: 17px;
+}
+#wp-admin-bar ul li#bp-adminbar-notifications-menu a span {
+	padding: 0 6px;
+	margin-right: 2px;
+	background: #fff;
+	color: #000;
+	-moz-border-radius: 3px;
+	-webkit-border-radius: 3px;
+	border-radius: 3px;
+}
+#wp-admin-bar-user-info img.avatar {
+	height: 64px;
+	width: 64px;
+}
Index: /tags/2.4.3/src/bp-core/css/buddybar.css
===================================================================
--- /tags/2.4.3/src/bp-core/css/buddybar.css	(revision 10410)
+++ /tags/2.4.3/src/bp-core/css/buddybar.css	(revision 10410)
@@ -0,0 +1,203 @@
+body:not(.wp-admin) {
+	padding-top: 25px !important;
+}
+#wp-admin-bar {
+	position: fixed;
+	top: 0;
+	left: 0;
+	height: 25px;
+	font-size: 11px;
+	width: 100%;
+	z-index: 9999;
+}
+#wp-admin-bar .padder {
+	position: relative;
+	padding: 0;
+	width: 100%;
+	margin: 0 auto;
+	background: url('../images/60pc_black.png');
+	height: 25px;
+}
+body#bp-default #wp-admin-bar .padder {
+	max-width: 1250px;
+}
+#wp-admin-bar * {
+	z-index: 999;
+}
+#wp-admin-bar div#admin-bar-logo {
+	position: absolute;
+	top: 5px;
+	left: 10px;
+}
+#wp-admin-bar a img {
+	border: none;
+}
+#wp-admin-bar li {
+	list-style: none;
+	margin: 0;
+	padding: 0;
+	line-height: 100%;
+	text-align: left;
+}
+#wp-admin-bar li a {
+	padding: 7px 15px 7px 15px;
+	color: #eee;
+	text-decoration: none;
+	font-size: 11px;
+}
+#wp-admin-bar li.alt {
+	border: none;
+}
+#wp-admin-bar li.no-arrow a {
+	padding-right: 15px;
+}
+#wp-admin-bar ul li ul li a span {
+	display: none;
+}
+#wp-admin-bar li:hover,
+#wp-admin-bar li.hover {
+	position: static;
+}
+#admin-bar-logo {
+	float: left;
+	font-weight: bold;
+	font-size: 11px;
+	padding: 5px 8px;
+	margin: 0;
+	text-decoration: none;
+	color: #fff;
+}
+body#bp-default #admin-bar-logo {
+	padding: 2px 8px;
+}
+
+/* all lists */
+#wp-admin-bar ul {
+	margin: 0;
+	list-style: none;
+	line-height: 1;
+	cursor: pointer;
+	height: auto;
+	padding: 0;
+}
+
+/* all list items */
+#wp-admin-bar ul li {
+	padding: 0;
+	float: left;
+	position: relative;
+	background: url('../images/admin-menu-arrow.gif') 88% 53% no-repeat;
+	padding-right: 11px;
+}
+#wp-admin-bar ul li.no-arrow {
+	background: none;
+	padding-right: 0;
+}
+#wp-admin-bar ul li ul li {
+	background-image: none;
+}
+#wp-admin-bar ul li.align-right {
+	position: absolute;
+	right: 0;
+}
+#wp-admin-bar ul li a {
+	display: block;
+}
+#wp-admin-bar ul.main-nav li:hover,
+#wp-admin-bar ul.main-nav li.sfhover,
+#wp-admin-bar ul.main-nav li ul li.sfhover {
+	background-color: #333;
+}
+
+/* second-level lists */
+#wp-admin-bar ul li ul {
+	position: absolute;
+	width: 185px;
+	left: -999em;
+	margin-left: 0;
+	background: #333;
+	border: 1px solid #222;
+	-moz-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+	-webkit-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+	-moz-border-radius: 3px;
+	-webkit-border-radius: 3px;
+	-moz-border-radius-topleft: 0;
+	-webkit-border-top-left-radius: 0;
+	-moz-border-radius-topright: 0;
+	-webkit-border-top-right-radius: 0;
+}
+#wp-admin-bar ul li > ul {
+	border-top: none;
+}
+#wp-admin-bar ul li ul a {
+	color: #eee;
+}
+#wp-admin-bar ul li ul li {
+	float: left;
+	width: 174px;
+	margin: 0;
+}
+#wp-admin-bar ul li ul li:hover a {
+	color: #fff;
+}
+#wp-admin-bar ul li div.admin-bar-clear {
+	clear: both;
+}
+#wp-admin-bar ul.main-nav li ul li:hover,
+#wp-admin-bar ul.main-nav li ul li.sfhover,
+#wp-admin-bar ul.main-nav li ul li.sfhover {
+	background-color: #222;
+}
+
+/* third-and-above-level lists */
+#wp-admin-bar ul li ul ul {
+	margin: -25px 0 0 184px;
+	-moz-border-radius: 3px;
+	-webkit-border-radius: 3px;
+}
+#wp-admin-bar ul li ul li:hover ul li a {
+	color: #eee;
+}
+#wp-admin-bar ul li ul li ul li:hover a {
+	color: #fff;
+}
+#wp-admin-bar ul li:hover ul,
+#wp-admin-bar ul li ul li:hover ul,
+#wp-admin-bar ul li.sfhover ul,
+#wp-admin-bar ul li ul li.sfhover ul {
+	left: auto;
+}
+#wp-admin-bar ul li.align-right:hover ul {
+	right: 0;
+}
+#wp-admin-bar ul li:hover ul ul,
+#wp-admin-bar li.sfhover ul li ul {
+	left: -999em;
+}
+
+/* Menu item css */
+#wp-admin-bar img.avatar {
+	float: left;
+	margin-right: 8px;
+}
+#wp-admin-bar span.activity {
+	display: block;
+	margin-left: 34px;
+	padding: 0;
+}
+#wp-admin-bar ul.author-list li a {
+	height: 17px;
+}
+#wp-admin-bar ul li#bp-adminbar-notifications-menu a span {
+	padding: 0 6px;
+	margin-left: 2px;
+	background: #fff;
+	color: #000;
+	-moz-border-radius: 3px;
+	-webkit-border-radius: 3px;
+	border-radius: 3px;
+}
+#wp-admin-bar-user-info img.avatar {
+	height: 64px;
+	width: 64px;
+}
Index: /tags/2.4.3/src/bp-core/deprecated/1.2.php
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/1.2.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/1.2.php	(revision 10410)
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Deprecated Functions
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ */
+
+/**
+ * Retrieve sitewide activity
+ *
+ * You should use bp_activity_get() instead
+ *
+ * @since 1.0.0
+ * @deprecated 1.2.0
+ *
+ * @param array $args
+ *
+ * @uses BP_Activity_Activity::get() {@link BP_Activity_Activity}
+ *
+ * @return object $activity The activity/activities object
+ */
+function bp_activity_get_sitewide( $args = '' ) {
+	_deprecated_function( __FUNCTION__, '1.2', 'bp_activity_get()' );
+
+	$defaults = array(
+		'max' => false, // Maximum number of results to return
+		'page' => 1, // page 1 without a per_page will result in no pagination.
+		'per_page' => false, // results per page
+		'sort' => 'DESC', // sort ASC or DESC
+		'display_comments' => false, // false for no comments. 'stream' for within stream display, 'threaded' for below each activity item
+
+		'search_terms' => false, // Pass search terms as a string
+		'show_hidden' => false, // Show activity items that are hidden site-wide?
+
+		/**
+		 * Pass filters as an array:
+		 * array(
+		 * 	'user_id' => false, // user_id to filter on
+		 *	'object' => false, // object to filter on e.g. groups, profile, status, friends
+		 *	'action' => false, // action to filter on e.g. new_wire_post, new_forum_post, profile_updated
+		 *	'primary_id' => false, // object ID to filter on e.g. a group_id or forum_id or blog_id etc.
+		 *	'secondary_id' => false, // secondary object ID to filter on e.g. a post_id
+		 * );
+		 */
+		'filter' => array()
+	);
+
+	$args = wp_parse_args( $args, $defaults );
+
+	return apply_filters( 'bp_activity_get_sitewide', BP_Activity_Activity::get( $args ), $r );
+}
+
+
Index: /tags/2.4.3/src/bp-core/deprecated/1.5.php
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/1.5.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/1.5.php	(revision 10410)
@@ -0,0 +1,718 @@
+<?php
+/**
+ * Deprecated Functions
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @deprecated Since 1.5.0
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Loader ********************************************************************/
+
+/**
+ * @deprecated 1.5.0
+ */
+function bp_setup_root_components() {
+	do_action( 'bp_setup_root_components' );
+}
+add_action( 'bp_init', 'bp_setup_root_components', 6 );
+
+/** WP Abstraction ************************************************************/
+
+/**
+ * bp_core_is_multisite()
+ *
+ * This function originally served as a wrapper when WordPress and WordPress MU were separate entities.
+ * Use is_multisite() instead.
+ *
+ * @deprecated 1.5.0
+ * @deprecated Use is_multisite()
+ *
+ * @return bool
+ */
+function bp_core_is_multisite() {
+	_deprecated_function( __FUNCTION__, '1.5', 'is_multisite()' );
+	return is_multisite();
+}
+
+/**
+ * bp_core_is_main_site
+ *
+ * Checks if current blog is root blog of site. Deprecated in 1.5.
+ *
+ * @deprecated 1.5.0
+ * @deprecated Use is_main_site()
+ * @package BuddyPress
+ * @param int|string $blog_id optional blog id to test (default current blog)
+ * @return bool True if not multisite or $blog_id is main site
+ * @since 1.2.6
+ */
+function bp_core_is_main_site( $blog_id = '' ) {
+	_deprecated_function( __FUNCTION__, '1.5', 'is_main_site()' );
+	return is_main_site( $blog_id );
+}
+
+if ( !function_exists( 'is_site_admin' ) ) {
+	/**
+	 * WPMU version of is_super_admin()
+	 *
+	 * @deprecated 1.5.0
+	 * @deprecated Use is_super_admin()
+	 * @param int|bool $user_id Optional. Defaults to logged-in user
+	 * @return bool True if is super admin
+	 */
+	function is_site_admin( $user_id = false ) {
+		_deprecated_function( __FUNCTION__, '1.5', 'is_super_admin()' );
+		return is_super_admin( $user_id );
+	}
+}
+
+/** Admin ******************************************************************/
+
+/**
+ * In BuddyPress 1.1 - 1.2.x, this function provided a better version of add_menu_page()
+ * that allowed positioning of menus. Deprecated in 1.5 in favour of a WP core function.
+ *
+ * @deprecated 1.5.0
+ * @deprecated Use add_menu_page().
+ * @since 1.1.0
+ *
+ * @return string
+ */
+function bp_core_add_admin_menu_page( $args = '' ) {
+	global $_registered_pages, $admin_page_hooks, $menu;
+
+	_deprecated_function( __FUNCTION__, '1.5', 'Use add_menu_page()' );
+
+	$defaults = array(
+		'access_level' => 2,
+		'file'         => false,
+		'function'     => false,
+		'icon_url'     => false,
+		'menu_title'   => '',
+		'page_title'   => '',
+		'position'     => 100
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+	extract( $r, EXTR_SKIP );
+
+	$file     = plugin_basename( $file );
+	$hookname = get_plugin_page_hookname( $file, '' );
+
+	$admin_page_hooks[$file] = sanitize_title( $menu_title );
+
+	if ( !empty( $function ) && !empty ( $hookname ) )
+		add_action( $hookname, $function );
+
+	if ( empty( $icon_url ) )
+		$icon_url = 'images/generic.png';
+	elseif ( is_ssl() && 0 === strpos( $icon_url, 'http://' ) )
+		$icon_url = 'https://' . substr( $icon_url, 7 );
+
+	do {
+		$position++;
+	} while ( !empty( $menu[$position] ) );
+
+	$menu[$position] = array ( $menu_title, $access_level, $file, $page_title, 'menu-top ' . $hookname, $hookname, $icon_url );
+	$_registered_pages[$hookname] = true;
+
+	return $hookname;
+}
+/** Activity ******************************************************************/
+
+/**
+ * @deprecated 1.5.0
+ */
+function bp_is_activity_permalink() {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_is_single_activity' );
+	bp_is_single_activity();
+}
+
+/** Core **********************************************************************/
+
+/**
+ * @deprecated 1.5.0
+ */
+function bp_core_get_wp_profile() {
+	_deprecated_function( __FUNCTION__, '1.5' );
+
+	$ud = get_userdata( bp_displayed_user_id() ); ?>
+
+<div class="bp-widget wp-profile">
+	<h4><?php _e( 'My Profile', 'buddypress' ) ?></h4>
+
+	<table class="wp-profile-fields">
+
+		<?php if ( $ud->display_name ) : ?>
+
+			<tr id="wp_displayname">
+				<td class="label"><?php _e( 'Name', 'buddypress' ); ?></td>
+				<td class="data"><?php echo $ud->display_name; ?></td>
+			</tr>
+
+		<?php endif; ?>
+
+		<?php if ( $ud->user_description ) : ?>
+
+			<tr id="wp_desc">
+				<td class="label"><?php _e( 'About Me', 'buddypress' ); ?></td>
+				<td class="data"><?php echo $ud->user_description; ?></td>
+			</tr>
+
+		<?php endif; ?>
+
+		<?php if ( $ud->user_url ) : ?>
+
+			<tr id="wp_website">
+				<td class="label"><?php _e( 'Website', 'buddypress' ); ?></td>
+				<td class="data"><?php echo make_clickable( $ud->user_url ); ?></td>
+			</tr>
+
+		<?php endif; ?>
+
+		<?php if ( $ud->jabber ) : ?>
+
+			<tr id="wp_jabber">
+				<td class="label"><?php _e( 'Jabber', 'buddypress' ); ?></td>
+				<td class="data"><?php echo $ud->jabber; ?></td>
+			</tr>
+
+		<?php endif; ?>
+
+		<?php if ( $ud->aim ) : ?>
+
+			<tr id="wp_aim">
+				<td class="label"><?php _e( 'AOL Messenger', 'buddypress' ); ?></td>
+				<td class="data"><?php echo $ud->aim; ?></td>
+			</tr>
+
+		<?php endif; ?>
+
+		<?php if ( $ud->yim ) : ?>
+
+			<tr id="wp_yim">
+				<td class="label"><?php _e( 'Yahoo Messenger', 'buddypress' ); ?></td>
+				<td class="data"><?php echo $ud->yim; ?></td>
+			</tr>
+
+		<?php endif; ?>
+
+	</table>
+</div>
+
+<?php
+}
+
+/**
+ * @deprecated 1.5.0
+ * @deprecated Use bp_is_my_profile()
+ */
+function bp_is_home() {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_is_my_profile' );
+	return bp_is_my_profile();
+}
+
+/**
+ * Is the user on the front page of the site?
+ *
+ * @deprecated 1.5.0
+ * @deprecated Use is_front_page()
+ * @return bool
+ */
+function bp_is_front_page() {
+	_deprecated_function( __FUNCTION__, '1.5', "is_front_page()" );
+	return is_front_page();
+}
+
+/**
+ * Is the front page of the site set to the Activity component?
+ *
+ * @deprecated 1.5.0
+ * @deprecated Use bp_is_component_front_page( 'activity' )
+ * @return bool
+ */
+function bp_is_activity_front_page() {
+	_deprecated_function( __FUNCTION__, '1.5', "bp_is_component_front_page( 'activity' )" );
+	return bp_is_component_front_page( 'activity' );
+}
+
+/**
+ * @deprecated 1.5.0
+ * @deprecated use bp_is_user()
+ */
+function bp_is_member() {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_is_user' );
+	return bp_is_user();
+}
+
+/**
+ * @deprecated 1.5.0
+ * @deprecated use bp_loggedin_user_link()
+ */
+function bp_loggedinuser_link() {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_loggedin_user_link' );
+	bp_loggedin_user_link();
+}
+
+/**
+ * Only show the search form if there are available objects to search for.
+ * Deprecated in 1.5; not used anymore.
+ *
+ * @deprecated 1.5.0
+ * @return bool
+ */
+function bp_search_form_enabled() {
+	_deprecated_function( __FUNCTION__, '1.5', 'No longer required.' );
+	return apply_filters( 'bp_search_form_enabled', true );
+}
+
+/**
+ * Template tag version of bp_get_page_title()
+ *
+ * @deprecated 1.5.0
+ * @deprecated Use wp_title()
+ * @since 1.0.0
+ */
+function bp_page_title() {
+	echo bp_get_page_title();
+}
+	/**
+	 * Prior to BuddyPress 1.5, this was used to generate the page's <title> text.
+	 * Now, just simply use wp_title().
+	 *
+	 * @deprecated 1.5.0
+	 * @deprecated Use wp_title()
+	 * @since 1.0.0
+	 *
+	 * @return string
+	 */
+	function bp_get_page_title() {
+		_deprecated_function( __FUNCTION__, '1.5', 'wp_title()' );
+		$title = wp_title( '|', false, 'right' ) . get_bloginfo( 'name', 'display' );
+
+		// Backpat for BP 1.2 filter
+		$title = apply_filters( 'bp_page_title', esc_attr( $title ), esc_attr( $title ) );
+
+		return apply_filters( 'bp_get_page_title', $title );
+	}
+
+/**
+ * Generate a link to log out. Last used in BP 1.2-beta. You should be using wp_logout_url().
+ *
+ * @deprecated 1.5.0
+ * @deprecated Use wp_logout_url()
+ * @since 1.0.0
+ */
+function bp_log_out_link() {
+	_deprecated_function( __FUNCTION__, '1.5', 'wp_logout_url()' );
+
+	$logout_link = '<a href="' . wp_logout_url( bp_get_root_domain() ) . '">' . __( 'Log Out', 'buddypress' ) . '</a>';
+	echo apply_filters( 'bp_logout_link', $logout_link );
+}
+
+/**
+ * Send an email and a BP notification on receipt of an @-mention in a group
+ *
+ * @deprecated 1.5.0
+ * @deprecated Deprecated in favor of the more general bp_activity_at_message_notification()
+ */
+function groups_at_message_notification( $content, $poster_user_id, $group_id, $activity_id ) {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_activity_at_message_notification()' );
+
+	/* Scan for @username strings in an activity update. Notify each user. */
+	$pattern = '/[@]+([A-Za-z0-9-_\.@]+)/';
+	preg_match_all( $pattern, $content, $usernames );
+
+	/* Make sure there's only one instance of each username */
+	if ( !$usernames = array_unique( $usernames[1] ) )
+		return false;
+
+	$group = new BP_Groups_Group( $group_id );
+
+	foreach( (array) $usernames as $username ) {
+		if ( !$receiver_user_id = bp_core_get_userid( $username ) )
+			continue;
+
+		/* Check the user is a member of the group before sending the update. */
+		if ( !groups_is_user_member( $receiver_user_id, $group_id ) )
+			continue;
+
+		// Now email the user with the contents of the message (if they have enabled email notifications)
+		if ( 'no' != bp_get_user_meta( $receiver_user_id, 'notification_activity_new_mention', true ) ) {
+			$poster_name = bp_core_get_user_displayname( $poster_user_id );
+
+			$message_link  = bp_activity_get_permalink( $activity_id );
+			$settings_slug = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+			$settings_link = bp_core_get_user_domain( $receiver_user_id ) . $settings_slug . '/notifications/';
+
+			$poster_name = stripslashes( $poster_name );
+			$content = bp_groups_filter_kses( stripslashes( $content ) );
+
+			// Set up and send the message
+			$ud = bp_core_get_core_userdata( $receiver_user_id );
+			$to = $ud->user_email;
+			$subject = bp_get_email_subject( array( 'text' => sprintf( __( '%1$s mentioned you in the group "%2$s"', 'buddypress' ), $poster_name, $group->name ) ) );
+
+$message = sprintf( __(
+'%1$s mentioned you in the group "%2$s":
+
+"%3$s"
+
+To view and respond to the message, log in and visit: %4$s
+
+---------------------
+', 'buddypress' ), $poster_name, $group->name, $content, $message_link );
+
+			$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+
+			/* Send the message */
+			$to = apply_filters( 'groups_at_message_notification_to', $to );
+			$subject = apply_filters( 'groups_at_message_notification_subject', $subject, $group, $poster_name );
+			$message = apply_filters( 'groups_at_message_notification_message', $message, $group, $poster_name, $content, $message_link, $settings_link );
+
+			wp_mail( $to, $subject, $message );
+		}
+	}
+
+	do_action( 'bp_groups_sent_mention_email', $usernames, $subject, $message, $content, $poster_user_id, $group_id, $activity_id );
+}
+
+/**
+ * BP 1.5 simplified notification functions a bit
+ * @deprecated 1.5.0
+ *
+ * @return mixed
+ */
+function bp_core_delete_notifications_for_user_by_type( $user_id, $component_name, $component_action ) {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_core_delete_notifications_by_type()' );
+	return BP_Core_Notification::delete_for_user_by_type( $user_id, $component_name, $component_action );
+}
+
+/**
+ * @return mixed
+ */
+function bp_core_delete_notifications_for_user_by_item_id( $user_id, $item_id, $component_name, $component_action, $secondary_item_id = false ) {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_core_delete_notifications_by_item_id()' );
+	return BP_Core_Notification::delete_for_user_by_item_id( $user_id, $item_id, $component_name, $component_action, $secondary_item_id );
+}
+
+/**
+ * In BP 1.5, these functions were renamed for greater consistency
+ * @deprecated 1.5.0
+ */
+function bp_forum_directory_permalink() {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_forums_directory_permalink()' );
+	bp_forums_directory_permalink();
+}
+	function bp_get_forum_directory_permalink() {
+		_deprecated_function( __FUNCTION__, '1.5', 'bp_get_forums_directory_permalink()' );
+		return bp_get_forums_directory_permalink();
+	}
+
+/**
+ * Last used by core in BP 1.1. The markup was merged into DTheme's header.php template.
+ * @deprecated 1.5.0
+ */
+function bp_search_form() {
+	_deprecated_function( __FUNCTION__, '1.1', 'No longer required.' );
+
+	$form = '
+		<form action="' . bp_search_form_action() . '" method="post" id="search-form">
+			<input type="text" id="search-terms" name="search-terms" value="" />
+			' . bp_search_form_type_select() . '
+
+			<input type="submit" name="search-submit" id="search-submit" value="' . __( 'Search', 'buddypress' ) . '" />
+			' . wp_nonce_field( 'bp_search_form' ) . '
+		</form>
+	';
+
+	echo apply_filters( 'bp_search_form', $form );
+}
+
+/**
+ * Some _is_ function had their names normalized
+ * @deprecated 1.5.0
+ */
+function bp_is_profile_edit() {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_is_user_profile_edit()' );
+	return bp_is_user_profile_edit();
+}
+
+/**
+ * @deprecated 1.5.0
+ */
+function bp_is_change_avatar() {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_is_user_change_avatar()' );
+	return bp_is_user_change_avatar();
+}
+
+/**
+ * @deprecated 1.5.0
+ */
+function bp_is_friend_requests() {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_is_user_friend_requests()' );
+	return bp_is_user_friend_requests();
+}
+
+/**
+ * Checks to see if a component's URL should be in the root, not under a member page:
+ * eg: http://example.com/groups/the-group NOT http://example.com/members/andy/groups/the-group
+ * You should be using bp_is_root_component().
+ *
+ * @deprecated 1.5.0
+ * @deprecated bp_is_root_component()
+ * @return bool True if root component, else false.
+ */
+function bp_core_is_root_component( $component_name ) {
+	_deprecated_function( __FUNCTION__, '1.5', 'bp_is_root_component()' );
+	return bp_is_root_component( $component_name );
+}
+
+/** Theme *********************************************************************/
+
+/**
+ * Contains functions which were moved out of BP-Default's functions.php
+ * in BuddyPress 1.5.
+ *
+ * @deprecated 1.5.0
+ * @since 1.5.0
+ */
+function bp_dtheme_deprecated() {
+	if ( !function_exists( 'bp_dtheme_wp_pages_filter' ) ) :
+	/**
+	 * In BuddyPress 1.2.x, this function filtered the dropdown on the
+	 * Settings > Reading screen for selecting the page to show on front to
+	 * include "Activity Stream." As of 1.5.x, it is no longer required.
+	 *
+	 * @deprecated 1.5.0
+	 * @deprecated No longer required.
+	 * @param string $page_html A list of pages as a dropdown (select list)
+	 * @return string
+	 * @see wp_dropdown_pages()
+	 * @since 1.2.0
+	 */
+	function bp_dtheme_wp_pages_filter( $page_html ) {
+		_deprecated_function( __FUNCTION__, '1.5', "No longer required." );
+		return $page_html;
+	}
+	endif;
+
+	if ( !function_exists( 'bp_dtheme_page_on_front_update' ) ) :
+	/**
+	 * In BuddyPress 1.2.x, this function hijacked the saving of page on front setting to save the activity stream setting.
+	 * As of 1.5.x, it is no longer required.
+	 *
+	 * @deprecated 1.5.0
+	 * @deprecated No longer required.
+	 * @param string $oldvalue Previous value of get_option( 'page_on_front' )
+	 * @param string $oldvalue New value of get_option( 'page_on_front' )
+	 * @return bool|string
+	 * @since 1.2.0
+	 */
+	function bp_dtheme_page_on_front_update( $oldvalue, $newvalue ) {
+		_deprecated_function( __FUNCTION__, '1.5', "No longer required." );
+		if ( !is_admin() || !bp_current_user_can( 'bp_moderate' ) )
+			return false;
+
+		return $oldvalue;
+	}
+	endif;
+
+	if ( !function_exists( 'bp_dtheme_page_on_front_template' ) ) :
+	/**
+	 * In BuddyPress 1.2.x, this function loaded the activity stream template if the front page display settings allow.
+	 * As of 1.5.x, it is no longer required.
+	 *
+	 * @deprecated 1.5.0
+	 * @deprecated No longer required.
+	 * @param string $template Absolute path to the page template
+	 * @return string
+	 * @since 1.2.0
+	 */
+	function bp_dtheme_page_on_front_template( $template ) {
+		_deprecated_function( __FUNCTION__, '1.5', "No longer required." );
+		return $template;
+	}
+	endif;
+
+	if ( !function_exists( 'bp_dtheme_fix_get_posts_on_activity_front' ) ) :
+	/**
+	 * In BuddyPress 1.2.x, this forced the page ID as a string to stop the get_posts query from kicking up a fuss.
+	 * As of 1.5.x, it is no longer required.
+	 *
+	 * @deprecated 1.5.0
+	 * @deprecated No longer required.
+	 * @since 1.2.0
+	 */
+	function bp_dtheme_fix_get_posts_on_activity_front() {
+		_deprecated_function( __FUNCTION__, '1.5', "No longer required." );
+	}
+	endif;
+
+	if ( !function_exists( 'bp_dtheme_fix_the_posts_on_activity_front' ) ) :
+	/**
+	 * In BuddyPress 1.2.x, this was used as part of the code that set the activity stream to be on the front page.
+	 * As of 1.5.x, it is no longer required.
+	 *
+	 * @deprecated 1.5.0
+	 * @deprecated No longer required.
+	 * @param array $posts Posts as retrieved by WP_Query
+	 * @return array
+	 * @since 1.2.5
+	 */
+	function bp_dtheme_fix_the_posts_on_activity_front( $posts ) {
+		_deprecated_function( __FUNCTION__, '1.5', "No longer required." );
+		return $posts;
+	}
+	endif;
+
+	if ( !function_exists( 'bp_dtheme_add_blog_comments_js' ) ) :
+	/**
+	 * In BuddyPress 1.2.x, this added the JavaScript needed for blog comment replies.
+	 * As of 1.5.x, we recommend that you enqueue the comment-reply JavaScript in your theme's header.php.
+	 *
+	 * @deprecated 1.5.0
+	 * @deprecated Enqueue the comment-reply script in your theme's header.php.
+	 * @since 1.2.0
+	 */
+	function bp_dtheme_add_blog_comments_js() {
+		_deprecated_function( __FUNCTION__, '1.5', "Enqueue the comment-reply script in your theme's header.php." );
+		if ( is_singular() && bp_is_blog_page() && get_option( 'thread_comments' ) )
+			wp_enqueue_script( 'comment-reply' );
+	}
+	endif;
+}
+add_action( 'after_setup_theme', 'bp_dtheme_deprecated', 15 );
+
+/**
+ * In BP 1.5, the Settings functions were moved out of the Core and Members
+ * components, and moved into a new Settings component. This function is no
+ * longer needed as the nav structure is set up by the {@link BP_Component} class.
+ *
+ * @deprecated 1.5.0
+ * @since 1.6.0
+ */
+function bp_core_add_settings_nav() {
+	_deprecated_function( __FUNCTION__, '1.5' );
+}
+
+/**
+ * In BP 1.5, the Settings functions were moved out of the Core and Members
+ * components, and moved into a new Settings component. This function is no
+ * longer needed as new template files for the Settings component were
+ * introduced.
+ *
+ * @deprecated 1.5.0
+ * @since 1.6.0
+ */
+function bp_core_screen_general_settings() {
+	_deprecated_function( __FUNCTION__, '1.5', 'Moved into theme template' );
+}
+
+/**
+ * In BP 1.5, the Settings functions were moved out of the Core and Members
+ * components, and moved into a new Settings component. This function is no
+ * longer needed as new template files for the Settings component were
+ * introduced.
+ *
+ * @deprecated 1.5.0
+ * @since 1.6.0
+ */
+function bp_core_screen_general_settings_title() {
+	_deprecated_function( __FUNCTION__, '1.5', 'Moved into theme template' );
+}
+
+/**
+ * In BP 1.5, the Settings functions were moved out of the Core and Members
+ * components, and moved into a new Settings component. This function is no
+ * longer needed as new template files for the Settings component were
+ * introduced.
+ *
+ * @deprecated 1.5.0
+ * @since 1.6.0
+ */
+function bp_core_screen_general_settings_content() {
+	_deprecated_function( __FUNCTION__, '1.5', 'Moved into theme template' );
+}
+
+/**
+ * In BP 1.5, the Settings functions were moved out of the Core and Members
+ * components, and moved into a new Settings component. This function is no
+ * longer needed as new template files for the Settings component were
+ * introduced.
+ *
+ * @deprecated 1.5.0
+ * @since 1.6.0
+ */
+function bp_core_screen_notification_settings() {
+	_deprecated_function( __FUNCTION__, '1.5', 'Moved into theme template' );
+}
+
+/**
+ * In BP 1.5, the Settings functions were moved out of the Core and Members
+ * components, and moved into a new Settings component. This function is no
+ * longer needed as new template files for the Settings component were
+ * introduced.
+ *
+ * @deprecated 1.5.0
+ * @since 1.6.0
+ */
+function bp_core_screen_notification_settings_title() {
+	_deprecated_function( __FUNCTION__, '1.5', 'Moved into theme template' );
+}
+
+/**
+ * In BP 1.5, the Settings functions were moved out of the Core and Members
+ * components, and moved into a new Settings component. This function is no
+ * longer needed as new template files for the Settings component were
+ * introduced.
+ *
+ * @deprecated 1.5.0
+ * @since 1.6.0
+ */
+function bp_core_screen_notification_settings_content() {
+	_deprecated_function( __FUNCTION__, '1.5', 'Moved into theme template' );
+}
+
+/**
+ * In BP 1.5, the Settings functions were moved out of the Core and Members
+ * components, and moved into a new Settings component. This function is no
+ * longer needed as new template files for the Settings component were
+ * introduced.
+ *
+ * @deprecated 1.5.0
+ * @since 1.6.0
+ */
+function bp_core_screen_delete_account() {
+	_deprecated_function( __FUNCTION__, '1.5', 'Moved into theme template' );
+}
+
+/**
+ * In BP 1.5, the Settings functions were moved out of the Core and Members
+ * components, and moved into a new Settings component. This function is no
+ * longer needed as new template files for the Settings component were
+ * introduced.
+ *
+ * @deprecated 1.5.0
+ * @since 1.6.0
+ */
+function bp_core_screen_delete_account_title() {
+	_deprecated_function( __FUNCTION__, '1.5', 'Moved into theme template' );
+}
+
+/**
+ * In BP 1.5, the Settings functions were moved out of the Core and Members
+ * components, and moved into a new Settings component. This function is no
+ * longer needed as new template files for the Settings component were
+ * introduced.
+ *
+ * @deprecated 1.5.0
+ * @since 1.6.0
+ */
+function bp_core_screen_delete_account_content() {
+	_deprecated_function( __FUNCTION__, '1.5', 'Moved into theme template' );
+}
Index: /tags/2.4.3/src/bp-core/deprecated/1.6.php
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/1.6.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/1.6.php	(revision 10410)
@@ -0,0 +1,273 @@
+<?php
+/**
+ * Deprecated Functions
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @deprecated 1.6.0
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Toolbar functions *********************************************************/
+
+/**
+ * @deprecated 1.6.0
+ */
+function bp_admin_bar_remove_wp_menus() {
+	_deprecated_function( __FUNCTION__, '1.6' );
+}
+
+/**
+ * @deprecated 1.6.0
+ */
+function bp_admin_bar_root_site() {
+	_deprecated_function( __FUNCTION__, '1.6' );
+}
+
+/**
+ * @deprecated 1.6.0
+ */
+function bp_admin_bar_my_sites_menu() {
+	_deprecated_function( __FUNCTION__, '1.6' );
+}
+
+/**
+ * @deprecated 1.6.0
+ */
+function bp_admin_bar_comments_menu( $wp_admin_bar = '' ) {
+	_deprecated_function( __FUNCTION__, '1.6' );
+}
+
+/**
+ * @deprecated 1.6.0
+ */
+function bp_admin_bar_appearance_menu() {
+	_deprecated_function( __FUNCTION__, '1.6' );
+}
+
+/**
+ * @deprecated 1.6.0
+ */
+function bp_admin_bar_updates_menu() {
+	_deprecated_function( __FUNCTION__, '1.6' );
+}
+
+/**
+ * @deprecated 1.6.0
+ */
+function bp_members_admin_bar_my_account_logout() {
+	_deprecated_function( __FUNCTION__, '1.6' );
+}
+
+/**
+ * @deprecated 1.6.0
+ */
+function bp_core_is_user_deleted( $user_id = 0 ) {
+	_deprecated_function( __FUNCTION__, '1.6' );
+	bp_is_user_deleted( $user_id );
+}
+
+/**
+ * @deprecated 1.6.0
+ */
+function bp_core_is_user_spammer( $user_id = 0 ) {
+	_deprecated_function( __FUNCTION__, '1.6' );
+	bp_is_user_spammer( $user_id );
+}
+
+
+/**
+ * Blogs functions
+ */
+
+/**
+ * @deprecated 1.6.0
+ * @deprecated No longer used; see bp_blogs_transition_activity_status()
+ */
+function bp_blogs_manage_comment( $comment_id, $comment_status ) {
+	_deprecated_function( __FUNCTION__, '1.6', 'No longer used' );
+}
+
+/**
+ * Core functions
+ */
+
+/**
+ * @deprecated 1.6.0
+ * @deprecated No longer used; see BP_Admin::admin_menus()
+ */
+function bp_core_add_admin_menu() {
+	_deprecated_function( __FUNCTION__, '1.6', 'No longer used' );
+}
+
+/**
+ * @deprecated 1.6.0
+ * @deprecated No longer used. We do ajax properly now.
+ */
+function bp_core_add_ajax_hook() {
+	_deprecated_function( __FUNCTION__, '1.6', 'No longer used' );
+}
+
+/**
+ * Friends functions
+ */
+
+/**
+ * Displays Friends header tabs
+ *
+ * @deprecated 1.6.0
+ * @deprecated No longer used
+ */
+function bp_friends_header_tabs() {
+	_deprecated_function( __FUNCTION__, '1.6', 'Since BuddyPress 1.2, BP has not supported ordering of friend lists by URL parameters.' );
+?>
+
+	<li<?php if ( !bp_action_variable( 0 ) || bp_is_action_variable( 'recently-active', 0 ) ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( bp_displayed_user_domain() . bp_get_friends_slug() . '/my-friends/recently-active' ) ?>"><?php _e( 'Recently Active', 'buddypress' ) ?></a></li>
+	<li<?php if ( bp_is_action_variable( 'newest', 0 ) ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( bp_displayed_user_domain() . bp_get_friends_slug() . '/my-friends/newest' ) ?>"><?php _e( 'Newest', 'buddypress' ) ?></a></li>
+	<li<?php if ( bp_is_action_variable( 'alphabetically', 0 ) ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( bp_displayed_user_domain() . bp_get_friends_slug() . '/my-friends/alphabetically' ) ?>"><?php _e( 'Alphabetically', 'buddypress' ) ?></a></li>
+
+<?php
+	do_action( 'friends_header_tabs' );
+}
+
+/**
+ * Filters the title for the Friends component
+ *
+ * @deprecated 1.6.0
+ * @deprecated No longer used
+ */
+function bp_friends_filter_title() {
+	_deprecated_function( __FUNCTION__, '1.6', 'Since BuddyPress 1.2, BP has not supported ordering of friend lists by URL parameters.' );
+
+	$current_filter = bp_action_variable( 0 );
+
+	switch ( $current_filter ) {
+		case 'recently-active': default:
+			_e( 'Recently Active', 'buddypress' );
+			break;
+		case 'newest':
+			_e( 'Newest', 'buddypress' );
+			break;
+		case 'alphabetically':
+			_e( 'Alphabetically', 'buddypress' );
+			break;
+	}
+}
+
+
+/** Groups functions **********************************************************/
+
+/**
+ * @deprecated 1.6.0
+ * @deprecated Renamed to groups_get_id() for greater consistency
+ */
+function groups_check_group_exists( $group_slug ) {
+	_deprecated_function( __FUNCTION__, '1.6', 'groups_get_id()' );
+	return groups_get_id( $group_slug );
+}
+
+/** Admin functions ***********************************************************/
+
+/**
+ * Loads admin panel styles and scripts.
+ *
+ * @deprecated 1.6.0
+ */
+function bp_core_add_admin_menu_styles() {
+	_deprecated_function( __FUNCTION__, '1.6' );
+}
+
+/** Activity functions ********************************************************/
+
+/**
+ * @deprecated 1.6.0
+ */
+function updates_register_activity_actions() {
+	_deprecated_function( __FUNCTION__, '1.6' );
+}
+
+/**
+ * Sets the "From" address in emails sent
+ *
+ * @deprecated 1.6.0
+ * @return string email address
+ */
+function bp_core_email_from_address_filter() {
+	_deprecated_function( __FUNCTION__, '1.6' );
+
+	$domain = (array) explode( '/', site_url() );
+	return apply_filters( 'bp_core_email_from_address_filter', 'noreply@' . $domain[2] );
+}
+
+/**
+ * Backward compatibility for AJAX callbacks that do not die() on their own
+ *
+ * In BuddyPress 1.6, BP was altered so that it uses admin-ajax.php (instead of wp-load.php) for
+ * AJAX requests. admin-ajax.php dies with an output of '0' (to signify an error), so that if an
+ * AJAX callback does not kill PHP execution, a '0' character will be erroneously appended to the
+ * output. All bp-default AJAX callbacks (/bp-themes/bp-default/_inc/ajax.php) have been updated
+ * for BP 1.6 so that they die() properly; any theme that dynamically includes this file will
+ * inherit the fixes. However, any theme that contains a copy of BP's pre-1.5 ajax.php file will
+ * continue to witness the 'trailing "0"' problem.
+ *
+ * This function provides a backward compatible workaround for these themes, by hooking to the
+ * BP wp_ajax_ actions that were problematic prior to BP 1.6, and killing PHP execution with die().
+ *
+ * Note that this hack only runs if the function bp_dtheme_register_actions() is not found (this
+ * function was introduced in BP 1.6 for related backward compatibility reasons).
+ *
+ * @deprecated 1.6.0
+ */
+if ( !function_exists( 'bp_dtheme_register_actions' ) ) :
+	function bp_die_legacy_ajax_callbacks() {
+
+		// This is a list of the BP wp_ajax_ hook suffixes whose associated functions did
+		// not die properly before BP 1.6
+		$actions = array(
+			// Directory template loaders
+			'members_filter',
+			'groups_filter',
+			'blogs_filter',
+			'forums_filter',
+			'messages_filter',
+
+			// Activity
+			'activity_widget_filter',
+			'activity_get_older_updates',
+			'post_update',
+			'new_activity_comment',
+			'delete_activity',
+			'delete_activity_comment',
+			'spam_activity',
+			'spam_activity_comment',
+			'activity_mark_fav',
+			'activity_mark_unfav',
+
+			// Groups
+			'groups_invite_user',
+			'joinleave_group',
+
+			// Members
+			'addremove_friend',
+			'accept_friendship',
+			'reject_friendship',
+
+			// Messages
+			'messages_close_notice',
+			'messages_send_reply',
+			'messages_markunread',
+			'messages_markread',
+			'messages_delete',
+			'messages_autocomplete_results'
+		);
+
+		// For each of the problematic hooks, exit at the very end of execution
+		foreach( $actions as $action ) {
+			add_action( 'wp_ajax_'        . $action, create_function( '', 'exit;' ), 9999 );
+			add_action( 'wp_ajax_nopriv_' . $action, create_function( '', 'exit;' ), 9999 );
+		}
+	}
+	add_action( 'after_setup_theme', 'bp_die_legacy_ajax_callbacks', 20 );
+endif;
Index: /tags/2.4.3/src/bp-core/deprecated/1.7.php
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/1.7.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/1.7.php	(revision 10410)
@@ -0,0 +1,163 @@
+<?php
+/**
+ * Deprecated Functions
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @deprecated Since 1.7.0
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the BuddyPress maintenance mode
+ *
+ * @since 1.6.0
+ * @deprecated 1.7.0
+ * @uses bp_get_maintenance_mode() To get the BuddyPress maintenance mode
+ */
+function bp_maintenance_mode() {
+	echo bp_get_maintenance_mode();
+}
+	/**
+	 * Return the BuddyPress maintenance mode
+	 *
+	 * @since 1.6.0
+	 * @deprecated 1.7.0
+	 * @return string The BuddyPress maintenance mode
+	 */
+	function bp_get_maintenance_mode() {
+		return buddypress()->maintenance_mode;
+	}
+
+/**
+ * @deprecated 1.7.0
+ */
+function xprofile_get_profile() {
+	_deprecated_function( __FUNCTION__, '1.7' );
+	bp_locate_template( array( 'profile/profile-loop.php' ), true );
+}
+
+/**
+ * @deprecated 1.7.0
+ */
+function bp_get_profile_header() {
+	_deprecated_function( __FUNCTION__, '1.7' );
+	bp_locate_template( array( 'profile/profile-header.php' ), true );
+}
+
+/**
+ * @deprecated 1.7.0
+ * @param string $component_name
+ * @return boolean
+ */
+function bp_exists( $component_name ) {
+	_deprecated_function( __FUNCTION__, '1.7' );
+	if ( function_exists( $component_name . '_install' ) )
+		return true;
+
+	return false;
+}
+
+/**
+ * @deprecated 1.7.0
+ */
+function bp_get_plugin_sidebar() {
+	_deprecated_function( __FUNCTION__, '1.7' );
+	bp_locate_template( array( 'plugin-sidebar.php' ), true );
+}
+
+/**
+ * On multiblog installations you must first allow themes to be activated and
+ * show up on the theme selection screen. This function will let the BuddyPress
+ * bundled themes show up on the root blog selection screen and bypass this
+ * step. It also means that the themes won't show for selection on other blogs.
+ *
+ * @deprecated 1.7.0
+ * @package BuddyPress Core
+ */
+function bp_core_allow_default_theme( $themes ) {
+	_deprecated_function( __FUNCTION__, '1.7' );
+
+	if ( !bp_current_user_can( 'bp_moderate' ) )
+		return $themes;
+
+	if ( bp_get_root_blog_id() != get_current_blog_id() )
+		return $themes;
+
+	if ( isset( $themes['bp-default'] ) )
+		return $themes;
+
+	$themes['bp-default'] = true;
+
+	return $themes;
+}
+
+/**
+ * No longer used by BuddyPress core
+ *
+ * @deprecated 1.7.0
+ * @param string $page
+ * @return boolean True if is BuddyPress page
+ */
+function bp_is_page( $page = '' ) {
+	_deprecated_function( __FUNCTION__, '1.7' );
+
+	if ( !bp_is_user() && bp_is_current_component( $page )  )
+		return true;
+
+	if ( 'home' == $page )
+		return is_front_page();
+
+	return false;
+}
+
+/** Admin *********************************************************************/
+
+/**
+ * This function was originally used to update pre-1.1 schemas, but that was
+ * before we had a legitimate update process.
+ *
+ * @deprecated 1.7.0
+ * @global WPDB $wpdb
+ */
+function bp_update_db_stuff() {
+	global $wpdb;
+
+	$bp        = buddypress();
+	$bp_prefix = bp_core_get_table_prefix();
+
+	// Rename the old user activity cached table if needed.
+	if ( $wpdb->get_var( "SHOW TABLES LIKE '%{$bp_prefix}bp_activity_user_activity_cached%'" ) ) {
+		$wpdb->query( "RENAME TABLE {$bp_prefix}bp_activity_user_activity_cached TO {$bp->activity->table_name}" );
+	}
+
+	// Rename fields from pre BP 1.2
+	if ( $wpdb->get_var( "SHOW TABLES LIKE '%{$bp->activity->table_name}%'" ) ) {
+		if ( $wpdb->get_var( "SHOW COLUMNS FROM {$bp->activity->table_name} LIKE 'component_action'" ) ) {
+			$wpdb->query( "ALTER TABLE {$bp->activity->table_name} CHANGE component_action type varchar(75) NOT NULL" );
+		}
+
+		if ( $wpdb->get_var( "SHOW COLUMNS FROM {$bp->activity->table_name} LIKE 'component_name'" ) ) {
+			$wpdb->query( "ALTER TABLE {$bp->activity->table_name} CHANGE component_name component varchar(75) NOT NULL" );
+		}
+	}
+
+	// On first installation - record all existing blogs in the system.
+	if ( !(int) $bp->site_options['bp-blogs-first-install'] ) {
+		bp_blogs_record_existing_blogs();
+		bp_update_option( 'bp-blogs-first-install', 1 );
+	}
+
+	if ( is_multisite() ) {
+		bp_core_add_illegal_names();
+	}
+
+	// Update and remove the message threads table if it exists
+	if ( $wpdb->get_var( "SHOW TABLES LIKE '%{$bp_prefix}bp_messages_threads%'" ) ) {
+		if ( BP_Messages_Thread::update_tables() ) {
+			$wpdb->query( "DROP TABLE {$bp_prefix}bp_messages_threads" );
+		}
+	}
+}
Index: /tags/2.4.3/src/bp-core/deprecated/1.9.php
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/1.9.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/1.9.php	(revision 10410)
@@ -0,0 +1,264 @@
+<?php
+/**
+ * BuddyPress Member Notifications
+ *
+ * Backwards compatibility functions and filters used for member notifications.
+ * Use bp-notifications instead.
+ *
+ * @package BuddyPress
+ * @subpackage MembersNotifications
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Add a notification for a specific user, from a specific component.
+ *
+ * @deprecated Deprecated since BuddyPress 1.9.0. Use
+ *             bp_notifications_add_notification() instead.
+ *
+ * @since 1.0.0
+ * @param string $item_id
+ * @param int $user_id
+ * @param string $component_name
+ * @param string $component_action
+ * @param string $secondary_item_id
+ * @param string $date_notified
+ * @param int $is_new
+ * @return boolean True on success, false on failure.
+ */
+function bp_core_add_notification( $item_id, $user_id, $component_name, $component_action, $secondary_item_id = 0, $date_notified = false, $is_new = 1 ) {
+
+	// Bail if notifications is not active
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	// Trigger the deprecated function notice
+	_deprecated_function( __FUNCTION__, '1.9', 'bp_notifications_add_notification()' );
+
+	// Notifications must always have a time
+	if ( false === $date_notified ) {
+		$date_notified = bp_core_current_time();
+	}
+
+	// Add the notification
+	return bp_notifications_add_notification( array(
+		'item_id'           => $item_id,
+		'user_id'           => $user_id,
+		'component_name'    => $component_name,
+		'component_action'  => $component_action,
+		'secondary_item_id' => $secondary_item_id,
+		'date_notified'     => $date_notified,
+		'is_new'            => $is_new
+	) );
+}
+
+/**
+ * Delete a specific notification by its ID.
+ *
+ * @deprecated Deprecated since BuddyPress 1.9.0. Use
+ *             bp_notifications_delete_notification() instead.
+ *
+ * @since 1.0.0
+ * @param int $id ID of notification.
+ * @return boolean True on success, false on failure.
+ */
+function bp_core_delete_notification( $id ) {
+
+	// Bail if notifications is not active
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	// Trigger the deprecated function notice
+	_deprecated_function( __FUNCTION__, '1.9', 'bp_notifications_delete_notification()' );
+
+	return BP_Notifications_Notification::delete_by_id( $id );
+}
+
+/**
+ * Get a specific notification by its ID.
+ *
+ * @deprecated Deprecated since BuddyPress 1.9.0. Use
+ *             bp_notifications_get_notification() instead.
+ *
+ * @since 1.0.0
+ * @param int $id ID of notification.
+ * @return BP_Core_Notification
+ */
+function bp_core_get_notification( $id ) {
+
+	// Bail if notifications is not active
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	// Trigger the deprecated function notice
+	_deprecated_function( __FUNCTION__, '1.9', 'bp_notifications_get_notification()' );
+
+	return bp_notifications_get_notification( $id );
+}
+
+/**
+ * Get notifications for a specific user.
+ *
+ * @deprecated Deprecated since BuddyPress 1.9.0. Use
+ *             bp_notifications_get_notifications_for_user() instead.
+ *
+ * @since 1.0.0
+ * @param int $user_id ID of user.
+ * @param string $format
+ * @return boolean Object or array on success, false on failure.
+ */
+function bp_core_get_notifications_for_user( $user_id, $format = 'string' ) {
+
+	// Bail if notifications is not active
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	// Trigger the deprecated function notice
+	_deprecated_function( __FUNCTION__, '1.9', 'bp_notifications_get_notifications_for_user()' );
+
+	return bp_notifications_get_notifications_for_user( $user_id, $format );
+}
+
+/** Delete ********************************************************************/
+
+/**
+ * Delete notifications for a user by type.
+ *
+ * Used when clearing out notifications for a specific component when the user
+ * has visited that component.
+ *
+ * @deprecated Deprecated since BuddyPress 1.9.0. Use
+ *             bp_notifications_delete_notifications_by_type() instead.
+ *
+ * @since 1.0.0
+ * @param int $user_id
+ * @param string $component_name
+ * @param string $component_action
+ * @return boolean True on success, false on failure.
+ */
+function bp_core_delete_notifications_by_type( $user_id, $component_name, $component_action ) {
+
+	// Bail if notifications is not active
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	// Trigger the deprecated function notice
+	_deprecated_function( __FUNCTION__, '1.9', 'bp_notifications_delete_notifications_by_type()' );
+
+	return bp_notifications_delete_notifications_by_type( $user_id, $component_name, $component_action );
+}
+
+/**
+ * Delete notifications for an item ID.
+ *
+ * Used when clearing out notifications for a specific component when the user
+ * has visited that component.
+ *
+ * @deprecated Deprecated since BuddyPress 1.9.0. Use
+ *             bp_notifications_delete_notifications_by_item_id() instead.
+ *
+ * @since 1.0.0
+ * @param int $user_id
+ * @param string $component_name
+ * @param string $component_action
+ * @return boolean True on success, false on failure.
+ */
+function bp_core_delete_notifications_by_item_id( $user_id, $item_id, $component_name, $component_action, $secondary_item_id = false ) {
+
+	// Bail if notifications is not active
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	// Trigger the deprecated function notice
+	_deprecated_function( __FUNCTION__, '1.9', 'bp_notifications_delete_notifications_by_item_id()' );
+
+	return bp_notifications_delete_notifications_by_item_id( $user_id, $item_id, $component_name, $component_action, $secondary_item_id );
+}
+
+/**
+ * Delete all notifications for by type.
+ *
+ * @deprecated Deprecated since BuddyPress 1.9.0. Use
+ *             bp_notifications_delete_all_notifications_by_type() instead.
+ *
+ * @since 1.0.0
+ * @param int $user_id
+ * @param string $component_name
+ * @param string $component_action
+ * @return boolean True on success, false on failure.
+ */
+function bp_core_delete_all_notifications_by_type( $item_id, $component_name, $component_action = false, $secondary_item_id = false ) {
+
+	// Bail if notifications is not active
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	// Trigger the deprecated function notice
+	_deprecated_function( __FUNCTION__, '1.9', 'bp_notifications_delete_all_notifications_by_type()' );
+
+	bp_notifications_delete_all_notifications_by_type( $item_id, $component_name, $component_action, $secondary_item_id );
+}
+
+/**
+ * Delete all notifications for a user.
+ *
+ * Used when clearing out all notifications for a user, when deleted or spammed
+ *
+ * @deprecated Deprecated since BuddyPress 1.9.0. Use
+ *             bp_notifications_delete_notifications_from_user() instead.
+ *
+ * @since 1.0.0
+ * @param int $user_id
+ * @param string $component_name
+ * @param string $component_action
+ * @return boolean True on success, false on failure.
+ */
+function bp_core_delete_notifications_from_user( $user_id, $component_name, $component_action ) {
+
+	// Bail if notifications is not active
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	// Trigger the deprecated function notice
+	_deprecated_function( __FUNCTION__, '1.9', 'bp_notifications_delete_notifications_from_user()' );
+
+	return bp_notifications_delete_notifications_from_user( $user_id, $component_name, $component_action );
+}
+
+/** Helpers *******************************************************************/
+
+/**
+ * Check if a user has access to a specific notification.
+ *
+ * Used before deleting a notification for a user.
+ *
+ * @deprecated Deprecated since BuddyPress 1.9.0. Use
+ *             bp_notifications_check_notification_access() instead.
+ *
+ * @since 1.0.0
+ * @param int $user_id
+ * @param int $notification_id
+ * @return boolean True on success, false on failure.
+ */
+function bp_core_check_notification_access( $user_id, $notification_id ) {
+
+	// Bail if notifications is not active
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	// Trigger the deprecated function notice
+	_deprecated_function( __FUNCTION__, '1.9', 'bp_notifications_check_notification_access()' );
+
+	return bp_notifications_check_notification_access( $user_id, $notification_id );
+}
Index: /tags/2.4.3/src/bp-core/deprecated/2.0.php
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/2.0.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/2.0.php	(revision 10410)
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Deprecated Functions
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @deprecated 2.0.0
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * @deprecated 2.0.0
+ */
+function bp_activity_clear_meta_cache_for_activity() {
+	_deprecated_function( __FUNCTION__, '2.0.0', 'Use WP metadata API instead' );
+}
+
+/**
+ * @deprecated 2.0.0
+ */
+function bp_blogs_catch_published_post() {
+	_deprecated_function( __FUNCTION__, '2.0', 'bp_blogs_catch_transition_post_status()' );
+}
+
+/**
+ * @deprecated 2.0.0
+ */
+function bp_messages_screen_inbox_mark_notifications() {
+	_deprecated_function( __FUNCTION__, '2.0', 'bp_messages_screen_conversation_mark_notifications()' );
+}
Index: /tags/2.4.3/src/bp-core/deprecated/2.1.php
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/2.1.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/2.1.php	(revision 10410)
@@ -0,0 +1,648 @@
+<?php
+/**
+ * Deprecated functions
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @deprecated 2.1.0
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register (not enqueue) scripts that used to be used by BuddyPress.
+ *
+ * @since 2.1.0
+ */
+function bp_core_register_deprecated_scripts() {
+	$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+	$url = buddypress()->plugin_url . 'bp-core/deprecated/js/';
+
+	$scripts = apply_filters( 'bp_core_register_deprecated_scripts', array(
+
+		// Messages
+		'bp-jquery-autocomplete'    => array(
+			'file'          => "{$url}autocomplete/jquery.autocomplete{$min}.js",
+			'dependencies' => array( 'jquery' ),
+		),
+
+		'bp-jquery-autocomplete-fb' => array(
+			'file'         => "{$url}autocomplete/jquery.autocompletefb{$min}.js",
+			'dependencies' => array( 'jquery' ),
+		),
+
+		'bp-jquery-bgiframe' => array(
+			'file'         => "{$url}autocomplete/jquery.bgiframe{$min}.js",
+			'dependencies' => array( 'jquery' ),
+		),
+
+		'bp-jquery-dimensions' => array(
+			'file'         => "{$url}autocomplete/jquery.dimensions{$min}.js",
+			'dependencies' => array( 'jquery' ),
+		),
+	) );
+
+	foreach ( $scripts as $id => $script ) {
+		wp_register_script( $id, $script['file'], $script['dependencies'], bp_get_version(), true );
+	}
+}
+add_action( 'bp_enqueue_scripts', 'bp_core_register_deprecated_scripts', 1 );
+
+/**
+ * Register (not enqueue) styles that used to be used by BuddyPress.
+ *
+ * @since 2.1.0
+ */
+function bp_core_register_deprecated_styles() {
+	$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+	$url = buddypress()->plugin_url . 'bp-core/deprecated/css/';
+
+	$styles = apply_filters( 'bp_core_register_deprecated_styles', array(
+		// Messages
+		'bp-messages-autocomplete' => array(
+			'file'         => "{$url}autocomplete/jquery.autocompletefb{$min}.css",
+			'dependencies' => array(),
+		)
+	) );
+
+	foreach ( $styles as $id => $style ) {
+		wp_register_style( $id, $style['file'], $style['dependencies'], bp_get_version() );
+
+		wp_style_add_data( $id, 'rtl', true );
+		if ( $min ) {
+			wp_style_add_data( $id, 'suffix', $min );
+		}
+	}
+}
+add_action( 'bp_enqueue_scripts', 'bp_core_register_deprecated_styles', 1 );
+
+/** BuddyBar *****************************************************************/
+
+/**
+ * Add a Sites menu to the BuddyBar.
+ *
+ * @since 1.0.0
+ * @deprecated 2.1.0
+ *
+ * @return bool|null Returns false on failure. Otherwise echoes the menu item.
+ */
+function bp_adminbar_blogs_menu() {
+
+	if ( ! is_user_logged_in() || ! bp_is_active( 'blogs' ) ) {
+		return false;
+	}
+
+	if ( ! is_multisite() ) {
+		return false;
+	}
+
+	$blogs = wp_cache_get( 'bp_blogs_of_user_' . bp_loggedin_user_id() . '_inc_hidden', 'bp' );
+	if ( empty( $blogs ) ) {
+		$blogs = bp_blogs_get_blogs_for_user( bp_loggedin_user_id(), true );
+		wp_cache_set( 'bp_blogs_of_user_' . bp_loggedin_user_id() . '_inc_hidden', $blogs, 'bp' );
+	}
+
+	$counter = 0;
+	if ( is_array( $blogs['blogs'] ) && (int) $blogs['count'] ) {
+
+		echo '<li id="bp-adminbar-blogs-menu"><a href="' . trailingslashit( bp_loggedin_user_domain() . bp_get_blogs_slug() ) . '">';
+
+		_e( 'My Sites', 'buddypress' );
+
+		echo '</a>';
+		echo '<ul>';
+
+		foreach ( (array) $blogs['blogs'] as $blog ) {
+			$alt      = ( 0 == $counter % 2 ) ? ' class="alt"' : '';
+			$site_url = esc_attr( $blog->siteurl );
+
+			echo '<li' . $alt . '>';
+			echo '<a href="' . $site_url . '">' . esc_html( $blog->name ) . '</a>';
+			echo '<ul>';
+			echo '<li class="alt"><a href="' . $site_url . 'wp-admin/">' . __( 'Dashboard', 'buddypress' ) . '</a></li>';
+			echo '<li><a href="' . $site_url . 'wp-admin/post-new.php">' . __( 'New Post', 'buddypress' ) . '</a></li>';
+			echo '<li class="alt"><a href="' . $site_url . 'wp-admin/edit.php">' . __( 'Manage Posts', 'buddypress' ) . '</a></li>';
+			echo '<li><a href="' . $site_url . 'wp-admin/edit-comments.php">' . __( 'Manage Comments', 'buddypress' ) . '</a></li>';
+			echo '</ul>';
+
+			do_action( 'bp_adminbar_blog_items', $blog );
+
+			echo '</li>';
+			$counter++;
+		}
+
+		$alt = ( 0 == $counter % 2 ) ? ' class="alt"' : '';
+
+		if ( bp_blog_signup_enabled() ) {
+			echo '<li' . $alt . '>';
+			echo '<a href="' . trailingslashit( bp_get_blogs_directory_permalink() . 'create' ) . '">' . __( 'Create a Site!', 'buddypress' ) . '</a>';
+			echo '</li>';
+		}
+
+		echo '</ul>';
+		echo '</li>';
+	}
+}
+
+/**
+ * If user has upgraded to 1.6 and chose to retain their BuddyBar, offer then a switch to change over
+ * to the WP Toolbar.
+ *
+ * @since 1.6.0
+ * @deprecated 2.1.0
+ */
+function bp_admin_setting_callback_force_buddybar() {
+?>
+
+	<input id="_bp_force_buddybar" name="_bp_force_buddybar" type="checkbox" value="1" <?php checked( ! bp_force_buddybar( true ) ); ?> />
+	<label for="_bp_force_buddybar"><?php _e( 'Switch to WordPress Toolbar', 'buddypress' ); ?></label>
+
+<?php
+}
+
+
+/**
+ * Sanitization for _bp_force_buddybar
+ *
+ * If upgraded to 1.6 and you chose to keep the BuddyBar, a checkbox asks if you want to switch to
+ * the WP Toolbar. The option we store is 1 if the BuddyBar is forced on, so we use this function
+ * to flip the boolean before saving the intval.
+ *
+ * @since 1.6.0
+ * @deprecated 2.1.0
+ * @access Private
+ */
+function bp_admin_sanitize_callback_force_buddybar( $value = false ) {
+	return $value ? 0 : 1;
+}
+
+/**
+ * Wrapper function for rendering the BuddyBar.
+ *
+ * @return bool|null Returns false if the BuddyBar is disabled.
+ * @deprecated 2.1.0
+ */
+function bp_core_admin_bar() {
+	$bp = buddypress();
+
+	if ( defined( 'BP_DISABLE_ADMIN_BAR' ) && BP_DISABLE_ADMIN_BAR ) {
+		return false;
+	}
+
+	if ( (int) bp_get_option( 'hide-loggedout-adminbar' ) && !is_user_logged_in() ) {
+		return false;
+	}
+
+	$bp->doing_admin_bar = true;
+
+	echo '<div id="wp-admin-bar"><div class="padder">';
+
+	// **** Do bp-adminbar-logo Actions ********
+	do_action( 'bp_adminbar_logo' );
+
+	echo '<ul class="main-nav">';
+
+	// **** Do bp-adminbar-menus Actions ********
+	do_action( 'bp_adminbar_menus' );
+
+	echo '</ul>';
+	echo "</div></div><!-- #wp-admin-bar -->\n\n";
+
+	$bp->doing_admin_bar = false;
+}
+
+/**
+ * Output the BuddyBar logo.
+ *
+ * @deprecated 2.1.0
+ */
+function bp_adminbar_logo() {
+	echo '<a href="' . bp_get_root_domain() . '" id="admin-bar-logo">' . get_blog_option( bp_get_root_blog_id(), 'blogname' ) . '</a>';
+}
+
+/**
+ * Output the "Log In" and "Sign Up" names to the BuddyBar.
+ *
+ * Visible only to visitors who are not logged in.
+ *
+ * @deprecated 2.1.0
+ *
+ * @return bool|null Returns false if the current user is logged in.
+ */
+function bp_adminbar_login_menu() {
+
+	if ( is_user_logged_in() ) {
+		return false;
+	}
+
+	echo '<li class="bp-login no-arrow"><a href="' . wp_login_url() . '">' . __( 'Log In', 'buddypress' ) . '</a></li>';
+
+	// Show "Sign Up" link if user registrations are allowed
+	if ( bp_get_signup_allowed() ) {
+		echo '<li class="bp-signup no-arrow"><a href="' . bp_get_signup_page() . '">' . __( 'Sign Up', 'buddypress' ) . '</a></li>';
+	}
+}
+
+/**
+ * Output the My Account BuddyBar menu.
+ *
+ * @deprecated 2.1.0
+ *
+ * @return bool|null Returns false on failure.
+ */
+function bp_adminbar_account_menu() {
+	$bp = buddypress();
+
+	if ( empty( $bp->bp_nav ) || ! is_user_logged_in() ) {
+		return false;
+	}
+
+	echo '<li id="bp-adminbar-account-menu"><a href="' . bp_loggedin_user_domain() . '">';
+	echo __( 'My Account', 'buddypress' ) . '</a>';
+	echo '<ul>';
+
+	// Loop through each navigation item
+	$counter = 0;
+	foreach( (array) $bp->bp_nav as $nav_item ) {
+		$alt = ( 0 == $counter % 2 ) ? ' class="alt"' : '';
+
+		if ( -1 == $nav_item['position'] ) {
+			continue;
+		}
+
+		echo '<li' . $alt . '>';
+		echo '<a id="bp-admin-' . $nav_item['css_id'] . '" href="' . $nav_item['link'] . '">' . $nav_item['name'] . '</a>';
+
+		if ( isset( $bp->bp_options_nav[$nav_item['slug']] ) && is_array( $bp->bp_options_nav[$nav_item['slug']] ) ) {
+			echo '<ul>';
+			$sub_counter = 0;
+
+			foreach( (array) $bp->bp_options_nav[$nav_item['slug']] as $subnav_item ) {
+				$link = $subnav_item['link'];
+				$name = $subnav_item['name'];
+
+				if ( bp_displayed_user_domain() ) {
+					$link = str_replace( bp_displayed_user_domain(), bp_loggedin_user_domain(), $subnav_item['link'] );
+				}
+
+				if ( isset( $bp->displayed_user->userdata->user_login ) ) {
+					$name = str_replace( $bp->displayed_user->userdata->user_login, $bp->loggedin_user->userdata->user_login, $subnav_item['name'] );
+				}
+
+				$alt = ( 0 == $sub_counter % 2 ) ? ' class="alt"' : '';
+				echo '<li' . $alt . '><a id="bp-admin-' . $subnav_item['css_id'] . '" href="' . $link . '">' . $name . '</a></li>';
+				$sub_counter++;
+			}
+			echo '</ul>';
+		}
+
+		echo '</li>';
+
+		$counter++;
+	}
+
+	$alt = ( 0 == $counter % 2 ) ? ' class="alt"' : '';
+
+	echo '<li' . $alt . '><a id="bp-admin-logout" class="logout" href="' . wp_logout_url( home_url() ) . '">' . __( 'Log Out', 'buddypress' ) . '</a></li>';
+	echo '</ul>';
+	echo '</li>';
+}
+
+function bp_adminbar_thisblog_menu() {
+	if ( current_user_can( 'edit_posts' ) ) {
+		echo '<li id="bp-adminbar-thisblog-menu"><a href="' . admin_url() . '">';
+		_e( 'Dashboard', 'buddypress' );
+		echo '</a>';
+		echo '<ul>';
+
+		echo '<li class="alt"><a href="' . admin_url() . 'post-new.php">' . __( 'New Post', 'buddypress' ) . '</a></li>';
+		echo '<li><a href="' . admin_url() . 'edit.php">' . __( 'Manage Posts', 'buddypress' ) . '</a></li>';
+		echo '<li class="alt"><a href="' . admin_url() . 'edit-comments.php">' . __( 'Manage Comments', 'buddypress' ) . '</a></li>';
+
+		do_action( 'bp_adminbar_thisblog_items' );
+
+		echo '</ul>';
+		echo '</li>';
+	}
+}
+
+/**
+ * Output the Random BuddyBar menu.
+ *
+ * Not visible for logged-in users.
+ *
+ * @deprecated 2.1.0
+ */
+function bp_adminbar_random_menu() {
+?>
+
+	<li class="align-right" id="bp-adminbar-visitrandom-menu">
+		<a href="#"><?php _e( 'Visit', 'buddypress' ) ?></a>
+		<ul class="random-list">
+			<li><a href="<?php bp_members_directory_permalink(); ?>?random-member" rel="nofollow"><?php _e( 'Random Member', 'buddypress' ) ?></a></li>
+
+			<?php if ( bp_is_active( 'groups' ) ) : ?>
+
+				<li class="alt"><a href="<?php bp_groups_directory_permalink(); ?>?random-group"  rel="nofollow"><?php _e( 'Random Group', 'buddypress' ) ?></a></li>
+
+			<?php endif; ?>
+
+			<?php if ( is_multisite() && bp_is_active( 'blogs' ) ) : ?>
+
+				<li><a href="<?php bp_blogs_directory_permalink(); ?>?random-blog"  rel="nofollow"><?php _e( 'Random Site', 'buddypress' ) ?></a></li>
+
+			<?php endif; ?>
+
+			<?php do_action( 'bp_adminbar_random_menu' ) ?>
+
+		</ul>
+	</li>
+
+	<?php
+}
+
+/**
+ * Enqueue the BuddyBar CSS.
+ *
+ * @deprecated 2.1.0
+ */
+function bp_core_load_buddybar_css() {
+
+	if ( bp_use_wp_admin_bar() || ( (int) bp_get_option( 'hide-loggedout-adminbar' ) && !is_user_logged_in() ) || ( defined( 'BP_DISABLE_ADMIN_BAR' ) && BP_DISABLE_ADMIN_BAR ) ) {
+		return;
+	}
+
+	$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+
+	if ( file_exists( get_stylesheet_directory() . '/_inc/css/adminbar.css' ) ) { // Backwards compatibility
+		$stylesheet = get_stylesheet_directory_uri() . '/_inc/css/adminbar.css';
+	} else {
+		$stylesheet = buddypress()->plugin_url . "bp-core/css/buddybar{$min}.css";
+	}
+
+	wp_enqueue_style( 'bp-admin-bar', apply_filters( 'bp_core_buddybar_rtl_css', $stylesheet ), array(), bp_get_version() );
+
+	wp_style_add_data( 'bp-admin-bar', 'rtl', true );
+	if ( $min ) {
+		wp_style_add_data( 'bp-admin-bar', 'suffix', $min );
+	}
+}
+add_action( 'bp_init', 'bp_core_load_buddybar_css' );
+
+/**
+ * Add menu items to the BuddyBar.
+ *
+ * @since 1.0.0
+ *
+ * @deprecated 2.1.0
+ */
+function bp_groups_adminbar_admin_menu() {
+	$bp = buddypress();
+
+	if ( empty( $bp->groups->current_group ) ) {
+		return false;
+	}
+
+	// Only group admins and site admins can see this menu
+	if ( !current_user_can( 'edit_users' ) && !bp_current_user_can( 'bp_moderate' ) && !bp_is_item_admin() ) {
+		return false;
+	} ?>
+
+	<li id="bp-adminbar-adminoptions-menu">
+		<a href="<?php bp_groups_action_link( 'admin' ); ?>"><?php _e( 'Admin Options', 'buddypress' ); ?></a>
+
+		<ul>
+			<li><a href="<?php bp_groups_action_link( 'admin/edit-details' ); ?>"><?php _e( 'Edit Details', 'buddypress' ); ?></a></li>
+
+			<li><a href="<?php bp_groups_action_link( 'admin/group-settings' );  ?>"><?php _e( 'Group Settings', 'buddypress' ); ?></a></li>
+
+			<?php if ( !(int)bp_get_option( 'bp-disable-avatar-uploads' ) && $bp->avatar->show_avatars ) : ?>
+
+				<li><a href="<?php bp_groups_action_link( 'admin/group-avatar' ); ?>"><?php _e( 'Group Profile Photo', 'buddypress' ); ?></a></li>
+
+			<?php endif; ?>
+
+			<?php if ( bp_is_active( 'friends' ) ) : ?>
+
+				<li><a href="<?php bp_groups_action_link( 'send-invites' ); ?>"><?php _e( 'Manage Invitations', 'buddypress' ); ?></a></li>
+
+			<?php endif; ?>
+
+			<li><a href="<?php bp_groups_action_link( 'admin/manage-members' ); ?>"><?php _e( 'Manage Members', 'buddypress' ); ?></a></li>
+
+			<?php if ( $bp->groups->current_group->status == 'private' ) : ?>
+
+				<li><a href="<?php bp_groups_action_link( 'admin/membership-requests' ); ?>"><?php _e( 'Membership Requests', 'buddypress' ); ?></a></li>
+
+			<?php endif; ?>
+
+			<li><a class="confirm" href="<?php echo wp_nonce_url( bp_get_group_permalink( $bp->groups->current_group ) . 'admin/delete-group/', 'groups_delete_group' ); ?>&amp;delete-group-button=1&amp;delete-group-understand=1"><?php _e( "Delete Group", 'buddypress' ) ?></a></li>
+
+			<?php do_action( 'bp_groups_adminbar_admin_menu' ) ?>
+
+		</ul>
+	</li>
+
+	<?php
+}
+add_action( 'bp_adminbar_menus', 'bp_groups_adminbar_admin_menu', 20 );
+
+/**
+ * Add the Notifications menu to the BuddyBar.
+ *
+ * @deprecated 2.1.0
+ */
+function bp_adminbar_notifications_menu() {
+
+	// Bail if notifications is not active
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	bp_notifications_buddybar_menu();
+}
+add_action( 'bp_adminbar_menus', 'bp_adminbar_notifications_menu', 8 );
+
+/**
+ * Add the Blog Authors menu to the BuddyBar (visible when not logged in).
+ *
+ * @deprecated 2.1.0
+ */
+function bp_adminbar_authors_menu() {
+	global $wpdb;
+
+	// Only for multisite
+	if ( ! is_multisite() ) {
+		return false;
+	}
+
+	// Hide on root blog
+	if ( bp_is_root_blog( $wpdb->blogid ) || ! bp_is_active( 'blogs' ) ) {
+		return false;
+	}
+
+	$blog_prefix = $wpdb->get_blog_prefix( $wpdb->blogid );
+	$authors     = $wpdb->get_results( "SELECT user_id, user_login, user_nicename, display_name, user_email, meta_value as caps FROM $wpdb->users u, $wpdb->usermeta um WHERE u.ID = um.user_id AND meta_key = '{$blog_prefix}capabilities' ORDER BY um.user_id" );
+
+	if ( !empty( $authors ) ) {
+		// This is a blog, render a menu with links to all authors
+		echo '<li id="bp-adminbar-authors-menu"><a href="/">';
+		_e('Blog Authors', 'buddypress');
+		echo '</a>';
+
+		echo '<ul class="author-list">';
+		foreach( (array) $authors as $author ) {
+			$caps = maybe_unserialize( $author->caps );
+			if ( isset( $caps['subscriber'] ) || isset( $caps['contributor'] ) ) {
+				continue;
+			}
+
+			echo '<li>';
+			echo '<a href="' . bp_core_get_user_domain( $author->user_id, $author->user_nicename, $author->user_login ) . '">';
+			echo bp_core_fetch_avatar( array(
+				'item_id' => $author->user_id,
+				'email'   => $author->user_email,
+				'width'   => 15,
+				'height'  => 15,
+				'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), $author->display_name )
+			) );
+ 			echo ' ' . $author->display_name . '</a>';
+			echo '<div class="admin-bar-clear"></div>';
+			echo '</li>';
+		}
+		echo '</ul>';
+		echo '</li>';
+	}
+}
+add_action( 'bp_adminbar_menus', 'bp_adminbar_authors_menu', 12 );
+
+/**
+ * Add a member admin menu to the BuddyBar.
+ *
+ * Adds an Toolbar menu to any profile page providing site moderator actions
+ * that allow capable users to clean up a users account.
+ *
+ * @deprecated 2.1.0
+ */
+function bp_members_adminbar_admin_menu() {
+
+	// Only show if viewing a user
+	if ( ! bp_displayed_user_id() ) {
+		return false;
+	}
+
+	// Don't show this menu to non site admins or if you're viewing your own profile
+	if ( !current_user_can( 'edit_users' ) || bp_is_my_profile() ) {
+		return false;
+	} ?>
+
+	<li id="bp-adminbar-adminoptions-menu">
+
+		<a href=""><?php _e( 'Admin Options', 'buddypress' ) ?></a>
+
+		<ul>
+			<?php if ( bp_is_active( 'xprofile' ) ) : ?>
+
+				<li><a href="<?php bp_members_component_link( 'profile', 'edit' ); ?>"><?php printf( __( "Edit %s's Profile", 'buddypress' ), esc_attr( bp_get_displayed_user_fullname() ) ) ?></a></li>
+
+			<?php endif ?>
+
+			<li><a href="<?php bp_members_component_link( 'profile', 'change-avatar' ); ?>"><?php printf( __( "Edit %s's Profile Photo", 'buddypress' ), esc_attr( bp_get_displayed_user_fullname() ) ) ?></a></li>
+
+			<li><a href="<?php bp_members_component_link( 'settings', 'capabilities' ); ?>"><?php _e( 'User Capabilities', 'buddypress' ); ?></a></li>
+
+			<li><a href="<?php bp_members_component_link( 'settings', 'delete-account' ); ?>"><?php printf( __( "Delete %s's Account", 'buddypress' ), esc_attr( bp_get_displayed_user_fullname() ) ); ?></a></li>
+
+			<?php do_action( 'bp_members_adminbar_admin_menu' ) ?>
+
+		</ul>
+	</li>
+
+	<?php
+}
+add_action( 'bp_adminbar_menus', 'bp_members_adminbar_admin_menu', 20 );
+
+/**
+ * Create the Notifications menu for the BuddyBar.
+ *
+ * @since 1.9.0
+ * @deprecated 2.1.0
+ */
+function bp_notifications_buddybar_menu() {
+
+	if ( ! is_user_logged_in() ) {
+		return false;
+	}
+
+	echo '<li id="bp-adminbar-notifications-menu"><a href="' . esc_url( bp_loggedin_user_domain() ) . '">';
+	_e( 'Notifications', 'buddypress' );
+
+	$notification_count = bp_notifications_get_unread_notification_count( bp_loggedin_user_id() );
+	$notifications      = bp_notifications_get_notifications_for_user( bp_loggedin_user_id() );
+
+	if ( ! empty( $notification_count ) ) : ?>
+		<span><?php echo bp_core_number_format( $notification_count ); ?></span>
+	<?php
+	endif;
+
+	echo '</a>';
+	echo '<ul>';
+
+	if ( ! empty( $notifications ) ) {
+		$counter = 0;
+		for ( $i = 0, $count = count( $notifications ); $i < $count; ++$i ) {
+			$alt = ( 0 == $counter % 2 ) ? ' class="alt"' : ''; ?>
+
+			<li<?php echo $alt ?>><?php echo $notifications[$i] ?></li>
+
+			<?php $counter++;
+		}
+	} else { ?>
+
+		<li><a href="<?php echo esc_url( bp_loggedin_user_domain() ); ?>"><?php _e( 'No new notifications.', 'buddypress' ); ?></a></li>
+
+	<?php
+	}
+
+	echo '</ul>';
+	echo '</li>';
+}
+add_action( 'bp_adminbar_menus', 'bp_adminbar_notifications_menu', 8 );
+
+/**
+ * Output the base URL for subdomain installations of WordPress Multisite.
+ *
+ * @since 1.6.0
+ *
+ * @deprecated 2.1.0
+ */
+function bp_blogs_subdomain_base() {
+	_deprecated_function( __FUNCTION__, '2.1', 'bp_signup_subdomain_base()' );
+	echo bp_signup_get_subdomain_base();
+}
+
+/**
+ * Return the base URL for subdomain installations of WordPress Multisite.
+ *
+ * @since 1.6.0
+ *
+ * @return string The base URL - eg, 'example.com' for site_url() example.com or www.example.com.
+ *
+ * @deprecated 2.1.0
+ */
+function bp_blogs_get_subdomain_base() {
+	_deprecated_function( __FUNCTION__, '2.1', 'bp_signup_get_subdomain_base()' );
+	return bp_signup_get_subdomain_base();
+}
+
+/**
+ * Allegedly output an avatar upload form, but it hasn't done that since 2009.
+ *
+ * @since 1.0.0
+ * @deprecated 2.1.0
+ */
+function bp_avatar_upload_form() {
+	_deprecated_function(__FUNCTION__, '2.1', 'No longer used' );
+}
+
Index: /tags/2.4.3/src/bp-core/deprecated/2.2.php
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/2.2.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/2.2.php	(revision 10410)
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Deprecated functions
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @deprecated 2.2.0
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Detect a change in post status, and initiate an activity update if necessary.
+ *
+ * Posts get new activity updates when (a) they are being published, and (b)
+ * they have not already been published. This enables proper posting for
+ * regular posts as well as scheduled posts, while preventing post bumping.
+ *
+ * See #4090, #3746, #2546 for background.
+ *
+ * @since 2.0.0
+ * @deprecated 2.2.0
+ *
+ * @todo Support untrashing better
+ *
+ * @param string $new_status New status for the post.
+ * @param string $old_status Old status for the post.
+ * @param object $post Post data.
+ */
+function bp_blogs_catch_transition_post_status( $new_status, $old_status, $post ) {
+	_deprecated_function( __FUNCTION__, '2.2', 'bp_activity_catch_transition_post_type_status()' );
+	bp_activity_catch_transition_post_type_status( $new_status, $old_status, $post );
+}
+
+/**
+ * Record a new blog post in the BuddyPress activity stream.
+ *
+ * @deprecated 2.2.0
+ *
+ * @param int $post_id ID of the post being recorded.
+ * @param object $post The WP post object passed to the 'save_post' action.
+ * @param int $user_id Optional. The user to whom the activity item will be
+ *        associated. Defaults to the post_author.
+ * @return bool|null Returns false on failure.
+ */
+function bp_blogs_record_post( $post_id, $post, $user_id = 0 ) {
+	_deprecated_function( __FUNCTION__, '2.2', 'bp_activity_post_type_publish()' );
+	bp_activity_post_type_publish( $post_id, $post, $user_id );
+}
+
+/**
+ * Updates a blog post's corresponding activity entry during a post edit.
+ *
+ * @since 2.0.0
+ * @deprecated 2.2.0
+ *
+ * @see bp_blogs_catch_transition_post_status()
+ *
+ * @param WP_Post $post
+ */
+function bp_blogs_update_post( $post ) {
+	_deprecated_function( __FUNCTION__, '2.2', 'bp_activity_post_type_update()' );
+	bp_activity_post_type_update( $post );
+}
+
+/**
+ * Clear cache when a new blog is created.
+ *
+ * @since 1.0.0
+ * @deprecated 2.2.0
+ *
+ * @param BP_Blogs_Blog $recorded_blog_obj The recorded blog, passed by
+ *        'bp_blogs_new_blog'.
+ */
+function bp_blogs_format_clear_blog_cache( $recorded_blog_obj ) {
+	_deprecated_function( __FUNCTION__, '2.2', 'bp_blogs_clear_blog_object_cache()' );
+	bp_blogs_clear_blog_object_cache( false, $recorded_blog_obj->user_id );
+}
+
+/**
+ * Format 'new_member' activity actions.
+ *
+ * @since 2.0.0
+ * @deprecated 2.2.0
+ *
+ * @param string $action Static activity action.
+ * @param object $activity Activity object.
+ * @return string
+ */
+function bp_xprofile_format_activity_action_new_member( $action, $activity ) {
+	_deprecated_function( __FUNCTION__, '2.2', 'bp_members_format_activity_action_new_member()' );
+
+	$action = apply_filters( 'bp_xprofile_format_activity_action_new_member', $action, $activity );
+	return bp_members_format_activity_action_new_member( $action, $activity );
+}
+
+/**
+ * Add 'bp' to global group of network wide cachable objects.
+ *
+ * @since 1.1.0
+ * @deprecated 2.2.0
+ */
+function bp_core_add_global_group() {
+	_deprecated_function( __FUNCTION__, '2.2', 'This function has no replacement' );
+}
+
+/**
+ * Add a piece of message metadata.
+ *
+ * @deprecated 2.2.2
+ */
+function bp_message_add_meta( $message_id, $meta_key, $meta_value, $unique = false ) {
+	_deprecated_function( __FUNCTION__, '2.3.0', 'bp_messages_add_meta()' );
+	return bp_messages_add_meta( $message_id, $meta_key, $meta_value, $unique );
+}
Index: /tags/2.4.3/src/bp-core/deprecated/2.3.php
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/2.3.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/2.3.php	(revision 10410)
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Deprecated functions.
+ *
+ * @deprecated 2.3.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Return the referrer URL without the http(s)://
+ *
+ * @deprecated 2.3.0
+ *
+ * @return string The referrer URL.
+ */
+function bp_core_referrer() {
+	_deprecated_function( __FUNCTION__, '2.3.0', 'bp_get_referer_path()' );
+	$referer = explode( '/', wp_get_referer() );
+	unset( $referer[0], $referer[1], $referer[2] );
+	return implode( '/', $referer );
+}
+
Index: /tags/2.4.3/src/bp-core/deprecated/2.4.php
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/2.4.php	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/2.4.php	(revision 10410)
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Deprecated functions.
+ *
+ * @deprecated 2.4.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * @deprecated 2.4.0
+ */
+function bp_group_avatar_edit_form() {
+	_deprecated_function( __FUNCTION__, '2.4' );
+}
+
+/**
+ * @deprecated 2.4.0
+ *
+ * @param bool $deprecated Param deprecated 1.1.0
+ */
+function groups_avatar_upload( $deprecated = true ) {
+	_deprecated_function( __FUNCTION__, '2.4' );
+}
Index: /tags/2.4.3/src/bp-core/deprecated/css/autocomplete/jquery.autocompletefb-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/css/autocomplete/jquery.autocompletefb-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/css/autocomplete/jquery.autocompletefb-rtl.css	(revision 10410)
@@ -0,0 +1,80 @@
+.ac_results {
+	padding: 0px;
+	overflow: hidden;
+	z-index: 99999;
+	background: #fff;
+	border: 1px solid #ccc;
+	-moz-border-radius-bottomleft: 3px;
+	-khtml-border-bottom-right-radius: 3px;
+	-webkit-border-bottom-right-radius: 3px;
+	border-bottom-right-radius: 3px;
+	-moz-border-radius-bottomright: 3px;
+	-khtml-border-bottom-left-radius: 3px;
+	-webkit-border-bottom-left-radius: 3px;
+	border-bottom-left-radius: 3px;
+}
+	.ac_results ul {
+		width: 100%;
+		list-style-position: outside;
+		list-style: none;
+		padding: 0;
+		margin: 0;
+	}
+
+	.ac_results li {
+		margin: 0px;
+		padding: 5px 10px;
+		cursor: pointer;
+		display: block;
+		font-size: 1em;
+		line-height: 16px;
+		overflow: hidden;
+	}
+		.ac_results li img {
+			margin-left: 5px;
+		}
+
+.ac_odd {
+	background-color: #f0f0f0;
+}
+
+.ac_over {
+	background-color: #888;
+	color: #fff;
+}
+
+ul.acfb-holder {
+	margin  : 0;
+	height  : auto !important;
+	height  : 1%;
+	overflow: hidden;
+	padding: 0;
+	list-style: none;
+}
+	ul.acfb-holder li {
+		float   : right;
+		margin  : 0 0 4px 5px;
+		list-style-type: none;
+	}
+
+	ul.acfb-holder li.friend-tab {
+		border-radius         : 3px;
+		-moz-border-radius    : 3px;
+		-webkit-border-radius : 3px;
+		border     : 1px solid #ffe7c7;
+		padding    : 2px 7px 2px;
+		background : #FFF9DF;
+		font-size: 1em;
+	}
+		li.friend-tab img.avatar {
+			border-width: 2px !important;
+			vertical-align: middle;
+		}
+
+		li.friend-tab span.p {
+			padding-right: 5px;
+			font-size: 0.8em;
+			cursor: pointer;
+		}
+
+input#send-to-input { width: 275px; }
Index: /tags/2.4.3/src/bp-core/deprecated/css/autocomplete/jquery.autocompletefb.css
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/css/autocomplete/jquery.autocompletefb.css	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/css/autocomplete/jquery.autocompletefb.css	(revision 10410)
@@ -0,0 +1,80 @@
+.ac_results {
+	padding: 0px;
+	overflow: hidden;
+	z-index: 99999;
+	background: #fff;
+	border: 1px solid #ccc;
+	-moz-border-radius-bottomleft: 3px;
+	-khtml-border-bottom-left-radius: 3px;
+	-webkit-border-bottom-left-radius: 3px;
+	border-bottom-left-radius: 3px;
+	-moz-border-radius-bottomright: 3px;
+	-khtml-border-bottom-right-radius: 3px;
+	-webkit-border-bottom-right-radius: 3px;
+	border-bottom-right-radius: 3px;
+}
+	.ac_results ul {
+		width: 100%;
+		list-style-position: outside;
+		list-style: none;
+		padding: 0;
+		margin: 0;
+	}
+
+	.ac_results li {
+		margin: 0px;
+		padding: 5px 10px;
+		cursor: pointer;
+		display: block;
+		font-size: 1em;
+		line-height: 16px;
+		overflow: hidden;
+	}
+		.ac_results li img {
+			margin-right: 5px;
+		}
+
+.ac_odd {
+	background-color: #f0f0f0;
+}
+
+.ac_over {
+	background-color: #888;
+	color: #fff;
+}
+
+ul.acfb-holder {
+	margin  : 0;
+	height  : auto !important;
+	height  : 1%;
+	overflow: hidden;
+	padding: 0;
+	list-style: none;
+}
+	ul.acfb-holder li {
+		float   : left;
+		margin  : 0 5px 4px 0;
+		list-style-type: none;
+	}
+
+	ul.acfb-holder li.friend-tab {
+		border-radius         : 3px;
+		-moz-border-radius    : 3px;
+		-webkit-border-radius : 3px;
+		border     : 1px solid #ffe7c7;
+		padding    : 2px 7px 2px;
+		background : #FFF9DF;
+		font-size: 1em;
+	}
+		li.friend-tab img.avatar {
+			border-width: 2px !important;
+			vertical-align: middle;
+		}
+
+		li.friend-tab span.p {
+			padding-left: 5px;
+			font-size: 0.8em;
+			cursor: pointer;
+		}
+
+input#send-to-input { width: 275px; }
Index: /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.autocomplete.js
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.autocomplete.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.autocomplete.js	(revision 10410)
@@ -0,0 +1,753 @@
+/*!
+ * Autocomplete - jQuery plugin 1.0 Beta
+ *
+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id: jquery.autocomplete.js 4485 2008-01-20 13:52:47Z joern.zaefferer $
+ *
+ */
+
+;(function($) {
+
+$.fn.extend({
+	autocomplete: function(urlOrData, options) {
+		var isUrl = typeof urlOrData == "string";
+		options = $.extend({}, $.Autocompleter.defaults, {
+			url: isUrl ? urlOrData : null,
+			data: isUrl ? null : urlOrData,
+			delay: isUrl ? $.Autocompleter.defaults.delay : 10,
+			max: options && !options.scroll ? 10 : 150
+		}, options);
+
+		// if highlight is set to false, replace it with a do-nothing function
+		options.highlight = options.highlight || function(value) { return value; };
+
+		return this.each(function() {
+			new $.Autocompleter(this, options);
+		});
+	},
+	result: function(handler) {
+		return this.bind("result", handler);
+	},
+	search: function(handler) {
+		return this.trigger("search", [handler]);
+	},
+	flushCache: function() {
+		return this.trigger("flushCache");
+	},
+	setOptions: function(options){
+		return this.trigger("setOptions", [options]);
+	},
+	unautocomplete: function() {
+		return this.trigger("unautocomplete");
+	}
+});
+
+$.Autocompleter = function(input, options) {
+
+	var KEY = {
+		UP: 38,
+		DOWN: 40,
+		DEL: 46,
+		TAB: 9,
+		RETURN: 13,
+		ESC: 27,
+		COMMA: 188,
+		PAGEUP: 33,
+		PAGEDOWN: 34
+	};
+
+	// Create $ object for input element
+	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
+
+	var timeout;
+	var previousValue = "";
+	var cache = $.Autocompleter.Cache(options);
+	var hasFocus = 0;
+	var lastKeyPressCode;
+	var config = {
+		mouseDownOnSelect: false
+	};
+	var select = $.Autocompleter.Select(options, input, selectCurrent, config);
+
+	$input.keydown(function(event) {
+		// track last key pressed
+		lastKeyPressCode = event.keyCode;
+		switch(event.keyCode) {
+
+			case KEY.UP:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.prev();
+				} else {
+					onChange(0, true);
+				}
+				break;
+
+			case KEY.DOWN:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.next();
+				} else {
+					onChange(0, true);
+				}
+				break;
+
+			case KEY.PAGEUP:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.pageUp();
+				} else {
+					onChange(0, true);
+				}
+				break;
+
+			case KEY.PAGEDOWN:
+				event.preventDefault();
+				if ( select.visible() ) {
+					select.pageDown();
+				} else {
+					onChange(0, true);
+				}
+				break;
+
+			// matches also semicolon
+			case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
+			case KEY.TAB:
+			case KEY.RETURN:
+				if( selectCurrent() ){
+					// make sure to blur off the current field
+					if( !options.multiple )
+						$input.blur();
+					event.preventDefault();
+						$input.focus();
+				}
+				break;
+
+			case KEY.ESC:
+				select.hide();
+				break;
+
+			default:
+				clearTimeout(timeout);
+				timeout = setTimeout(onChange, options.delay);
+				break;
+		}
+	}).keypress(function() {
+		// having fun with opera - remove this binding and Opera submits the form when we select an entry via return
+	}).focus(function(){
+		// track whether the field has focus, we shouldn't process any
+		// results if the field no longer has focus
+		hasFocus++;
+	}).blur(function() {
+		hasFocus = 0;
+		if (!config.mouseDownOnSelect) {
+			hideResults();
+		}
+	}).click(function() {
+		// show select when clicking in a focused field
+		if ( hasFocus++ > 1 && !select.visible() ) {
+			onChange(0, true);
+		}
+	}).bind("search", function() {
+		// TODO why not just specifying both arguments?
+		var fn = (arguments.length > 1) ? arguments[1] : null;
+		function findValueCallback(q, data) {
+			var result;
+			if( data && data.length ) {
+				for (var i=0; i < data.length; i++) {
+					if( data[i].result.toLowerCase() == q.toLowerCase() ) {
+						result = data[i];
+						break;
+					}
+				}
+			}
+			if( typeof fn == "function" ) fn(result);
+			else $input.trigger("result", result && [result.data, result.value]);
+		}
+		$.each(trimWords($input.val()), function(i, value) {
+			request(value, findValueCallback, findValueCallback);
+		});
+	}).bind("flushCache", function() {
+		cache.flush();
+	}).bind("setOptions", function() {
+		$.extend(options, arguments[1]);
+		// if we've updated the data, repopulate
+		if ( "data" in arguments[1] )
+			cache.populate();
+	}).bind("unautocomplete", function() {
+		select.unbind();
+		$input.unbind();
+	});
+
+
+	function selectCurrent() {
+		var selected = select.selected();
+		if( !selected )
+			return false;
+
+		var v = selected.result;
+		previousValue = v;
+
+		if ( options.multiple ) {
+			var words = trimWords($input.val());
+			if ( words.length > 1 ) {
+				v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
+			}
+			v += options.multipleSeparator;
+		}
+
+		$input.val(v);
+		hideResultsNow();
+		$input.trigger("result", [selected.data, selected.value]);
+		return true;
+	}
+
+	function onChange(crap, skipPrevCheck) {
+		if( lastKeyPressCode == KEY.DEL ) {
+			select.hide();
+			return;
+		}
+
+		var currentValue = $input.val();
+
+		if ( !skipPrevCheck && currentValue == previousValue )
+			return;
+
+		previousValue = currentValue;
+
+		currentValue = lastWord(currentValue);
+		if ( currentValue.length >= options.minChars) {
+			$input.addClass(options.loadingClass);
+			jQuery('#send-to-input').addClass('loading');
+			if (!options.matchCase)
+				currentValue = currentValue.toLowerCase();
+			request(currentValue, receiveData, hideResultsNow);
+		} else {
+			stopLoading();
+			select.hide();
+		}
+	};
+
+	function trimWords(value) {
+		if ( !value ) {
+			return [""];
+		}
+		var words = value.split( $.trim( options.multipleSeparator ) );
+		var result = [];
+		$.each(words, function(i, value) {
+			if ( $.trim(value) )
+				result[i] = $.trim(value);
+		});
+		return result;
+	}
+
+	function lastWord(value) {
+		if ( !options.multiple )
+			return value;
+		var words = trimWords(value);
+		return words[words.length - 1];
+	}
+
+	// fills in the input box w/the first match (assumed to be the best match)
+	function autoFill(q, sValue){
+		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
+		// if the last user key pressed was backspace, don't autofill
+		if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != 8 ) {
+			// fill in the value (keep the case the user has typed)
+			$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
+			// select the portion of the value not typed by the user (so the next character will erase)
+			$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
+		}
+	};
+
+	function hideResults() {
+		clearTimeout(timeout);
+		timeout = setTimeout(hideResultsNow, 200);
+	};
+
+	function hideResultsNow() {
+		select.hide();
+		clearTimeout(timeout);
+		stopLoading();
+		if (options.mustMatch) {
+			// call search and run callback
+			$input.search(
+				function (result){
+					// if no value found, clear the input box
+					if( !result ) $input.val("");
+				}
+			);
+		}
+	};
+
+	function receiveData(q, data) {
+		if ( data && data.length && hasFocus ) {
+			stopLoading();
+			select.display(data, q);
+
+			var newData = data[0].value.split(';');
+			data.value = newData[0];
+
+			autoFill(q, data.value);
+			select.show();
+		} else {
+			hideResultsNow();
+		}
+	};
+
+	function request(term, success, failure) {
+		if (!options.matchCase)
+			term = term.toLowerCase();
+		var data = cache.load(term);
+		// recieve the cached data
+		if (data && data.length) {
+			success(term, data);
+		// if an AJAX url has been supplied, try loading the data now
+		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
+
+			var extraParams = {};
+			$.each(options.extraParams, function(key, param) {
+				extraParams[key] = typeof param == "function" ? param() : param;
+			});
+
+			$.ajax({
+				// try to leverage ajaxQueue plugin to abort previous requests
+				mode: "abort",
+				// limit abortion to this input
+				port: "autocomplete" + input.name,
+				dataType: options.dataType,
+				url: options.url,
+				data: $.extend({
+					q: lastWord(term),
+					limit: options.max,
+					action: 'messages_autocomplete_results',
+					'cookie': getAutocompleteCookies()
+				}, extraParams),
+				success: function(data) {
+					var parsed = options.parse && options.parse(data) || parse(data);
+					cache.add(term, parsed);
+					success(term, parsed);
+				}
+			});
+		} else {
+			failure(term);
+		}
+	};
+
+	function parse(data) {
+		var parsed = [];
+		var rows = data.split("\n");
+		for (var i=0; i < rows.length; i++) {
+			var row = $.trim(rows[i]);
+			if (row) {
+				row = row.split("|");
+				parsed[parsed.length] = {
+					data: row,
+					value: row[0],
+					result: options.formatResult && options.formatResult(row, row[0]) || row[0]
+				};
+			}
+		}
+		return parsed;
+	};
+
+	function stopLoading() {
+		$input.removeClass(options.loadingClass);
+		jQuery('#send-to-input').removeClass('loading');
+	};
+
+	/* Returns a querystring of BP cookies (cookies beginning with 'bp-') */
+	function getAutocompleteCookies() {
+		var allCookies = document.cookie.split(';'),  // get all cookies and split into an array
+			bpCookies      = {},
+			cookiePrefix   = 'bp-',
+			i, cookie, delimiter, name, value;
+
+		// loop through cookies
+		for (i = 0; i < allCookies.length; i++) {
+			cookie    = allCookies[i];
+			delimiter = cookie.indexOf('=');
+			name      = jq.trim( unescape( cookie.slice(0, delimiter) ) );
+			value     = unescape( cookie.slice(delimiter + 1) );
+
+			// if BP cookie, store it
+			if ( name.indexOf(cookiePrefix) === 0 ) {
+				bpCookies[name] = value;
+			}
+		}
+
+		// returns BP cookies as querystring
+		return encodeURIComponent( jq.param(bpCookies) );
+	}
+};
+
+$.Autocompleter.defaults = {
+	inputClass: "ac_input",
+	resultsClass: "ac_results",
+	loadingClass: "ac_loading",
+	minChars: 1,
+	delay: 400,
+	matchCase: false,
+	matchSubset: true,
+	matchContains: false,
+	cacheLength: 10,
+	max: 100,
+	mustMatch: false,
+	extraParams: {},
+	selectFirst: true,
+	formatItem: function(row) { return row[0]; },
+	autoFill: false,
+	width: 0,
+	multiple: false,
+	multipleSeparator: ", ",
+	highlight: function(value, term) {
+		return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
+	},
+    scroll: true,
+    scrollHeight: 250,
+	attachTo: 'body'
+};
+
+$.Autocompleter.Cache = function(options) {
+
+	var data = {};
+	var length = 0;
+
+	function matchSubset(s, sub) {
+		if (!options.matchCase)
+			s = s.toLowerCase();
+		var i = s.indexOf(sub);
+		if (i == -1) return false;
+		return i == 0 || options.matchContains;
+	};
+
+	function add(q, value) {
+		if (length > options.cacheLength){
+			flush();
+		}
+		if (!data[q]){
+			length++;
+		}
+		data[q] = value;
+	}
+
+	function populate(){
+		if( !options.data ) return false;
+		// track the matches
+		var stMatchSets = {},
+			nullData = 0;
+
+		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
+		if( !options.url ) options.cacheLength = 1;
+
+		// track all options for minChars = 0
+		stMatchSets[""] = [];
+
+		// loop through the array and create a lookup structure
+		for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
+			var rawValue = options.data[i];
+			// if rawValue is a string, make an array otherwise just reference the array
+			rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
+
+			var value = options.formatItem(rawValue, i+1, options.data.length);
+			if ( value === false )
+				continue;
+
+			var firstChar = value.charAt(0).toLowerCase();
+			// if no lookup array for this character exists, look it up now
+			if( !stMatchSets[firstChar] )
+				stMatchSets[firstChar] = [];
+
+			// if the match is a string
+			var row = {
+				value: value,
+				data: rawValue,
+				result: options.formatResult && options.formatResult(rawValue) || value
+			};
+
+			// push the current match into the set list
+			stMatchSets[firstChar].push(row);
+
+			// keep track of minChars zero items
+			if ( nullData++ < options.max ) {
+				stMatchSets[""].push(row);
+			}
+		};
+
+		// add the data items to the cache
+		$.each(stMatchSets, function(i, value) {
+			// increase the cache size
+			options.cacheLength++;
+			// add to the cache
+			add(i, value);
+		});
+	}
+
+	// populate any existing data
+	setTimeout(populate, 25);
+
+	function flush(){
+		data = {};
+		length = 0;
+	}
+
+	return {
+		flush: flush,
+		add: add,
+		populate: populate,
+		load: function(q) {
+			if (!options.cacheLength || !length)
+				return null;
+			/*
+			 * if dealing w/local data and matchContains than we must make sure
+			 * to loop through all the data collections looking for matches
+			 */
+			if( !options.url && options.matchContains ){
+				// track all matches
+				var csub = [];
+				// loop through all the data grids for matches
+				for( var k in data ){
+					// don't search through the stMatchSets[""] (minChars: 0) cache
+					// this prevents duplicates
+					if( k.length > 0 ){
+						var c = data[k];
+						$.each(c, function(i, x) {
+							// if we've got a match, add it to the array
+							if (matchSubset(x.value, q)) {
+								csub.push(x);
+							}
+						});
+					}
+				}
+				return csub;
+			} else
+			// if the exact item exists, use it
+			if (data[q]){
+				return data[q];
+			} else
+			if (options.matchSubset) {
+				for (var i = q.length - 1; i >= options.minChars; i--) {
+					var c = data[q.substr(0, i)];
+					if (c) {
+						var csub = [];
+						$.each(c, function(i, x) {
+							if (matchSubset(x.value, q)) {
+								csub[csub.length] = x;
+							}
+						});
+						return csub;
+					}
+				}
+			}
+			return null;
+		}
+	};
+};
+
+$.Autocompleter.Select = function (options, input, select, config) {
+	var CLASSES = {
+		ACTIVE: "ac_over"
+	};
+
+	var listItems,
+		active = -1,
+		data,
+		term = "",
+		needsInit = true,
+		element,
+		list;
+
+	// Create results
+	function init() {
+		if (!needsInit)
+			return;
+		element = $("<div/>")
+		.hide()
+		.addClass(options.resultsClass)
+		.css("position", "absolute")
+		.appendTo(options.attachTo);
+
+		list = $("<ul>").appendTo(element).mouseover( function(event) {
+			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
+	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
+			    $(target(event)).addClass(CLASSES.ACTIVE);
+	        }
+		}).click(function(event) {
+			$(target(event)).addClass(CLASSES.ACTIVE);
+			select();
+			input.focus();
+			return false;
+		}).mousedown(function() {
+			config.mouseDownOnSelect = true;
+		}).mouseup(function() {
+			config.mouseDownOnSelect = false;
+		});
+
+		if( options.width > 0 )
+			element.css("width", options.width);
+
+		needsInit = false;
+	}
+
+	function target(event) {
+		var element = event.target;
+		while(element && element.tagName != "LI")
+			element = element.parentNode;
+		// more fun with IE, sometimes event.target is empty, just ignore it then
+		if(!element)
+			return [];
+		return element;
+	}
+
+	function moveSelect(step) {
+		listItems.slice(active, active + 1).removeClass();
+		movePosition(step);
+        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
+        if(options.scroll) {
+            var offset = 0;
+            listItems.slice(0, active).each(function() {
+				offset += this.offsetHeight;
+			});
+            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
+                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
+            } else if(offset < list.scrollTop()) {
+                list.scrollTop(offset);
+            }
+        }
+	};
+
+	function movePosition(step) {
+		active += step;
+		if (active < 0) {
+			active = listItems.size() - 1;
+		} else if (active >= listItems.size()) {
+			active = 0;
+		}
+	}
+
+	function limitNumberOfItems(available) {
+		return options.max && options.max < available
+			? options.max
+			: available;
+	}
+
+	function fillList() {
+		list.empty();
+		var max = limitNumberOfItems(data.length);
+		for (var i=0; i < max; i++) {
+			if (!data[i])
+				continue;
+			var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
+			if ( formatted === false )
+				continue;
+			var li = $("<li>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_event" : "ac_odd").appendTo(list)[0];
+			$.data(li, "ac_data", data[i]);
+		}
+		listItems = list.find("li");
+		if ( options.selectFirst ) {
+			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
+			active = 0;
+		}
+		list.bgiframe();
+	}
+
+	return {
+		display: function(d, q) {
+			init();
+			data = d;
+			term = q;
+			fillList();
+		},
+		next: function() {
+			moveSelect(1);
+		},
+		prev: function() {
+			moveSelect(-1);
+		},
+		pageUp: function() {
+			if (active != 0 && active - 8 < 0) {
+				moveSelect( -active );
+			} else {
+				moveSelect(-8);
+			}
+		},
+		pageDown: function() {
+			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
+				moveSelect( listItems.size() - 1 - active );
+			} else {
+				moveSelect(8);
+			}
+		},
+		hide: function() {
+			element && element.hide();
+			active = -1;
+		},
+		visible : function() {
+			return element && element.is(":visible");
+		},
+		current: function() {
+			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
+		},
+		show: function() {
+			var offset = $(input).offset();
+			element.css({
+				width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
+				top: offset.top + input.offsetHeight,
+				left: offset.left
+			}).show();
+            if(options.scroll) {
+                list.scrollTop(0);
+                list.css({
+					maxHeight: options.scrollHeight,
+					overflow: 'auto'
+				});
+
+                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
+					var listHeight = 0;
+					listItems.each(function() {
+						listHeight += this.offsetHeight;
+					});
+					var scrollbarsVisible = listHeight > options.scrollHeight;
+                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
+					if (!scrollbarsVisible) {
+						// IE doesn't recalculate width when scrollbar disappears
+						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
+					}
+                }
+
+            }
+		},
+		selected: function() {
+			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
+			return selected && selected.length && $.data(selected[0], "ac_data");
+		},
+		unbind: function() {
+			element && element.remove();
+		}
+	};
+};
+
+$.Autocompleter.Selection = function(field, start, end) {
+	if( field.createTextRange ){
+		var selRange = field.createTextRange();
+		selRange.collapse(true);
+		selRange.moveStart("character", start);
+		selRange.moveEnd("character", end);
+		selRange.select();
+	} else if( field.setSelectionRange ){
+		field.setSelectionRange(start, end);
+	} else {
+		if( field.selectionStart ){
+			field.selectionStart = start;
+			field.selectionEnd = end;
+		}
+	}
+	field.focus();
+};
+
+})(jQuery);
Index: /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.autocompletefb.js
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.autocompletefb.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.autocompletefb.js	(revision 10410)
@@ -0,0 +1,76 @@
+/*!
+ * jQuery plugin: autoCompletefb(AutoComplete Facebook)
+ * @requires jQuery v1.2.2 or later
+ * using plugin:jquery.autocomplete.js
+ *
+ * Credits:
+ * - Idea: Facebook
+ * - Guillermo Rauch: Original MooTools script
+ * - InteRiders <http://interiders.com/>
+ *
+ * Copyright (c) 2008 Widi Harsojo <wharsojo@gmail.com>, http://wharsojo.wordpress.com/
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ */
+
+jQuery.fn.autoCompletefb = function(options)
+{
+	var tmp = this;
+	var settings =
+	{
+		ul         : tmp,
+		urlLookup  : [""],
+		acOptions  : {},
+		foundClass : ".friend-tab",
+		inputClass : ".send-to-input"
+	}
+
+	if(options) jQuery.extend(settings, options);
+
+	var acfb =
+	{
+		params  : settings,
+		removeFind : function(o){
+			acfb.removeUsername(o);
+			jQuery(o).unbind('click').parent().remove();
+			jQuery(settings.inputClass,tmp).focus();
+			return tmp.acfb;
+		},
+		removeUsername: function(o){
+			var newID = o.parentNode.id.substr( o.parentNode.id.indexOf('-')+1 );
+			jQuery('#send-to-usernames').removeClass(newID);
+		}
+	}
+
+	jQuery(settings.foundClass+" img.p").click(function(){
+		acfb.removeFind(this);
+	});
+
+	jQuery(settings.inputClass,tmp).autocomplete(settings.urlLookup,settings.acOptions);
+	jQuery(settings.inputClass,tmp).result(function(e,d,f){
+		var f = settings.foundClass.replace(/\./,'');
+		var d = String(d).split(' (');
+		var un = d[1].substr(0, d[1].length-1);
+		
+		/* Don't add the same user multiple times */
+		if( 0 === jQuery(settings.inputClass).siblings('#un-' + un).length ) {		
+			var ln = '#link-' + un;
+			var l = jQuery(ln).attr('href');
+			var v = '<li class="'+f+'" id="un-'+un+'"><span><a href="'+l+'">'+d[0]+'</a></span> <span class="p">X</span></li>';
+			
+			var x = jQuery(settings.inputClass,tmp).before(v);
+			jQuery('#send-to-usernames').addClass(un);
+				
+			jQuery('.p',x[0].previousSibling).click(function(){
+				acfb.removeFind(this);
+			});
+		} 
+			
+		jQuery(settings.inputClass,tmp).val('');
+
+	});
+
+	jQuery(settings.inputClass,tmp).focus();
+	return acfb;
+}
Index: /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.bgiframe.js
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.bgiframe.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.bgiframe.js	(revision 10410)
@@ -0,0 +1,39 @@
+/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
+ * Licensed under the MIT License (license.bgiframe.txt).
+ *
+ * Version 2.1.2
+ */
+
+(function($){
+
+$.fn.bgiframe = ($.browser.msie && /msie 6\.0/i.test(navigator.userAgent) ? function(s) {
+    s = $.extend({
+        top     : 'auto', // auto == .currentStyle.borderTopWidth
+        left    : 'auto', // auto == .currentStyle.borderLeftWidth
+        width   : 'auto', // auto == offsetWidth
+        height  : 'auto', // auto == offsetHeight
+        opacity : true,
+        src     : 'javascript:false;'
+    }, s);
+    var html = '<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+
+                   'style="display:block;position:absolute;z-index:-1;'+
+                       (s.opacity !== false?'filter:Alpha(Opacity=\'0\');':'')+
+                       'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+
+                       'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+
+                       'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+
+                       'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+
+                '"/>';
+    return this.each(function() {
+        if ( $(this).children('iframe.bgiframe').length === 0 )
+            this.insertBefore( document.createElement(html), this.firstChild );
+    });
+} : function() { return this; });
+
+// old alias
+$.fn.bgIframe = $.fn.bgiframe;
+
+function prop(n) {
+    return n && n.constructor === Number ? n + 'px' : n;
+}
+
+})(jQuery);
Index: /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.dimensions.js
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.dimensions.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/jquery.dimensions.js	(revision 10410)
@@ -0,0 +1,117 @@
+/*!
+ * Copyright (c) 2007 Paul Bakaus (paul.bakaus@googlemail.com) and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * $LastChangedDate: 2007-09-11 05:38:31 +0300 (Вт, 11 сен 2007) $
+ * $Rev: 3238 $
+ *
+ * Version: @VERSION
+ *
+ * Requires: jQuery 1.2+
+ */
+
+(function($){
+
+$.dimensions = {
+	version: '@VERSION'
+};
+
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+$.each( [ 'Height', 'Width' ], function(i, name){
+
+	// innerHeight and innerWidth
+	$.fn[ 'inner' + name ] = function() {
+		if (!this[0]) return;
+
+		var torl = name == 'Height' ? 'Top'    : 'Left',  // top or left
+		    borr = name == 'Height' ? 'Bottom' : 'Right'; // bottom or right
+
+		return this[ name.toLowerCase() ]() + num(this, 'padding' + torl) + num(this, 'padding' + borr);
+	};
+
+	// outerHeight and outerWidth
+	$.fn[ 'outer' + name ] = function(options) {
+		if (!this[0]) return;
+
+		var torl = name == 'Height' ? 'Top'    : 'Left',  // top or left
+		    borr = name == 'Height' ? 'Bottom' : 'Right'; // bottom or right
+
+		options = $.extend({ margin: false }, options || {});
+
+		return this[ name.toLowerCase() ]()
+				+ num(this, 'border' + torl + 'Width') + num(this, 'border' + borr + 'Width')
+				+ num(this, 'padding' + torl) + num(this, 'padding' + borr)
+				+ (options.margin ? (num(this, 'margin' + torl) + num(this, 'margin' + borr)) : 0);
+	};
+});
+
+// Create scrollLeft and scrollTop methods
+$.each( ['Left', 'Top'], function(i, name) {
+	$.fn[ 'scroll' + name ] = function(val) {
+		if (!this[0]) return;
+
+		return val != undefined ?
+
+			// Set the scroll offset
+			this.each(function() {
+				this == window || this == document ?
+					window.scrollTo(
+						name == 'Left' ? val : $(window)[ 'scrollLeft' ](),
+						name == 'Top'  ? val : $(window)[ 'scrollTop'  ]()
+					) :
+					this[ 'scroll' + name ] = val;
+			}) :
+
+			// Return the scroll offset
+			this[0] == window || this[0] == document ?
+				self[ (name == 'Left' ? 'pageXOffset' : 'pageYOffset') ] ||
+					$.boxModel && document.documentElement[ 'scroll' + name ] ||
+					document.body[ 'scroll' + name ] :
+				this[0][ 'scroll' + name ];
+	};
+});
+
+$.fn.extend({
+	position: function() {
+		var left = 0, top = 0, elem = this[0], offset, parentOffset, offsetParent, results;
+
+		if (elem) {
+			// Get *real* offsetParent
+			offsetParent = this.offsetParent();
+
+			// Get correct offsets
+			offset       = this.offset();
+			parentOffset = offsetParent.offset();
+
+			// Subtract element margins
+			offset.top  -= num(elem, 'marginTop');
+			offset.left -= num(elem, 'marginLeft');
+
+			// Add offsetParent borders
+			parentOffset.top  += num(offsetParent, 'borderTopWidth');
+			parentOffset.left += num(offsetParent, 'borderLeftWidth');
+
+			// Subtract the two offsets
+			results = {
+				top:  offset.top  - parentOffset.top,
+				left: offset.left - parentOffset.left
+			};
+		}
+
+		return results;
+	},
+
+	offsetParent: function() {
+		var offsetParent = this[0].offsetParent;
+		while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && $.css(offsetParent, 'position') == 'static') )
+			offsetParent = offsetParent.offsetParent;
+		return $(offsetParent);
+	}
+});
+
+var num = function(el, prop) {
+	return parseInt($.css(el.jquery?el[0]:el,prop))||0;
+};
+
+})(jQuery);
Index: /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/license.bgiframe.txt
===================================================================
--- /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/license.bgiframe.txt	(revision 10410)
+++ /tags/2.4.3/src/bp-core/deprecated/js/autocomplete/license.bgiframe.txt	(revision 10410)
@@ -0,0 +1,20 @@
+Copyright 2010, Brandon Aaron (http://brandonaaron.net/)
+ 
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+ 
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+ 
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Index: /tags/2.4.3/src/bp-core/js/avatar.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/avatar.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/avatar.js	(revision 10410)
@@ -0,0 +1,672 @@
+/* global bp, BP_Uploader, _, Backbone */
+
+window.bp = window.bp || {};
+
+( function( exports, $ ) {
+
+	// Bail if not set
+	if ( typeof BP_Uploader === 'undefined' ) {
+		return;
+	}
+
+	bp.Models      = bp.Models || {};
+	bp.Collections = bp.Collections || {};
+	bp.Views       = bp.Views || {};
+
+	bp.Avatar = {
+		start: function() {
+			/**
+			 * Remove the bp-legacy UI
+			 *
+			 * bp.Avatar successfully loaded, we can now
+			 * safely remove the Legacy UI.
+			 */
+			this.removeLegacyUI();
+
+			// Init some vars
+			this.views    = new Backbone.Collection();
+			this.jcropapi = {};
+			this.warning = null;
+
+			// Set up nav
+			this.setupNav();
+
+			// Avatars are uploaded files
+			this.avatars = bp.Uploader.filesUploaded;
+
+			// The Avatar Attachment object.
+			this.Attachment = new Backbone.Model();
+
+			// Wait till the queue is reset
+			bp.Uploader.filesQueue.on( 'reset', this.cropView, this );
+
+			/**
+			 * In Administration screens we're using Thickbox
+			 * We need to make sure to reset the views if it's closed
+			 */
+			$( 'body.wp-admin' ).on( 'tb_unload', '#TB_window', function() {
+				// Reset to the uploader view
+				bp.Avatar.nav.trigger( 'bp-avatar-view:changed', 'upload' );
+
+				// Reset to the uploader nav
+				_.each( bp.Avatar.navItems.models, function( model ) {
+					if ( model.id === 'upload' ) {
+						model.set( { active: 1 } );
+					} else {
+						model.set( { active: 0 } );
+					}
+				} );
+			} );
+		},
+
+		removeLegacyUI: function() {
+			// User
+			if ( $( '#avatar-upload-form' ).length ) {
+				$( '#avatar-upload' ).remove();
+				$( '#avatar-upload-form p' ).remove();
+
+			// Group Manage
+			} else if ( $( '#group-settings-form' ).length ) {
+				$( '#group-settings-form p' ).each( function( i ) {
+					if ( 0 !== i ) {
+						$( this ).remove();
+					}
+				} );
+
+				if ( $( '#delete-group-avatar-button' ).length ) {
+					$( '#delete-group-avatar-button' ).remove();
+				}
+
+			// Group Create
+			} else if ( $( '#group-create-body' ).length ) {
+				$( '.main-column p #file' ).remove();
+				$( '.main-column p #upload' ).remove();
+
+			// Admin Extended Profile
+			} else if ( $( '#bp_xprofile_user_admin_avatar a.bp-xprofile-avatar-user-admin' ).length ) {
+				$( '#bp_xprofile_user_admin_avatar a.bp-xprofile-avatar-user-admin' ).remove();
+			}
+		},
+
+		setView: function( view ) {
+			// Clear views
+			if ( ! _.isUndefined( this.views.models ) ) {
+				_.each( this.views.models, function( model ) {
+					model.get( 'view' ).remove();
+				}, this );
+			}
+
+			// Reset Views
+			this.views.reset();
+
+			// Reset Avatars (file uploaded)
+			if ( ! _.isUndefined( this.avatars ) ) {
+				this.avatars.reset();
+			}
+
+			// Reset the Jcrop API
+			if ( ! _.isEmpty( this.jcropapi ) ) {
+				this.jcropapi.destroy();
+				this.jcropapi = {};
+			}
+
+			// Load the required view
+			switch ( view ) {
+				case 'upload':
+					this.uploaderView();
+					break;
+
+				case 'delete':
+					this.deleteView();
+					break;
+			}
+		},
+
+		setupNav: function() {
+			var self = this,
+			    initView, activeView;
+
+			this.navItems = new Backbone.Collection();
+
+			_.each( BP_Uploader.settings.nav, function( item, index ) {
+				if ( ! _.isObject( item ) ) {
+					return;
+				}
+
+				// Reset active View
+				activeView = 0;
+
+				if ( 0 === index ) {
+					initView = item.id;
+					activeView = 1;
+				}
+
+				self.navItems.add( {
+					id     : item.id,
+					name   : item.caption,
+					href   : '#',
+					active : activeView,
+					hide   : _.isUndefined( item.hide ) ? 0 : item.hide
+				} );
+			} );
+
+			this.nav = new bp.Views.Nav( { collection: this.navItems } );
+			this.nav.inject( '.bp-avatar-nav' );
+
+			// Activate the initial view (uploader)
+			this.setView( initView );
+
+			// Listen to nav changes (it's like a do_action!)
+			this.nav.on( 'bp-avatar-view:changed', _.bind( this.setView, this ) );
+		},
+
+		uploaderView: function() {
+			// Listen to the Queued uploads
+			bp.Uploader.filesQueue.on( 'add', this.uploadProgress, this );
+
+			// Create the BuddyPress Uploader
+			var uploader = new bp.Views.Uploader();
+
+			// Add it to views
+			this.views.add( { id: 'upload', view: uploader } );
+
+			// Display it
+			uploader.inject( '.bp-avatar' );
+		},
+
+		uploadProgress: function() {
+			// Create the Uploader status view
+			var avatarStatus = new bp.Views.uploaderStatus( { collection: bp.Uploader.filesQueue } );
+
+			if ( ! _.isUndefined( this.views.get( 'status' ) ) ) {
+				this.views.set( { id: 'status', view: avatarStatus } );
+			} else {
+				this.views.add( { id: 'status', view: avatarStatus } );
+			}
+
+			// Display it
+			avatarStatus.inject( '.bp-avatar-status' );
+		},
+
+		cropView: function() {
+			var status;
+
+			// Bail there was an error during the Upload
+			if ( _.isEmpty( this.avatars.models ) ) {
+				return;
+			}
+
+			// Make sure to remove the uploads status
+			if ( ! _.isUndefined( this.views.get( 'status' ) ) ) {
+				status = this.views.get( 'status' );
+				status.get( 'view' ).remove();
+				this.views.remove( { id: 'status', view: status } );
+			}
+
+			// Create the Avatars view
+			var avatar = new bp.Views.Avatars( { collection: this.avatars } );
+			this.views.add( { id: 'crop', view: avatar } );
+
+			avatar.inject( '.bp-avatar' );
+		},
+
+		setAvatar: function( avatar ) {
+			var self = this,
+				crop;
+
+			// Remove the crop view
+			if ( ! _.isUndefined( this.views.get( 'crop' ) ) ) {
+				// Remove the JCrop API
+				if ( ! _.isEmpty( this.jcropapi ) ) {
+					this.jcropapi.destroy();
+					this.jcropapi = {};
+				}
+				crop = this.views.get( 'crop' );
+				crop.get( 'view' ).remove();
+				this.views.remove( { id: 'crop', view: crop } );
+			}
+
+			// Set the avatar !
+			bp.ajax.post( 'bp_avatar_set', {
+				json:          true,
+				original_file: avatar.get( 'url' ),
+				crop_w:        avatar.get( 'w' ),
+				crop_h:        avatar.get( 'h' ),
+				crop_x:        avatar.get( 'x' ),
+				crop_y:        avatar.get( 'y' ),
+				item_id:       avatar.get( 'item_id' ),
+				object:        avatar.get( 'object' ),
+				type:          _.isUndefined( avatar.get( 'type' ) ) ? 'crop' : avatar.get( 'type' ),
+				nonce:         avatar.get( 'nonces' ).set
+			} ).done( function( response ) {
+				var avatarStatus = new bp.Views.AvatarStatus( {
+					value : BP_Uploader.strings.feedback_messages[ response.feedback_code ],
+					type : 'success'
+				} );
+
+				self.views.add( {
+					id   : 'status',
+					view : avatarStatus
+				} );
+
+				avatarStatus.inject( '.bp-avatar-status' );
+
+				// Update each avatars of the page
+				$( '.' + avatar.get( 'object' ) + '-' + response.item_id + '-avatar' ).each( function() {
+					$(this).prop( 'src', response.avatar );
+				} );
+
+				// Inject the Delete nav
+				bp.Avatar.navItems.get( 'delete' ).set( { hide: 0 } );
+
+				/**
+				 * Set the Attachment object
+				 *
+				 * You can run extra actions once the avatar is set using:
+				 * bp.Avatar.Attachment.on( 'change:url', function( data ) { your code } );
+				 *
+				 * In this case data.attributes will include the url to the newly
+				 * uploaded avatar, the object and the item_id concerned.
+				 */
+				self.Attachment.set( _.extend(
+					_.pick( avatar.attributes, ['object', 'item_id'] ),
+					{ url: response.avatar, action: 'uploaded' }
+				) );
+
+			} ).fail( function( response ) {
+				var feedback = BP_Uploader.strings.default_error;
+				if ( ! _.isUndefined( response ) ) {
+					feedback = BP_Uploader.strings.feedback_messages[ response.feedback_code ];
+				}
+
+				var avatarStatus = new bp.Views.AvatarStatus( {
+					value : feedback,
+					type : 'error'
+				} );
+
+				self.views.add( {
+					id   : 'status',
+					view : avatarStatus
+				} );
+
+				avatarStatus.inject( '.bp-avatar-status' );
+			} );
+		},
+
+		deleteView:function() {
+			// Create the delete model
+			var delete_model = new Backbone.Model( _.pick( BP_Uploader.settings.defaults.multipart_params.bp_params,
+				'object',
+				'item_id',
+				'nonces'
+			) );
+
+			// Create the delete view
+			var deleteView = new bp.Views.DeleteAvatar( { model: delete_model } );
+
+			// Add it to views
+			this.views.add( { id: 'delete', view: deleteView } );
+
+			// Display it
+			deleteView.inject( '.bp-avatar' );
+		},
+
+		deleteAvatar: function( model ) {
+			var self = this,
+				deleteView;
+
+			// Remove the delete view
+			if ( ! _.isUndefined( this.views.get( 'delete' ) ) ) {
+				deleteView = this.views.get( 'delete' );
+				deleteView.get( 'view' ).remove();
+				this.views.remove( { id: 'delete', view: deleteView } );
+			}
+
+			// Remove the avatar !
+			bp.ajax.post( 'bp_avatar_delete', {
+				json:          true,
+				item_id:       model.get( 'item_id' ),
+				object:        model.get( 'object' ),
+				nonce:         model.get( 'nonces' ).remove
+			} ).done( function( response ) {
+				var avatarStatus = new bp.Views.AvatarStatus( {
+					value : BP_Uploader.strings.feedback_messages[ response.feedback_code ],
+					type : 'success'
+				} );
+
+				self.views.add( {
+					id   : 'status',
+					view : avatarStatus
+				} );
+
+				avatarStatus.inject( '.bp-avatar-status' );
+
+				// Update each avatars of the page
+				$( '.' + model.get( 'object' ) + '-' + response.item_id + '-avatar').each( function() {
+					$( this ).prop( 'src', response.avatar );
+				} );
+
+				// Remove the Delete nav
+				bp.Avatar.navItems.get( 'delete' ).set( { active: 0, hide: 1 } );
+
+				/**
+				 * Reset the Attachment object
+				 *
+				 * You can run extra actions once the avatar is set using:
+				 * bp.Avatar.Attachment.on( 'change:url', function( data ) { your code } );
+				 *
+				 * In this case data.attributes will include the url to the gravatar,
+				 * the object and the item_id concerned.
+				 */
+				self.Attachment.set( _.extend(
+					_.pick( model.attributes, ['object', 'item_id'] ),
+					{ url: response.avatar, action: 'deleted' }
+				) );
+
+			} ).fail( function( response ) {
+				var feedback = BP_Uploader.strings.default_error;
+				if ( ! _.isUndefined( response ) ) {
+					feedback = BP_Uploader.strings.feedback_messages[ response.feedback_code ];
+				}
+
+				var avatarStatus = new bp.Views.AvatarStatus( {
+					value : feedback,
+					type : 'error'
+				} );
+
+				self.views.add( {
+					id   : 'status',
+					view : avatarStatus
+				} );
+
+				avatarStatus.inject( '.bp-avatar-status' );
+			} );
+		},
+
+		removeWarning: function() {
+			if ( ! _.isNull( this.warning ) ) {
+				this.warning.remove();
+			}
+		},
+
+		displayWarning: function( message ) {
+			this.removeWarning();
+
+			this.warning = new bp.Views.uploaderWarning( {
+				value: message
+			} );
+
+			this.warning.inject( '.bp-avatar-status' );
+		}
+	};
+
+	// Main Nav view
+	bp.Views.Nav = bp.View.extend( {
+		tagName:    'ul',
+		className:  'avatar-nav-items',
+
+		events: {
+			'click .bp-avatar-nav-item' : 'toggleView'
+		},
+
+		initialize: function() {
+			var hasAvatar = _.findWhere( this.collection.models, { id: 'delete' } );
+
+			// Display a message to inform about the delete tab
+			if ( 1 !== hasAvatar.get( 'hide' ) ) {
+				bp.Avatar.displayWarning( BP_Uploader.strings.has_avatar_warning );
+			}
+
+			_.each( this.collection.models, this.addNavItem, this );
+			this.collection.on( 'change:hide', this.showHideNavItem, this );
+		},
+
+		addNavItem: function( item ) {
+			/**
+			 * The delete nav is not added if no avatar
+			 * is set for the object
+			 */
+			if ( 1 === item.get( 'hide' ) ) {
+				return;
+			}
+
+			this.views.add( new bp.Views.NavItem( { model: item } ) );
+		},
+
+		showHideNavItem: function( item ) {
+			var isRendered = null;
+
+			/**
+			 * Loop in views to show/hide the nav item
+			 * BuddyPress is only using this for the delete nav
+			 */
+			_.each( this.views._views[''], function( view ) {
+				if ( 1 === view.model.get( 'hide' ) ) {
+					view.remove();
+				}
+
+				// Check to see if the nav is not already rendered
+				if ( item.get( 'id' ) === view.model.get( 'id' ) ) {
+					isRendered = true;
+				}
+			} );
+
+			// Add the Delete nav if not rendered
+			if ( ! _.isBoolean( isRendered ) ) {
+				this.addNavItem( item );
+			}
+		},
+
+		toggleView: function( event ) {
+			event.preventDefault();
+
+			// First make sure to remove all warnings
+			bp.Avatar.removeWarning();
+
+			var active = $( event.target ).data( 'nav' );
+
+			_.each( this.collection.models, function( model ) {
+				if ( model.id === active ) {
+					model.set( { active: 1 } );
+					this.trigger( 'bp-avatar-view:changed', model.id );
+				} else {
+					model.set( { active: 0 } );
+				}
+			}, this );
+		}
+	} );
+
+	// Nav item view
+	bp.Views.NavItem = bp.View.extend( {
+		tagName:    'li',
+		className:  'avatar-nav-item',
+		template: bp.template( 'bp-avatar-nav' ),
+
+		initialize: function() {
+			if ( 1 === this.model.get( 'active' ) ) {
+				this.el.className += ' current';
+			}
+			this.el.id += 'bp-avatar-' + this.model.get( 'id' );
+
+			this.model.on( 'change:active', this.setCurrentNav, this );
+		},
+
+		setCurrentNav: function( model ) {
+			if ( 1 === model.get( 'active' ) ) {
+				this.$el.addClass( 'current' );
+			} else {
+				this.$el.removeClass( 'current' );
+			}
+		}
+	} );
+
+	// Avatars view
+	bp.Views.Avatars = bp.View.extend( {
+		className: 'items',
+
+		initialize: function() {
+			_.each( this.collection.models, this.addItemView, this );
+		},
+
+		addItemView: function( item ) {
+			// Defaults to 150
+			var full_d = { full_h: 150, full_w: 150 };
+
+			// Make sure to take in account bp_core_avatar_full_height or bp_core_avatar_full_width php filters
+			if ( ! _.isUndefined( BP_Uploader.settings.crop.full_h ) && ! _.isUndefined( BP_Uploader.settings.crop.full_w ) ) {
+				full_d.full_h = BP_Uploader.settings.crop.full_h;
+				full_d.full_w = BP_Uploader.settings.crop.full_w;
+			}
+
+			// Set the avatar model
+			item.set( _.extend( _.pick( BP_Uploader.settings.defaults.multipart_params.bp_params,
+				'object',
+				'item_id',
+				'nonces'
+			), full_d ) );
+
+			// Add the view
+			this.views.add( new bp.Views.Avatar( { model: item } ) );
+		}
+	} );
+
+	// Avatar view
+	bp.Views.Avatar = bp.View.extend( {
+		className: 'item',
+		template: bp.template( 'bp-avatar-item' ),
+
+		events: {
+			'click .avatar-crop-submit': 'cropAvatar'
+		},
+
+		initialize: function() {
+			_.defaults( this.options, {
+				full_h:  BP_Uploader.settings.crop.full_h,
+				full_w:  BP_Uploader.settings.crop.full_w,
+				aspectRatio : 1
+			} );
+
+			// Display a warning if the image is smaller than minimum advised
+			if ( false !== this.model.get( 'feedback' ) ) {
+				bp.Avatar.displayWarning( this.model.get( 'feedback' ) );
+			}
+
+			this.on( 'ready', this.initCropper );
+		},
+
+		initCropper: function() {
+			var self = this,
+				tocrop = this.$el.find( '#avatar-to-crop img' ),
+				availableWidth = this.$el.width(),
+				selection = {}, crop_top, crop_bottom, crop_left, crop_right, nh, nw;
+
+			if ( ! _.isUndefined( this.options.full_h ) && ! _.isUndefined( this.options.full_w ) ) {
+				this.options.aspectRatio = this.options.full_w / this.options.full_h;
+			}
+
+			selection.w = this.model.get( 'width' );
+			selection.h = this.model.get( 'height' );
+
+			/**
+			 * Make sure the crop preview is at the right of the avatar
+			 * if the available width allowes it.
+			 */
+			if ( this.options.full_w + selection.w + 20 < availableWidth ) {
+				$( '#avatar-to-crop' ).addClass( 'adjust' );
+				this.$el.find( '.avatar-crop-management' ).addClass( 'adjust' );
+			}
+
+			if ( selection.h <= selection.w ) {
+				crop_top    = Math.round( selection.h / 4 );
+				nh = nw     = Math.round( selection.h / 2 );
+				crop_bottom = nh + crop_top;
+				crop_left   = ( selection.w - nw ) / 2;
+				crop_right  = nw + crop_left;
+			} else {
+				crop_left   = Math.round( selection.w / 4 );
+				nh = nw     = Math.round( selection.w / 2 );
+				crop_right  = nw + crop_left;
+				crop_top    = ( selection.h - nh ) / 2;
+				crop_bottom = nh + crop_top;
+			}
+
+			// Add the cropping interface
+			tocrop.Jcrop( {
+				onChange: _.bind( self.showPreview, self ),
+				onSelect: _.bind( self.showPreview, self ),
+				aspectRatio: self.options.aspectRatio,
+				setSelect: [ crop_left, crop_top, crop_right, crop_bottom ]
+			}, function() {
+				// Get the Jcrop API
+				bp.Avatar.jcropapi = this;
+			} );
+		},
+
+		cropAvatar: function( event ) {
+			event.preventDefault();
+
+			bp.Avatar.setAvatar( this.model );
+		},
+
+		showPreview: function( coords ) {
+			if ( ! coords.w || ! coords.h ) {
+				return;
+			}
+
+			if ( parseInt( coords.w, 10 ) > 0 ) {
+				var fw = this.options.full_w;
+				var fh = this.options.full_h;
+				var rx = fw / coords.w;
+				var ry = fh / coords.h;
+
+				// Update the model
+				this.model.set( { x: coords.x, y: coords.y, w: coords.w, h: coords.h } );
+
+				$( '#avatar-crop-preview' ).css( {
+					maxWidth:'none',
+					width: Math.round( rx *  this.model.get( 'width' ) )+ 'px',
+					height: Math.round( ry * this.model.get( 'height' ) )+ 'px',
+					marginLeft: '-' + Math.round( rx * this.model.get( 'x' ) ) + 'px',
+					marginTop: '-' + Math.round( ry * this.model.get( 'y' ) ) + 'px'
+				} );
+			}
+		}
+	} );
+
+	// BuddyPress Avatar Feedback view
+	bp.Views.AvatarStatus = bp.View.extend( {
+		tagName: 'p',
+		className: 'updated',
+		id: 'bp-avatar-feedback',
+
+		initialize: function() {
+			this.el.className += ' ' + this.options.type;
+			this.value = this.options.value;
+		},
+
+		render: function() {
+			this.$el.html( this.value );
+			return this;
+		}
+	} );
+
+	// BuddyPress Avatar Delete view
+	bp.Views.DeleteAvatar = bp.View.extend( {
+		tagName: 'div',
+		id: 'bp-delete-avatar-container',
+		template: bp.template( 'bp-avatar-delete' ),
+
+		events: {
+			'click #bp-delete-avatar': 'deleteAvatar'
+		},
+
+		deleteAvatar: function( event ) {
+			event.preventDefault();
+
+			bp.Avatar.deleteAvatar( this.model );
+		}
+	} );
+
+	bp.Avatar.start();
+
+})( bp, jQuery );
Index: /tags/2.4.3/src/bp-core/js/bp-plupload.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/bp-plupload.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/bp-plupload.js	(revision 10410)
@@ -0,0 +1,414 @@
+/* global bp, plupload, BP_Uploader, _, JSON, Backbone */
+
+window.wp = window.wp || {};
+window.bp = window.bp || {};
+
+( function( exports, $ ) {
+
+	// Bail if not set
+	if ( typeof BP_Uploader === 'undefined' ) {
+		return;
+	}
+
+	/**
+	 * Extend the bp global with what we need from the wp one.
+	 * and make sure previously defined BuddyPress attributes
+	 * are not removed (eg: bp.mentions)
+	 */
+	_.extend( bp, _.pick( wp, 'Backbone', 'ajax', 'template' ) );
+
+	// Init Models, Collections, Views and the BuddyPress Uploader
+	bp.Models      = bp.Models || {};
+	bp.Collections = bp.Collections || {};
+	bp.Views       = bp.Views || {};
+	bp.Uploader    = {};
+
+	/**
+	 * BuddyPress Uploader.
+	 *
+	 * This is an adapted version of wp.Uploader
+	 */
+	bp.Uploader.uploader = function() {
+		var self = this,
+			isIE = navigator.userAgent.indexOf('Trident/') !== -1 || navigator.userAgent.indexOf('MSIE ') !== -1;
+
+		this.params  = BP_Uploader.settings;
+		this.strings = BP_Uploader.strings;
+
+		this.supports = {
+			upload: this.params.browser.supported
+		};
+
+		this.supported = this.supports.upload;
+
+		if ( ! this.supported ) {
+			/*jshint -W020 */
+			BP_Uploader = undefined;
+			return;
+		}
+
+		// Make sure flash sends cookies (seems in IE it does without switching to urlstream mode)
+		if ( ! isIE && 'flash' === plupload.predictRuntime( this.params.defaults ) &&
+			( ! this.params.defaults.required_features || ! this.params.defaults.required_features.hasOwnProperty( 'send_binary_string' ) ) ) {
+
+			this.params.defaults.required_features = this.params.defaults.required_features || {};
+			this.params.defaults.required_features.send_binary_string = true;
+		}
+
+		this.uploader = new plupload.Uploader( this.params.defaults );
+
+		/**
+		 * After the Uploader has been initialized, initialize some behaviors for the dropzone.
+		 *
+		 * @event Init
+		 * @param {plupload.Uploader} uploader Uploader instance.
+		 */
+		this.uploader.bind( 'Init', function( uploader ) {
+			var container    = $( '#' + self.params.defaults.container ),
+			    drop_element = $( '#' + self.params.defaults.drop_element );
+
+			if ( 'html4' === uploader.runtime ) {
+				uploader.settings.multipart_params.html4 = true;
+			}
+
+			/**
+			 * Avatars need to be cropped, by default we are using an original
+			 * max width of 450px, but there can be cases when this max width
+			 * is larger than the one of the Avatar UI (eg: on mobile). To avoid any
+			 * difficulties, we're adding a ui_available_width argument to the bp_params
+			 * object and set it according to the container width. This value will be
+			 * checked during the upload process to eventually adapt the resized avatar.
+			 */
+			if ( 'bp_avatar_upload' ===  uploader.settings.multipart_params.action ) {
+				 uploader.settings.multipart_params.bp_params.ui_available_width = container.width();
+			}
+
+			if ( uploader.features.dragdrop && ! self.params.browser.mobile ) {
+				container.addClass( 'drag-drop' );
+				drop_element.bind( 'dragover.wp-uploader', function() {
+					container.addClass( 'drag-over' );
+				} ).bind( 'dragleave.wp-uploader, drop.wp-uploader', function() {
+					container.removeClass( 'drag-over' );
+				} );
+			} else {
+				container.removeClass( 'drag-drop' );
+				drop_element.unbind( '.wp-uploader' );
+			}
+
+		} );
+
+		// Init BuddyPress Uploader
+		this.uploader.init();
+
+		/**
+		 * Feedback callback.
+		 *
+		 * Add a new message to the errors collection, so it's possible
+		 * to give some feedback to the user
+		 *
+		 * @param  {string}        message
+		 * @param  {object}        data
+		 * @param  {plupload.File} file     File that was uploaded.
+		 */
+		this.feedback = function( message, data, file ) {
+			if ( ! _.isNull( file ) && file.item ) {
+				file.item.clear();
+			}
+
+			bp.Uploader.filesError.unshift( {
+				message: message,
+				data:    data,
+				file:    file
+			} );
+		};
+
+		/**
+		 * After files were filtered and added to the queue, create a model for each.
+		 *
+		 * @event FilesAdded
+		 * @param {plupload.Uploader} uploader Uploader instance.
+		 * @param {Array}             files    Array of file objects that were added to queue by the user.
+		 */
+		this.uploader.bind( 'FilesAdded', function( uploader, files ) {
+			var hundredmb = 100 * 1024 * 1024, max = parseInt( uploader.settings.max_file_size, 10 ),
+			    _this = this;
+
+			/**
+			 * In case the multiple selection is false (eg: avatar) stop the process and send
+			 * and event containing a warning
+			 */
+			if ( ! uploader.settings.multi_selection && files.length > 1 ) {
+				for ( var i in files ) {
+					uploader.removeFile( files[i] );
+				}
+
+				$( self ).trigger( 'bp-uploader-warning', self.strings.unique_file_warning );
+				return;
+			}
+
+			_.each( files, function( file ) {
+				var attributes;
+
+				// Ignore failed uploads.
+				if ( plupload.FAILED === file.status ) {
+					return;
+				}
+
+				if ( max > hundredmb && file.size > hundredmb && uploader.runtime !== 'html5' ) {
+					_this.uploadSizeError( uploader, file, true );
+				} else {
+					attributes = _.extend( {
+						id:        file.id,
+						file:      file,
+						uploading: true,
+						date:      new Date(),
+						filename:  file.name
+					}, _.pick( file, 'loaded', 'size', 'percent' ) );
+
+					file.item = new bp.Models.File( attributes );
+					bp.Uploader.filesQueue.add( file.item );
+				}
+
+			} );
+
+			uploader.refresh();
+			uploader.start();
+		} );
+
+		/**
+		 * Update each file item on progress
+		 *
+		 * @event UploadProgress
+		 * @param {plupload.Uploader} uploader Uploader instance.
+		 * @param {Object}            file
+		 */
+		this.uploader.bind( 'UploadProgress', function( uploader, file ) {
+			file.item.set( _.pick( file, 'loaded', 'percent' ) );
+		} );
+
+		/**
+		 * After a file is successfully uploaded, update its model.
+		 *
+		 * @event FileUploaded
+		 * @param {plupload.Uploader} uploader Uploader instance.
+		 * @param {plupload.File}     file     File that was uploaded.
+		 * @param {Object}            response Object with response properties.
+		 * @return {mixed}
+		 */
+		this.uploader.bind( 'FileUploaded', function( uploader, file, response ) {
+			var message = self.strings.default_error;
+
+			try {
+				response = JSON.parse( response.response );
+			} catch ( e ) {
+				return self.feedback( message, e, file );
+			}
+
+			if ( ! _.isObject( response ) || _.isUndefined( response.success ) ) {
+				return self.feedback( message, null, file );
+			} else if ( ! response.success ) {
+				if ( response.data && response.data.message ) {
+					message = response.data.message;
+				}
+
+				return self.feedback( message, response.data, file );
+			}
+
+			_.each(['file','loaded','size','percent'], function( key ) {
+				file.item.unset( key );
+			} );
+
+			file.item.set( _.extend( response.data, { uploading: false } ) );
+
+			//  Add the file to the Uploaded ones
+			bp.Uploader.filesUploaded.add( file.item );
+
+		} );
+
+		/**
+		 * Trigger an event to inform a new upload is being processed
+		 *
+		 * Mainly used to remove an eventual warning
+		 *
+		 * @event BeforeUpload
+		 * @param {plupload.Uploader} uploader Uploader instance.
+		 * @param {Array}             files    Array of file objects that were added to queue by the user.
+		 */
+		this.uploader.bind( 'BeforeUpload', function( uploader, files ) {
+			$( self ).trigger( 'bp-uploader-new-upload', uploader, files );
+		} );
+
+		/**
+		 * Reset the filesQueue once the upload is complete
+		 *
+		 * @event BeforeUpload
+		 * @param {plupload.Uploader} uploader Uploader instance.
+		 * @param {Array}             files    Array of file objects that were added to queue by the user.
+		 */
+		this.uploader.bind( 'UploadComplete', function( uploader, files ) {
+			$( self ).trigger( 'bp-uploader-upload-complete', uploader, files );
+			bp.Uploader.filesQueue.reset();
+		} );
+
+		/**
+		 * Map Plupload errors & Create a warning when plupload failed
+		 *
+		 * @event Error
+		 * @param {plupload.Uploader} uploader Uploader instance.
+		 * @param {Object}            pluploadError Plupload error
+		 */
+		this.uploader.bind( 'Error', function( uploader, pluploadError ) {
+			var message = self.strings.default_error,
+				key,
+				errors = {
+					'FAILED':                 self.strings.upload_failed,
+					'FILE_EXTENSION_ERROR':   self.strings.invalid_filetype,
+					'IMAGE_FORMAT_ERROR':     self.strings.not_an_image,
+					'IMAGE_MEMORY_ERROR':     self.strings.image_memory_exceeded,
+					'IMAGE_DIMENSIONS_ERROR': self.strings.image_dimensions_exceeded,
+					'GENERIC_ERROR':          self.strings.upload_failed,
+					'IO_ERROR':               self.strings.io_error,
+					'HTTP_ERROR':             self.strings.http_error,
+					'SECURITY_ERROR':         self.strings.security_error,
+					'FILE_SIZE_ERROR':        self.strings.file_exceeds_size_limit.replace( '%s' , pluploadError.file.name )
+				};
+
+			// Check for plupload errors.
+			for ( key in errors ) {
+				if ( pluploadError.code === plupload[ key ] ) {
+					message = errors[ key ];
+					break;
+				}
+			}
+
+			$( self ).trigger( 'bp-uploader-warning', message );
+			uploader.refresh();
+		} );
+	};
+
+	// Create a very generic Model for files
+	bp.Models.File = Backbone.Model.extend( {
+		file: {}
+	} );
+
+	// Add Collections to store queue, uploaded files and errors
+	$.extend( bp.Uploader, {
+		filesQueue    : new Backbone.Collection(),
+		filesUploaded : new Backbone.Collection(),
+		filesError    : new Backbone.Collection()
+	} );
+
+	// Extend wp.Backbone.View with .prepare() and .inject()
+	bp.View = bp.Backbone.View.extend( {
+		inject: function( selector ) {
+			this.render();
+			$(selector).html( this.el );
+			this.views.ready();
+		},
+
+		prepare: function() {
+			if ( ! _.isUndefined( this.model ) && _.isFunction( this.model.toJSON ) ) {
+				return this.model.toJSON();
+			} else {
+				return {};
+			}
+		}
+	} );
+
+	// BuddyPress Uploader main view
+	bp.Views.Uploader = bp.View.extend( {
+		className: 'bp-uploader-window',
+		template: bp.template( 'upload-window' ),
+
+		defaults: _.pick( BP_Uploader.settings.defaults, 'container', 'drop_element', 'browse_button' ),
+
+		initialize: function() {
+			this.warnings = [];
+			this.model    = new Backbone.Model( this.defaults );
+			this.on( 'ready', this.initUploader );
+		},
+
+		initUploader: function() {
+			this.uploader = new bp.Uploader.uploader();
+			$( this.uploader ).on( 'bp-uploader-warning', _.bind( this.setWarning, this ) );
+			$( this.uploader ).on( 'bp-uploader-new-upload', _.bind( this.resetWarning, this ) );
+		},
+
+		setWarning: function( event, message ) {
+			if ( _.isUndefined( message ) ) {
+				return;
+			}
+
+			var warning = new bp.Views.uploaderWarning( {
+				value: message
+			} ).render();
+
+			this.warnings.push( warning );
+
+			this.$el.after( warning.el );
+		},
+
+		resetWarning: function() {
+			if ( 0 === this.warnings.length ) {
+				return;
+			}
+
+			// Remove all warning views
+			_.each( this.warnings, function( view ) {
+				view.remove();
+			} );
+
+			// Reset Warnings
+			this.warnings = [];
+		}
+	} );
+
+	// BuddyPress Uploader warning view
+	bp.Views.uploaderWarning = bp.View.extend( {
+		tagName: 'p',
+		className: 'warning',
+
+		initialize: function() {
+			this.value = this.options.value;
+		},
+
+		render: function() {
+			this.$el.html( this.value );
+			return this;
+		}
+	} );
+
+	// BuddyPress Uploader Files view
+	bp.Views.uploaderStatus = bp.View.extend( {
+		className: 'files',
+
+		initialize: function() {
+			_.each( this.collection.models, this.addFile, this );
+			this.collection.on( 'change:percent', this.progress, this );
+			bp.Uploader.filesError.on( 'add', this.feedback, this );
+		},
+
+		addFile: function( file ) {
+			this.views.add( new bp.Views.uploaderProgress( { model: file } ) );
+		},
+
+		progress:function( model ) {
+			if ( ! _.isUndefined( model.get( 'percent' ) ) ) {
+				$( '#' + model.get('id') + ' .bp-progress .bp-bar' ).css( 'width', model.get('percent') + '%' );
+			}
+		},
+
+		feedback: function( model ) {
+			if ( ! _.isUndefined( model.get( 'message' ) ) && ! _.isUndefined( model.get( 'file' ) ) ) {
+				$( '#' + model.get( 'file' ).id ).html( model.get( 'message' ) ).addClass( 'error' );
+			}
+		}
+	} );
+
+	// BuddyPress Uploader File progress view
+	bp.Views.uploaderProgress = bp.View.extend( {
+		className: 'bp-uploader-progress',
+		template: bp.template( 'progress-window' )
+	} );
+
+})( bp, jQuery );
Index: /tags/2.4.3/src/bp-core/js/confirm.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/confirm.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/confirm.js	(revision 10410)
@@ -0,0 +1,12 @@
+/* jshint devel: true */
+/* global BP_Confirm */
+
+jQuery( document ).ready( function() {
+	jQuery( 'a.confirm').click( function() {
+		if ( confirm( BP_Confirm.are_you_sure ) ) {
+			return true;
+		} else {
+			return false;
+		}
+	});
+});
Index: /tags/2.4.3/src/bp-core/js/cover-image.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/cover-image.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/cover-image.js	(revision 10410)
@@ -0,0 +1,277 @@
+/* global bp, BP_Uploader, _, Backbone */
+
+window.bp = window.bp || {};
+
+( function( exports, $ ) {
+
+	// Bail if not set
+	if ( typeof BP_Uploader === 'undefined' ) {
+		return;
+	}
+
+	bp.Models      = bp.Models || {};
+	bp.Collections = bp.Collections || {};
+	bp.Views       = bp.Views || {};
+
+	bp.CoverImage = {
+		start: function() {
+
+			// Init some vars
+			this.views   = new Backbone.Collection();
+			this.warning = null;
+
+			// The Cover Image Attachment object.
+			this.Attachment = new Backbone.Model();
+
+			// Set up views
+			this.uploaderView();
+
+			// Inform about the needed dimensions
+			this.displayWarning( BP_Uploader.strings.cover_image_warnings.dimensions );
+
+			// Set up the delete view if needed
+			if ( true === BP_Uploader.settings.defaults.multipart_params.bp_params.has_cover_image ) {
+				this.deleteView();
+			}
+		},
+
+		uploaderView: function() {
+			// Listen to the Queued uploads
+			bp.Uploader.filesQueue.on( 'add', this.uploadProgress, this );
+
+			// Create the BuddyPress Uploader
+			var uploader = new bp.Views.Uploader();
+
+			// Add it to views
+			this.views.add( { id: 'upload', view: uploader } );
+
+			// Display it
+			uploader.inject( '.bp-cover-image' );
+		},
+
+		uploadProgress: function() {
+			// Create the Uploader status view
+			var coverImageUploadProgress = new bp.Views.coverImageUploadProgress( { collection: bp.Uploader.filesQueue } );
+
+			if ( ! _.isUndefined( this.views.get( 'status' ) ) ) {
+				this.views.set( { id: 'status', view: coverImageUploadProgress } );
+			} else {
+				this.views.add( { id: 'status', view: coverImageUploadProgress } );
+			}
+
+			// Display it
+			coverImageUploadProgress.inject( '.bp-cover-image-status' );
+		},
+
+		deleteView: function() {
+			// Create the delete model
+			var delete_model = new Backbone.Model( _.pick( BP_Uploader.settings.defaults.multipart_params.bp_params,
+				['object', 'item_id', 'nonces']
+			) );
+
+			// Do not add it if already there!
+			if ( ! _.isUndefined( this.views.get( 'delete' ) ) ) {
+				return;
+			}
+
+			// Create the delete view
+			var deleteView = new bp.Views.DeleteCoverImage( { model: delete_model } );
+
+			// Add it to views
+			this.views.add( { id: 'delete', view: deleteView } );
+
+			// Display it
+			deleteView.inject( '.bp-cover-image-manage' );
+		},
+
+		deleteCoverImage: function( model ) {
+			var self = this,
+				deleteView;
+
+			// Remove the delete view
+			if ( ! _.isUndefined( this.views.get( 'delete' ) ) ) {
+				deleteView = this.views.get( 'delete' );
+				deleteView.get( 'view' ).remove();
+				this.views.remove( { id: 'delete', view: deleteView } );
+			}
+
+			// Remove the cover image !
+			bp.ajax.post( 'bp_cover_image_delete', {
+				json:          true,
+				item_id:       model.get( 'item_id' ),
+				object:        model.get( 'object' ),
+				nonce:         model.get( 'nonces' ).remove
+			} ).done( function( response ) {
+				var coverImageStatus = new bp.Views.CoverImageStatus( {
+					value : BP_Uploader.strings.feedback_messages[ response.feedback_code ],
+					type : 'success'
+				} );
+
+				self.views.add( {
+					id   : 'status',
+					view : coverImageStatus
+				} );
+
+				coverImageStatus.inject( '.bp-cover-image-status' );
+
+				// Reset the header of the page
+				if ( '' === response.reset_url ) {
+					$( '#header-cover-image' ).css( {
+						'background-image': 'none'
+					} );
+				} else {
+					$( '#header-cover-image' ).css( {
+						'background-image': 'url( ' + response.reset_url + ' )'
+					} );
+				}
+
+				// Reset the has_cover_image bp_param
+				BP_Uploader.settings.defaults.multipart_params.bp_params.has_cover_image = false;
+
+				/**
+				 * Reset the Attachment object
+				 *
+				 * You can run extra actions once the cover image is set using:
+				 * bp.CoverImage.Attachment.on( 'change:url', function( data ) { your code } );
+				 *
+				 * In this case data.attributes will include the default url for the
+				 * cover image (most of the time: ''), the object and the item_id concerned.
+				 */
+				self.Attachment.set( _.extend(
+					_.pick( model.attributes, ['object', 'item_id'] ),
+					{ url: response.reset_url, action: 'deleted' }
+				) );
+
+			} ).fail( function( response ) {
+				var feedback = BP_Uploader.strings.default_error;
+				if ( ! _.isUndefined( response ) ) {
+					feedback = BP_Uploader.strings.feedback_messages[ response.feedback_code ];
+				}
+
+				var coverImageStatus = new bp.Views.CoverImageStatus( {
+					value : feedback,
+					type : 'error'
+				} );
+
+				self.views.add( {
+					id   : 'status',
+					view : coverImageStatus
+				} );
+
+				coverImageStatus.inject( '.bp-cover-image-status' );
+
+				// Put back the delete view
+				bp.CoverImage.deleteView();
+			} );
+		},
+
+		removeWarning: function() {
+			if ( ! _.isNull( this.warning ) ) {
+				this.warning.remove();
+			}
+		},
+
+		displayWarning: function( message ) {
+			this.removeWarning();
+
+			this.warning = new bp.Views.uploaderWarning( {
+				value: message
+			} );
+
+			this.warning.inject( '.bp-cover-image-status' );
+		}
+	};
+
+	// Custom Uploader Files view
+	bp.Views.coverImageUploadProgress = bp.Views.uploaderStatus.extend( {
+		className: 'files',
+
+		initialize: function() {
+			bp.Views.uploaderStatus.prototype.initialize.apply( this, arguments );
+
+			this.collection.on( 'change:url', this.uploadResult, this );
+		},
+
+		uploadResult: function( model ) {
+			var message, type;
+
+			if ( ! _.isUndefined( model.get( 'url' ) ) ) {
+
+				// Image is too small
+				if ( 0 === model.get( 'feedback_code' ) ) {
+					message = BP_Uploader.strings.cover_image_warnings.dimensions;
+					type    = 'warning';
+
+				// Success, Rock n roll!
+				} else {
+					message = BP_Uploader.strings.feedback_messages[ model.get( 'feedback_code' ) ];
+					type = 'success';
+				}
+
+				this.views.set( '.bp-uploader-progress', new bp.Views.CoverImageStatus( {
+					value : message,
+					type  : type
+				} ) );
+
+				// Update the header of the page
+				$( '#header-cover-image' ).css( {
+					'background-image': 'url( ' + model.get( 'url' ) + ' )'
+				} );
+
+				// Add the delete view
+				bp.CoverImage.deleteView();
+
+				/**
+				 * Set the Attachment object
+				 *
+				 * You can run extra actions once the cover image is set using:
+				 * bp.CoverImage.Attachment.on( 'change:url', function( data ) { your code } );
+				 *
+				 * In this case data.attributes will include the url to the newly
+				 * uploaded cover image, the object and the item_id concerned.
+				 */
+				bp.CoverImage.Attachment.set( _.extend(
+					_.pick( BP_Uploader.settings.defaults.multipart_params.bp_params, ['object', 'item_id'] ),
+					{ url: model.get( 'url' ), action: 'uploaded' }
+				) );
+			}
+		}
+	} );
+
+	// BuddyPress Cover Image Feedback view
+	bp.Views.CoverImageStatus = bp.View.extend( {
+		tagName: 'p',
+		className: 'updated',
+		id: 'bp-cover-image-feedback',
+
+		initialize: function() {
+			this.el.className += ' ' + this.options.type;
+			this.value = this.options.value;
+		},
+
+		render: function() {
+			this.$el.html( this.value );
+			return this;
+		}
+	} );
+
+	// BuddyPress Cover Image Delete view
+	bp.Views.DeleteCoverImage = bp.View.extend( {
+		tagName: 'div',
+		id: 'bp-delete-cover-image-container',
+		template: bp.template( 'bp-cover-image-delete' ),
+
+		events: {
+			'click #bp-delete-cover-image': 'deleteCoverImage'
+		},
+
+		deleteCoverImage: function( event ) {
+			event.preventDefault();
+
+			bp.CoverImage.deleteCoverImage( this.model );
+		}
+	} );
+
+	bp.CoverImage.start();
+
+})( bp, jQuery );
Index: /tags/2.4.3/src/bp-core/js/jquery-cookie.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/jquery-cookie.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/jquery-cookie.js	(revision 10410)
@@ -0,0 +1,120 @@
+/* jshint undef: false */
+
+/*!
+ * jQuery Cookie Plugin v1.4.1
+ * https://github.com/carhartl/jquery-cookie
+ *
+ * Copyright 2013 Klaus Hartl
+ * Released under the MIT license
+ */
+(function(factory) {
+	// AMD
+	if (typeof define === 'function' && define.amd) {
+		define(['jquery'], factory);
+	// CommonJS
+	} else if (typeof exports === 'object') {
+		factory(require('jquery'));
+	// Browser globals
+	} else {
+		factory(jQuery);
+	}
+}(function($) {
+
+	var pluses = /\+/g;
+
+	function encode(s) {
+		return config.raw ? s : encodeURIComponent(s);
+	}
+
+	function decode(s) {
+		return config.raw ? s : decodeURIComponent(s);
+	}
+
+	function stringifyCookieValue(value) {
+		return encode(config.json ? JSON.stringify(value) : String(value));
+	}
+
+	function parseCookieValue(s) {
+		if (s.indexOf('"') === 0) {
+			// This is a quoted cookie as according to RFC2068, unescape...
+			s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
+		}
+
+		try {
+			// Replace server-side written pluses with spaces.
+			// If we can't decode the cookie, ignore it, it's unusable.
+			// If we can't parse the cookie, ignore it, it's unusable.
+			s = decodeURIComponent(s.replace(pluses, ' '));
+			return config.json ? JSON.parse(s) : s;
+		} catch (e) {
+		}
+	}
+
+	function read(s, converter) {
+		var value = config.raw ? s : parseCookieValue(s);
+		return $.isFunction(converter) ? converter(value) : value;
+	}
+
+	var config = $.cookie = function(key, value, options) {
+
+		// Write
+
+		if (value !== undefined && !$.isFunction(value)) {
+			options = $.extend({}, config.defaults, options);
+
+			if (typeof options.expires === 'number') {
+				var days = options.expires, t = options.expires = new Date();
+				t.setTime(+t + days * 864e+5);
+			}
+
+			return (document.cookie = [
+				encode(key), '=', stringifyCookieValue(value),
+				options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+				options.path ? '; path=' + options.path : '',
+				options.domain ? '; domain=' + options.domain : '',
+				options.secure ? '; secure' : ''
+			].join(''));
+		}
+
+		// Read
+
+		var result = key ? undefined : {};
+
+		// To prevent the for loop in the first place assign an empty array
+		// in case there are no cookies at all. Also prevents odd result when
+		// calling $.cookie().
+		var cookies = document.cookie ? document.cookie.split('; ') : [];
+
+		for (var i = 0, l = cookies.length; i < l; i++) {
+			var parts = cookies[i].split('=');
+			var name = decode(parts.shift());
+			var cookie = parts.join('=');
+
+			if (key && key === name) {
+				// If second argument (value) is a function it's a converter...
+				result = read(cookie, value);
+				break;
+			}
+
+			// Prevent storing a cookie that we couldn't decode.
+			if (!key && (cookie = read(cookie)) !== undefined) {
+				result[name] = cookie;
+			}
+		}
+
+		return result;
+	};
+
+	config.defaults = {};
+
+	$.removeCookie = function(key, options) {
+		if ($.cookie(key) === undefined) {
+			return false;
+		}
+
+		// Must not alter options, thus extending a fresh object...
+		$.cookie(key, '', $.extend({}, options, {expires: -1}));
+		return !$.cookie(key);
+	};
+
+}));
Index: /tags/2.4.3/src/bp-core/js/jquery-query.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/jquery-query.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/jquery-query.js	(revision 10410)
@@ -0,0 +1,6 @@
+/* jshint unused: false */
+
+function bp_get_querystring( n ) {
+	var half = location.search.split( n + '=' )[1];
+	return half ? decodeURIComponent( half.split('&')[0] ) : null;
+}
Index: /tags/2.4.3/src/bp-core/js/jquery-scroll-to.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/jquery-scroll-to.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/jquery-scroll-to.js	(revision 10410)
@@ -0,0 +1,208 @@
+/* jshint undef: false */
+/* jshint -W065 */
+
+/*!
+ * jQuery.ScrollTo
+ * Copyright (c) 2007-2014 Ariel Flesler - aflesler<a>gmail<d>com | http://flesler.blogspot.com
+ * Licensed under MIT
+ * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
+ * @projectDescription Easy element scrolling using jQuery.
+ * @author Ariel Flesler
+ * @version 1.4.12
+ */
+
+(function(factory) {
+	// AMD
+	if (typeof define === 'function' && define.amd) {
+		define(['jquery'], factory);
+	// CommonJS
+	} else if (typeof exports === 'object') {
+		factory(require('jquery'));
+	// Browser globals
+	} else {
+		factory(jQuery);
+	}
+}(function($) {
+
+	var $scrollTo = $.scrollTo = function(target, duration, settings) {
+		return $(window).scrollTo(target, duration, settings);
+	};
+
+	$scrollTo.defaults = {
+		axis: 'xy',
+		duration: parseFloat($.fn.jquery) >= 1.3 ? 0 : 1,
+		limit: true
+	};
+
+	// Returns the element that needs to be animated to scroll the window.
+	// Kept for backwards compatibility (specially for localScroll & serialScroll)
+	$scrollTo.window = function() {
+		return $(window)._scrollable();
+	};
+
+	// Hack, hack, hack :)
+	// Returns the real elements to scroll (supports window/iframes, documents and regular nodes)
+	$.fn._scrollable = function() {
+		return this.map(function() {
+			var elem = this,
+					isWin = !elem.nodeName || $.inArray(elem.nodeName.toLowerCase(), ['iframe', '#document', 'html', 'body']) !== -1;
+
+			if (!isWin) {
+				return elem;
+			}
+
+			var doc = (elem.contentWindow || elem).document || elem.ownerDocument || elem;
+
+			return /webkit/i.test(navigator.userAgent) || doc.compatMode === 'BackCompat' ?
+					doc.body :
+					doc.documentElement;
+		});
+	};
+
+	$.fn.scrollTo = function(target, duration, settings) {
+		if (typeof duration === 'object') {
+			settings = duration;
+			duration = 0;
+		}
+		if (typeof settings === 'function') {
+			settings = {onAfter: settings};
+		}
+
+		if (target === 'max') {
+			target = 9e9;
+		}
+
+		settings = $.extend({}, $scrollTo.defaults, settings);
+		// Speed is still recognized for backwards compatibility
+		duration = duration || settings.duration;
+		// Make sure the settings are given right
+		settings.queue = settings.queue && settings.axis.length > 1;
+
+		// Let's keep the overall duration
+		if (settings.queue) {
+			duration /= 2;
+		}
+
+		settings.offset = both(settings.offset);
+		settings.over = both(settings.over);
+
+		return this._scrollable().each(function() {
+
+			// Null target yields nothing, just like jQuery does
+			if (target === null) {
+				return;
+			}
+
+			var elem = this,
+					$elem = $(elem),
+					targ = target, toff, attr = {},
+					win = $elem.is('html,body');
+
+			switch (typeof targ) {
+				// A number will pass the regex
+				case 'number':
+				case 'string':
+					if (/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)) {
+						targ = both(targ);
+						// We are done
+						break;
+					}
+					// Relative/Absolute selector, no break!
+					targ = win ? $(targ) : $(targ, this);
+					if (!targ.length) {
+						return;
+					}
+					/* falls through */
+				case 'object':
+					// DOMElement / jQuery
+					if (targ.is || targ.style) {
+						// Get the real position of the target
+						toff = (targ = $(targ)).offset();
+					}
+			}
+
+			var offset = $.isFunction(settings.offset) && settings.offset(elem, targ) || settings.offset;
+
+			$.each(settings.axis.split(''), function(i, axis) {
+				var Pos = axis === 'x' ? 'Left' : 'Top',
+						pos = Pos.toLowerCase(),
+						key = 'scroll' + Pos,
+						old = elem[key],
+						max = $scrollTo.max(elem, axis);
+
+				if (toff) {// jQuery / DOMElement
+					attr[key] = toff[pos] + (win ? 0 : old - $elem.offset()[pos]);
+
+					// If it's a dom element, reduce the margin
+					if (settings.margin) {
+						attr[key] -= parseInt(targ.css('margin' + Pos)) || 0;
+						attr[key] -= parseInt(targ.css('border' + Pos + 'Width')) || 0;
+					}
+
+					attr[key] += offset[pos] || 0;
+
+					// Scroll to a fraction of its width/height
+					if (settings.over[pos]) {
+						attr[key] += targ[axis === 'x' ? 'width' : 'height']() * settings.over[pos];
+					}
+				} else {
+					var val = targ[pos];
+					// Handle percentage values
+					attr[key] = val.slice && val.slice(-1) === '%' ?
+							parseFloat(val) / 100 * max
+							: val;
+				}
+
+				// Number or 'number'
+				if (settings.limit && /^\d+$/.test(attr[key])) {
+					// Check the limits
+					attr[key] = attr[key] <= 0 ? 0 : Math.min(attr[key], max);
+				}
+
+				// Queueing axes
+				if (!i && settings.queue) {
+					// Don't waste time animating, if there's no need.
+					if (old !== attr[key]) {
+						// Intermediate animation
+						animate(settings.onAfterFirst);
+					}
+					// Don't animate this axis again in the next iteration.
+					delete attr[key];
+				}
+			});
+
+			animate(settings.onAfter);
+
+			function animate(callback) {
+				$elem.animate(attr, duration, settings.easing, callback && function() {
+					callback.call(this, targ, settings);
+				});
+			}
+
+		}).end();
+	};
+
+	// Max scrolling position, works on quirks mode
+	// It only fails (not too badly) on IE, quirks mode.
+	$scrollTo.max = function(elem, axis) {
+		var Dim = axis === 'x' ? 'Width' : 'Height',
+				scroll = 'scroll' + Dim;
+
+		if (!$(elem).is('html,body')) {
+			return elem[scroll] - $(elem)[Dim.toLowerCase()]();
+		}
+
+		var size = 'client' + Dim,
+				html = elem.ownerDocument.documentElement,
+				body = elem.ownerDocument.body;
+
+		return Math.max(html[scroll], body[scroll]) - Math.min(html[size], body[size]);
+	};
+
+	function both(val) {
+		return $.isFunction(val) || typeof val === 'object' ? val : {top: val, left: val};
+	}
+
+	// AMD requirement
+	return $scrollTo;
+}));
Index: /tags/2.4.3/src/bp-core/js/jquery.atwho.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/jquery.atwho.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/jquery.atwho.js	(revision 10410)
@@ -0,0 +1,875 @@
+/*! jquery.atwho - v0.5.2 %>
+* Copyright (c) 2014 chord.luo <chord.luo@gmail.com>;
+* homepage: http://ichord.github.com/At.js
+* Licensed MIT
+*/
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(["jquery"], function ($) {
+      return (root.returnExportsGlobal = factory($));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like enviroments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"));
+  } else {
+    factory(jQuery);
+  }
+}(this, function ($) {
+
+var Api, App, Controller, DEFAULT_CALLBACKS, KEY_CODE, Model, View,
+  __slice = [].slice;
+
+App = (function() {
+  function App(inputor) {
+    this.current_flag = null;
+    this.controllers = {};
+    this.alias_maps = {};
+    this.$inputor = $(inputor);
+    this.setIframe();
+    this.listen();
+  }
+
+  App.prototype.createContainer = function(doc) {
+    if ((this.$el = $("#atwho-container", doc)).length === 0) {
+      return $(doc.body).append(this.$el = $("<div id='atwho-container'></div>"));
+    }
+  };
+
+  App.prototype.setIframe = function(iframe, standalone) {
+    var _ref;
+    if (standalone == null) {
+      standalone = false;
+    }
+    if (iframe) {
+      this.window = iframe.contentWindow;
+      this.document = iframe.contentDocument || this.window.document;
+      this.iframe = iframe;
+    } else {
+      this.document = document;
+      this.window = window;
+      this.iframe = null;
+    }
+    if (this.iframeStandalone = standalone) {
+      if ((_ref = this.$el) != null) {
+        _ref.remove();
+      }
+      return this.createContainer(this.document);
+    } else {
+      return this.createContainer(document);
+    }
+  };
+
+  App.prototype.controller = function(at) {
+    var c, current, current_flag, _ref;
+    if (this.alias_maps[at]) {
+      current = this.controllers[this.alias_maps[at]];
+    } else {
+      _ref = this.controllers;
+      for (current_flag in _ref) {
+        c = _ref[current_flag];
+        if (current_flag === at) {
+          current = c;
+          break;
+        }
+      }
+    }
+    if (current) {
+      return current;
+    } else {
+      return this.controllers[this.current_flag];
+    }
+  };
+
+  App.prototype.set_context_for = function(at) {
+    this.current_flag = at;
+    return this;
+  };
+
+  App.prototype.reg = function(flag, setting) {
+    var controller, _base;
+    controller = (_base = this.controllers)[flag] || (_base[flag] = new Controller(this, flag));
+    if (setting.alias) {
+      this.alias_maps[setting.alias] = flag;
+    }
+    controller.init(setting);
+    return this;
+  };
+
+  App.prototype.listen = function() {
+    return this.$inputor.on('keyup.atwhoInner', (function(_this) {
+      return function(e) {
+        return _this.on_keyup(e);
+      };
+    })(this)).on('keydown.atwhoInner', (function(_this) {
+      return function(e) {
+        return _this.on_keydown(e);
+      };
+    })(this)).on('scroll.atwhoInner', (function(_this) {
+      return function(e) {
+        var _ref;
+        return (_ref = _this.controller()) != null ? _ref.view.hide(e) : void 0;
+      };
+    })(this)).on('blur.atwhoInner', (function(_this) {
+      return function(e) {
+        var c;
+        if (c = _this.controller()) {
+          return c.view.hide(e, c.get_opt("display_timeout"));
+        }
+      };
+    })(this)).on('click.atwhoInner', (function(_this) {
+      return function(e) {
+        return _this.dispatch();
+      };
+    })(this));
+  };
+
+  App.prototype.shutdown = function() {
+    var c, _, _ref;
+    _ref = this.controllers;
+    for (_ in _ref) {
+      c = _ref[_];
+      c.destroy();
+      delete this.controllers[_];
+    }
+    this.$inputor.off('.atwhoInner');
+    return this.$el.remove();
+  };
+
+  App.prototype.dispatch = function() {
+    return $.map(this.controllers, (function(_this) {
+      return function(c) {
+        var delay;
+        if (delay = c.get_opt('delay')) {
+          clearTimeout(_this.delayedCallback);
+          return _this.delayedCallback = setTimeout(function() {
+            if (c.look_up()) {
+              return _this.set_context_for(c.at);
+            }
+          }, delay);
+        } else {
+          if (c.look_up()) {
+            return _this.set_context_for(c.at);
+          }
+        }
+      };
+    })(this));
+  };
+
+  App.prototype.on_keyup = function(e) {
+    var _ref;
+    switch (e.keyCode) {
+      case KEY_CODE.ESC:
+        e.preventDefault();
+        if ((_ref = this.controller()) != null) {
+          _ref.view.hide();
+        }
+        break;
+      case KEY_CODE.DOWN:
+      case KEY_CODE.UP:
+      case KEY_CODE.CTRL:
+        $.noop();
+        break;
+      case KEY_CODE.P:
+      case KEY_CODE.N:
+        if (!e.ctrlKey) {
+          this.dispatch();
+        }
+        break;
+      default:
+        this.dispatch();
+    }
+  };
+
+  App.prototype.on_keydown = function(e) {
+    var view, _ref;
+    view = (_ref = this.controller()) != null ? _ref.view : void 0;
+    if (!(view && view.visible())) {
+      return;
+    }
+    switch (e.keyCode) {
+      case KEY_CODE.ESC:
+        e.preventDefault();
+        view.hide(e);
+        break;
+      case KEY_CODE.UP:
+        e.preventDefault();
+        view.prev();
+        break;
+      case KEY_CODE.DOWN:
+        e.preventDefault();
+        view.next();
+        break;
+      case KEY_CODE.P:
+        if (!e.ctrlKey) {
+          return;
+        }
+        e.preventDefault();
+        view.prev();
+        break;
+      case KEY_CODE.N:
+        if (!e.ctrlKey) {
+          return;
+        }
+        e.preventDefault();
+        view.next();
+        break;
+      case KEY_CODE.TAB:
+      case KEY_CODE.ENTER:
+        if (!view.visible()) {
+          return;
+        }
+        e.preventDefault();
+        view.choose(e);
+        break;
+      default:
+        $.noop();
+    }
+  };
+
+  return App;
+
+})();
+
+Controller = (function() {
+  Controller.prototype.uid = function() {
+    return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime());
+  };
+
+  function Controller(app, at) {
+    this.app = app;
+    this.at = at;
+    this.$inputor = this.app.$inputor;
+    this.id = this.$inputor[0].id || this.uid();
+    this.setting = null;
+    this.query = null;
+    this.pos = 0;
+    this.cur_rect = null;
+    this.range = null;
+    if ((this.$el = $("#atwho-ground-" + this.id, this.app.$el)).length === 0) {
+      this.app.$el.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>"));
+    }
+    this.model = new Model(this);
+    this.view = new View(this);
+  }
+
+  Controller.prototype.init = function(setting) {
+    this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
+    this.view.init();
+    return this.model.reload(this.setting.data);
+  };
+
+  Controller.prototype.destroy = function() {
+    this.trigger('beforeDestroy');
+    this.model.destroy();
+    this.view.destroy();
+    return this.$el.remove();
+  };
+
+  Controller.prototype.call_default = function() {
+    var args, error, func_name;
+    func_name = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
+    try {
+      return DEFAULT_CALLBACKS[func_name].apply(this, args);
+    } catch (_error) {
+      error = _error;
+      return $.error("" + error + " Or maybe At.js doesn't have function " + func_name);
+    }
+  };
+
+  Controller.prototype.trigger = function(name, data) {
+    var alias, event_name;
+    if (data == null) {
+      data = [];
+    }
+    data.push(this);
+    alias = this.get_opt('alias');
+    event_name = alias ? "" + name + "-" + alias + ".atwho" : "" + name + ".atwho";
+    return this.$inputor.trigger(event_name, data);
+  };
+
+  Controller.prototype.callbacks = function(func_name) {
+    return this.get_opt("callbacks")[func_name] || DEFAULT_CALLBACKS[func_name];
+  };
+
+  Controller.prototype.get_opt = function(at, default_value) {
+    var e;
+    try {
+      return this.setting[at];
+    } catch (_error) {
+      e = _error;
+      return null;
+    }
+  };
+
+  Controller.prototype.content = function() {
+    var range;
+    if (this.$inputor.is('textarea, input')) {
+      return this.$inputor.val();
+    } else {
+      if (!(range = this.mark_range())) {
+        return;
+      }
+      return (range.startContainer.textContent || "").slice(0, range.startOffset);
+    }
+  };
+
+  Controller.prototype.catch_query = function() {
+    var caret_pos, content, end, query, start, subtext;
+    content = this.content();
+    caret_pos = this.$inputor.caret('pos', {
+      iframe: this.app.iframe
+    });
+    subtext = content.slice(0, caret_pos);
+    query = this.callbacks("matcher").call(this, this.at, subtext, this.get_opt('start_with_space'));
+    if (typeof query === "string" && query.length <= this.get_opt('max_len', 20)) {
+      start = caret_pos - query.length;
+      end = start + query.length;
+      this.pos = start;
+      query = {
+        'text': query,
+        'head_pos': start,
+        'end_pos': end
+      };
+      this.trigger("matched", [this.at, query.text]);
+    } else {
+      query = null;
+      this.view.hide();
+    }
+    return this.query = query;
+  };
+
+  Controller.prototype.rect = function() {
+    var c, iframe_offset, scale_bottom;
+    if (!(c = this.$inputor.caret('offset', this.pos - 1, {
+      iframe: this.app.iframe
+    }))) {
+      return;
+    }
+    if (this.app.iframe && !this.app.iframeStandalone) {
+      iframe_offset = $(this.app.iframe).offset();
+      c.left += iframe_offset.left;
+      c.top += iframe_offset.top;
+    }
+    if (this.$inputor.is('[contentEditable]')) {
+      c = this.cur_rect || (this.cur_rect = c);
+    }
+    scale_bottom = this.app.document.selection ? 0 : 2;
+    return {
+      left: c.left,
+      top: c.top,
+      bottom: c.top + c.height + scale_bottom
+    };
+  };
+
+  Controller.prototype.reset_rect = function() {
+    if (this.$inputor.is('[contentEditable]')) {
+      return this.cur_rect = null;
+    }
+  };
+
+  Controller.prototype.mark_range = function() {
+    var sel;
+    if (!this.$inputor.is('[contentEditable]')) {
+      return;
+    }
+    if (this.app.window.getSelection && (sel = this.app.window.getSelection()).rangeCount > 0) {
+      return this.range = sel.getRangeAt(0);
+    } else if (this.app.document.selection) {
+      return this.ie8_range = this.app.document.selection.createRange();
+    }
+  };
+
+  Controller.prototype.insert_content_for = function($li) {
+    var data, data_value, tpl;
+    data_value = $li.data('value');
+    tpl = this.get_opt('insert_tpl');
+    if (this.$inputor.is('textarea, input') || !tpl) {
+      return data_value;
+    }
+    data = $.extend({}, $li.data('item-data'), {
+      'atwho-data-value': data_value,
+      'atwho-at': this.at
+    });
+    return this.callbacks("tpl_eval").call(this, tpl, data);
+  };
+
+  Controller.prototype.insert = function(content, $li) {
+    var $inputor, node, pos, range, sel, source, start_str, text, wrapped_contents, _i, _len, _ref;
+    $inputor = this.$inputor;
+    wrapped_contents = this.callbacks('inserting_wrapper').call(this, $inputor, content, this.get_opt("suffix"));
+    if ($inputor.is('textarea, input')) {
+      source = $inputor.val();
+      start_str = source.slice(0, Math.max(this.query.head_pos - this.at.length, 0));
+      text = "" + start_str + wrapped_contents + (source.slice(this.query['end_pos'] || 0));
+      $inputor.val(text);
+      $inputor.caret('pos', start_str.length + wrapped_contents.length, {
+        iframe: this.app.iframe
+      });
+    } else if (range = this.range) {
+      pos = range.startOffset - (this.query.end_pos - this.query.head_pos) - this.at.length;
+      range.setStart(range.endContainer, Math.max(pos, 0));
+      range.setEnd(range.endContainer, range.endOffset);
+      range.deleteContents();
+      _ref = $(wrapped_contents, this.app.document);
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        node = _ref[_i];
+        range.insertNode(node);
+        range.setEndAfter(node);
+        range.collapse(false);
+      }
+      sel = this.app.window.getSelection();
+      sel.removeAllRanges();
+      sel.addRange(range);
+    } else if (range = this.ie8_range) {
+      range.moveStart('character', this.query.end_pos - this.query.head_pos - this.at.length);
+      range.pasteHTML(wrapped_contents);
+      range.collapse(false);
+      range.select();
+    }
+    if (!$inputor.is(':focus')) {
+      $inputor.focus();
+    }
+    return $inputor.change();
+  };
+
+  Controller.prototype.render_view = function(data) {
+    var search_key;
+    search_key = this.get_opt("search_key");
+    data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), search_key);
+    return this.view.render(data.slice(0, this.get_opt('limit')));
+  };
+
+  Controller.prototype.look_up = function() {
+    var query, _callback;
+    if (!(query = this.catch_query())) {
+      return;
+    }
+    _callback = function(data) {
+      if (data && data.length > 0) {
+        return this.render_view(data);
+      } else {
+        return this.view.hide();
+      }
+    };
+    this.model.query(query.text, $.proxy(_callback, this));
+    return query;
+  };
+
+  return Controller;
+
+})();
+
+Model = (function() {
+  function Model(context) {
+    this.context = context;
+    this.at = this.context.at;
+    this.storage = this.context.$inputor;
+  }
+
+  Model.prototype.destroy = function() {
+    return this.storage.data(this.at, null);
+  };
+
+  Model.prototype.saved = function() {
+    return this.fetch() > 0;
+  };
+
+  Model.prototype.query = function(query, callback) {
+    var data, search_key, _remote_filter;
+    data = this.fetch();
+    search_key = this.context.get_opt("search_key");
+    data = this.context.callbacks('filter').call(this.context, query, data, search_key) || [];
+    _remote_filter = this.context.callbacks('remote_filter');
+    if (data.length > 0 || (!_remote_filter && data.length === 0)) {
+      return callback(data);
+    } else {
+      return _remote_filter.call(this.context, query, callback);
+    }
+  };
+
+  Model.prototype.fetch = function() {
+    return this.storage.data(this.at) || [];
+  };
+
+  Model.prototype.save = function(data) {
+    return this.storage.data(this.at, this.context.callbacks("before_save").call(this.context, data || []));
+  };
+
+  Model.prototype.load = function(data) {
+    if (!(this.saved() || !data)) {
+      return this._load(data);
+    }
+  };
+
+  Model.prototype.reload = function(data) {
+    return this._load(data);
+  };
+
+  Model.prototype._load = function(data) {
+    if (typeof data === "string") {
+      return $.ajax(data, {
+        dataType: "json"
+      }).done((function(_this) {
+        return function(data) {
+          return _this.save(data);
+        };
+      })(this));
+    } else {
+      return this.save(data);
+    }
+  };
+
+  return Model;
+
+})();
+
+View = (function() {
+  function View(context) {
+    this.context = context;
+    this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>");
+    this.timeout_id = null;
+    this.context.$el.append(this.$el);
+    this.bind_event();
+  }
+
+  View.prototype.init = function() {
+    var id;
+    id = this.context.get_opt("alias") || this.context.at.charCodeAt(0);
+    return this.$el.attr({
+      'id': "at-view-" + id
+    });
+  };
+
+  View.prototype.destroy = function() {
+    return this.$el.remove();
+  };
+
+  View.prototype.bind_event = function() {
+    var $menu;
+    $menu = this.$el.find('ul');
+    return $menu.on('mouseenter.atwho-view', 'li', function(e) {
+      $menu.find('.cur').removeClass('cur');
+      return $(e.currentTarget).addClass('cur');
+    }).on('click.atwho-view', 'li', (function(_this) {
+      return function(e) {
+        $menu.find('.cur').removeClass('cur');
+        $(e.currentTarget).addClass('cur');
+        _this.choose(e);
+        return e.preventDefault();
+      };
+    })(this));
+  };
+
+  View.prototype.visible = function() {
+    return this.$el.is(":visible");
+  };
+
+  View.prototype.choose = function(e) {
+    var $li, content;
+    if (($li = this.$el.find(".cur")).length) {
+      content = this.context.insert_content_for($li);
+      this.context.insert(this.context.callbacks("before_insert").call(this.context, content, $li), $li);
+      this.context.trigger("inserted", [$li, e]);
+      this.hide(e);
+    }
+    if (this.context.get_opt("hide_without_suffix")) {
+      return this.stop_showing = true;
+    }
+  };
+
+  View.prototype.reposition = function(rect) {
+    var offset, overflowOffset, _ref, _window;
+    _window = this.context.app.iframeStandalone ? this.context.app.window : window;
+    if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) {
+      rect.bottom = rect.top - this.$el.height();
+    }
+    if (rect.left > (overflowOffset = $(_window).width() - this.$el.width() - 5)) {
+      rect.left = overflowOffset;
+    }
+    offset = {
+      left: rect.left,
+      top: rect.bottom
+    };
+    if ((_ref = this.context.callbacks("before_reposition")) != null) {
+      _ref.call(this.context, offset);
+    }
+    this.$el.offset(offset);
+    return this.context.trigger("reposition", [offset]);
+  };
+
+  View.prototype.next = function() {
+    var cur, next;
+    cur = this.$el.find('.cur').removeClass('cur');
+    next = cur.next();
+    if (!next.length) {
+      next = this.$el.find('li:first');
+    }
+    next.addClass('cur');
+    return this.$el.animate({
+      scrollTop: Math.max(0, cur.innerHeight() * (next.index() + 2) - this.$el.height())
+    }, 150);
+  };
+
+  View.prototype.prev = function() {
+    var cur, prev;
+    cur = this.$el.find('.cur').removeClass('cur');
+    prev = cur.prev();
+    if (!prev.length) {
+      prev = this.$el.find('li:last');
+    }
+    prev.addClass('cur');
+    return this.$el.animate({
+      scrollTop: Math.max(0, cur.innerHeight() * (prev.index() + 2) - this.$el.height())
+    }, 150);
+  };
+
+  View.prototype.show = function() {
+    var rect;
+    if (this.stop_showing) {
+      this.stop_showing = false;
+      return;
+    }
+    this.context.mark_range();
+    if (!this.visible()) {
+      this.$el.show();
+      this.$el.scrollTop(0);
+      this.context.trigger('shown');
+    }
+    if (rect = this.context.rect()) {
+      return this.reposition(rect);
+    }
+  };
+
+  View.prototype.hide = function(e, time) {
+    var callback;
+    if (!this.visible()) {
+      return;
+    }
+    if (isNaN(time)) {
+      this.context.reset_rect();
+      this.$el.hide();
+      return this.context.trigger('hidden', [e]);
+    } else {
+      callback = (function(_this) {
+        return function() {
+          return _this.hide();
+        };
+      })(this);
+      clearTimeout(this.timeout_id);
+      return this.timeout_id = setTimeout(callback, time);
+    }
+  };
+
+  View.prototype.render = function(list) {
+    var $li, $ul, item, li, tpl, _i, _len;
+    if (!($.isArray(list) && list.length > 0)) {
+      this.hide();
+      return;
+    }
+    this.$el.find('ul').empty();
+    $ul = this.$el.find('ul');
+    tpl = this.context.get_opt('tpl');
+    for (_i = 0, _len = list.length; _i < _len; _i++) {
+      item = list[_i];
+      item = $.extend({}, item, {
+        'atwho-at': this.context.at
+      });
+      li = this.context.callbacks("tpl_eval").call(this.context, tpl, item);
+      $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text));
+      $li.data("item-data", item);
+      $ul.append($li);
+    }
+    this.show();
+    if (this.context.get_opt('highlight_first')) {
+      return $ul.find("li:first").addClass("cur");
+    }
+  };
+
+  return View;
+
+})();
+
+KEY_CODE = {
+  DOWN: 40,
+  UP: 38,
+  ESC: 27,
+  TAB: 9,
+  ENTER: 13,
+  CTRL: 17,
+  P: 80,
+  N: 78
+};
+
+DEFAULT_CALLBACKS = {
+  before_save: function(data) {
+    var item, _i, _len, _results;
+    if (!$.isArray(data)) {
+      return data;
+    }
+    _results = [];
+    for (_i = 0, _len = data.length; _i < _len; _i++) {
+      item = data[_i];
+      if ($.isPlainObject(item)) {
+        _results.push(item);
+      } else {
+        _results.push({
+          name: item
+        });
+      }
+    }
+    return _results;
+  },
+  matcher: function(flag, subtext, should_start_with_space) {
+    var match, regexp, _a, _y;
+    flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+    if (should_start_with_space) {
+      flag = '(?:^|\\s)' + flag;
+    }
+    _a = decodeURI("%C3%80");
+    _y = decodeURI("%C3%BF");
+    regexp = new RegExp("" + flag + "([A-Za-z" + _a + "-" + _y + "0-9_\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
+    match = regexp.exec(subtext);
+    if (match) {
+      return match[2] || match[1];
+    } else {
+      return null;
+    }
+  },
+  filter: function(query, data, search_key) {
+    var item, _i, _len, _results;
+    _results = [];
+    for (_i = 0, _len = data.length; _i < _len; _i++) {
+      item = data[_i];
+      if (~new String(item[search_key]).toLowerCase().indexOf(query.toLowerCase())) {
+        _results.push(item);
+      }
+    }
+    return _results;
+  },
+  remote_filter: null,
+  sorter: function(query, items, search_key) {
+    var item, _i, _len, _results;
+    if (!query) {
+      return items;
+    }
+    _results = [];
+    for (_i = 0, _len = items.length; _i < _len; _i++) {
+      item = items[_i];
+      item.atwho_order = new String(item[search_key]).toLowerCase().indexOf(query.toLowerCase());
+      if (item.atwho_order > -1) {
+        _results.push(item);
+      }
+    }
+    return _results.sort(function(a, b) {
+      return a.atwho_order - b.atwho_order;
+    });
+  },
+  tpl_eval: function(tpl, map) {
+    var error;
+    try {
+      return tpl.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
+        return map[key];
+      });
+    } catch (_error) {
+      error = _error;
+      return "";
+    }
+  },
+  highlighter: function(li, query) {
+    var regexp;
+    if (!query) {
+      return li;
+    }
+    regexp = new RegExp(">\\s*(\\w*?)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig');
+    return li.replace(regexp, function(str, $1, $2, $3) {
+      return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
+    });
+  },
+  before_insert: function(value, $li) {
+    return value;
+  },
+  inserting_wrapper: function($inputor, content, suffix) {
+    var wrapped_content;
+    suffix = suffix === "" ? suffix : suffix || " ";
+    if ($inputor.is('textarea, input')) {
+      return '' + content + suffix;
+    } else if ($inputor.attr('contentEditable') === 'true') {
+      suffix = suffix === " " ? "&nbsp;" : suffix;
+      if (/firefox/i.test(navigator.userAgent)) {
+        wrapped_content = "<span>" + content + suffix + "</span>";
+      } else {
+        suffix = "<span contenteditable='false'>" + suffix + "</span>";
+        wrapped_content = "<span contenteditable='false'>" + content + suffix + "</span>";
+      }
+      if (this.app.document.selection) {
+        wrapped_content = "<span contenteditable='true'>" + content + "</span>";
+      }
+      return wrapped_content + "<span></span>";
+    }
+  }
+};
+
+Api = {
+  load: function(at, data) {
+    var c;
+    if (c = this.controller(at)) {
+      return c.model.load(data);
+    }
+  },
+  setIframe: function(iframe, standalone) {
+    this.setIframe(iframe, standalone);
+    return null;
+  },
+  run: function() {
+    return this.dispatch();
+  },
+  destroy: function() {
+    this.shutdown();
+    return this.$inputor.data('atwho', null);
+  }
+};
+
+$.fn.atwho = function(method) {
+  var result, _args;
+  _args = arguments;
+  result = null;
+  this.filter('textarea, input, [contenteditable=""], [contenteditable=true]').each(function() {
+    var $this, app;
+    if (!(app = ($this = $(this)).data("atwho"))) {
+      $this.data('atwho', (app = new App(this)));
+    }
+    if (typeof method === 'object' || !method) {
+      return app.reg(method.at, method);
+    } else if (Api[method] && app) {
+      return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));
+    } else {
+      return $.error("Method " + method + " does not exist on jQuery.caret");
+    }
+  });
+  return result || this;
+};
+
+$.fn.atwho["default"] = {
+  at: void 0,
+  alias: void 0,
+  data: null,
+  tpl: "<li data-value='${atwho-at}${name}'>${name}</li>",
+  insert_tpl: "<span id='${id}'>${atwho-data-value}</span>",
+  callbacks: DEFAULT_CALLBACKS,
+  search_key: "name",
+  suffix: void 0,
+  hide_without_suffix: false,
+  start_with_space: true,
+  highlight_first: true,
+  limit: 5,
+  max_len: 20,
+  display_timeout: 300,
+  delay: null
+};
+
+
+
+}));
Index: /tags/2.4.3/src/bp-core/js/jquery.atwho.txt
===================================================================
--- /tags/2.4.3/src/bp-core/js/jquery.atwho.txt	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/jquery.atwho.txt	(revision 10410)
@@ -0,0 +1,22 @@
+Copyright (c) 2013 chord.luo@gmail.com
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
Index: /tags/2.4.3/src/bp-core/js/jquery.caret.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/jquery.caret.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/jquery.caret.js	(revision 10410)
@@ -0,0 +1,405 @@
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(["jquery"], function ($) {
+      return (root.returnExportsGlobal = factory($));
+    });
+  } else if (typeof exports === 'object') {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like enviroments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"));
+  } else {
+    factory(jQuery);
+  }
+}(this, function ($) {
+
+/*
+  Implement Github like autocomplete mentions
+  http://ichord.github.com/At.js
+
+  Copyright (c) 2013 chord.luo@gmail.com
+  Licensed under the MIT license.
+*/
+
+/*
+本插件操作 textarea 或者 input 内的插入符
+只实现了获得插入符在文本框中的位置，我设置
+插入符的位置.
+*/
+
+"use strict";
+var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy;
+
+pluginName = 'caret';
+
+EditableCaret = (function() {
+  function EditableCaret($inputor) {
+    this.$inputor = $inputor;
+    this.domInputor = this.$inputor[0];
+  }
+
+  EditableCaret.prototype.setPos = function(pos) {
+    return this.domInputor;
+  };
+
+  EditableCaret.prototype.getIEPosition = function() {
+    return this.getPosition();
+  };
+
+  EditableCaret.prototype.getPosition = function() {
+    var inputor_offset, offset;
+    offset = this.getOffset();
+    inputor_offset = this.$inputor.offset();
+    offset.left -= inputor_offset.left;
+    offset.top -= inputor_offset.top;
+    return offset;
+  };
+
+  EditableCaret.prototype.getOldIEPos = function() {
+    var preCaretTextRange, textRange;
+    textRange = oDocument.selection.createRange();
+    preCaretTextRange = oDocument.body.createTextRange();
+    preCaretTextRange.moveToElementText(this.domInputor);
+    preCaretTextRange.setEndPoint("EndToEnd", textRange);
+    return preCaretTextRange.text.length;
+  };
+
+  EditableCaret.prototype.getPos = function() {
+    var clonedRange, pos, range;
+    if (range = this.range()) {
+      clonedRange = range.cloneRange();
+      clonedRange.selectNodeContents(this.domInputor);
+      clonedRange.setEnd(range.endContainer, range.endOffset);
+      pos = clonedRange.toString().length;
+      clonedRange.detach();
+      return pos;
+    } else if (oDocument.selection) {
+      return this.getOldIEPos();
+    }
+  };
+
+  EditableCaret.prototype.getOldIEOffset = function() {
+    var range, rect;
+    range = oDocument.selection.createRange().duplicate();
+    range.moveStart("character", -1);
+    rect = range.getBoundingClientRect();
+    return {
+      height: rect.bottom - rect.top,
+      left: rect.left,
+      top: rect.top
+    };
+  };
+
+  EditableCaret.prototype.getOffset = function(pos) {
+    var clonedRange, offset, range, rect, shadowCaret;
+    if (oWindow.getSelection && (range = this.range())) {
+      if (range.endOffset - 1 > 0 && range.endContainer === !this.domInputor) {
+        clonedRange = range.cloneRange();
+        clonedRange.setStart(range.endContainer, range.endOffset - 1);
+        clonedRange.setEnd(range.endContainer, range.endOffset);
+        rect = clonedRange.getBoundingClientRect();
+        offset = {
+          height: rect.height,
+          left: rect.left + rect.width,
+          top: rect.top
+        };
+        clonedRange.detach();
+      }
+      if (!offset || (offset != null ? offset.height : void 0) === 0) {
+        clonedRange = range.cloneRange();
+        shadowCaret = $(oDocument.createTextNode("|"));
+        clonedRange.insertNode(shadowCaret[0]);
+        clonedRange.selectNode(shadowCaret[0]);
+        rect = clonedRange.getBoundingClientRect();
+        offset = {
+          height: rect.height,
+          left: rect.left,
+          top: rect.top
+        };
+        shadowCaret.remove();
+        clonedRange.detach();
+      }
+    } else if (oDocument.selection) {
+      offset = this.getOldIEOffset();
+    }
+    if (offset) {
+      offset.top += $(oWindow).scrollTop();
+      offset.left += $(oWindow).scrollLeft();
+    }
+    return offset;
+  };
+
+  EditableCaret.prototype.range = function() {
+    var sel;
+    if (!oWindow.getSelection) {
+      return;
+    }
+    sel = oWindow.getSelection();
+    if (sel.rangeCount > 0) {
+      return sel.getRangeAt(0);
+    } else {
+      return null;
+    }
+  };
+
+  return EditableCaret;
+
+})();
+
+InputCaret = (function() {
+  function InputCaret($inputor) {
+    this.$inputor = $inputor;
+    this.domInputor = this.$inputor[0];
+  }
+
+  InputCaret.prototype.getIEPos = function() {
+    var endRange, inputor, len, normalizedValue, pos, range, textInputRange;
+    inputor = this.domInputor;
+    range = oDocument.selection.createRange();
+    pos = 0;
+    if (range && range.parentElement() === inputor) {
+      normalizedValue = inputor.value.replace(/\r\n/g, "\n");
+      len = normalizedValue.length;
+      textInputRange = inputor.createTextRange();
+      textInputRange.moveToBookmark(range.getBookmark());
+      endRange = inputor.createTextRange();
+      endRange.collapse(false);
+      if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
+        pos = len;
+      } else {
+        pos = -textInputRange.moveStart("character", -len);
+      }
+    }
+    return pos;
+  };
+
+  InputCaret.prototype.getPos = function() {
+    if (oDocument.selection) {
+      return this.getIEPos();
+    } else {
+      return this.domInputor.selectionStart;
+    }
+  };
+
+  InputCaret.prototype.setPos = function(pos) {
+    var inputor, range;
+    inputor = this.domInputor;
+    if (oDocument.selection) {
+      range = inputor.createTextRange();
+      range.move("character", pos);
+      range.select();
+    } else if (inputor.setSelectionRange) {
+      inputor.setSelectionRange(pos, pos);
+    }
+    return inputor;
+  };
+
+  InputCaret.prototype.getIEOffset = function(pos) {
+    var h, textRange, x, y;
+    textRange = this.domInputor.createTextRange();
+    pos || (pos = this.getPos());
+    textRange.move('character', pos);
+    x = textRange.boundingLeft;
+    y = textRange.boundingTop;
+    h = textRange.boundingHeight;
+    return {
+      left: x,
+      top: y,
+      height: h
+    };
+  };
+
+  InputCaret.prototype.getOffset = function(pos) {
+    var $inputor, offset, position;
+    $inputor = this.$inputor;
+    if (oDocument.selection) {
+      offset = this.getIEOffset(pos);
+      offset.top += $(oWindow).scrollTop() + $inputor.scrollTop();
+      offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft();
+      return offset;
+    } else {
+      offset = $inputor.offset();
+      position = this.getPosition(pos);
+      return offset = {
+        left: offset.left + position.left - $inputor.scrollLeft(),
+        top: offset.top + position.top - $inputor.scrollTop(),
+        height: position.height
+      };
+    }
+  };
+
+  InputCaret.prototype.getPosition = function(pos) {
+    var $inputor, at_rect, end_range, format, html, mirror, start_range;
+    $inputor = this.$inputor;
+    format = function(value) {
+      value = value.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g, "<br/>");
+      if (/firefox/i.test(navigator.userAgent)) {
+        value = value.replace(/\s/g, '&nbsp;');
+      }
+      return value;
+    };
+    if (pos === void 0) {
+      pos = this.getPos();
+    }
+    start_range = $inputor.val().slice(0, pos);
+    end_range = $inputor.val().slice(pos);
+    html = "<span style='position: relative; display: inline;'>" + format(start_range) + "</span>";
+    html += "<span id='caret' style='position: relative; display: inline;'>|</span>";
+    html += "<span style='position: relative; display: inline;'>" + format(end_range) + "</span>";
+    mirror = new Mirror($inputor);
+    return at_rect = mirror.create(html).rect();
+  };
+
+  InputCaret.prototype.getIEPosition = function(pos) {
+    var h, inputorOffset, offset, x, y;
+    offset = this.getIEOffset(pos);
+    inputorOffset = this.$inputor.offset();
+    x = offset.left - inputorOffset.left;
+    y = offset.top - inputorOffset.top;
+    h = offset.height;
+    return {
+      left: x,
+      top: y,
+      height: h
+    };
+  };
+
+  return InputCaret;
+
+})();
+
+Mirror = (function() {
+  Mirror.prototype.css_attr = ["borderBottomWidth", "borderLeftWidth", "borderRightWidth", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle", "borderTopWidth", "boxSizing", "fontFamily", "fontSize", "fontWeight", "height", "letterSpacing", "lineHeight", "marginBottom", "marginLeft", "marginRight", "marginTop", "outlineWidth", "overflow", "overflowX", "overflowY", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", "textAlign", "textOverflow", "textTransform", "whiteSpace", "wordBreak", "wordWrap"];
+
+  function Mirror($inputor) {
+    this.$inputor = $inputor;
+  }
+
+  Mirror.prototype.mirrorCss = function() {
+    var css,
+      _this = this;
+    css = {
+      position: 'absolute',
+      left: -9999,
+      top: 0,
+      zIndex: -20000
+    };
+    if (this.$inputor.prop('tagName') === 'TEXTAREA') {
+      this.css_attr.push('width');
+    }
+    $.each(this.css_attr, function(i, p) {
+      return css[p] = _this.$inputor.css(p);
+    });
+    return css;
+  };
+
+  Mirror.prototype.create = function(html) {
+    this.$mirror = $('<div></div>');
+    this.$mirror.css(this.mirrorCss());
+    this.$mirror.html(html);
+    this.$inputor.after(this.$mirror);
+    return this;
+  };
+
+  Mirror.prototype.rect = function() {
+    var $flag, pos, rect;
+    $flag = this.$mirror.find("#caret");
+    pos = $flag.position();
+    rect = {
+      left: pos.left,
+      top: pos.top,
+      height: $flag.height()
+    };
+    this.$mirror.remove();
+    return rect;
+  };
+
+  return Mirror;
+
+})();
+
+Utils = {
+  contentEditable: function($inputor) {
+    return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true');
+  }
+};
+
+methods = {
+  pos: function(pos) {
+    if (pos || pos === 0) {
+      return this.setPos(pos);
+    } else {
+      return this.getPos();
+    }
+  },
+  position: function(pos) {
+    if (oDocument.selection) {
+      return this.getIEPosition(pos);
+    } else {
+      return this.getPosition(pos);
+    }
+  },
+  offset: function(pos) {
+    var offset;
+    offset = this.getOffset(pos);
+    return offset;
+  }
+};
+
+oDocument = null;
+
+oWindow = null;
+
+oFrame = null;
+
+setContextBy = function(settings) {
+  var iframe;
+  if (iframe = settings != null ? settings.iframe : void 0) {
+    oFrame = iframe;
+    oWindow = iframe.contentWindow;
+    return oDocument = iframe.contentDocument || oWindow.document;
+  } else {
+    oFrame = void 0;
+    oWindow = window;
+    return oDocument = document;
+  }
+};
+
+discoveryIframeOf = function($dom) {
+  var error;
+  oDocument = $dom[0].ownerDocument;
+  oWindow = oDocument.defaultView || oDocument.parentWindow;
+  try {
+    return oFrame = oWindow.frameElement;
+  } catch (_error) {
+    error = _error;
+  }
+};
+
+$.fn.caret = function(method, value, settings) {
+  var caret;
+  if (methods[method]) {
+    if ($.isPlainObject(value)) {
+      setContextBy(value);
+      value = void 0;
+    } else {
+      setContextBy(settings);
+    }
+    caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this);
+    return methods[method].apply(caret, [value]);
+  } else {
+    return $.error("Method " + method + " does not exist on jQuery.caret");
+  }
+};
+
+$.fn.caret.EditableCaret = EditableCaret;
+
+$.fn.caret.InputCaret = InputCaret;
+
+$.fn.caret.Utils = Utils;
+
+$.fn.caret.apis = methods;
+
+
+}));
Index: /tags/2.4.3/src/bp-core/js/jquery.caret.txt
===================================================================
--- /tags/2.4.3/src/bp-core/js/jquery.caret.txt	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/jquery.caret.txt	(revision 10410)
@@ -0,0 +1,22 @@
+Copyright (c) 2013 chord.luo@gmail.com
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
Index: /tags/2.4.3/src/bp-core/js/webcam.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/webcam.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/webcam.js	(revision 10410)
@@ -0,0 +1,307 @@
+/* global bp, BP_Uploader, _, Backbone */
+
+window.bp = window.bp || {};
+
+( function() {
+
+	// Bail if not set
+	if ( typeof BP_Uploader === 'undefined' ) {
+		return;
+	}
+
+	bp.Models      = bp.Models || {};
+	bp.Collections = bp.Collections || {};
+	bp.Views       = bp.Views || {};
+
+	bp.WebCam = {
+		start: function() {
+			this.params = {
+				video:          null,
+				videoStream:    null,
+				capture_enable: false,
+				capture:        null,
+				canvas:         null,
+				warning:        null,
+				flipped:        false
+			};
+
+			bp.Avatar.nav.on( 'bp-avatar-view:changed', _.bind( this.setView, this ) );
+		},
+
+		setView: function( view ) {
+			if ( 'camera' !== view ) {
+				// Stop the camera if needed
+				if ( ! _.isNull( this.params.video ) ) {
+					this.stop();
+
+					// Remove all warnings as we're changing the view
+					this.removeWarning();
+				}
+
+				// Stop as this is not Camera area
+				return;
+			}
+
+			// Create the WebCam view
+			var cameraView = new bp.Views.WebCamAvatar( { model: new Backbone.Model( { user_media: false } ) } );
+
+			// Make sure the flipped param is reset
+			this.params.flipped = false;
+
+			// Add it to views
+			bp.Avatar.views.add( { id: 'camera', view: cameraView } );
+
+			// Display it
+	        cameraView.inject( '.bp-avatar' );
+		},
+
+		removeView: function() {
+			var camera;
+
+			if ( ! _.isUndefined( bp.Avatar.views.get( 'camera' ) ) ) {
+				camera = bp.Avatar.views.get( 'camera' );
+				camera.get( 'view' ).remove();
+				bp.Avatar.views.remove( { id: 'camera', view: camera } );
+			}
+		},
+
+		gotStream: function( stream ) {
+			var video = bp.WebCam.params.video;
+			bp.WebCam.params.videoStream = stream;
+
+			// User Feedback
+			bp.WebCam.displayWarning( 'loaded' );
+
+			video.onerror = function () {
+				// User Feedback
+				bp.WebCam.displayWarning( 'videoerror' );
+
+				if ( video ) {
+					bp.WebCam.stop();
+				}
+			};
+
+			stream.onended = bp.WebCam.noStream();
+
+			if ( video.mozSrcObject !== undefined ) {
+				video.mozSrcObject = stream;
+				video.play();
+			} else if ( navigator.mozGetUserMedia ) {
+				video.src = stream;
+				video.play();
+			} else if ( window.URL ) {
+				video.src = window.URL.createObjectURL( stream );
+			} else {
+				video.src = stream;
+			}
+
+			bp.WebCam.params.capture_enable = true;
+		},
+
+		stop: function() {
+			bp.WebCam.params.capture_enable = false;
+			if ( bp.WebCam.params.videoStream ) {
+				if ( bp.WebCam.params.videoStream.stop ) {
+					bp.WebCam.params.videoStream.stop();
+				} else if ( bp.WebCam.params.videoStream.msStop ) {
+					bp.WebCam.params.videoStream.msStop();
+				}
+				bp.WebCam.params.videoStream.onended = null;
+				bp.WebCam.params.videoStream = null;
+			}
+			if ( bp.WebCam.params.video ) {
+				bp.WebCam.params.video.onerror = null;
+				bp.WebCam.params.video.pause();
+				if ( bp.WebCam.params.video.mozSrcObject ) {
+					bp.WebCam.params.video.mozSrcObject = null;
+				}
+				bp.WebCam.params.video.src = '';
+			}
+		},
+
+		noStream: function() {
+			if ( _.isNull( bp.WebCam.params.videoStream ) ) {
+				// User Feedback
+				bp.WebCam.displayWarning( 'noaccess' );
+
+				bp.WebCam.removeView();
+			}
+		},
+
+		setAvatar: function( avatar ) {
+			if ( ! avatar.get( 'url' ) ) {
+				bp.WebCam.displayWarning( 'nocapture' );
+			}
+
+			// Remove the view
+			bp.WebCam.removeView();
+
+			bp.Avatar.setAvatar( avatar );
+		},
+
+		removeWarning: function() {
+			if ( ! _.isNull( this.params.warning ) ) {
+				this.params.warning.remove();
+			}
+		},
+
+		displayWarning: function( code ) {
+			this.removeWarning();
+
+			this.params.warning = new bp.Views.uploaderWarning( {
+				value: BP_Uploader.strings.camera_warnings[code]
+			} );
+
+			this.params.warning.inject( '.bp-avatar-status' );
+		}
+	};
+
+	// BuddyPress WebCam view
+	bp.Views.WebCamAvatar = bp.View.extend( {
+		tagName: 'div',
+		id: 'bp-webcam-avatar',
+		template: bp.template( 'bp-avatar-webcam' ),
+
+		events: {
+			'click .avatar-webcam-capture': 'captureStream',
+			'click .avatar-webcam-save': 'saveCapture'
+		},
+
+		initialize: function() {
+			var params;
+
+			if ( navigator.getUserMedia || navigator.oGetUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia ) {
+
+				// We need to add some cropping stuff to use bp.Avatar.setAvatar()
+				params = _.extend( _.pick( BP_Uploader.settings.defaults.multipart_params.bp_params,
+					'object',
+					'item_id',
+					'nonces'
+					), {
+						user_media:  true,
+						w: BP_Uploader.settings.crop.full_w,
+						h: BP_Uploader.settings.crop.full_h,
+						x: 0,
+						y: 0,
+						type: 'camera'
+					}
+				);
+
+				this.model.set( params );
+			}
+
+			this.on( 'ready', this.useStream, this );
+		},
+
+		useStream:function() {
+			// No support for user media... Stop!
+			if ( ! this.model.get( 'user_media' ) ) {
+				return;
+			}
+
+			this.options.video = new bp.Views.WebCamVideo();
+			this.options.canvas = new bp.Views.WebCamCanvas();
+
+			this.$el.find( '#avatar-to-crop' ).append( this.options.video.el );
+			this.$el.find( '#avatar-crop-pane' ).append( this.options.canvas.el );
+
+			bp.WebCam.params.video = this.options.video.el;
+			bp.WebCam.params.canvas = this.options.canvas.el;
+
+			// User Feedback
+			bp.WebCam.displayWarning( 'requesting' );
+
+			if ( navigator.getUserMedia ) {
+				navigator.getUserMedia( { video:true }, bp.WebCam.gotStream, bp.WebCams.noStream );
+			}  else if ( navigator.oGetUserMedia ) {
+				navigator.oGetUserMedia( { video:true }, bp.WebCam.gotStream, bp.WebCam.noStream );
+			} else if ( navigator.mozGetUserMedia ) {
+				navigator.mozGetUserMedia( { video:true }, bp.WebCam.gotStream, bp.WebCam.noStream );
+			} else if ( navigator.webkitGetUserMedia ) {
+				navigator.webkitGetUserMedia( { video:true }, bp.WebCam.gotStream, bp.WebCam.noStream );
+			} else if (navigator.msGetUserMedia) {
+				navigator.msGetUserMedia( { video:true, audio:false }, bp.WebCams.gotStream, bp.WebCam.noStream );
+			} else {
+				// User Feedback
+				bp.WebCam.displayWarning( 'errormsg' );
+			}
+		},
+
+		captureStream: function( event ) {
+			var sx, sc;
+			event.preventDefault();
+
+			if ( ! bp.WebCam.params.capture_enable ) {
+				// User Feedback
+				bp.WebCam.displayWarning( 'loading' );
+				return;
+			}
+
+			if ( this.model.get( 'h' ) > this.options.video.el.videoHeight || this.model.get( 'w' ) > this.options.video.el.videoWidth ) {
+				bp.WebCam.displayWarning( 'videoerror' );
+				return;
+			}
+
+			// Set the offset
+			sc = this.options.video.el.videoHeight;
+			sx = ( this.options.video.el.videoWidth - sc ) / 2;
+
+			// Flip only once.
+			if ( ! bp.WebCam.params.flipped ) {
+				this.options.canvas.el.getContext( '2d' ).translate( this.model.get( 'w' ), 0 );
+				this.options.canvas.el.getContext( '2d' ).scale( -1, 1 );
+				bp.WebCam.params.flipped = true;
+			}
+
+			this.options.canvas.el.getContext( '2d' ).drawImage( this.options.video.el, sx, 0, sc, sc, 0, 0, this.model.get( 'w' ), this.model.get( 'h' ) );
+			bp.WebCam.params.capture = this.options.canvas.el.toDataURL( 'image/png' );
+			this.model.set( 'url', bp.WebCam.params.capture );
+
+			// User Feedback
+			bp.WebCam.displayWarning( 'ready' );
+		},
+
+		saveCapture: function( event ) {
+			event.preventDefault();
+
+			if ( ! bp.WebCam.params.capture ) {
+				// User Feedback
+				bp.WebCam.displayWarning( 'nocapture' );
+				return;
+			}
+
+			bp.WebCam.stop();
+			bp.WebCam.setAvatar( this.model );
+		}
+	} );
+
+	// BuddyPress Video stream view
+	bp.Views.WebCamVideo = bp.View.extend( {
+		tagName: 'video',
+		id: 'bp-webcam-video',
+		attributes: {
+			autoplay: 'autoplay'
+		}
+	} );
+
+	// BuddyPress Canvas (capture) view
+	bp.Views.WebCamCanvas = bp.View.extend( {
+		tagName: 'canvas',
+		id: 'bp-webcam-canvas',
+		attributes: {
+			width:  150,
+			height: 150
+		},
+
+		initialize: function() {
+			// Make sure to take in account bp_core_avatar_full_height or bp_core_avatar_full_width php filters
+			if ( ! _.isUndefined( BP_Uploader.settings.crop.full_h ) && ! _.isUndefined( BP_Uploader.settings.crop.full_w ) ) {
+				this.el.attributes.width.value  = BP_Uploader.settings.crop.full_w;
+				this.el.attributes.height.value = BP_Uploader.settings.crop.full_h;
+			}
+		}
+	} );
+
+	bp.WebCam.start();
+
+})( bp, jQuery );
Index: /tags/2.4.3/src/bp-core/js/widget-members.js
===================================================================
--- /tags/2.4.3/src/bp-core/js/widget-members.js	(revision 10410)
+++ /tags/2.4.3/src/bp-core/js/widget-members.js	(revision 10410)
@@ -0,0 +1,49 @@
+jQuery(document).ready( function() {
+	jQuery('.widget div#members-list-options a').on('click',
+		function() {
+			var link = this;
+			jQuery(link).addClass('loading');
+
+			jQuery('.widget div#members-list-options a').removeClass('selected');
+			jQuery(this).addClass('selected');
+
+			jQuery.post( ajaxurl, {
+				action: 'widget_members',
+				'cookie': encodeURIComponent(document.cookie),
+				'_wpnonce': jQuery('input#_wpnonce-members').val(),
+				'max-members': jQuery('input#members_widget_max').val(),
+				'filter': jQuery(this).attr('id')
+			},
+			function(response)
+			{
+				jQuery(link).removeClass('loading');
+				member_widget_response(response);
+			});
+
+			return false;
+		}
+	);
+});
+
+function member_widget_response(response) {
+	response = response.substr(0, response.length-1);
+	response = response.split('[[SPLIT]]');
+
+	if ( response[0] !== '-1' ) {
+		jQuery('.widget ul#members-list').fadeOut(200,
+			function() {
+				jQuery('.widget ul#members-list').html(response[1]);
+				jQuery('.widget ul#members-list').fadeIn(200);
+			}
+		);
+
+	} else {
+		jQuery('.widget ul#members-list').fadeOut(200,
+			function() {
+				var message = '<p>' + response[1] + '</p>';
+				jQuery('.widget ul#members-list').html(message);
+				jQuery('.widget ul#members-list').fadeIn(200);
+			}
+		);
+	}
+}
Index: /tags/2.4.3/src/bp-forums/bb-config.php
===================================================================
--- /tags/2.4.3/src/bp-forums/bb-config.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/bb-config.php	(revision 10410)
@@ -0,0 +1,13 @@
+<?php
+/**
+ * *** IMPORTANT ****
+ * This file will stop people from accessing your bbPress installation directly.
+ * It is very important from a security standpoint that this file is not moved.
+ * Your actual bb-config.php file will be installed in the root of your WordPress
+ * installation once you have set up the forums component in BuddyPress.
+ *
+ * @package BuddyPress
+ * @subpackage ForumsbbPressConfig
+ */
+
+header("HTTP/1.0 403 Forbidden"); die;
Index: /tags/2.4.3/src/bp-forums/bp-forums-actions.php
===================================================================
--- /tags/2.4.3/src/bp-forums/bp-forums-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/bp-forums-actions.php	(revision 10410)
@@ -0,0 +1,10 @@
+<?php
+/**
+ * BuddyPress Forums Actions.
+ *
+ * @package BuddyPress
+ * @subpackage ForumsActions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
Index: /tags/2.4.3/src/bp-forums/bp-forums-bbpress-sa.php
===================================================================
--- /tags/2.4.3/src/bp-forums/bp-forums-bbpress-sa.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/bp-forums-bbpress-sa.php	(revision 10410)
@@ -0,0 +1,520 @@
+<?php
+/**
+ * BuddyPress bbPress 1.x integration.
+ *
+ * @package BuddyPress
+ * @subpackage ForumsbbPress
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Bootstrap bbPress 1.x, and manipulate globals to integrate with BuddyPress.
+ *
+ * @return bool|null Returns false on failure.
+ */
+function bp_forums_load_bbpress() {
+	global $wpdb, $wp_roles, $current_user, $wp_users_object;
+	global $bb, $bbdb, $bb_table_prefix, $bb_current_user;
+	global $bb_roles, $wp_taxonomy_object, $bb_queries;
+
+	// Return if we've already run this function.
+	if ( is_object( $bbdb ) )
+		return;
+
+	if ( !bp_forums_is_installed_correctly() )
+		return false;
+
+	$bp = buddypress();
+
+	define( 'BB_PATH',        $bp->plugin_dir . '/bp-forums/bbpress/' );
+	define( 'BACKPRESS_PATH', $bp->plugin_dir . '/bp-forums/bbpress/bb-includes/backpress/' );
+	define( 'BB_URL',         $bp->plugin_url . 'bp-forums/bbpress/' );
+	define( 'BB_INC', 'bb-includes/' );
+
+	require( BB_PATH . BB_INC . 'class.bb-query.php' );
+	require( BB_PATH . BB_INC . 'class.bb-walker.php' );
+
+	require( BB_PATH . BB_INC . 'functions.bb-core.php' );
+	require( BB_PATH . BB_INC . 'functions.bb-forums.php' );
+	require( BB_PATH . BB_INC . 'functions.bb-topics.php' );
+	require( BB_PATH . BB_INC . 'functions.bb-posts.php' );
+	require( BB_PATH . BB_INC . 'functions.bb-topic-tags.php' );
+	require( BB_PATH . BB_INC . 'functions.bb-capabilities.php' );
+	require( BB_PATH . BB_INC . 'functions.bb-meta.php' );
+	require( BB_PATH . BB_INC . 'functions.bb-pluggable.php' );
+	require( BB_PATH . BB_INC . 'functions.bb-formatting.php' );
+	require( BB_PATH . BB_INC . 'functions.bb-template.php' );
+
+	require( BACKPRESS_PATH . 'class.wp-taxonomy.php' );
+	require( BB_PATH . BB_INC . 'class.bb-taxonomy.php' );
+
+	require( BB_PATH . 'bb-admin/includes/functions.bb-admin.php' );
+
+	$bb = new stdClass();
+	require( bp_get_option(	'bb-config-location' ) );
+
+	// Setup the global database connection.
+	$bbdb = new BPDB ( BBDB_USER, BBDB_PASSWORD, BBDB_NAME, BBDB_HOST );
+
+	// Set the table names.
+	$bbdb->forums             = $bb_table_prefix . 'forums';
+	$bbdb->meta               = $bb_table_prefix . 'meta';
+	$bbdb->posts              = $bb_table_prefix . 'posts';
+	$bbdb->terms              = $bb_table_prefix . 'terms';
+	$bbdb->term_relationships = $bb_table_prefix . 'term_relationships';
+	$bbdb->term_taxonomy      = $bb_table_prefix . 'term_taxonomy';
+	$bbdb->topics             = $bb_table_prefix . 'topics';
+
+	if ( isset( $bb->custom_user_table ) )
+		$bbdb->users = $bb->custom_user_table;
+	else
+		$bbdb->users = $wpdb->users;
+
+	if ( isset( $bb->custom_user_meta_table ) )
+		$bbdb->usermeta = $bb->custom_user_meta_table;
+	else
+		$bbdb->usermeta = $wpdb->usermeta;
+
+	$bbdb->prefix = $bb_table_prefix;
+
+	define( 'BB_INSTALLING', false );
+
+	if ( is_object( $wp_roles ) ) {
+		$bb_roles = $wp_roles;
+		bb_init_roles( $bb_roles );
+	}
+
+	/**
+	 * Fires during the bootstrap setup for bbPress 1.x.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bb_got_roles' );
+
+	/**
+	 * Fires during the bootstrap setup for bbPress 1.x.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bb_init'      );
+
+	/**
+	 * Fires during the bootstrap setup for bbPress 1.x.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'init_roles'   );
+
+	$bb_current_user = $current_user;
+	$wp_users_object = new BP_Forums_BB_Auth;
+
+	if ( !isset( $wp_taxonomy_object ) )
+		$wp_taxonomy_object = new BB_Taxonomy( $bbdb );
+
+	$wp_taxonomy_object->register_taxonomy( 'bb_topic_tag', 'bb_topic' );
+
+	// Set a site id if there isn't one already.
+	if ( !isset( $bb->site_id ) )
+		$bb->site_id = bp_get_root_blog_id();
+
+	// Check if the tables are installed, if not, install them.
+	if ( !$tables_installed = (boolean) $bbdb->get_results( 'DESCRIBE `' . $bbdb->forums . '`;', ARRAY_A ) ) {
+		require( BB_PATH . 'bb-admin/includes/defaults.bb-schema.php' );
+
+		// Backticks and "IF NOT EXISTS" break the dbDelta function.
+		bp_bb_dbDelta( str_replace( ' IF NOT EXISTS', '', str_replace( '`', '', $bb_queries ) ) );
+
+		require( BB_PATH . 'bb-admin/includes/functions.bb-upgrade.php' );
+		bb_update_db_version();
+
+		// Set the site admins as the keymasters.
+		$site_admins = get_site_option( 'site_admins', array('admin') );
+		foreach ( (array) $site_admins as $site_admin )
+			bp_update_user_meta( bp_core_get_userid( $site_admin ), $bb_table_prefix . 'capabilities', array( 'keymaster' => true ) );
+
+		// Create the first forum.
+		bb_new_forum( array( 'forum_name' => 'Default Forum' ) );
+
+		// Set the site URI.
+		bb_update_option( 'uri', BB_URL );
+	}
+
+	/**
+	 * Fires inside an anonymous function that is run on bbPress shutdown.
+	 *
+	 * @since 1.1.0
+	 */
+	register_shutdown_function( create_function( '', 'do_action("bb_shutdown");' ) );
+}
+add_action( 'bbpress_init', 'bp_forums_load_bbpress' );
+
+/** WP to bbPress wrapper functions ******************************************/
+
+/**
+ * Get the current bbPress user.
+ *
+ * @return object $current_user Current user object from WordPress.
+ */
+function bb_get_current_user() { global $current_user; return $current_user; }
+
+/**
+ * Get userdata for a bbPress user.
+ *
+ * @param int $user_id User ID.
+ * @return object User data from WordPress.
+ */
+function bb_get_user( $user_id ) { return get_userdata( $user_id ); }
+
+/**
+ * Cache users.
+ *
+ * Noop.
+ *
+ * @param array $users Array of users.
+ */
+function bb_cache_users( $users ) {}
+
+/**
+ * The bbPress plugin needs this class for its usermeta manipulation.
+ */
+class BP_Forums_BB_Auth {
+	function update_meta( $args = '' ) {
+		$defaults = array( 'id' => 0, 'meta_key' => null, 'meta_value' => null, 'meta_table' => 'usermeta', 'meta_field' => 'user_id', 'cache_group' => 'users' );
+		$args = wp_parse_args( $args, $defaults );
+		extract( $args, EXTR_SKIP );
+
+		return bp_update_user_meta( $id, $meta_key, $meta_value );
+	}
+}
+
+/**
+ * The bbPress plugin needs the DB class to be BPDB, but we want to use WPDB, so we can extend it and use this.
+ *
+ * The class is pluggable, so that plugins that swap out WPDB with a custom
+ * database class (such as HyperDB and ShareDB) can provide their own versions
+ * of BPDB which extend the appropriate base class.
+ */
+if ( ! class_exists( 'BPDB' ) ) :
+	class BPDB extends WPDB {
+		var $db_servers = array();
+
+		/**
+		 * Constructor.
+		 *
+		 * @see WPDB::__construct() for description of parameters.
+		 */
+		function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) {
+			parent::__construct( $dbuser, $dbpassword, $dbname, $dbhost );
+
+			$args = func_get_args();
+			$args = call_user_func_array( array( &$this, 'init' ), $args );
+
+			if ( $args['host'] )
+				$this->db_servers['dbh_global'] = $args;
+		}
+
+		/**
+		 * Determine if a database supports a particular feature.
+		 *
+		 * Overridden here to work around differences between bbPress's
+		 * and WordPress's implementations. In particular, when
+		 * BuddyPress tries to run bbPress' SQL installation script,
+		 * the collation check always failed. The capability is long
+		 * supported by WordPress' minimum required MySQL version, so
+		 * this is safe.
+		 *
+		 * @see WPDB::has_cap() for a description of parameters and
+		 *      return values.
+		 *
+		 * @param string $db_cap See {@link WPDB::has_cap()}.
+		 * @param string $_table_name See {@link WPDB::has_cap()}.
+		 * @return bool See {@link WPDB::has_cap()}.
+		 */
+		function has_cap( $db_cap, $_table_name='' ) {
+			if ( 'collation' == $db_cap )
+				return true;
+
+			return parent::has_cap( $db_cap );
+		}
+
+		/**
+		 * Initialize the class variables based on provided arguments.
+		 *
+		 * Based on, and taken from, the BackPress class in turn taken
+		 * from the 1.0 branch of bbPress.
+		 *
+		 * @see BBDB::__construct() for a description of params.
+		 *
+		 * @param array $args Array of args to parse.
+		 * @return array $args.
+		 */
+		function init( $args ) {
+			if ( 4 == func_num_args() ) {
+				$args = array(
+						'user'     => $args,
+						'password' => func_get_arg( 1 ),
+						'name'     => func_get_arg( 2 ),
+						'host'     => func_get_arg( 3 ),
+						'charset'  => defined( 'BBDB_CHARSET' ) ? BBDB_CHARSET : false,
+						'collate'  => defined( 'BBDB_COLLATE' ) ? BBDB_COLLATE : false,
+					     );
+			}
+
+			$defaults = array(
+					'user'     => false,
+					'password' => false,
+					'name'     => false,
+					'host'     => 'localhost',
+					'charset'  => false,
+					'collate'  => false,
+					'errors'   => false
+					);
+
+			return wp_parse_args( $args, $defaults );
+		}
+
+		/**
+		 * Stub for escape_deep() compatibility.
+		 *
+		 * @see WPDB::escape_deep() for description of parameters and
+		 *      return values.
+		 *
+		 * @param mixed $data See {@link WPDB::escape_deep()}.
+		 * @return mixed $data See {@link WPDB::escape_deep()}.
+		 */
+		function escape_deep( $data ) {
+			return $this->escape( $data );
+		}
+	}
+endif; // End class_exists( 'BPDB' ).
+
+/**
+ * Convert object to given output format.
+ *
+ * The bbPress plugin needs this to convert vars.
+ *
+ * @param object $object Object to convert.
+ * @param string $output Type of object to return. OBJECT, ARRAY_A, or ARRAY_N.
+ */
+function backpress_convert_object( &$object, $output ) {
+	if ( is_array( $object ) ) {
+		foreach ( array_keys( $object ) as $key )
+			backpress_convert_object( $object[$key], $output );
+	} else {
+		switch ( $output ) {
+			case OBJECT  : break;
+			case ARRAY_A : $object = get_object_vars($object); break;
+			case ARRAY_N : $object = array_values(get_object_vars($object)); break;
+		}
+	}
+}
+
+/**
+ * Parse and execute queries for updating a set of database tables.
+ *
+ * Copied from wp-admin/includes/upgrade.php, this will take care of creating
+ * the bbPress stand-alone tables without loading a conflicting WP Admin.
+ *
+ * @see dbDelta() for a description of parameters and return value.
+ *
+ * @param array $queries See {@link dbDelta()}.
+ * @param bool  $execute See {@link dbDelta()}.
+ * @return array See {@link dbDelta()}.
+ */
+function bp_bb_dbDelta($queries, $execute = true) {
+	global $wpdb;
+
+	// Separate individual queries into an array.
+	if ( !is_array($queries) ) {
+		$queries = explode( ';', $queries );
+		if ('' == $queries[count($queries) - 1]) array_pop($queries);
+	}
+
+	$cqueries = array(); // Creation Queries.
+	$iqueries = array(); // Insertion Queries.
+	$for_update = array();
+
+	// Create a tablename index for an array ($cqueries) of queries.
+	foreach($queries as $qry) {
+		if (preg_match("|CREATE TABLE ([^ ]*)|", $qry, $matches)) {
+			$cqueries[trim( strtolower($matches[1]), '`' )] = $qry;
+			$for_update[$matches[1]] = 'Created table '.$matches[1];
+		} else if (preg_match("|CREATE DATABASE ([^ ]*)|", $qry, $matches)) {
+			array_unshift($cqueries, $qry);
+		} else if (preg_match("|INSERT INTO ([^ ]*)|", $qry, $matches)) {
+			$iqueries[] = $qry;
+		} else if (preg_match("|UPDATE ([^ ]*)|", $qry, $matches)) {
+			$iqueries[] = $qry;
+		} else {
+			// Unrecognized query type.
+		}
+	}
+
+	// Check to see which tables and fields exist.
+	if ($tables = $wpdb->get_col('SHOW TABLES;')) {
+		// For every table in the database.
+		foreach ($tables as $table) {
+			// Upgrade global tables only for the main site. Don't upgrade at all if DO_NOT_UPGRADE_GLOBAL_TABLES is defined.
+			if ( in_array($table, $wpdb->tables('global')) && ( !is_main_site() || defined('DO_NOT_UPGRADE_GLOBAL_TABLES') ) )
+				continue;
+
+			// If a table query exists for the database table...
+			if ( array_key_exists(strtolower($table), $cqueries) ) {
+				// Clear the field and index arrays.
+				$cfields = $indices = array();
+				// Get all of the field names in the query from between the parents.
+				preg_match("|\((.*)\)|ms", $cqueries[strtolower($table)], $match2);
+				$qryline = trim($match2[1]);
+
+				// Separate field lines into an array.
+				$flds = explode("\n", $qryline);
+
+				//echo "<hr/><pre>\n".print_r(strtolower($table), true).":\n".print_r($cqueries, true)."</pre><hr/>";
+
+				// For every field line specified in the query.
+				foreach ($flds as $fld) {
+					// Extract the field name.
+					preg_match("|^([^ ]*)|", trim($fld), $fvals);
+					$fieldname = trim( $fvals[1], '`' );
+
+					// Verify the found field name.
+					$validfield = true;
+					switch (strtolower($fieldname)) {
+					case '':
+					case 'primary':
+					case 'index':
+					case 'fulltext':
+					case 'unique':
+					case 'key':
+						$validfield = false;
+						$indices[] = trim(trim($fld), ", \n");
+						break;
+					}
+					$fld = trim($fld);
+
+					// If it's a valid field, add it to the field array.
+					if ($validfield) {
+						$cfields[strtolower($fieldname)] = trim($fld, ", \n");
+					}
+				}
+
+				// Fetch the table column structure from the database.
+				$tablefields = $wpdb->get_results("DESCRIBE {$table};");
+
+				// For every field in the table.
+				foreach ($tablefields as $tablefield) {
+					// If the table field exists in the field array...
+					if (array_key_exists(strtolower($tablefield->Field), $cfields)) {
+						// Get the field type from the query.
+						preg_match("|".$tablefield->Field." ([^ ]*( unsigned)?)|i", $cfields[strtolower($tablefield->Field)], $matches);
+						$fieldtype = $matches[1];
+
+						// Is actual field type different from the field type in query?
+						if ($tablefield->Type != $fieldtype) {
+							// Add a query to change the column type.
+							$cqueries[] = "ALTER TABLE {$table} CHANGE COLUMN {$tablefield->Field} " . $cfields[strtolower($tablefield->Field)];
+							$for_update[$table.'.'.$tablefield->Field] = "Changed type of {$table}.{$tablefield->Field} from {$tablefield->Type} to {$fieldtype}";
+						}
+
+						// Get the default value from the array.
+							//echo "{$cfields[strtolower($tablefield->Field)]}<br>";
+						if (preg_match("| DEFAULT '(.*)'|i", $cfields[strtolower($tablefield->Field)], $matches)) {
+							$default_value = $matches[1];
+							if ($tablefield->Default != $default_value) {
+								// Add a query to change the column's default value.
+								$cqueries[] = "ALTER TABLE {$table} ALTER COLUMN {$tablefield->Field} SET DEFAULT '{$default_value}'";
+								$for_update[$table.'.'.$tablefield->Field] = "Changed default value of {$table}.{$tablefield->Field} from {$tablefield->Default} to {$default_value}";
+							}
+						}
+
+						// Remove the field from the array (so it's not added).
+						unset($cfields[strtolower($tablefield->Field)]);
+					} else {
+						// This field exists in the table, but not in the creation queries?
+					}
+				}
+
+				// For every remaining field specified for the table.
+				foreach ($cfields as $fieldname => $fielddef) {
+					// Push a query line into $cqueries that adds the field to that table.
+					$cqueries[] = "ALTER TABLE {$table} ADD COLUMN $fielddef";
+					$for_update[$table.'.'.$fieldname] = 'Added column '.$table.'.'.$fieldname;
+				}
+
+				// Index stuff goes here
+				// Fetch the table index structure from the database.
+				$tableindices = $wpdb->get_results("SHOW INDEX FROM {$table};");
+
+				if ($tableindices) {
+					// Clear the index array.
+					unset($index_ary);
+
+					// For every index in the table.
+					foreach ($tableindices as $tableindex) {
+						// Add the index to the index data array.
+						$keyname = $tableindex->Key_name;
+						$index_ary[$keyname]['columns'][] = array('fieldname' => $tableindex->Column_name, 'subpart' => $tableindex->Sub_part);
+						$index_ary[$keyname]['unique'] = ($tableindex->Non_unique == 0)?true:false;
+					}
+
+					// For each actual index in the index array.
+					foreach ($index_ary as $index_name => $index_data) {
+						// Build a create string to compare to the query.
+						$index_string = '';
+						if ($index_name == 'PRIMARY') {
+							$index_string .= 'PRIMARY ';
+						} else if($index_data['unique']) {
+							$index_string .= 'UNIQUE ';
+						}
+						$index_string .= 'KEY ';
+						if ($index_name != 'PRIMARY') {
+							$index_string .= $index_name;
+						}
+						$index_columns = '';
+						// For each column in the index.
+						foreach ($index_data['columns'] as $column_data) {
+							if ($index_columns != '') $index_columns .= ',';
+							// Add the field to the column list string.
+							$index_columns .= $column_data['fieldname'];
+							if ($column_data['subpart'] != '') {
+								$index_columns .= '('.$column_data['subpart'].')';
+							}
+						}
+						// Add the column list to the index create string.
+						$index_string .= ' ('.$index_columns.')';
+						if (!(($aindex = array_search($index_string, $indices)) === false)) {
+							unset($indices[$aindex]);
+							//echo "<pre style=\"border:1px solid #ccc;margin-top:5px;\">{$table}:<br />Found index:".$index_string."</pre>\n";
+						}
+						//else echo "<pre style=\"border:1px solid #ccc;margin-top:5px;\">{$table}:<br /><b>Did not find index:</b>".$index_string."<br />".print_r($indices, true)."</pre>\n";
+					}
+				}
+
+				// For every remaining index specified for the table.
+				foreach ( (array) $indices as $index ) {
+					// Push a query line into $cqueries that adds the index to that table.
+					$cqueries[] = "ALTER TABLE {$table} ADD $index";
+					$for_update[$table.'.'.$fieldname] = 'Added index '.$table.' '.$index;
+				}
+
+				// Remove the original table creation query from processing.
+				unset($cqueries[strtolower($table)]);
+				unset($for_update[strtolower($table)]);
+			} else {
+				// This table exists in the database, but not in the creation queries?
+			}
+		}
+	}
+
+	$allqueries = array_merge($cqueries, $iqueries);
+	if ($execute) {
+		foreach ($allqueries as $query) {
+			//echo "<pre style=\"border:1px solid #ccc;margin-top:5px;\">".print_r($query, true)."</pre>\n";
+			$wpdb->query($query);
+		}
+	}
+
+	return $for_update;
+}
Index: /tags/2.4.3/src/bp-forums/bp-forums-bbpress.php
===================================================================
--- /tags/2.4.3/src/bp-forums/bp-forums-bbpress.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/bp-forums-bbpress.php	(revision 10410)
@@ -0,0 +1,10 @@
+<?php
+/**
+ * Placeholder for bbPress plugin bridge.
+ *
+ * @package BuddyPress
+ * @subpackage ForumsbbPress
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
Index: /tags/2.4.3/src/bp-forums/bp-forums-filters.php
===================================================================
--- /tags/2.4.3/src/bp-forums/bp-forums-filters.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/bp-forums-filters.php	(revision 10410)
@@ -0,0 +1,238 @@
+<?php
+/**
+ * BuddyPress Forums Filters.
+ *
+ * @package BuddyPress
+ * @subpackage ForumsFilters
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/* Apply WordPress defined filters */
+add_filter( 'bp_forums_bbconfig_location', 'wp_filter_kses', 1 );
+add_filter( 'bp_forums_bbconfig_location', 'esc_attr', 1 );
+
+add_filter( 'bp_get_the_topic_title', 'wp_filter_kses', 1 );
+add_filter( 'bp_get_the_topic_latest_post_excerpt', 'bp_forums_filter_kses', 1 );
+add_filter( 'bp_get_the_topic_post_content', 'bp_forums_filter_kses', 1 );
+
+add_filter( 'bp_get_the_topic_title', 'force_balance_tags' );
+add_filter( 'bp_get_the_topic_latest_post_excerpt', 'force_balance_tags' );
+add_filter( 'bp_get_the_topic_post_content', 'force_balance_tags' );
+
+add_filter( 'bp_get_the_topic_title', 'wptexturize' );
+add_filter( 'bp_get_the_topic_poster_name', 'wptexturize' );
+add_filter( 'bp_get_the_topic_last_poster_name', 'wptexturize' );
+add_filter( 'bp_get_the_topic_post_content', 'wptexturize' );
+add_filter( 'bp_get_the_topic_post_poster_name', 'wptexturize' );
+
+add_filter( 'bp_get_the_topic_title', 'convert_smilies' );
+add_filter( 'bp_get_the_topic_latest_post_excerpt', 'convert_smilies' );
+add_filter( 'bp_get_the_topic_post_content', 'convert_smilies' );
+
+add_filter( 'bp_get_the_topic_title', 'convert_chars' );
+add_filter( 'bp_get_the_topic_latest_post_excerpt', 'convert_chars' );
+add_filter( 'bp_get_the_topic_post_content', 'convert_chars' );
+
+add_filter( 'bp_get_the_topic_post_content', 'wpautop' );
+add_filter( 'bp_get_the_topic_latest_post_excerpt', 'wpautop' );
+
+add_filter( 'bp_get_the_topic_post_content', 'stripslashes_deep' );
+add_filter( 'bp_get_the_topic_title', 'stripslashes_deep' );
+add_filter( 'bp_get_the_topic_latest_post_excerpt', 'stripslashes_deep' );
+add_filter( 'bp_get_the_topic_poster_name', 'stripslashes_deep' );
+add_filter( 'bp_get_the_topic_last_poster_name', 'stripslashes_deep' );
+add_filter( 'bp_get_the_topic_object_name', 'stripslashes_deep' );
+
+add_filter( 'bp_get_the_topic_post_content', 'make_clickable', 9 );
+
+add_filter( 'bp_get_forum_topic_count_for_user', 'bp_core_number_format' );
+add_filter( 'bp_get_forum_topic_count', 'bp_core_number_format' );
+
+add_filter( 'bp_get_the_topic_title', 'bp_forums_make_nofollow_filter' );
+add_filter( 'bp_get_the_topic_latest_post_excerpt', 'bp_forums_make_nofollow_filter' );
+add_filter( 'bp_get_the_topic_post_content', 'bp_forums_make_nofollow_filter' );
+
+/**
+ * Custom KSES filter for the Forums component.
+ *
+ * @param string $content Content to sanitize.
+ * @return string Sanitized string.
+ */
+function bp_forums_filter_kses( $content ) {
+	global $allowedtags;
+
+	$forums_allowedtags = $allowedtags;
+	$forums_allowedtags['span'] = array();
+	$forums_allowedtags['span']['class'] = array();
+	$forums_allowedtags['div'] = array();
+	$forums_allowedtags['div']['class'] = array();
+	$forums_allowedtags['div']['id'] = array();
+	$forums_allowedtags['a']['class'] = array();
+	$forums_allowedtags['img'] = array();
+	$forums_allowedtags['br'] = array();
+	$forums_allowedtags['p'] = array();
+	$forums_allowedtags['img']['src'] = array();
+	$forums_allowedtags['img']['alt'] = array();
+	$forums_allowedtags['img']['class'] = array();
+	$forums_allowedtags['img']['width'] = array();
+	$forums_allowedtags['img']['height'] = array();
+	$forums_allowedtags['img']['class'] = array();
+	$forums_allowedtags['img']['id'] = array();
+	$forums_allowedtags['code'] = array();
+	$forums_allowedtags['blockquote'] = array();
+
+	/**
+	 * Filters the allowed HTML tags for forum posts.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $forums_allowedtags Array of allowed HTML tags.
+	 */
+	$forums_allowedtags = apply_filters( 'bp_forums_allowed_tags', $forums_allowedtags );
+	return wp_kses( $content, $forums_allowedtags );
+}
+
+/**
+ * Get a link for a forum topic tags directory.
+ *
+ * @param string $link    Link passed from filter.
+ * @param string $tag     Name of the tag.
+ * @param string $page    Page number, passed from the filter.
+ * @param string $context Passed from the filter but unused here.
+ * @return string Link of the form http://example.com/forums/tag/tagname/.
+ */
+function bp_forums_filter_tag_link( $link, $tag, $page, $context ) {
+	/**
+	 * Filters the link for a forum topic tags directory.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string $value Link for the forum topic tag directory.
+	 */
+	return apply_filters( 'bp_forums_filter_tag_link', bp_get_root_domain() . '/' . bp_get_forums_root_slug() . '/tag/' . $tag . '/' );
+}
+add_filter( 'bb_get_tag_link', 'bp_forums_filter_tag_link', 10, 4);
+
+/**
+ * Add rel="nofollow" to bbPress content.
+ *
+ * @param string $text Post content.
+ * @return string Modified post content.
+ */
+function bp_forums_make_nofollow_filter( $text ) {
+	return preg_replace_callback( '|<a (.+?)>|i', 'bp_forums_make_nofollow_filter_callback', $text );
+}
+	/**
+	 * Callback for preg_replace_callback() in bp_forums_make_nofollow_filter().
+	 *
+	 * @param array $matches Regex matches from {@link bp_forums_make_nofollow_filter()}.
+	 * @return string Text with nofollow links.
+	 */
+	function bp_forums_make_nofollow_filter_callback( $matches ) {
+		$text = $matches[1];
+		$text = str_replace( array( ' rel="nofollow"', " rel='nofollow'"), '', $text );
+		return "<a $text rel=\"nofollow\">";
+	}
+
+/**
+ * Append forum topic to page title.
+ *
+ * @see bp_modify_page_title()
+ *
+ * @param string $title          New page title; see {@link bp_modify_page_title()}.
+ * @param string $original_title Original page title.
+ * @param string $sep            How to separate the various items within the page title.
+ * @param string $seplocation    Direction to display title.
+ * @return string Page title with forum topic title appended.
+ */
+function bp_forums_add_forum_topic_to_page_title( $title, $original_title, $sep, $seplocation  ) {
+
+	if ( bp_is_current_action( 'forum' ) && bp_is_action_variable( 'topic', 0 ) )
+		if ( bp_has_forum_topic_posts() )
+			$title .= bp_get_the_topic_title() . " $sep ";
+
+	return $title;
+}
+add_filter( 'bp_modify_page_title', 'bp_forums_add_forum_topic_to_page_title', 9, 4 );
+
+/**
+ * Remove the anchor tag autogenerated for at-mentions when forum topics and posts are edited.
+ *
+ * Prevents embedded anchor tags.
+ *
+ * @param string $content Edited post content.
+ * @return string $content Sanitized post content.
+ */
+function bp_forums_strip_mentions_on_post_edit( $content ) {
+	$content   = htmlspecialchars_decode( $content );
+	$directory = bp_get_members_directory_permalink();
+	$pattern   = "|<a href=&#039;{$directory}[A-Za-z0-9-_\.]+/&#039; rel=&#039;nofollow&#039;>(@[A-Za-z0-9-_\.@]+)</a>|";
+	$content   = preg_replace( $pattern, "$1", $content );
+
+	return $content;
+}
+add_filter( 'bp_get_the_topic_post_edit_text', 'bp_forums_strip_mentions_on_post_edit' );
+add_filter( 'bp_get_the_topic_text',           'bp_forums_strip_mentions_on_post_edit' );
+
+/** "Replied to" SQL filters *************************************************/
+
+/**
+ * Filter the get_topics_distinct portion of the Forums SQL when on a user's Replied To page.
+ *
+ * This filter is added in bp_has_forum_topics().
+ *
+ * @since 1.5.0
+ *
+ * @param string $sql SQL fragment.
+ * @return string $sql SQL fragment of the form "DISTINCT t.topic_id, ".
+ */
+function bp_forums_add_replied_distinct_sql( $sql ) {
+	$sql = "DISTINCT t.topic_id, ";
+
+	return $sql;
+}
+
+/**
+ * Filter the get_topics_join portion of the Forums sql when on a user's Replied To page.
+ *
+ * This filter is added in bp_has_forum_topics().
+ *
+ * @since 1.5.0
+ *
+ * @global object $bbdb The bbPress database global.
+ *
+ * @param string $sql SQL statement.
+ * @return string $sql SQL statement.
+ */
+function bp_forums_add_replied_join_sql( $sql ) {
+	global $bbdb;
+
+	$sql .= " LEFT JOIN {$bbdb->posts} p ON p.topic_id = t.topic_id ";
+
+	return $sql;
+}
+
+/**
+ * Filter the get_topics_where portion of the Forums sql when on a user's Replied To page.
+ *
+ * This filter is added in bp_has_forum_topics().
+ *
+ * @since 1.5.0
+ *
+ * @global object $wpdb The WordPress database global.
+ *
+ * @param string $sql SQL fragment.
+ * @return string $sql SQL fragment.
+ */
+function bp_forums_add_replied_where_sql( $sql ) {
+	global $wpdb;
+
+	$sql .= $wpdb->prepare( " AND p.poster_id = %s ", bp_displayed_user_id() );
+
+	// Remove any topic_author information.
+	$sql = str_replace( " AND t.topic_poster = '" . bp_displayed_user_id() . "'", '', $sql );
+
+	return $sql;
+}
Index: /tags/2.4.3/src/bp-forums/bp-forums-functions.php
===================================================================
--- /tags/2.4.3/src/bp-forums/bp-forums-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/bp-forums-functions.php	(revision 10410)
@@ -0,0 +1,1104 @@
+<?php
+/**
+ * BuddyPress Forums Functions.
+ *
+ * @package BuddyPress
+ * @subpackage ForumsFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/** bbPress 2.x ***************************************************************/
+
+/**
+ * Is see bbPress 2.x is installed and active?
+ *
+ * @since 1.6.0
+ *
+ * @return boolean True if bbPress 2.x is active, false if not.
+ */
+function bp_forums_is_bbpress_active() {
+
+	// Single site.
+	if ( is_plugin_active( 'bbpress/bbpress.php' ) )
+		return true;
+
+	// Network active.
+	if ( is_plugin_active_for_network( 'bbpress/bbpress.php' ) )
+		return true;
+
+	// Nope.
+	return false;
+}
+
+/** bbPress 1.x ***************************************************************/
+
+/**
+ * See if bbPress 1.x is installed correctly.
+ *
+ * "Installed correctly" means that the bb-config-location option is set, and
+ * the referenced file exists.
+ *
+ * @since 1.2.0
+ *
+ * @return boolean True if option exists, false if not.
+ */
+function bp_forums_is_installed_correctly() {
+	$bp = buddypress();
+
+	if ( isset( $bp->forums->bbconfig ) && is_file( $bp->forums->bbconfig ) )
+		return true;
+
+	return false;
+}
+
+/**
+ * Does the forums component have a directory page registered?
+ *
+ * Checks $bp pages global and looks for directory page.
+ *
+ * @since 1.5.0
+ *
+ * @global BuddyPress $bp The one true BuddyPress instance.
+ *
+ * @return bool True if set, False if empty.
+ */
+function bp_forums_has_directory() {
+	return (bool) !empty( buddypress()->pages->forums->id );
+}
+
+/** Forum Functions ***********************************************************/
+
+/**
+ * Get a forum by ID.
+ *
+ * Wrapper for {@link bb_get_forum()}.
+ *
+ * @param int $forum_id ID of the forum being fetched.
+ * @return object bbPress forum object.
+ */
+function bp_forums_get_forum( $forum_id ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+	return bb_get_forum( $forum_id );
+}
+
+/**
+ * Create a forum.
+ *
+ * Wrapper for {@link bb_new_forum()}.
+ *
+ * @param array|string $args {
+ *     Forum setup arguments.
+ *     @type string $forum_name        Name of the forum.
+ *     @type string $forum_desc        Description of the forum.
+ *     @type int    $forum_parent_id   ID of the forum parent. Default: value of
+ *                                     {@link bp_forums_parent_forums_id()}.
+ *     @type bool   $forum_order       Order.
+ *     @type int    $forum_is_category Whether the forum is a category. Default: 0.
+ * }
+ * @return int ID of the newly created forum.
+ */
+function bp_forums_new_forum( $args = '' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$r = wp_parse_args( $args, array(
+		'forum_name'        => '',
+		'forum_desc'        => '',
+		'forum_parent_id'   => bp_forums_parent_forum_id(),
+		'forum_order'       => false,
+		'forum_is_category' => 0
+	) );
+	extract( $r, EXTR_SKIP );
+
+	return bb_new_forum( array( 'forum_name' => stripslashes( $forum_name ), 'forum_desc' => stripslashes( $forum_desc ), 'forum_parent' => $forum_parent_id, 'forum_order' => $forum_order, 'forum_is_category' => $forum_is_category ) );
+}
+
+/**
+ * Update a forum.
+ *
+ * Wrapper for {@link bb_update_forum(}.
+ *
+ * @param array|string $args {
+ *     Forum setup arguments.
+ *     @type int    $forum_id          ID of the forum to be updated.
+ *     @type string $forum_name        Name of the forum.
+ *     @type string $forum_desc        Description of the forum.
+ *     @type int    $forum_parent_id   ID of the forum parent. Default: value of
+ *                                     {@link bp_forums_parent_forums_id()}.
+ *     @type bool   $forum_order       Order.
+ *     @type int    $forum_is_category Whether the forum is a category. Default: 0.
+ * }
+ * @return bool True on success, false on failure.
+ */
+function bp_forums_update_forum( $args = '' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$r = wp_parse_args( $args, array(
+		'forum_id'          => '',
+		'forum_name'        => '',
+		'forum_desc'        => '',
+		'forum_slug'        => '',
+		'forum_parent_id'   => bp_forums_parent_forum_id(),
+		'forum_order'       => false,
+		'forum_is_category' => 0
+	) );
+	extract( $r, EXTR_SKIP );
+
+	return bb_update_forum( array( 'forum_id' => (int) $forum_id, 'forum_name' => stripslashes( $forum_name ), 'forum_desc' => stripslashes( $forum_desc ), 'forum_slug' => stripslashes( $forum_slug ), 'forum_parent' => $forum_parent_id, 'forum_order' => $forum_order, 'forum_is_category' => $forum_is_category ) );
+}
+
+/**
+ * Delete a group forum by the group id.
+ *
+ * @param int $group_id ID of the group whose forum is to be deleted.
+ */
+function bp_forums_delete_group_forum( $group_id ) {
+	$forum_id = groups_get_groupmeta( $group_id, 'forum_id' );
+
+	if ( !empty( $forum_id ) && is_int( $forum_id ) ) {
+
+		/** This action is documented in bp-forums/bp-forums-screens.php */
+		do_action( 'bbpress_init' );
+		bb_delete_forum( $forum_id );
+	}
+}
+add_action( 'groups_delete_group', 'bp_forums_delete_group_forum' );
+
+/** Topic Functions ***********************************************************/
+
+/**
+ * Fetch a set of forum topics.
+ *
+ * @param array|string $args {
+ *     @type string $type          Order or filter type. Default: 'newest'.
+ *     @type int    $forum_id      Optional. Pass a forum ID to limit results to topics
+ *                                 associated with that forum.
+ *     @type int    $user_id       Optional. Pass a user ID to limit results to topics
+ *                                 belonging to that user.
+ *     @type int    $page          Optional. Number of the results page to return.
+ *                                 Default: 1.
+ *     @type int    $per_page      Optional. Number of results to return per page.
+ *                                 Default: 15.
+ *     @type int    $offset        Optional. Numeric offset for results.
+ *     @type int    $number        Amount to query for.
+ *     @type array  $exclude       Optional. Topic IDs to exclude.
+ *     @type string $show_stickies Whether to show sticky topics.
+ *     @type mixed  $filter        If $type = 'tag', filter is the tag name. Otherwise,
+ *                                 $filter is terms to search on.
+ * }
+ * @return array Found topics.
+ */
+function bp_forums_get_forum_topics( $args = '' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$r = wp_parse_args( $args, array(
+		'type'          => 'newest',
+		'forum_id'      => false,
+		'user_id'       => false,
+		'page'          => 1,
+		'per_page'      => 15,
+		'offset'        => false,
+		'number'        => false,
+		'exclude'       => false,
+		'show_stickies' => 'all',
+		'filter'        => false // If $type = tag then filter is the tag name, otherwise it's terms to search on.
+	) );
+	extract( $r, EXTR_SKIP );
+
+	if ( class_exists( 'BB_Query' ) ) {
+		switch ( $type ) {
+			case 'newest':
+				$query = new BB_Query( 'topic', array( 'forum_id' => $forum_id, 'topic_author_id' => $user_id, 'per_page' => $per_page, 'page' => $page, 'number' => $per_page, 'exclude' => $exclude, 'topic_title' => $filter, 'sticky' => $show_stickies, 'offset' => $offset, 'number' => $number ), 'get_latest_topics' );
+				$topics =& $query->results;
+				break;
+
+			case 'popular':
+				$query = new BB_Query( 'topic', array( 'forum_id' => $forum_id, 'topic_author_id' => $user_id, 'per_page' => $per_page, 'page' => $page, 'order_by' => 't.topic_posts', 'topic_title' => $filter, 'sticky' => $show_stickies, 'offset' => $offset, 'number' => $number ) );
+				$topics =& $query->results;
+				break;
+
+			case 'unreplied':
+				$query = new BB_Query( 'topic', array( 'forum_id' => $forum_id, 'topic_author_id' => $user_id, 'post_count' => 1, 'per_page' => $per_page, 'page' => $page, 'order_by' => 't.topic_time', 'topic_title' => $filter, 'sticky' => $show_stickies, 'offset' => $offset, 'number' => $number ) );
+				$topics =& $query->results;
+				break;
+
+			case 'tags':
+				$query = new BB_Query( 'topic', array( 'forum_id' => $forum_id, 'topic_author_id' => $user_id, 'tag' => $filter, 'per_page' => $per_page, 'page' => $page, 'order_by' => 't.topic_time', 'sticky' => $show_stickies, 'offset' => $offset, 'number' => $number ) );
+				$topics =& $query->results;
+				break;
+		}
+	} else {
+		$topics = array();
+	}
+
+	/**
+	 * Filters the found forum topics for provided arguments.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param array $topics Array of found topics. Passed by reference.
+	 * @param array $r      Array of parsed arguments for query. Passed by reference.
+	 */
+	return apply_filters_ref_array( 'bp_forums_get_forum_topics', array( &$topics, &$r ) );
+}
+
+/**
+ * Get additional details about a given forum topic.
+ *
+ * @param int $topic_id ID of the topic for which you're fetching details.
+ * @return object Details about the topic.
+ */
+function bp_forums_get_topic_details( $topic_id ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$query = new BB_Query( 'topic', 'topic_id=' . $topic_id . '&page=1' /* Page override so bbPress doesn't use the URI */ );
+
+	return $query->results[0];
+}
+
+/**
+ * Get the numeric ID of a topic from the topic slug.
+ *
+ * Wrapper for {@link bb_get_id_from_slug()}.
+ *
+ * @param string $topic_slug Slug of the topic.
+ * @return int|bool ID of the topic (if found), false on failure.
+ */
+function bp_forums_get_topic_id_from_slug( $topic_slug ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	if ( empty( $topic_slug ) )
+		return false;
+
+	return bb_get_id_from_slug( 'topic', $topic_slug );
+}
+
+/**
+ * Create a new forum topic.
+ *
+ * @param array|string $args {
+ *     @type string            $topic_title            Title of the new topic.
+ *     @type string            $topic_slug             Slug of the new topic.
+ *     @type string            $topic_text             Text of the new topic.
+ *     @type int               $topic_poster           ID of the user posting the topic. Default: ID of
+ *                                                     the logged-in user.
+ *     @type string            $topic_poster_name      Display name of the user posting the
+ *                                                     topic. Default: 'fullname' of the logged-in user.
+ *     @type int               $topic_last_poster      ID of the user who last posted to the topic.
+ *                                                     Default: ID of the logged-in user.
+ *     @type string            $topic_last_poster_name Display name of the user who last
+ *                                                     posted to the topic. Default: 'fullname' of the logged-in user.
+ *     @type string            $topic_start_time       Date/time when the topic was created.
+ *                                                     Default: the current time, as reported by bp_core_current_time().
+ *     @type string            $topic_time             Date/time when the topic was created.
+ *                                                     Default: the current time, as reported by bp_core_current_time().
+ *     @type int               $topic_open             Whether the topic is open. Default: 1 (open).
+ *     @type array|string|bool $topic_tags             Array or comma-separated list of
+ *                                                     topic tags. False to leave empty. Default: false.
+ *     @type int               $forum_id               ID of the forum to which the topic belongs.
+ *                                                     Default: 0.
+ * }
+ * @return object Details about the new topic, as returned by
+ *                {@link bp_forums_get_topic_details()}.
+ */
+function bp_forums_new_topic( $args = '' ) {
+	$bp = buddypress();
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$r = wp_parse_args( $args, array(
+		'topic_title'            => '',
+		'topic_slug'             => '',
+		'topic_text'             => '',
+		'topic_poster'           => bp_loggedin_user_id(),       // Accepts ids.
+		'topic_poster_name'      => $bp->loggedin_user->fullname, // Accept names.
+		'topic_last_poster'      => bp_loggedin_user_id(),       // Accepts ids.
+		'topic_last_poster_name' => $bp->loggedin_user->fullname, // Accept names.
+		'topic_start_time'       => bp_core_current_time(),
+		'topic_time'             => bp_core_current_time(),
+		'topic_open'             => 1,
+		'topic_tags'             => false, // Accepts array or comma delimited.
+		'forum_id'               => 0      // Accepts ids or slugs.
+	) );
+	extract( $r, EXTR_SKIP );
+
+	$topic_title = strip_tags( $topic_title );
+
+	if ( empty( $topic_title ) || !strlen( trim( $topic_title ) ) )
+		return false;
+
+	if ( empty( $topic_poster ) )
+		return false;
+
+	if ( bp_is_user_inactive( $topic_poster ) )
+		return false;
+
+	if ( empty( $topic_slug ) )
+		$topic_slug = sanitize_title( $topic_title );
+
+	if ( !$topic_id = bb_insert_topic( array( 'topic_title' => stripslashes( $topic_title ), 'topic_slug' => $topic_slug, 'topic_poster' => $topic_poster, 'topic_poster_name' => $topic_poster_name, 'topic_last_poster' => $topic_last_poster, 'topic_last_poster_name' => $topic_last_poster_name, 'topic_start_time' => $topic_start_time, 'topic_time' => $topic_time, 'topic_open' => $topic_open, 'forum_id' => (int) $forum_id, 'tags' => $topic_tags ) ) )
+		return false;
+
+	// Now insert the first post.
+	if ( !bp_forums_insert_post( array( 'topic_id' => $topic_id, 'post_text' => $topic_text, 'post_time' => $topic_time, 'poster_id' => $topic_poster ) ) )
+		return false;
+
+	/**
+	 * Fires after a new forum topic has been created.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $topic_id ID of the newly created topic post.
+	 */
+	do_action( 'bp_forums_new_topic', $topic_id );
+
+	return $topic_id;
+}
+
+/**
+ * Update a topic's details.
+ *
+ * @param array|string $args {
+ *     Array of arguments.
+ *     @type int               $topic_id    ID of the topic being updated.
+ *     @type string            $topic_title Updated title of the topic.
+ *     @type string            $topic_text  Updated text of the topic.
+ *     @type array|string|bool $topic_tags  Array or comma-separated list of
+ *                                          topic tags. False to leave empty.
+ *                                          Default false.
+ * }
+ * @return object Details about the new topic, as returned by
+ *                {@link bp_forums_get_topic_details()}.
+ */
+function bp_forums_update_topic( $args = '' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$r = wp_parse_args( $args, array(
+		'topic_id'    => false,
+		'topic_title' => '',
+		'topic_text'  => '',
+		'topic_tags'  => false
+	) );
+	extract( $r, EXTR_SKIP );
+
+	// Check if the user is a spammer.
+	if ( bp_is_user_inactive( bp_loggedin_user_id() ) )
+		return false;
+
+	// The bb_insert_topic() function will append tags, but not remove them. So we remove all existing tags.
+	bb_remove_topic_tags( $topic_id );
+
+	if ( !$topic_id = bb_insert_topic( array( 'topic_id' => $topic_id, 'topic_title' => stripslashes( $topic_title ), 'tags' => $topic_tags ) ) )
+		return false;
+
+	if ( !$post = bb_get_first_post( $topic_id ) )
+		return false;
+
+	// Update the first post.
+	if ( !$post = bp_forums_insert_post( array( 'post_id' => $post->post_id, 'topic_id' => $topic_id, 'post_text' => $topic_text, 'post_time' => $post->post_time, 'poster_id' => $post->poster_id, 'poster_ip' => $post->poster_ip, 'post_status' => $post->post_status, 'post_position' => $post->post_position ) ) )
+		return false;
+
+	return bp_forums_get_topic_details( $topic_id );
+}
+
+function bp_forums_sticky_topic( $args = '' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$r = wp_parse_args( $args, array(
+		'topic_id' => false,
+		'mode'     => 'stick' // Stick/unstick.
+	) );
+	extract( $r, EXTR_SKIP );
+
+	if ( 'stick' == $mode )
+		return bb_stick_topic( $topic_id );
+	else if ( 'unstick' == $mode )
+		return bb_unstick_topic( $topic_id );
+
+	return false;
+}
+
+/**
+ * Set a topic's open/closed status.
+ *
+ * @param array|string $args {
+ *     @type int    $topic_id ID of the topic whose status is being changed.
+ *     @type string $mode     New status of the topic. 'open' or 'close'.
+ *                            Default: 'close'.
+ * }
+ * @return bool True on success, false on failure.
+ */
+function bp_forums_openclose_topic( $args = '' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$r = wp_parse_args( $args, array(
+		'topic_id' => false,
+		'mode'     => 'close' // Stick/unstick.
+	) );
+	extract( $r, EXTR_SKIP );
+
+	if ( 'close' == $mode )
+		return bb_close_topic( $topic_id );
+	else if ( 'open' == $mode )
+		return bb_open_topic( $topic_id );
+
+	return false;
+}
+
+/**
+ * Delete a topic.
+ *
+ * @param array|string $args {
+ *     @type int $topic_id ID of the topic being deleted.
+ * }
+ * @return bool True on success, false on failure.
+ */
+function bp_forums_delete_topic( $args = '' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$r = wp_parse_args( $args, array(
+		'topic_id' => false
+	) );
+	extract( $r, EXTR_SKIP );
+
+	return bb_delete_topic( $topic_id, 1 );
+}
+
+/**
+ * Get a count of the total topics on the site.
+ *
+ * @return int $count Total topic count.
+ */
+function bp_forums_total_topic_count() {
+	global $bbdb;
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	if ( isset( $bbdb ) ) {
+		if ( bp_is_active( 'groups' ) ) {
+			$groups_table_sql = groups_add_forum_tables_sql();
+			$groups_where_sql = groups_add_forum_where_sql( "t.topic_status = 0" );
+		} else {
+			$groups_table_sql = '';
+			$groups_where_sql = "t.topic_status = 0";
+		}
+		$count = $bbdb->get_results( "SELECT t.topic_id FROM {$bbdb->topics} AS t {$groups_table_sql} WHERE {$groups_where_sql}" );
+		$count = count( (array) $count );
+	} else {
+		$count = 0;
+	}
+
+	/**
+	 * Filters the total topic count for the site.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $count Total topic count.
+	 */
+	return apply_filters( 'bp_forums_total_topic_count', $count );
+}
+
+/**
+ * Check to see whether a user has already left this particular reply on a given post.
+ *
+ * Used to prevent dupes.
+ *
+ * @since 1.6.0
+ *
+ * @param string $text     The text of the comment.
+ * @param int    $topic_id The topic id.
+ * @param int    $user_id  The user id.
+ * @return bool True if a duplicate reply exists, otherwise false.
+ */
+function bp_forums_reply_exists( $text = '', $topic_id = 0, $user_id = 0 ) {
+
+	$reply_exists = false;
+
+	if ( $text && $topic_id && $user_id ) {
+
+		/** This action is documented in bp-forums/bp-forums-screens.php */
+		do_action( 'bbpress_init' );
+
+		$args = array(
+			'post_author_id' => $user_id,
+			'topic_id'       => $topic_id
+		);
+
+		// Set the reply_exists_text so we can check it in the filter below.
+		buddypress()->forums->reply_exists_text = $text;
+
+		// BB_Query's post_text parameter does a MATCH, while we need exact matches.
+		add_filter( 'get_posts_where', '_bp_forums_reply_exists_posts_where' );
+		$query = new BB_Query( 'post', $args );
+		remove_filter( 'get_posts_where', '_bp_forums_reply_exists_posts_where' );
+
+		// Cleanup.
+		unset( buddypress()->forums->reply_exists_text );
+
+		$reply_exists = (bool) !empty( $query->results );
+	}
+
+	/**
+	 * Filters whether a user has already left this particular reply on a given post.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool   $reply_exists Whether or not a reply exists.
+	 * @param string $text         The text of the comment.
+	 * @param int    $topic_id     The topic ID.
+	 * @param int    $user_id      The user ID.
+	 */
+	return (bool) apply_filters( 'bp_forums_reply_exists', $reply_exists, $text, $topic_id, $user_id );
+}
+	/**
+	 * Private one-time-use function used in conjunction with bp_forums_reply_exists().
+	 *
+	 * @access private
+	 * @since 1.7.0
+	 *
+	 * @global WPDB $wpdb WordPress database access object.
+	 *
+	 * @param string $where SQL fragment.
+	 * @return string SQL fragment.
+	 */
+	function _bp_forums_reply_exists_posts_where( $where = '' ) {
+		return $where . " AND p.post_text = '" . buddypress()->forums->reply_exists_text . "'";
+	}
+
+/**
+ * Get a total "Topics Started" count for a given user.
+ *
+ * @param int    $user_id ID of the user being queried. Falls back on displayed
+ *                        user, then loggedin.
+ * @param string $type    The current filter/sort type. 'active', 'popular',
+ *                        'unreplied'.
+ * @return int $count The topic count.
+ */
+function bp_forums_total_topic_count_for_user( $user_id = 0, $type = 'active' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	if ( !$user_id )
+		$user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+
+	if ( class_exists( 'BB_Query' ) ) {
+		$args = array(
+			'topic_author_id' => $user_id,
+			'page' 		  => 1,
+			'per_page'	  => -1,
+			'count'		  => true
+		);
+
+		if ( 'unreplied' == $type )
+			$args['post_count'] = 1;
+
+		$query = new BB_Query( 'topic', $args );
+		$count = $query->count;
+		$query = null;
+	} else {
+		$count = 0;
+	}
+
+	return $count;
+}
+
+/**
+ * Return the total number of topics replied to by a given user.
+ *
+ * Uses an unfortunate technique to count unique topics, due to limitations in
+ * BB_Query.
+ *
+ * @since 1.5.0
+ *
+ * @param int    $user_id ID of the user whose replied topics are being counted.
+ *                        Defaults to displayed user, then to logged-in user.
+ * @param string $type    Forum thread type.
+ * @return int $count Topic count.
+ */
+function bp_forums_total_replied_count_for_user( $user_id = 0, $type = 'active' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	if ( !$user_id )
+		$user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+
+	if ( !$user_id )
+		return 0;
+
+	if ( class_exists( 'BB_Query' ) ) {
+		$query = new BB_Query( 'post', array( 'post_author_id' => $user_id, 'page' => 1, 'per_page' => -1, 'count' => true ) );
+
+		// Count the unique topics. No better way to do this in the bbPress query API.
+		$topics = array();
+		foreach( $query->results as $result ) {
+			if ( !in_array( $result->topic_id, $topics ) )
+				$topics[] = $result->topic_id;
+		}
+
+		// Even more unfortunate. If this is filtered by 'unreplied', we have to requery.
+		if ( 'unreplied' == $type ) {
+			$topic_ids = implode( ',', $topics );
+			$topics_query = new BB_Query( 'topic', array( 'topic_id' => $topic_ids, 'page' => 1, 'per_page' => -1, 'post_count' => 1 ) );
+			$count = count( $topics_query->results );
+		} else {
+			$count = count( $topics );
+		}
+		$query = null;
+	} else {
+		$count = 0;
+	}
+
+	/**
+	 * Filters the total number of topics replied to by a given user.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $count   Total number of topics replied to by a given user.
+	 * @param int $user_id The user ID.
+	 */
+	return apply_filters( 'bp_forums_total_replied_count_for_user', $count, $user_id );
+}
+
+/**
+ * Fetch BP-specific details for an array of topics.
+ *
+ * Done in one fell swoop to reduce query overhead. Currently determines the
+ * following:
+ * - details about the last poster
+ * - information about topic users that may have been deleted/spammed
+ *
+ * @param array $topics Array of topics.
+ * @return array $topics Topics with BP details added.
+ */
+function bp_forums_get_topic_extras( $topics ) {
+	global $wpdb, $bbdb;
+
+	if ( empty( $topics ) )
+		return $topics;
+
+	$bp = buddypress();
+
+	// Get the topic ids.
+	foreach ( (array) $topics as $topic ) $topic_ids[] = $topic->topic_id;
+	$topic_ids = implode( ',', wp_parse_id_list( $topic_ids ) );
+
+	// Fetch the topic's last poster details.
+	$poster_details = $wpdb->get_results( "SELECT t.topic_id, t.topic_last_poster, u.user_login, u.user_nicename, u.user_email, u.display_name FROM {$wpdb->users} u, {$bbdb->topics} t WHERE u.ID = t.topic_last_poster AND t.topic_id IN ( {$topic_ids} )" );
+	for ( $i = 0, $count = count( $topics ); $i < $count; ++$i ) {
+		foreach ( (array) $poster_details as $poster ) {
+			if ( $poster->topic_id == $topics[$i]->topic_id ) {
+				$topics[$i]->topic_last_poster_email       = $poster->user_email;
+				$topics[$i]->topic_last_poster_nicename    = $poster->user_nicename;
+				$topics[$i]->topic_last_poster_login       = $poster->user_login;
+				$topics[$i]->topic_last_poster_displayname = $poster->display_name;
+			}
+		}
+	}
+
+	// Fetch fullname for the topic's last poster.
+	if ( bp_is_active( 'xprofile' ) ) {
+		$poster_names = $wpdb->get_results( "SELECT t.topic_id, pd.value FROM {$bp->profile->table_name_data} pd, {$bbdb->topics} t WHERE pd.user_id = t.topic_last_poster AND pd.field_id = 1 AND t.topic_id IN ( {$topic_ids} )" );
+		for ( $i = 0, $count = count( $topics ); $i < $count; ++$i ) {
+			foreach ( (array) $poster_names as $name ) {
+				if ( $name->topic_id == $topics[$i]->topic_id )
+					$topics[$i]->topic_last_poster_displayname = $name->value;
+			}
+		}
+	}
+
+	// Loop through to make sure that each topic has the proper values set. This covers the
+	// case of deleted users.
+	foreach ( (array) $topics as $key => $topic ) {
+		if ( !isset( $topic->topic_last_poster_email ) )
+			$topics[$key]->topic_last_poster_email = '';
+
+		if ( !isset( $topic->topic_last_poster_nicename ) )
+			$topics[$key]->topic_last_poster_nicename = '';
+
+		if ( !isset( $topic->topic_last_poster_login ) )
+			$topics[$key]->topic_last_poster_login = '';
+
+		if ( !isset( $topic->topic_last_poster_displayname ) )
+			$topics[$key]->topic_last_poster_displayname = '';
+	}
+
+	return $topics;
+}
+
+/** Post Functions ************************************************************/
+
+/**
+ * Get the posts belonging to a topic.
+ *
+ * @param array|string $args {
+ *     @type int    $topic_id ID of the topic for which posts are being fetched.
+ *     @type int    $page     Optional. Page of results to return. Default: 1.
+ *     @type int    $page     Optional. Number of results to return per page.
+ *                            Default: 15.
+ *     @type string $order    'ASC' or 'DESC'. Default: 'ASC'.
+ * }
+ * @return array List of posts.
+ */
+function bp_forums_get_topic_posts( $args = '' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$defaults = array(
+		'topic_id' => false,
+		'page'     => 1,
+		'per_page' => 15,
+		'order'    => 'ASC'
+	);
+
+	$args  = wp_parse_args( $args, $defaults );
+	$query = new BB_Query( 'post', $args, 'get_thread' );
+
+	return bp_forums_get_post_extras( $query->results );
+}
+
+/**
+ * Get a single post object by ID.
+ *
+ * Wrapper for {@link bb_get_post()}.
+ *
+ * @param int $post_id ID of the post being fetched.
+ * @return object Post object.
+ */
+function bp_forums_get_post( $post_id ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+	return bb_get_post( $post_id );
+}
+
+/**
+ * Delete a post.
+ *
+ * Wrapper for {@link bb_delete_post()}.
+ *
+ * @param array|string $args {
+ *     @type int $post_id ID of the post being deleted.
+ * }
+ * @return bool True on success, false on failure.
+ */
+function bp_forums_delete_post( $args = '' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$r = wp_parse_args( $args, array(
+		'post_id' => false
+	) );
+
+	extract( $r, EXTR_SKIP );
+
+	return bb_delete_post( $post_id, 1 );
+}
+
+/**
+ * Create a new post.
+ *
+ * @param array|string $args {
+ *     @type int    $post_id       Optional. ID of an existing post, if you want to
+ *                                 update rather than create. Default: false.
+ *     @type int    $topic_id      ID of the topic to which the post belongs.
+ *     @type string $post_text     Contents of the post.
+ *     @type string $post_time     Optional. Time when the post was recorded.
+ *                                 Default: current time, as reported by {@link bp_core_current_time()}.
+ *     @type int    $poster_id     Optional. ID of the user creating the post.
+ *                                 Default: ID of the logged-in user.
+ *     @type string $poster_ip     Optional. IP address of the user creating the
+ *                                 post. Default: the IP address found in $_SERVER['REMOTE_ADDR'].
+ *     @type int    $post_status   Post status. Default: 0.
+ *     @type int    $post_position Optional. Default: false (auto).
+ * }
+ * @return int|bool ID of the new post on success, false on failure.
+ */
+function bp_forums_insert_post( $args = '' ) {
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	$defaults = array(
+		'post_id'       => false,
+		'topic_id'      => false,
+		'post_text'     => '',
+		'post_time'     => bp_core_current_time(),
+		'poster_id'     => bp_loggedin_user_id(), // Accepts ids or names.
+		'poster_ip'     => $_SERVER['REMOTE_ADDR'],
+		'post_status'   => 0, // Use bb_delete_post() instead.
+		'post_position' => false
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+	extract( $r, EXTR_SKIP );
+
+	if ( !$post = bp_forums_get_post( $post_id ) )
+		$post_id = false;
+
+	if ( !isset( $topic_id ) )
+		$topic_id = $post->topic_id;
+
+	if ( empty( $post_text ) )
+		$post_text = $post->post_text;
+
+	if ( !isset( $post_time ) )
+		$post_time = $post->post_time;
+
+	if ( !isset( $post_position ) )
+		$post_position = $post->post_position;
+
+	if ( empty( $poster_id ) )
+		return false;
+
+	if ( bp_is_user_inactive( bp_loggedin_user_id() ) )
+		return false;
+
+	$post_id = bb_insert_post( array( 'post_id' => $post_id, 'topic_id' => $topic_id, 'post_text' => stripslashes( trim( $post_text ) ), 'post_time' => $post_time, 'poster_id' => $poster_id, 'poster_ip' => $poster_ip, 'post_status' => $post_status, 'post_position' => $post_position ) );
+
+	if ( !empty( $post_id ) ) {
+
+		/**
+		 * Fires if there was a new post created.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $post_id ID of the newly created forum post.
+		 */
+		do_action( 'bp_forums_new_post', $post_id );
+	}
+
+	return $post_id;
+}
+
+/**
+ * Get BP-specific details about a set of posts.
+ *
+ * Currently fetches the following:
+ * - WP userdata for each poster
+ * - BP fullname for each poster
+ *
+ * @param array $posts List of posts.
+ * @return array Posts with BP-data added.
+ */
+function bp_forums_get_post_extras( $posts ) {
+	global $wpdb;
+
+	if ( empty( $posts ) )
+		return $posts;
+
+	$bp = buddypress();
+
+	// Get the user ids.
+	foreach ( (array) $posts as $post ) $user_ids[] = $post->poster_id;
+	$user_ids = implode( ',', wp_parse_id_list( $user_ids ) );
+
+	// Fetch the poster's user_email, user_nicename and user_login.
+	$poster_details = $wpdb->get_results( "SELECT u.ID as user_id, u.user_login, u.user_nicename, u.user_email, u.display_name FROM {$wpdb->users} u WHERE u.ID IN ( {$user_ids} )" );
+
+	for ( $i = 0, $count = count( $posts ); $i < $count; ++$i ) {
+		foreach ( (array) $poster_details as $poster ) {
+			if ( $poster->user_id == $posts[$i]->poster_id ) {
+				$posts[$i]->poster_email    = $poster->user_email;
+				$posts[$i]->poster_login    = $poster->user_login;
+				$posts[$i]->poster_nicename = $poster->user_nicename;
+				$posts[$i]->poster_name     = $poster->display_name;
+			}
+		}
+	}
+
+	// Fetch fullname for each poster.
+	if ( bp_is_active( 'xprofile' ) ) {
+		$poster_names = $wpdb->get_results( "SELECT pd.user_id, pd.value FROM {$bp->profile->table_name_data} pd WHERE pd.user_id IN ( {$user_ids} )" );
+		for ( $i = 0, $count = count( $posts ); $i < $count; ++$i ) {
+			foreach ( (array) $poster_names as $name ) {
+				if ( isset( $topics[$i] ) && $name->user_id == $topics[$i]->user_id )
+				$posts[$i]->poster_name = $poster->value;
+			}
+		}
+	}
+
+	/**
+	 * Filters BP-specific details about a set of posts.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $posts Array of posts holding BP-specific details.
+	 */
+	return apply_filters( 'bp_forums_get_post_extras', $posts );
+}
+
+/**
+ * Get topic and post counts for a given forum.
+ *
+ * @param int $forum_id ID of the forum.
+ * @return object Object with properties $topics (topic count) and $posts
+ *                (post count).
+ */
+function bp_forums_get_forum_topicpost_count( $forum_id ) {
+	global $wpdb, $bbdb;
+
+	/** This action is documented in bp-forums/bp-forums-screens.php */
+	do_action( 'bbpress_init' );
+
+	// Need to find a bbPress function that does this.
+	return $wpdb->get_results( $wpdb->prepare( "SELECT topics, posts from {$bbdb->forums} WHERE forum_id = %d", $forum_id ) );
+}
+
+/**
+ * Map WordPress caps onto bbPress users, to ensure that they can post.
+ *
+ * @param array $allcaps Array of capabilities.
+ * @return array Caps array with bbPress caps added.
+ */
+function bp_forums_filter_caps( $allcaps ) {
+	global $wp_roles, $bb_table_prefix;
+
+	if ( !bp_loggedin_user_id() )
+		return $allcaps;
+
+	$bb_cap = bp_get_user_meta( bp_loggedin_user_id(), $bb_table_prefix . 'capabilities', true );
+
+	if ( empty( $bb_cap ) )
+		return $allcaps;
+
+	$bb_cap = array_keys($bb_cap);
+	$bb_cap = $wp_roles->get_role( $bb_cap[0] );
+	$bb_cap = $bb_cap->capabilities;
+
+	return array_merge( (array) $allcaps, (array) $bb_cap );
+}
+add_filter( 'user_has_cap', 'bp_forums_filter_caps' );
+
+/**
+ * Return the parent forum ID for the bbPress abstraction layer.
+ *
+ * @since 1.5.0
+ *
+ * @return int Forum ID.
+ */
+function bp_forums_parent_forum_id() {
+
+	/**
+	 * Filters the parent forum ID for the bbPress abstraction layer.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int BP_FORUMS_PARENT_FORUM_ID The Parent forum ID constant.
+	 */
+	return apply_filters( 'bp_forums_parent_forum_id', BP_FORUMS_PARENT_FORUM_ID );
+}
+
+/**
+ * Should sticky topics be broken out of regular topic order on forum directories?
+ *
+ * Defaults to false. Define BP_FORUMS_ENABLE_GLOBAL_DIRECTORY_STICKIES, or
+ * filter 'bp_forums_enable_global_directory_stickies', to change this behavior.
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if stickies should be displayed at the top of the global
+ *              directory, otherwise false.
+ */
+function bp_forums_enable_global_directory_stickies() {
+
+	/**
+	 * Filters whether or not sticky topics should be broken out of regular topic order.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $value Whether or not to break out of topic order.
+	 */
+	return apply_filters( 'bp_forums_enable_global_directory_stickies', defined( 'BP_FORUMS_ENABLE_GLOBAL_DIRECTORY_STICKIES' ) && BP_FORUMS_ENABLE_GLOBAL_DIRECTORY_STICKIES );
+}
+
+
+/** Caching ******************************************************************/
+
+/**
+ * Caching functions handle the clearing of cached objects and pages on specific
+ * actions throughout BuddyPress.
+ */
+
+// List actions to clear super cached pages on, if super cache is installed.
+add_action( 'bp_forums_new_forum', 'bp_core_clear_cache' );
+add_action( 'bp_forums_new_topic', 'bp_core_clear_cache' );
+add_action( 'bp_forums_new_post',  'bp_core_clear_cache' );
+
+
+/** Embeds *******************************************************************/
+
+/**
+ * Attempt to retrieve the oEmbed cache for a forum topic.
+ *
+ * Grabs the topic post ID and attempts to retrieve the oEmbed cache (if it exists)
+ * during the forum topic loop.  If no cache and link is embeddable, cache it.
+ *
+ * @since 1.5.0
+ *
+ * @see BP_Embed
+ * @see bp_embed_forum_cache()
+ * @see bp_embed_forum_save_cache()
+ */
+function bp_forums_embed() {
+	add_filter( 'embed_post_id',         'bp_get_the_topic_post_id'         );
+	add_filter( 'bp_embed_get_cache',    'bp_embed_forum_cache',      10, 3 );
+	add_action( 'bp_embed_update_cache', 'bp_embed_forum_save_cache', 10, 3 );
+}
+add_action( 'topic_loop_start', 'bp_forums_embed' );
+
+/**
+ * Used during {@link BP_Embed::parse_oembed()} via {@link bp_forums_embed()}.
+ *
+ * Wrapper function for {@link bb_get_postmeta()}.
+ *
+ * @since 1.5.0
+ *
+ * @param object $cache    Cache object.
+ * @param int    $id       ID of the forum being cached.
+ * @param string $cachekey Key to use with forum embed cache.
+ */
+function bp_embed_forum_cache( $cache, $id, $cachekey ) {
+	return bb_get_postmeta( $id, $cachekey );
+}
+
+/**
+ * Used during {@link BP_Embed::parse_oembed()} via {@link bp_forums_embed()}.
+ *
+ * Wrapper function for {@link bb_update_postmeta()}.
+ *
+ * @since 1.5.0
+ *
+ * @param object $cache    Cache object.
+ * @param string $cachekey Key to use with forum embed cache.
+ * @param int    $id       ID of the forum being cached.
+ */
+function bp_embed_forum_save_cache( $cache, $cachekey, $id ) {
+	bb_update_postmeta( $id, $cachekey, $cache );
+}
Index: /tags/2.4.3/src/bp-forums/bp-forums-loader.php
===================================================================
--- /tags/2.4.3/src/bp-forums/bp-forums-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/bp-forums-loader.php	(revision 10410)
@@ -0,0 +1,269 @@
+<?php
+/**
+ * BuddyPress Forums Loader.
+ *
+ * A discussion forums component. Comes bundled with bbPress stand-alone.
+ *
+ * Note: The bp-forums component has been retired. Use the bbPress WordPress
+ * plugin instead.
+ *
+ * @package BuddyPress
+ * @subpackage ForumsLoader
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+class BP_Forums_Component extends BP_Component {
+
+	/**
+	 * Start the forums component creation process.
+	 *
+	 * @since 1.5.0
+	 */
+	public function __construct() {
+		parent::start(
+			'forums',
+			__( 'Discussion Forums', 'buddypress' ),
+			buddypress()->plugin_dir,
+			array(
+				'adminbar_myaccount_order' => 80
+			)
+		);
+	}
+
+	/**
+	 * Set up bp-forums global settings.
+	 *
+	 * The BP_FORUMS_SLUG constant is deprecated, and only used here for
+	 * backwards compatibility.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_globals() for description of parameters.
+	 *
+	 * @param array $args See {@link BP_Component::setup_globals()}.
+	 */
+	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 );
+		}
+
+		// The location of the bbPress stand-alone config file.
+		$bbconfig = bp_core_get_root_option( 'bb-config-location' );
+		if ( '' !== $bbconfig ) {
+			$this->bbconfig = $bbconfig;
+		}
+
+		// All globals for messaging component.
+		// Note that global_tables is included in this array.
+		$globals = array(
+			'slug'                  => BP_FORUMS_SLUG,
+			'root_slug'             => isset( $bp->pages->forums->slug ) ? $bp->pages->forums->slug : BP_FORUMS_SLUG,
+			'has_directory'         => true,
+			'notification_callback' => 'messages_format_notifications',
+			'search_string'         => __( 'Search Forums...', 'buddypress' ),
+		);
+
+		parent::setup_globals( $globals );
+	}
+
+	/**
+	 * Include bp-forums files.
+	 *
+	 * @see BP_Component::includes() for description of parameters.
+	 *
+	 * @param array $includes See {@link BP_Component::includes()}.
+	 */
+	public function includes( $includes = array() ) {
+
+		// Files to include.
+		$includes = array(
+			'actions',
+			'screens',
+			'classes',
+			'filters',
+			'template',
+			'functions',
+		);
+
+		// bbPress stand-alone.
+		if ( ! defined( 'BB_PATH' ) ) {
+			$includes[] = 'bbpress-sa';
+		}
+
+		// Admin-specific code.
+		if ( is_admin() ) {
+			$includes[] = 'deprecated/1.6';
+			$includes[] = 'deprecated/1.7';
+		}
+
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Set up component navigation.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_nav() for a description of arguments.
+	 *
+	 * @param array $main_nav Optional. See BP_Component::setup_nav() for
+	 *                        description.
+	 * @param array $sub_nav  Optional. See BP_Component::setup_nav() for
+	 *                        description.
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		// Stop if forums haven't been set up yet.
+		if ( ! bp_forums_is_installed_correctly() ) {
+			return;
+		}
+
+		// Stop if there is no user displayed or logged in.
+		if ( ! is_user_logged_in() && ! bp_displayed_user_id() ) {
+			return;
+		}
+
+		// Determine user to use.
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		// User link.
+		$slug        = bp_get_forums_slug();
+		$forums_link = trailingslashit( $user_domain . $slug );
+
+		// Add 'Forums' to the main navigation.
+		$main_nav = array(
+			'name'                => __( 'Forums', 'buddypress' ),
+			'slug'                => $slug,
+			'position'            => 80,
+			'screen_function'     => 'bp_member_forums_screen_topics',
+			'default_subnav_slug' => 'topics',
+			'item_css_id'         => $this->id
+		);
+
+		// Topics started.
+		$sub_nav[] = array(
+			'name'            => __( 'Topics Started', 'buddypress' ),
+			'slug'            => 'topics',
+			'parent_url'      => $forums_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'bp_member_forums_screen_topics',
+			'position'        => 20,
+			'item_css_id'     => 'topics'
+		);
+
+		// Topics replied to.
+		$sub_nav[] = array(
+			'name'            => __( 'Replied To', 'buddypress' ),
+			'slug'            => 'replies',
+			'parent_url'      => $forums_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'bp_member_forums_screen_replies',
+			'position'        => 40,
+			'item_css_id'     => 'replies'
+		);
+
+		parent::setup_nav( $main_nav, $sub_nav );
+	}
+
+	/**
+	 * Set up bp-forums integration with the WordPress admin bar.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_admin_bar() for a description of arguments.
+	 *
+	 * @param array $wp_admin_nav See BP_Component::setup_admin_bar()
+	 *                            for description.
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		// Menus for logged in user.
+		if ( is_user_logged_in() ) {
+
+			// Setup the logged in user variables.
+			$forums_link = trailingslashit( bp_loggedin_user_domain() . bp_get_forums_slug() );
+
+			// Add the "My Account" sub menus.
+			$wp_admin_nav[] = array(
+				'parent' => buddypress()->my_account_menu_id,
+				'id'     => 'my-account-' . $this->id,
+				'title'  => __( 'Forums', 'buddypress' ),
+				'href'   => $forums_link
+			);
+
+			// Topics.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-topics-started',
+				'title'  => __( 'Topics Started', 'buddypress' ),
+				'href'   => $forums_link
+			);
+
+			// Replies.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-replies',
+				'title'  => __( 'Replies', 'buddypress' ),
+				'href'   => trailingslashit( $forums_link . 'replies' )
+			);
+
+			// Favorites.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-favorite-topics',
+				'title'  => __( 'Favorite Topics', 'buddypress' ),
+				'href'   => trailingslashit( $forums_link . 'favorites' )
+			);
+		}
+
+		parent::setup_admin_bar( $wp_admin_nav );
+	}
+
+	/**
+	 * Set up the title for pages and the <title> element.
+	 */
+	public function setup_title() {
+
+		// Adjust title based on view.
+		if ( bp_is_forums_component() ) {
+			$bp = buddypress();
+
+			if ( bp_is_my_profile() ) {
+				$bp->bp_options_title = __( 'Forums', 'buddypress' );
+			} else {
+				$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+					'item_id' => bp_displayed_user_id(),
+					'type'    => 'thumb',
+					'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_displayed_user_fullname() )
+				) );
+				$bp->bp_options_title  = bp_get_displayed_user_fullname();
+			}
+		}
+
+		parent::setup_title();
+	}
+}
+
+/**
+ * Set up the bp-forums component.
+ */
+function bp_setup_forums() {
+	buddypress()->forums = new BP_Forums_Component();
+}
+add_action( 'bp_setup_components', 'bp_setup_forums', 6 );
Index: /tags/2.4.3/src/bp-forums/bp-forums-screens.php
===================================================================
--- /tags/2.4.3/src/bp-forums/bp-forums-screens.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/bp-forums-screens.php	(revision 10410)
@@ -0,0 +1,315 @@
+<?php
+/**
+ * BuddyPress Forums Screen Functions.
+ *
+ * @package BuddyPress
+ * @subpackage ForumsScreens
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Load the Forums directory.
+ */
+function bp_forums_directory_forums_setup() {
+
+	// Get BuddyPress once.
+	$bp = buddypress();
+
+	if ( bp_is_forums_component() && ( !bp_current_action() || ( 'tag' == bp_current_action() && bp_action_variables() ) ) && !bp_current_item() ) {
+		if ( !bp_forums_has_directory() )
+			return false;
+
+		if ( !bp_forums_is_installed_correctly() ) {
+			bp_core_add_message( __( 'The forums component has not been set up yet.', 'buddypress' ), 'error' );
+			bp_core_redirect( bp_get_root_domain() );
+		}
+
+		bp_update_is_directory( true, 'forums' );
+
+		/**
+		 * Fires early in the initialization of bbPress-based areas of BuddyPress.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bbpress_init' );
+
+		// Check to see if the user has posted a new topic from the forums page.
+		if ( isset( $_POST['submit_topic'] ) && bp_is_active( 'forums' ) ) {
+			check_admin_referer( 'bp_forums_new_topic' );
+
+			$bp->groups->current_group = groups_get_group( array( 'group_id' => $_POST['topic_group_id'] ) );
+			if ( !empty( $bp->groups->current_group->id ) ) {
+				// Auto join this user if they are not yet a member of this group.
+				if ( !bp_current_user_can( 'bp_moderate' ) && 'public' == $bp->groups->current_group->status && !groups_is_user_member( bp_loggedin_user_id(), $bp->groups->current_group->id ) )
+					groups_join_group( $bp->groups->current_group->id );
+
+				$error_message = '';
+
+				$forum_id = groups_get_groupmeta( $bp->groups->current_group->id, 'forum_id' );
+				if ( !empty( $forum_id ) ) {
+					if ( empty( $_POST['topic_title'] ) )
+						$error_message = __( 'Please provide a title for your forum topic.', 'buddypress' );
+					else if ( empty( $_POST['topic_text'] ) )
+						$error_message = __( 'Forum posts cannot be empty. Please enter some text.', 'buddypress' );
+
+					if ( $error_message ) {
+						bp_core_add_message( $error_message, 'error' );
+						$redirect = bp_get_group_permalink( $bp->groups->current_group ) . 'forum';
+					} else {
+						if ( !$topic = groups_new_group_forum_topic( $_POST['topic_title'], $_POST['topic_text'], $_POST['topic_tags'], $forum_id ) ) {
+							bp_core_add_message( __( 'There was an error when creating the topic', 'buddypress'), 'error' );
+							$redirect = bp_get_group_permalink( $bp->groups->current_group ) . 'forum';
+						} else {
+							bp_core_add_message( __( 'The topic was created successfully', 'buddypress') );
+							$redirect = bp_get_group_permalink( $bp->groups->current_group ) . 'forum/topic/' . $topic->topic_slug . '/';
+						}
+					}
+
+					bp_core_redirect( $redirect );
+
+				} else {
+					bp_core_add_message( __( 'Please pick the group forum where you would like to post this topic.', 'buddypress' ), 'error' );
+					bp_core_redirect( add_query_arg( 'new', '', bp_get_forums_directory_permalink() ) );
+				}
+
+			} else {
+				bp_core_add_message( __( 'Please pick the group forum where you would like to post this topic.', 'buddypress' ), 'error' );
+				bp_core_redirect( add_query_arg( 'new', '', bp_get_forums_directory_permalink() ) );
+			}
+		}
+
+		/**
+		 * Fires right before the loading of the forums directory screen template file.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_forums_directory_forums_setup' );
+
+		/**
+		 * Filters the template to load for the forums directory screen.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $template Path to the forums template to load.
+		 */
+		bp_core_load_template( apply_filters( 'bp_forums_template_directory_forums_setup', 'forums/index' ) );
+	}
+}
+add_action( 'bp_screens', 'bp_forums_directory_forums_setup', 2 );
+
+/**
+ * Load the Topics Started screen.
+ */
+function bp_member_forums_screen_topics() {
+
+	/**
+	 * Fires right before the loading of the forums topics started screen template file.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_member_forums_screen_topics' );
+
+	/**
+	 * Filters the template to load for the forums topics started screen.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $template Path to the forums topics started template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_member_forums_screen_topics', 'members/single/home' ) );
+}
+
+/**
+ * Load the Replied To screen.
+ */
+function bp_member_forums_screen_replies() {
+
+	/**
+	 * Fires right before the loading of the forums replied to screen template file.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_member_forums_screen_replies' );
+
+	/**
+	 * Filters the template to load for the forums replied to screen.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $template Path to the forums replied to template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_member_forums_screen_replies', 'members/single/home' ) );
+}
+
+/**
+ * Load the template content for a user's Favorites forum tab.
+ *
+ * Note that this feature is not fully implemented at the moment.
+ */
+function bp_member_forums_screen_favorites() {
+
+	/**
+	 * Fires right before the loading of the forums favorites screen template file.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_member_forums_screen_favorites' );
+
+	/**
+	 * Filters the template to load for the forums favorites screen.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $template Path to the forums favorites template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_member_forums_screen_favorites', 'members/single/home' ) );
+}
+
+/**
+ * Load a single forum page.
+ */
+function bp_forums_screen_single_forum() {
+
+	if ( !bp_is_forums_component() || !bp_is_current_action( 'forum' ) || !bp_action_variable( 0 ) )
+		return false;
+
+	/**
+	 * Fires right before the loading of the forums single forum screen template file.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_forums_screen_single_forum' );
+
+	/**
+	 * Filters the template to load for the forums single forum screen.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $template Path to the forums single forum template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_forums_screen_single_forum', 'forums/single/forum' ) );
+}
+add_action( 'bp_screens', 'bp_forums_screen_single_forum' );
+
+/**
+ * Load a single forum topic page.
+ */
+function bp_forums_screen_single_topic() {
+
+	if ( !bp_is_forums_component() || !bp_is_current_action( 'topic' ) || !bp_action_variable( 0 ) )
+		return false;
+
+	/**
+	 * Fires right before the loading of the forums single topic screen template file.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_forums_screen_single_topic' );
+
+	/**
+	 * Filters the template to load for the forums single topic screen.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $template Path to the forums single topic template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_forums_screen_single_topic', 'forums/single/topic' ) );
+}
+add_action( 'bp_screens', 'bp_forums_screen_single_topic' );
+
+
+/** Theme Compatibility *******************************************************/
+
+/**
+ * The main theme compat class for legacy BuddyPress forums.
+ *
+ * This class sets up the necessary theme compatibility actions to safely output
+ * old forum template parts to the_title and the_content areas of a theme.
+ *
+ * @since 1.7.0
+ */
+class BP_Forum_Legacy_Theme_Compat {
+
+	/**
+	 * Set up theme compatibility for the legacy forums component.
+	 *
+	 * @since 1.7.0
+	 */
+	public function __construct() {
+		add_action( 'bp_setup_theme_compat', array( $this, 'is_legacy_forum' ) );
+	}
+
+	/**
+	 * Are we looking at something that needs old forum theme compatibility?
+	 *
+	 * @since 1.7.0
+	 */
+	public function is_legacy_forum() {
+
+		// Bail if not looking at a group.
+		if ( ! bp_is_forums_component() )
+			return;
+
+		// Forum Directory.
+		if ( ( ! bp_current_action() || ( 'tag' == bp_current_action() && bp_action_variables() ) ) && ! bp_current_item() ) {
+
+			if ( ! bp_forums_has_directory() )
+				return false;
+
+			if ( ! bp_forums_is_installed_correctly() ) {
+				bp_core_add_message( __( 'The forums component has not been set up yet.', 'buddypress' ), 'error' );
+				bp_core_redirect( bp_get_root_domain() );
+			}
+
+			bp_update_is_directory( true, 'forums' );
+
+			do_action( 'bp_forums_directory_forums_setup' );
+
+			add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'directory_dummy_post' ) );
+			add_filter( 'bp_replace_the_content',                    array( $this, 'directory_content'    ) );
+
+		}
+
+	}
+
+	/** Directory *************************************************************/
+
+	/**
+	 * Update the global $post with directory data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function directory_dummy_post() {
+
+		// Title based on ability to create groups.
+		if ( is_user_logged_in() ) {
+			$title = __( 'Forums', 'buddypress' ) . '&nbsp;<a class="button show-hide-new bp-title-button" href="#new-topic" id="new-topic-button">' . __( 'New Topic', 'buddypress' ) . '</a>';
+		} else {
+			$title = __( 'Forums', 'buddypress' );
+		}
+
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => $title,
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with the old forum index template part.
+	 *
+	 * @since 1.7.0
+	 */
+	public function directory_content() {
+		return bp_buffer_template_part( 'forums/index', null, false );
+	}
+}
+new BP_Forum_Legacy_Theme_Compat();
Index: /tags/2.4.3/src/bp-forums/bp-forums-template.php
===================================================================
--- /tags/2.4.3/src/bp-forums/bp-forums-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/bp-forums-template.php	(revision 10410)
@@ -0,0 +1,2956 @@
+<?php
+/**
+ * BuddyPress Forums Template Tags.
+ *
+ * @package BuddyPress
+ * @subpackage ForumsTemplate
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the forums component slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_forums_slug()
+ */
+function bp_forums_slug() {
+	echo bp_get_forums_slug();
+}
+	/**
+	 * Return the forums component slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string Slug for the forums component.
+	 */
+	function bp_get_forums_slug() {
+		/**
+		 * Filters the forums component slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $slug Forums component slug.
+		 */
+		return apply_filters( 'bp_get_forums_slug', buddypress()->forums->slug );
+	}
+
+/**
+ * Output the forums component root slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_forums_root_slug()
+ */
+function bp_forums_root_slug() {
+	echo bp_get_forums_root_slug();
+}
+	/**
+	 * Return the forums component root slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string Root slug for the forums component.
+	 */
+	function bp_get_forums_root_slug() {
+		/**
+		 * Filters the forums component root slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $root_slug Forums component root slug.
+		 */
+		return apply_filters( 'bp_get_forums_root_slug', buddypress()->forums->root_slug );
+	}
+
+/**
+ * Output permalink for the forum directory.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_forums_directory_permalink()
+ */
+function bp_forums_directory_permalink() {
+	echo esc_url( bp_get_forums_directory_permalink() );
+}
+	/**
+	 * Return permalink for the forum directory.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses apply_filters()
+	 * @uses trailingslashit()
+	 * @uses bp_get_root_domain()
+	 * @uses bp_get_forums_root_slug()
+	 *
+	 * @return string The permalink for the forums component directory.
+	 */
+	function bp_get_forums_directory_permalink() {
+
+		/**
+		 * Filters the permalink for the forum directory.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Permalink for the forum directory.
+		 */
+		return apply_filters( 'bp_get_forums_directory_permalink', trailingslashit( bp_get_root_domain() . '/' . bp_get_forums_root_slug() ) );
+	}
+
+/**
+ * The main forums template loop class.
+ *
+ * Responsible for loading a group of forum topics into a loop for display.
+ */
+class BP_Forums_Template_Forum {
+	/**
+	 * The loop iterator.
+	 *
+	 * @var int
+	 */
+	public $current_topic = -1;
+
+	/**
+	 * The number of topics returned by the paged query.
+	 *
+	 * @var int
+	 */
+	public $topic_count;
+
+	/**
+	 * Array of topics located by the query.
+	 *
+	 * @var array
+	 */
+	public $topics;
+
+	/**
+	 * The topic object currently being iterated on.
+	 *
+	 * @var object
+	 */
+	public $topic;
+
+	/**
+	 * The ID of the forum whose topics are being queried.
+	 *
+	 * @var int
+	 */
+	public $forum_id;
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @var bool
+	 */
+	public $in_the_loop;
+
+	/**
+	 * The page number being requested.
+	 *
+	 * @var int
+	 */
+	public $pag_page;
+
+	/**
+	 * The number of items being requested per page.
+	 *
+	 * @var int
+	 */
+	public $pag_num;
+
+	/**
+	 * An HTML string containing pagination links.
+	 *
+	 * @var string
+	 */
+	public $pag_links;
+
+	/**
+	 * The total number of topics matching the query parameters.
+	 *
+	 * @var int
+	 */
+	public $total_topic_count;
+
+	/**
+	 * Whether requesting a single topic. Not currently used.
+	 *
+	 * @var bool
+	 */
+	public $single_topic = false;
+
+	/**
+	 * Term to sort by. Not currently used.
+	 *
+	 * @var string
+	 */
+	public $sort_by;
+
+	/**
+	 * Sort order. Not currently used.
+	 *
+	 * @var string
+	 */
+	public $order;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @param string   $type         The 'type' is the sort order/kind. 'newest',
+	 *                               'popular', 'unreplied', 'tags'.
+	 * @param int      $forum_id     The ID of the forum for which topics are being
+	 *                               queried.
+	 * @param int      $user_id      The ID of the user to whom topics should be
+	 *                               limited. Pass false to remove this filter.
+	 * @param int      $page         The number of the page being requested.
+	 * @param int      $per_page     The number of items being requested per page.
+	 * @param int      $max          Max amount to retrieve.
+	 * @param string   $no_stickies  Requested sticky format.
+	 * @param string   $search_terms Filter results by a string.
+	 * @param int|bool $offset       Optional. Offset results by a given numeric value.
+	 * @param int|bool $number       Optional. Total number of items to retrieve.
+	 */
+	function __construct( $type, $forum_id, $user_id, $page, $per_page, $max, $no_stickies, $search_terms, $offset = false, $number = false ) {
+		$bp = buddypress();
+
+		$this->pag_page     = $page;
+		$this->pag_num      = $per_page;
+		$this->type         = $type;
+		$this->search_terms = $search_terms;
+		$this->forum_id     = $forum_id;
+		$this->offset	    = $offset;
+		$this->number	    = $number;
+
+		switch ( $type ) {
+			case 'newest': default:
+				$this->topics = bp_forums_get_forum_topics( array( 'user_id' => $user_id, 'forum_id' => $forum_id, 'filter' => $search_terms, 'page' => $this->pag_page, 'per_page' => $this->pag_num, 'show_stickies' => $no_stickies, 'offset' => $offset, 'number' => $number ) );
+				break;
+
+			case 'popular':
+				$this->topics = bp_forums_get_forum_topics( array( 'user_id' => $user_id, 'type' => 'popular', 'filter' => $search_terms, 'forum_id' => $forum_id, 'page' => $this->pag_page, 'per_page' => $this->pag_num, 'show_stickies' => $no_stickies, 'offset' => $offset, 'number' => $number ) );
+				break;
+
+			case 'unreplied':
+				$this->topics = bp_forums_get_forum_topics( array( 'user_id' => $user_id, 'type' => 'unreplied', 'filter' => $search_terms, 'forum_id' => $forum_id, 'page' => $this->pag_page, 'per_page' => $this->pag_num, 'show_stickies' => $no_stickies, 'offset' => $offset, 'number' => $number ) );
+				break;
+
+			case 'tags':
+				$this->topics = bp_forums_get_forum_topics( array( 'user_id' => $user_id, 'type' => 'tags', 'filter' => $search_terms, 'forum_id' => $forum_id, 'page' => $this->pag_page, 'per_page' => $this->pag_num, 'show_stickies' => $no_stickies, 'offset' => $offset, 'number' => $number ) );
+				break;
+		}
+
+		/**
+		 * Filters the forums template topics.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param array  $topics      Array of topics being set for display.
+		 * @param string $type        The sort order/kind. 'newest',
+		 *                            'popular', 'unreplied', 'tags'.
+		 * @param int    $forum_id    ID for the current forum.
+		 * @param int    $per_page    The number of the page being requested.
+		 * @param int    $max         The max number of posts to show.
+		 * @param string $no_stickies Requested sticky format.
+		 */
+		$this->topics = apply_filters( 'bp_forums_template_topics', $this->topics, $type, $forum_id, $per_page, $max, $no_stickies );
+
+		if ( !(int) $this->topics ) {
+			$this->topic_count       = 0;
+			$this->total_topic_count = 0;
+		} else {
+			// Get a total topic count, for use in pagination. This value will differ
+			// depending on scope.
+			if ( !empty( $forum_id ) ) {
+				// Group forums.
+				$topic_count = bp_forums_get_forum( $forum_id );
+				$topic_count = (int) $topic_count->topics;
+			} else if ( !empty( $bp->groups->current_group ) ) {
+				$topic_count = (int)groups_total_public_forum_topic_count( $type );
+			} else if ( bp_is_user_forums_started() || ( bp_is_directory() && $user_id ) ) {
+				// This covers the case of Profile > Forums > Topics Started, as
+				// well as Forum Directory > My Topics.
+				$topic_count = bp_forums_total_topic_count_for_user( bp_displayed_user_id(), $type );
+			} else if ( bp_is_user_forums_replied_to() ) {
+				// Profile > Forums > Replied To.
+				$topic_count = bp_forums_total_replied_count_for_user( bp_displayed_user_id(), $type );
+			} else if ( 'tags' == $type ) {
+				$tag         = bb_get_tag( $search_terms );
+				$topic_count = $tag->count;
+			} else {
+				// For forum directories (All Topics), get a true count
+				$status = bp_current_user_can( 'bp_moderate' ) ? 'all' : 'public'; // @todo: member-of.
+				$topic_count = (int)groups_total_forum_topic_count( $status, $search_terms );
+			}
+
+			if ( !$max || $max >= $topic_count ) {
+				$this->total_topic_count = $topic_count;
+			} else {
+				$this->total_topic_count = (int) $max;
+			}
+
+			if ( $max ) {
+				if ( $max >= count($this->topics) ) {
+					$this->topic_count = count( $this->topics );
+				} else {
+					$this->topic_count = (int) $max;
+				}
+			} else {
+				$this->topic_count = count( $this->topics );
+			}
+		}
+
+		/**
+		 * Filters the topic count for the forum being displayed.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int    $topic_count Topic count to be displayed.
+		 * @param array  $topics      Array of topics to be displayed.
+		 * @param string $type        The sort order/kind. 'newest',
+		 *                            'popular', 'unreplied', 'tags'.
+		 * @param int    $forum_id    ID for the current forum.
+		 * @param int    $per_page    The number of the page being requested.
+		 * @param int    $max         The max number of posts to show.
+		 * @param string $no_stickies Requested sticky format.
+		 */
+		$this->topic_count = apply_filters_ref_array( 'bp_forums_template_topic_count', array( $this->topic_count, &$this->topics, $type, $forum_id, $per_page, $max, $no_stickies ) );
+
+		/**
+		 * Filters the total topic count for the forum being displayed.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int    $total_topic_count Total topic count found.
+		 * @param int    $topic_count       Topic count to be displayed.
+		 * @param array  $topics            Array of topics to be displayed.
+		 * @param string $type              The sort order/kind. 'newest',
+		 *                                  'popular', 'unreplied', 'tags'.
+		 * @param int    $forum_id          ID for the current forum.
+		 * @param int    $per_page          The number of the page being requested.
+		 * @param int    $max               The max number of posts to show.
+		 * @param string $no_stickies       Requested sticky format.
+		 */
+		$this->total_topic_count = apply_filters_ref_array( 'bp_forums_template_total_topic_count', array( $this->total_topic_count, $this->topic_count, &$this->topics, $type, $forum_id, $per_page, $max, $no_stickies ) );
+
+		// Fetch extra information for topics, so we don't have to query inside the loop.
+		$this->topics = bp_forums_get_topic_extras( $this->topics );
+
+		if ( (int) $this->total_topic_count && (int) $this->pag_num ) {
+			$this->pag_links = paginate_links( array(
+				'base'      => add_query_arg( array( 'p' => '%#%', 'n' => $this->pag_num ) ),
+				'format'    => '',
+				'total'     => ceil( (int) $this->total_topic_count / (int) $this->pag_num),
+				'current'   => $this->pag_page,
+				'prev_text' => _x( '&larr;', 'Forum topic pagination previous text', 'buddypress' ),
+				'next_text' => _x( '&rarr;', 'Forum topic pagination next text', 'buddypress' ),
+				'mid_size'  => 1,
+				'add_args'  => array(),
+			) );
+		}
+	}
+
+	/**
+	 * Whether there are topics available in the loop.
+	 *
+	 * @see bp_has_forum_topics()
+	 *
+	 * @return bool True if there are items in the loop, otherwise false.
+	 */
+	function has_topics() {
+		if ( $this->topic_count ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Set up the next topic and iterate index.
+	 *
+	 * @return object The next topic to iterate over.
+	 */
+	function next_topic() {
+		$this->current_topic++;
+		$this->topic = $this->topics[$this->current_topic];
+
+		return $this->topic;
+	}
+
+	/**
+	 * Rewind the topics and reset topic index.
+	 */
+	function rewind_topics() {
+		$this->current_topic = -1;
+		if ( $this->topic_count > 0 ) {
+			$this->topic = $this->topics[0];
+		}
+	}
+
+	/**
+	 * Whether there are blogs left in the loop to iterate over.
+	 *
+	 * This method is used by {@link bp_forum_topics()} as part of the while loop
+	 * that controls iteration inside the blogs loop, eg:
+	 *     while ( bp_forum_topics() ) { ...
+	 *
+	 * @see bp_forum_topics()
+	 *
+	 * @return bool True if there are more topics to show, otherwise false.
+	 */
+	function user_topics() {
+		if ( $this->current_topic + 1 < $this->topic_count ) {
+			return true;
+		} elseif ( $this->current_topic + 1 == $this->topic_count ) {
+
+			/**
+			 * Fires right before the rewinding of user topics.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action('forum_loop_end');
+			// Do some cleaning up after the loop.
+			$this->rewind_topics();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	/**
+	 * Set up the current topic in the loop.
+	 *
+	 * @see bp_the_forum_topic()
+	 */
+	function the_topic() {
+		global $topic;
+
+		$this->in_the_loop = true;
+		$this->topic = $this->next_topic();
+		$this->topic = (object)$this->topic;
+
+		if ( $this->current_topic == 0 ) {
+
+			/**
+			 * Fires if the current topic item is the first in the topic loop.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'forum_loop_start' );
+		}
+	}
+}
+
+/**
+ * Initiate the forum topics loop.
+ *
+ * Like other BuddyPress custom loops, the default arguments for this function
+ * are determined dynamically, depending on your current page. All of these
+ * $defaults can be overridden in the $args parameter.
+ *
+ * @uses apply_filters() Filter 'bp_has_topics' to manipulate the
+ *       $forums_template global before it's rendered, or to modify the value
+ *       of has_topics().
+ *
+ * @param array|string $args {
+ *     Arguments for limiting the contents of the forum topics loop.
+ *
+ *     @type string      $type         The 'type' is the sort order/kind. 'newest',
+ *                                     'popular', 'unreplied', 'tags'. Default: 'newest'.
+ *     @type int         $forum_id     The ID of the forum for which topics are being
+ *                                     queried. Default: the ID of the forum belonging to the current
+ *                                     group, if available.
+ *     @type int         $user_id      The ID of a user to whom to limit results. If viewing
+ *                                     a member's profile, defaults to that member's ID; otherwise
+ *                                     defaults to 0.
+ *     @type int         $page         The number of the page being requested. Default: 1, or
+ *                                     the value of $_GET['p'].
+ *     @type int         $per_pag      The number items to return per page. Default: 20, or
+ *                                     the value of $_GET['n'].
+ *     @type int         $max          Optional. Max records to return. Default: false (no max).
+ *     @type int         $number       Optional. Number of records to return. Default: false.
+ *     @type int         $offset       Optional. Offset results by a given value.
+ *                                     Default: false.
+ *     @type string      $search_terms Optional. A string to which results should be
+ *                                     limited. Default: false, or the value of $_GET['fs'].
+ *     @type string|bool $do_stickies  Whether to move stickies to the top of
+ *                                     the sort order. Default: true if looking at a group forum,
+ *                                     otherwise false.
+ * }
+ * @return bool True when forum topics are found corresponding to the args,
+ *         false otherwise.
+ */
+function bp_has_forum_topics( $args = '' ) {
+	global $forum_template;
+
+	$bp = buddypress();
+
+	/*
+	 * Set the defaults based on the current page. Any of these will be overridden
+	 * if arguments are directly passed into the loop. Custom plugins should always
+	 * pass their parameters directly to the loop.
+	 */
+	$type         = 'newest';
+	$user_id      = 0;
+	$forum_id     = false;
+	$search_terms = false;
+	$do_stickies  = false;
+
+	// User filtering.
+	if ( bp_displayed_user_id() )
+		$user_id = bp_displayed_user_id();
+
+	// "Replied" query must be manually modified
+	if ( 'replies' == bp_current_action() ) {
+		$user_id = 0; // User id must be handled manually by the filter, not by BB_Query.
+
+		add_filter( 'get_topics_distinct',   'bp_forums_add_replied_distinct_sql', 20 );
+		add_filter( 'get_topics_join', 	     'bp_forums_add_replied_join_sql', 20 );
+		add_filter( 'get_topics_where',      'bp_forums_add_replied_where_sql', 20  );
+	}
+
+	// If we're in a single group, set this group's forum_id.
+	if ( !$forum_id && !empty( $bp->groups->current_group ) ) {
+		$bp->groups->current_group->forum_id = groups_get_groupmeta( $bp->groups->current_group->id, 'forum_id' );
+
+		// If it turns out there is no forum for this group, return false so
+		// we don't fetch all global topics.
+		if ( empty( $bp->groups->current_group->forum_id ) )
+			return false;
+
+		$forum_id = $bp->groups->current_group->forum_id;
+	}
+
+	// If $_GET['fs'] is set, let's auto populate the search_terms var.
+	if ( bp_is_directory() && !empty( $_GET['fs'] ) )
+		$search_terms = $_GET['fs'];
+
+	// Get the pagination arguments from $_REQUEST.
+	$page     = isset( $_REQUEST['p'] ) ? intval( $_REQUEST['p'] ) : 1;
+	$per_page = isset( $_REQUEST['n'] ) ? intval( $_REQUEST['n'] ) : 20;
+
+	// By default, stickies are only pushed to the top of the order on individual group forums.
+	if ( bp_is_group_forum() )
+		$do_stickies = true;
+
+	$defaults = array(
+		'type'         => $type,
+		'forum_id'     => $forum_id,
+		'user_id'      => $user_id,
+		'page'         => $page,
+		'per_page'     => $per_page,
+		'max'          => false,
+		'number'       => false,
+		'offset'       => false,
+		'search_terms' => $search_terms,
+		'do_stickies'  => $do_stickies
+	);
+
+	$r = bp_parse_args( $args, $defaults, 'has_forum_topics' );
+	extract( $r );
+
+	// If we're viewing a tag URL in the directory, let's override the type and
+	// set it to tags and the filter to the tag name.
+	if ( bp_is_current_action( 'tag' ) && $search_terms = bp_action_variable( 0 ) ) {
+		$type = 'tags';
+	}
+
+	/** Sticky logic ******************************************************************/
+
+	if ( $do_stickies ) {
+		// Fetch the stickies.
+		$stickies_template = new BP_Forums_Template_Forum( $type, $forum_id, $user_id, 0, 0, $max, 'sticky', $search_terms );
+
+		// If stickies are found, try merging them.
+		if ( $stickies_template->has_topics() ) {
+
+			// If stickies are for current $page.
+			$page_start_num = ( ( $page - 1 ) * $per_page ) + 1;
+			$page_end_num 	= $page * $per_page <= $stickies_template->total_topic_count ? $page * $per_page : $stickies_template->total_topic_count;
+
+			// Calculate the number of sticky topics that will be shown on this page.
+			if ( $stickies_template->topic_count < $page_start_num ) {
+				$this_page_stickies = 0;
+			} else {
+				$this_page_stickies = $stickies_template->topic_count - $per_page * floor( $stickies_template->topic_count / $per_page ) * ( $page - 1 ); // Total stickies minus sticky count through this page.
+
+				// $this_page_stickies cannot be more than $per_page or less than 0
+				if ( $this_page_stickies > $per_page )
+					$this_page_stickies = $per_page;
+				else if ( $this_page_stickies < 0 )
+					$this_page_stickies = 0;
+			}
+
+			// Calculate the total number of topics that will be shown on this page.
+			$this_page_topics = $stickies_template->total_topic_count >= ( $page * $per_page ) ? $per_page : $page_end_num - ( $page_start_num - 1 );
+
+			// If the number of stickies to be shown is less than $per_page, fetch some
+			// non-stickies to fill in the rest.
+			if ( $this_page_stickies < $this_page_topics ) {
+				// How many non-stickies do we need?
+				$non_sticky_number = $this_page_topics - $this_page_stickies;
+
+				// Calculate the non-sticky offset
+				// How many non-stickies on all pages up to this point?
+				$non_sticky_total = $page_end_num - $stickies_template->topic_count;
+
+				// The offset is the number of total non-stickies, less the number
+				// to be shown on this page.
+				$non_sticky_offset = $non_sticky_total - $non_sticky_number;
+
+				// Fetch the non-stickies.
+				$forum_template = new BP_Forums_Template_Forum( $type, $forum_id, $user_id, 1, $per_page, $max, 'no', $search_terms, $non_sticky_offset, $non_sticky_number );
+
+				// If there are stickies to merge on this page, do it now.
+				if ( $this_page_stickies ) {
+					// Correct the topic_count.
+					$forum_template->topic_count += (int) $this_page_stickies;
+
+					// Figure out which stickies need to be included.
+					$this_page_sticky_topics = array_slice( $stickies_template->topics, 0 - $this_page_stickies );
+
+					// Merge these topics into the forum template.
+					$forum_template->topics = array_merge( $this_page_sticky_topics, (array) $forum_template->topics );
+				}
+			} else {
+				// This page has no non-stickies.
+				$forum_template = $stickies_template;
+
+				// Adjust the topic count and trim the topics.
+				$forum_template->topic_count = $this_page_stickies;
+				$forum_template->topics      = array_slice( $forum_template->topics, $page - 1 );
+			}
+
+			// Because we're using a manual offset and number for the topic query, we
+			// must set the page number manually, and recalculate the pagination links.
+			$forum_template->pag_num     = $per_page;
+			$forum_template->pag_page    = $page;
+
+			$forum_template->pag_links = paginate_links( array(
+				'base'      => add_query_arg( array( 'p' => '%#%', 'n' => $forum_template->pag_num ) ),
+				'format'    => '',
+				'total'     => ceil( (int) $forum_template->total_topic_count / (int) $forum_template->pag_num ),
+				'current'   => $forum_template->pag_page,
+				'prev_text' => _x( '&larr;', 'Forum topic pagination previous text', 'buddypress' ),
+				'next_text' => _x( '&rarr;', 'Forum topic pagination next text', 'buddypress' ),
+				'mid_size'  => 1,
+				'add_args'  => array(),
+			) );
+
+		} else {
+			// Fetch the non-sticky topics if no stickies were found.
+			$forum_template = new BP_Forums_Template_Forum( $type, $forum_id, $user_id, $page, $per_page, $max, 'all', $search_terms );
+		}
+	} else {
+		// When skipping the sticky logic, just pull up the forum topics like usual.
+		$forum_template = new BP_Forums_Template_Forum( $type, $forum_id, $user_id, $page, $per_page, $max, 'all', $search_terms );
+	}
+
+	/**
+	 * Filters whether or not there are topics to display.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool   $value          Whether or not there are topics to display.
+	 * @param object $forum_template Global $forum_template object.
+	 */
+	return apply_filters( 'bp_has_topics', $forum_template->has_topics(), $forum_template );
+}
+
+/**
+ * Determine whether there are still topics left in the loop.
+ *
+ * @global BP_Forums_Template_Forum $forum_template Template global.
+ *
+ * @return bool Returns true when topics are found.
+ */
+function bp_forum_topics() {
+	global $forum_template;
+	return $forum_template->user_topics();
+}
+
+/**
+ * Get the current topic object in the loop.
+ *
+ * @global BP_Forums_Template_Forum $forum_template Template global.
+ *
+ * @return object The current topic object.
+ */
+function bp_the_forum_topic() {
+	global $forum_template;
+	return $forum_template->the_topic();
+}
+
+/**
+ * Output the ID of the current topic in the loop.
+ */
+function bp_the_topic_id() {
+	echo bp_get_the_topic_id();
+}
+	/**
+	 * Return the ID of the current topic in the loop.
+	 *
+	 * @return int ID of the current topic in the loop.
+	 */
+	function bp_get_the_topic_id() {
+		global $forum_template;
+
+		/**
+		 * Filters the ID of the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $topic_id ID for the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_id', $forum_template->topic->topic_id );
+	}
+
+/**
+ * Output the title of the current topic in the loop.
+ */
+function bp_the_topic_title() {
+	echo bp_get_the_topic_title();
+}
+	/**
+	 * Return the title of the current topic in the loop.
+	 *
+	 * @return string Title of the current topic in the loop.
+	 */
+	function bp_get_the_topic_title() {
+		global $forum_template;
+
+		/**
+		 * Filters the title of the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $topic_title Title for the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_title', stripslashes( $forum_template->topic->topic_title ) );
+	}
+
+/**
+ * Output the slug of the current topic in the loop.
+ */
+function bp_the_topic_slug() {
+	echo bp_get_the_topic_slug();
+}
+	/**
+	 * Return the slug of the current topic in the loop.
+	 *
+	 * @return string Slug of the current topic in the loop.
+	 */
+	function bp_get_the_topic_slug() {
+		global $forum_template;
+
+		/**
+		 * Filters the slug of the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $topic_slug Slug for the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_slug', $forum_template->topic->topic_slug );
+	}
+
+/**
+ * Output the text of the first post in the current topic in the loop.
+ */
+function bp_the_topic_text() {
+	echo bp_get_the_topic_text();
+}
+	/**
+	 * Return the text of the first post in the current topic in the loop.
+	 *
+	 * @return string Text of the first post in the current topic.
+	 */
+	function bp_get_the_topic_text() {
+		global $forum_template;
+
+		$post = bb_get_first_post( (int) $forum_template->topic->topic_id, false );
+
+		/**
+		 * Filters the text of the first post in the current topic in the loop.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $post_text Text for the first post in the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_text', esc_attr( $post->post_text ) );
+	}
+
+/**
+ * Output the ID of the user who posted the current topic in the loop.
+ */
+function bp_the_topic_poster_id() {
+	echo bp_get_the_topic_poster_id();
+}
+	/**
+	 * Return the ID of the user who posted the current topic in the loop.
+	 *
+	 * @return int ID of the user who posted the current topic.
+	 */
+	function bp_get_the_topic_poster_id() {
+		global $forum_template;
+
+		/**
+		 * Filters the ID of the user who posted the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $topic_poster ID for the user.
+		 */
+		return apply_filters( 'bp_get_the_topic_poster_id', $forum_template->topic->topic_poster );
+	}
+
+/**
+ * Output the avatar of the user who posted the current topic in the loop.
+ *
+ * @see bp_get_the_topic_poster_avatar() for a description of arguments.
+ *
+ * @param array|string $args See {@link bp_get_the_topic_poster_avatar()}.
+ */
+function bp_the_topic_poster_avatar( $args = '' ) {
+	echo bp_get_the_topic_poster_avatar( $args );
+}
+	/**
+	 * Return the avatar of the user who posted the current topic in the loop.
+	 *
+	 * @param array|string $args {
+	 *     Arguments for building the avatar.
+	 *     @type string $type   Avatar type. 'thumb' or 'full'. Default:
+	 *                          'thumb'.
+	 *     @type int    $width  Width of the avatar, in pixels. Default: the
+	 *                          width corresponding to $type.
+	 *                          See {@link bp_core_fetch_avatar()}.
+	 *     @type int    $height Height of the avatar, in pixels. Default: the
+	 *                          height corresponding to $type.
+	 *                          See {@link bp_core_fetch_avatar()}.
+	 *     @type string $alt    The text of the image's 'alt' attribute.
+	 *                          Default: 'Profile picture of [user name]'.
+	 * }
+	 * @return string HTML of user avatar.
+	 */
+	function bp_get_the_topic_poster_avatar( $args = '' ) {
+		global $forum_template;
+
+		$defaults = array(
+			'type'   => 'thumb',
+			'width'  => false,
+			'height' => false,
+			'alt'    => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_core_get_user_displayname( $forum_template->topic->topic_poster ) )
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		/**
+		 * Filters the avatar for the user who posted the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value HTML for the display of the user's avatar.
+		 */
+		return apply_filters( 'bp_get_the_topic_poster_avatar', bp_core_fetch_avatar( array( 'item_id' => $forum_template->topic->topic_poster, 'type' => $type, 'width' => $width, 'height' => $height, 'alt' => $alt ) ) );
+	}
+
+/**
+ * Output the name of the user who posted the current topic in the loop.
+ */
+function bp_the_topic_poster_name() {
+	echo bp_get_the_topic_poster_name();
+}
+	/**
+	 * Return the name of the user who posted the current topic in the loop.
+	 *
+	 * @return string Name of the user who posted the current topic.
+	 */
+	function bp_get_the_topic_poster_name() {
+		global $forum_template;
+
+		$poster_id = ( empty( $forum_template->topic->poster_id ) ) ? $forum_template->topic->topic_poster : $forum_template->topic->poster_id;
+
+		if ( !$name = bp_core_get_userlink( $poster_id ) )
+			return __( 'Deleted User', 'buddypress' );
+
+		/**
+		 * Filters the name of the user who posted the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $name Name for the user who posted the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_poster_name', $name );
+	}
+
+/**
+ * Output the ID of the object associated with the current topic in the loop.
+ */
+function bp_the_topic_object_id() {
+	echo bp_get_the_topic_object_id();
+}
+	/**
+	 * Return the ID of the object associated with the current topic in the loop.
+	 *
+	 * Objects are things like associated groups.
+	 *
+	 * @return int ID of the associated object.
+	 */
+	function bp_get_the_topic_object_id() {
+		global $forum_template;
+
+		/**
+		 * Filters the ID of the object associated with the current topic in the loop.
+		 *
+		 * Objects are things like associated groups.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int $object_id ID for the object associated with the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_object_id', $forum_template->topic->object_id );
+	}
+
+/**
+ * Output the name of the object associated with the current topic in the loop.
+ */
+function bp_the_topic_object_name() {
+	echo bp_get_the_topic_object_name();
+}
+	/**
+	 * Return the name of the object associated with the current topic in the loop.
+	 *
+	 * Objects are things like groups. So this function would return the
+	 * name of the group associated with the forum topic, if it exists.
+	 *
+	 * @return string Object name.
+	 */
+	function bp_get_the_topic_object_name() {
+		global $forum_template;
+
+		if ( isset( $forum_template->topic->object_name ) )
+			$retval = $forum_template->topic->object_name;
+		else
+			$retval = '';
+
+		/**
+		 * Filters the name of the object associated with the current topic in the loop.
+		 *
+		 * Objects are things like groups. So this filter would return the
+		 * name of the group associated with the forum topic, if it exists.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $retval Name for the object associated with the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_object_name', $retval );
+	}
+
+/**
+ * Output the slug of the object associated with the current topic in the loop.
+ */
+function bp_the_topic_object_slug() {
+	echo bp_get_the_topic_object_slug();
+}
+	/**
+	 * Return the slug of the object associated with the current topic in the loop.
+	 *
+	 * Objects are things like groups. So this function would return the
+	 * slug of the group associated with the forum topic, if it exists.
+	 *
+	 * @return string Object slug.
+	 */
+	function bp_get_the_topic_object_slug() {
+		global $forum_template;
+
+		/**
+		 * Filters the slug of the object associated with the current topic in the loop.
+		 *
+		 * Objects are things like groups. So this filter would return the
+		 * slug of the group associated with the forum topic, if it exists.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $object_slug Slug for the object associated with the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_object_slug', $forum_template->topic->object_slug );
+	}
+
+/**
+ * Output the permalink of the object associated with the current topic in the loop.
+ */
+function bp_the_topic_object_permalink() {
+	echo bp_get_the_topic_object_permalink();
+}
+	/**
+	 * Return the permalink of the object associated with the current topic in the loop.
+	 *
+	 * Objects are things like groups. So this function would return the
+	 * permalink of the group associated with the forum topic, if it exists.
+	 *
+	 * @return string Object permalink.
+	 */
+	function bp_get_the_topic_object_permalink() {
+
+		// Currently this will only work with group forums, extended support in the future.
+		if ( bp_is_active( 'groups' ) ) {
+			$permalink = trailingslashit( bp_get_groups_directory_permalink() . bp_get_the_topic_object_slug() . '/forum' );
+		} else {
+			$permalink = '';
+		}
+
+		/**
+		 * Filters the permalink of the object associated with the current topic in the loop.
+		 *
+		 * Objects are things like groups. So this filter would return the
+		 * slug of the group associated with the forum topic, if it exists.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $permalink Permalink for the object associated with the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_object_permalink', $permalink );
+	}
+
+/**
+ * Output the linked name of the user who last posted to the current topic in the loop.
+ */
+function bp_the_topic_last_poster_name() {
+	echo bp_get_the_topic_last_poster_name();
+}
+	/**
+	 * Return the linked name of the user who last posted to the current topic in the loop.
+	 *
+	 * @return string HTML link to the profile of the user who last posted
+	 *                to the current topic.
+	 */
+	function bp_get_the_topic_last_poster_name() {
+		global $forum_template;
+
+		$domain = bp_core_get_user_domain( $forum_template->topic->topic_last_poster, $forum_template->topic->topic_last_poster_nicename, $forum_template->topic->topic_last_poster_login );
+
+		// In the case where no user is found, bp_core_get_user_domain() may return the URL
+		// of the Members directory.
+		if ( empty( $domain ) || ( bp_get_members_directory_permalink() === $domain ) ) {
+			return __( 'Deleted User', 'buddypress' );
+		}
+
+		/**
+		 * Filters the linked name of the user who last posted to the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value HTML link to the profile of the user who last posted.
+		 */
+		return apply_filters( 'bp_get_the_topic_last_poster_name', '<a href="' . $domain . '">' . $forum_template->topic->topic_last_poster_displayname . '</a>' );
+	}
+
+/**
+ * Output the permalink of the object associated with the current topic in the loop.
+ *
+ * @see bp_get_the_topic_object_avatar() for description of arguments.
+ *
+ * @param array|string $args See {@bp_get_the_topic_object_avatar()}.
+ */
+function bp_the_topic_object_avatar( $args = '' ) {
+	echo bp_get_the_topic_object_avatar( $args );
+}
+	/**
+	 * Return the avatar of the object associated with the current topic in the loop.
+	 *
+	 * Objects are things like groups. So this function would return the
+	 * avatar of the group associated with the forum topic, if it exists.
+	 *
+	 * @param array|string $args {
+	 *     Arguments for building the avatar.
+	 *     @type string $type   Avatar type. 'thumb' or 'full'. Default:
+	 *                         'thumb'.
+	 *     @type int    $width  Width of the avatar, in pixels. Default: the
+	 *                          width corresponding to $type.
+	 *                          See {@link bp_core_fetch_avatar()}.
+	 *     @type int    $height Height of the avatar, in pixels. Default:
+	 *                          the height corresponding to $type.
+	 *                          See {@link bp_core_fetch_avatar()}.
+	 *     @type string $alt    The text of the image's 'alt' attribute.
+	 *                          Default: 'Group logo for [group name]'.
+	 * }
+	 * @return string Object avatar.
+	 */
+	function bp_get_the_topic_object_avatar( $args = '' ) {
+		global $forum_template;
+
+		if ( !isset( $forum_template->topic->object_id ) )
+			return false;
+
+		$defaults = array(
+			'type'   => 'thumb',
+			'width'  => false,
+			'height' => false,
+			'alt'    => __( 'Group logo for %s', 'buddypress' )
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		/**
+		 * Filters the avatar of the object associated with the current topic in the loop.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value HTML output for the object associated with the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_object_avatar', bp_core_fetch_avatar( array( 'item_id' => $forum_template->topic->object_id, 'type' => $type, 'object' => 'group', 'width' => $width, 'height' => $height, 'alt' => $alt ) ) );
+	}
+
+/**
+ * Output the avatar for the user who last posted to the current topic in the loop.
+ *
+ * @see bp_get_the_topic_last_poster_avatar() for description of arguments.
+ *
+ * @param array|string $args See {@bp_get_the_topic_last_poster_avatar()}.
+ */
+function bp_the_topic_last_poster_avatar( $args = '' ) {
+	echo bp_get_the_topic_last_poster_avatar( $args );
+}
+	/**
+	 * Return the avatar for the user who last posted to the current topic in the loop.
+	 *
+	 * @param array|string $args {
+	 *     Arguments for building the avatar.
+	 *     @type string $type   Avatar type. 'thumb' or 'full'. Default:
+	 *                          'thumb'.
+	 *     @type int    $width  Width of the avatar, in pixels. Default: the
+	 *                          width corresponding to $type.
+	 *                          See {@link bp_core_fetch_avatar()}.
+	 *     @type int    $height Height of the avatar, in pixels. Default:
+	 *                          the height corresponding to $type.
+	 *                          See {@link bp_core_fetch_avatar()}.
+	 *     @type string $alt    The text of the image's 'alt' attribute.
+	 *                          Default: 'Profile picture of [group name]'.
+	 * }
+	 * @return string User avatar.
+	 */
+	function bp_get_the_topic_last_poster_avatar( $args = '' ) {
+		global $forum_template;
+
+		$defaults = array(
+			'type'   => 'thumb',
+			'width'  => false,
+			'height' => false,
+			'alt'    => __( 'Profile picture of %s', 'buddypress' )
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		/**
+		 * Filters the avatar for the user who last posted to the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value HTML output for the avatar for the user who last posted.
+		 */
+		return apply_filters( 'bp_get_the_topic_last_poster_avatar', bp_core_fetch_avatar( array( 'email' => $forum_template->topic->topic_last_poster_email, 'item_id' => $forum_template->topic->topic_last_poster, 'type' => $type, 'width' => $width, 'height' => $height, 'alt' => $alt ) ) );
+	}
+
+/**
+ * Output the start time of the current topic in the loop.
+ */
+function bp_the_topic_start_time() {
+	echo bp_get_the_topic_start_time();
+}
+	/**
+	 * Return the start time of the current topic in the loop.
+	 *
+	 * @return string Start time of the current topic.
+	 */
+	function bp_get_the_topic_start_time() {
+		global $forum_template;
+
+		/**
+		 * Filters the start time of the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $topic_start_time Start time for the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_start_time', $forum_template->topic->topic_start_time );
+	}
+
+/**
+ * Output the topic time of the current topic in the loop.
+ */
+function bp_the_topic_time() {
+	echo bp_get_the_topic_time();
+}
+	/**
+	 * Return the topic time of the current topic in the loop.
+	 *
+	 * @return string Topic time of the current topic.
+	 */
+	function bp_get_the_topic_time() {
+		global $forum_template;
+
+		/**
+		 * Filters the topic time of the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $topic_time Topic time for the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_time', $forum_template->topic->topic_time );
+	}
+
+/**
+ * Output the ID of the forum associated with the current topic in the loop.
+ */
+function bp_the_topic_forum_id() {
+	echo bp_get_the_topic_forum_id();
+}
+	/**
+	 * Return the ID of the forum associated with the current topic in the loop.
+	 *
+	 * @return int ID of the forum associated with the current topic.
+	 */
+	function bp_get_the_topic_forum_id() {
+		global $forum_template;
+
+		/**
+		 * Filters the ID of the forum associated with the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $forum_id ID of the forum associated with the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_forum_id', $forum_template->topic->forum_id );
+	}
+
+/**
+ * Output the status of the current topic in the loop.
+ */
+function bp_the_topic_status() {
+	echo bp_get_the_topic_status();
+}
+	/**
+	 * Return the status of the current topic in the loop.
+	 *
+	 * @return string Status of the current topic.
+	 */
+	function bp_get_the_topic_status() {
+		global $forum_template;
+
+		/**
+		 * Filters the status of the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $topic_status Status of the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_status', $forum_template->topic->topic_status );
+	}
+
+/**
+ * Output whether the current topic in the loop is open.
+ */
+function bp_the_topic_is_topic_open() {
+	echo bp_get_the_topic_is_topic_open();
+}
+	/**
+	 * Return whether the current topic in the loop is open.
+	 *
+	 * @return string
+	 */
+	function bp_get_the_topic_is_topic_open() {
+		global $forum_template;
+
+		/**
+		 * Filters whether or not the current topic in the loop is open.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $topic_open Whether or not the current topic is open.
+		 */
+		return apply_filters( 'bp_get_the_topic_is_topic_open', $forum_template->topic->topic_open );
+	}
+
+/**
+ * Output the ID of the last post in the current topic in the loop.
+ */
+function bp_the_topic_last_post_id() {
+	echo bp_get_the_topic_last_post_id();
+}
+	/**
+	 * Return the ID of the last post in the current topic in the loop.
+	 *
+	 * @return int ID of the last post in the current topic.
+	 */
+	function bp_get_the_topic_last_post_id() {
+		global $forum_template;
+
+		/**
+		 * Filters the ID of the last post in the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $topic_last_post_id ID for the last post in the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_last_post_id', $forum_template->topic->topic_last_post_id );
+	}
+
+/**
+ * Output whether the current topic in the loop is sticky.
+ */
+function bp_the_topic_is_sticky() {
+	echo bp_get_the_topic_is_sticky();
+}
+	/**
+	 * Return whether the current topic in the loop is sticky.
+	 *
+	 * @return bool
+	 */
+	function bp_get_the_topic_is_sticky() {
+		global $forum_template;
+
+		/**
+		 * Filters whether or not the current topic in the loop is sticky.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param bool $topic_sticky Whether or not the current topic is sticky.
+		 */
+		return apply_filters( 'bp_get_the_topic_is_sticky', $forum_template->topic->topic_sticky );
+	}
+
+/**
+ * Output a 'x posts' string with the number of posts in the current topic.
+ */
+function bp_the_topic_total_post_count() {
+	echo bp_get_the_topic_total_post_count();
+}
+	/**
+	 * Return a 'x posts' string with the number of posts in the current topic.
+	 *
+	 * @return string String of the form 'x posts'.
+	 */
+	function bp_get_the_topic_total_post_count() {
+		global $forum_template;
+
+		if ( $forum_template->topic->topic_posts == 1 ) {
+
+			/**
+			 * Filters a 'x posts' string with the number of posts in the current topic.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param string $value 'X posts' string value for the current topic.
+			 */
+			return apply_filters( 'bp_get_the_topic_total_post_count', sprintf( __( '%d post', 'buddypress' ), $forum_template->topic->topic_posts ) );
+		} else {
+
+			/** This filter is documented in bp-forums/bp-forums-template.php */
+			return apply_filters( 'bp_get_the_topic_total_post_count', sprintf( __( '%d posts', 'buddypress' ), $forum_template->topic->topic_posts ) );
+		}
+	}
+
+/**
+ * Output the total number of posts in the current topic in the loop.
+ */
+function bp_the_topic_total_posts() {
+	echo bp_get_the_topic_total_posts();
+}
+	/**
+	 * Return the total number of posts in the current topic in the loop.
+	 *
+	 * @return int Total number of posts in the current topic.
+	 */
+	function bp_get_the_topic_total_posts() {
+		global $forum_template;
+
+		/**
+		 * Filters the total number of posts in the current topic in the loop.
+		 *
+		 * @since 1.2.4
+		 *
+		 * @param int $topic_posts Total number of posts in the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_total_posts', $forum_template->topic->topic_posts );
+	}
+
+/**
+ * Output the tag count for the current topic in the loop.
+ */
+function bp_the_topic_tag_count() {
+	echo bp_get_the_topic_tag_count();
+}
+	/**
+	 * Return the tag count for the current topic in the loop.
+	 *
+	 * @return int Tag count for the current topic.
+	 */
+	function bp_get_the_topic_tag_count() {
+		global $forum_template;
+
+		/**
+		 * Filters the tag count for the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $tag_count Tag count for the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_tag_count', $forum_template->topic->tag_count );
+	}
+
+/**
+ * Output the permalink of the current topic in the loop.
+ */
+function bp_the_topic_permalink() {
+	echo bp_get_the_topic_permalink();
+}
+	/**
+	 * Return the permalink for the current topic in the loop.
+	 *
+	 * @return string Permalink for the current topic.
+	 */
+	function bp_get_the_topic_permalink() {
+		global $forum_template;
+
+		// The topic is in a loop where its parent object is loaded.
+		if ( bp_get_the_topic_object_slug() ) {
+			$permalink = trailingslashit( bp_get_groups_directory_permalink() . bp_get_the_topic_object_slug() . '/forum' );
+
+		// We are viewing a single group topic, so use the current item.
+		} elseif ( bp_is_group_forum_topic() ) {
+			$permalink = trailingslashit( bp_get_groups_directory_permalink() . bp_current_item() . '/forum' );
+
+		// We are unsure what the context is, so fallback to forum root slug.
+		} elseif ( bp_is_single_item() ) {
+			$permalink = trailingslashit( bp_get_root_domain() . '/' . bp_get_forums_root_slug() . '/' . bp_current_item() );
+
+		// This is some kind of error situation, so use forum root.
+		} else {
+			$permalink = trailingslashit( bp_get_root_domain() . '/' . bp_get_forums_root_slug() );
+		}
+
+		/**
+		 * Filters the permalink for the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Permalink for the current topic in the loop.
+		 */
+		return apply_filters( 'bp_get_the_topic_permalink', trailingslashit( $permalink . 'topic/' . $forum_template->topic->topic_slug ) );
+	}
+
+/**
+ * Output a 'since' string describing when the current topic was created.
+ */
+function bp_the_topic_time_since_created() {
+	echo bp_get_the_topic_time_since_created();
+}
+	/**
+	 * Return a 'since' string describing when the current topic was created.
+	 *
+	 * @see bp_core_time_since() for a description of return value.
+	 *
+	 * @return string
+	 */
+	function bp_get_the_topic_time_since_created() {
+		global $forum_template;
+
+		/**
+		 * Filters a 'time since' string describing when the current topic was created.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value 'Time since' value for the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_time_since_created', bp_core_time_since( strtotime( $forum_template->topic->topic_start_time ) ) );
+	}
+
+/**
+ * Output an excerpt from the latest post of the current topic in the loop.
+ *
+ * @param array|string $args See {@bp_get_the_topic_latest_post_excerpt()}.
+ */
+function bp_the_topic_latest_post_excerpt( $args = '' ) {
+	echo bp_get_the_topic_latest_post_excerpt( $args );
+}
+	/**
+	 * Return an excerpt from the latest post of the current topic in the loop.
+	 *
+	 * @param array|string $args {
+	 *     @type int $length The length of the excerpted text. Default: 225.
+	 * }
+	 * @return string Post excerpt.
+	 */
+	function bp_get_the_topic_latest_post_excerpt( $args = '' ) {
+		global $forum_template;
+
+		$defaults = array(
+			'length' => 225
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		$post = bp_forums_get_post( $forum_template->topic->topic_last_post_id );
+		$post = bp_create_excerpt( $post->post_text, $length );
+
+		/**
+		 * Filters an excerpt from the latest post of the current topic in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $post   Post exceprt for the current topic.
+		 * @param string $length String length to trim excerpt down to.
+		 */
+		return apply_filters( 'bp_get_the_topic_latest_post_excerpt', $post, $length );
+	}
+
+/**
+ * Output a 'since' string describing when the last post in the current topic was created.
+ */
+function bp_the_topic_time_since_last_post() {
+	echo bp_get_the_topic_time_since_last_post();
+}
+	/**
+	 * Return a 'since' string describing when the last post in the current topic was created.
+	 *
+	 * @see bp_core_time_since() for a description of return value.
+	 *
+	 * @return string
+	 */
+	function bp_get_the_topic_time_since_last_post() {
+		global $forum_template;
+
+		/**
+		 * Filters a 'time since' string describing when the last post in the current topic was created.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value The 'time since' string for the last post in the current topic.
+		 */
+		return apply_filters( 'bp_get_the_topic_time_since_last_post', bp_core_time_since( strtotime( $forum_template->topic->topic_time ) ) );
+	}
+
+/**
+ * Output whether the current topic in the loop belongs to the logged-in user.
+ */
+function bp_the_topic_is_mine() {
+	echo bp_get_the_topic_is_mine();
+}
+	/**
+	 * Does the current topic belong to the logged-in user?
+	 *
+	 * @return bool True if the current topic in the loop was created by
+	 *              the logged-in user, otherwise false.
+	 */
+	function bp_get_the_topic_is_mine() {
+		global $forum_template;
+
+		return bp_loggedin_user_id() == $forum_template->topic->topic_poster;
+	}
+
+/**
+ * Output the admin links for the current topic in the loop.
+ *
+ * @see bp_get_the_topic_admin_links() for a description of arguments.
+ *
+ * @param array|string $args See {@link bp_get_the_topic_admin_links()}.
+ */
+function bp_the_topic_admin_links( $args = '' ) {
+	echo bp_get_the_topic_admin_links( $args );
+}
+	/**
+	 * Return the admin links for the current topic in the loop.
+	 *
+	 * @param array|string $args {
+	 *     @type string $separator The character to use when separating links. Default: '|'.
+	 * }
+	 * @return string HTML string containing the admin links for the current topic.
+	 */
+	function bp_get_the_topic_admin_links( $args = '' ) {
+		global $forum_template;
+
+		$defaults = array(
+			'separator' => '|'
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		$links[] = '<a href="' . wp_nonce_url( bp_get_the_topic_permalink() . 'edit', 'bp_forums_edit_topic' ) . '">' . __( 'Edit Topic', 'buddypress' ) . '</a>';
+
+		if ( bp_is_item_admin() || bp_is_item_mod() || bp_current_user_can( 'bp_moderate' ) ) {
+			if ( 0 == (int) $forum_template->topic->topic_sticky )
+				$links[] = '<a href="' . wp_nonce_url( bp_get_the_topic_permalink() . 'stick', 'bp_forums_stick_topic' ) . '">' . __( 'Sticky Topic', 'buddypress' ) . '</a>';
+			else
+				$links[] = '<a href="' . wp_nonce_url( bp_get_the_topic_permalink() . 'unstick', 'bp_forums_unstick_topic' ) . '">' . __( 'Un-stick Topic', 'buddypress' ) . '</a>';
+
+			if ( 0 == (int) $forum_template->topic->topic_open )
+				$links[] = '<a href="' . wp_nonce_url( bp_get_the_topic_permalink() . 'open', 'bp_forums_open_topic' ) . '">' . __( 'Open Topic', 'buddypress' ) . '</a>';
+			else
+				$links[] = '<a href="' . wp_nonce_url( bp_get_the_topic_permalink() . 'close', 'bp_forums_close_topic' ) . '">' . __( 'Close Topic', 'buddypress' ) . '</a>';
+
+			$links[] = '<a class="confirm" id="topic-delete-link" href="' . wp_nonce_url( bp_get_the_topic_permalink() . 'delete', 'bp_forums_delete_topic' ) . '">' . __( 'Delete Topic', 'buddypress' ) . '</a>';
+		}
+
+		return implode( ' ' . $separator . ' ', (array) $links );
+	}
+
+/**
+ * Output the CSS class for the current topic in the loop.
+ */
+function bp_the_topic_css_class() {
+	echo bp_get_the_topic_css_class();
+}
+	/**
+	 * Return the CSS class for the current topic in the loop.
+	 *
+	 * This class may contain keywords like 'alt', 'sticky', or 'closed',
+	 * based on context.
+	 *
+	 * @return string Contents of the 'class' attribute.
+	 */
+	function bp_get_the_topic_css_class() {
+		global $forum_template;
+
+		$class = false;
+
+		if ( $forum_template->current_topic % 2 == 1 )
+			$class .= 'alt';
+
+		if ( isset( $forum_template->topic->topic_sticky ) && 1 == (int) $forum_template->topic->topic_sticky )
+			$class .= ' sticky';
+
+		if ( !isset( $forum_template->topic->topic_open ) || 0 == (int) $forum_template->topic->topic_open )
+			$class .= ' closed';
+
+		/**
+		 * Filters the CSS class for the current topic in the loop.
+		 *
+		 * This class may contain keywords like 'alt', 'sticky', or 'closed',
+		 * based on context.
+		 *
+		 * @since 1.2.4
+		 *
+		 * @param string $value Concatenated classes for the current topic in the loop.
+		 */
+		return apply_filters( 'bp_get_the_topic_css_class', trim( $class ) );
+	}
+
+/**
+ * Output the permalink to the 'personal' topics tab.
+ */
+function bp_my_forum_topics_link() {
+	echo bp_get_my_forum_topics_link();
+}
+	/**
+	 * Return the permalink to the 'personal' topics tab.
+	 *
+	 * @return string Link to the 'personal' topics tab.
+	 */
+	function bp_get_my_forum_topics_link() {
+		/**
+		 * Filters the permalink to the 'personal' topics tab.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Permalink to the 'personal' topics tab.
+		 */
+		return apply_filters( 'bp_get_my_forum_topics_link', bp_get_root_domain() . '/' . bp_get_forums_root_slug() . '/personal/' );
+	}
+
+/**
+ * Output the permalink to the 'unreplied' topics tab.
+ */
+function bp_unreplied_forum_topics_link() {
+	echo bp_get_unreplied_forum_topics_link();
+}
+	/**
+	 * Return the permalink to the 'unreplied' topics tab.
+	 *
+	 * @return string Link to the 'unreplied' topics tab.
+	 */
+	function bp_get_unreplied_forum_topics_link() {
+		/**
+		 * Filters the permalink to the 'unreplied' topics tab.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Permalink to the 'unreplied' topics tab.
+		 */
+		return apply_filters( 'bp_get_unreplied_forum_topics_link', bp_get_root_domain() . '/' . bp_get_forums_root_slug() . '/unreplied/' );
+	}
+
+/**
+ * Output the permalink to the 'popular' topics tab.
+ */
+function bp_popular_forum_topics_link() {
+	echo bp_get_popular_forum_topics_link();
+}
+	/**
+	 * Return the permalink to the 'popular' topics tab.
+	 *
+	 * @return string Link to the 'popular' topics tab.
+	 */
+	function bp_get_popular_forum_topics_link() {
+		/**
+		 * Filters the permalink to the 'popular' topics tab.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Permalink to the 'popular' topics tab.
+		 */
+		return apply_filters( 'bp_get_popular_forum_topics_link', bp_get_root_domain() . '/' . bp_get_forums_root_slug() . '/popular/' );
+	}
+
+/**
+ * Output the link to the forums directory.
+ */
+function bp_newest_forum_topics_link() {
+	echo bp_get_newest_forum_topics_link();
+}
+	/**
+	 * Return the link to the forums directory.
+	 *
+	 * @return string Link to the forums directory.
+	 */
+	function bp_get_newest_forum_topics_link() {
+		/**
+		 * Filters the link to the forums directory.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Link to the forums directory.
+		 */
+		return apply_filters( 'bp_get_newest_forum_topics_link', bp_get_root_domain() . '/' . bp_get_forums_root_slug() . '/' );
+	}
+
+/**
+ * Output the currently viewed topic list type.
+ */
+function bp_forum_topic_type() {
+	echo bp_get_forum_topic_type();
+}
+	/**
+	 * Return the currently viewed topic list type.
+	 *
+	 * Eg, 'newest', 'popular', etc.
+	 *
+	 * @return string Type of the currently viewed topic list.
+	 */
+	function bp_get_forum_topic_type() {
+
+		if ( !bp_is_directory() || !bp_current_action() )
+			return 'newest';
+
+		/**
+		 * Filters the currently viewed topic list type.
+		 *
+		 * Eg, 'newest', 'popular', etc.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Currently viewed topic list type.
+		 */
+		return apply_filters( 'bp_get_forum_topic_type', bp_current_action() );
+	}
+
+/**
+ * Output the value of bp_get_forum_topic_new_reply_link().
+ *
+ * @since 1.5.0
+ */
+function bp_forum_topic_new_reply_link() {
+	echo esc_url( bp_get_forum_topic_new_reply_link() );
+}
+	/**
+	 * Return the permalink for the New Reply button at the top of forum topics.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @uses apply_filters() Filter bp_get_forum_topic_new_reply_link to
+	 *       modify.
+	 *
+	 * @return string The URL for the New Reply link.
+	 */
+	function bp_get_forum_topic_new_reply_link() {
+		global $topic_template;
+
+		if ( $topic_template->pag->total_pages == $topic_template->pag_page ) {
+			// If we are on the last page, no need for a URL base.
+			$link = '';
+		} else {
+			// Create a link to the last page for the topic.
+			$link = add_query_arg( array(
+				'topic_page' =>	$topic_template->pag->total_pages,
+				'num'        => $topic_template->pag_num
+			), bp_get_the_topic_permalink() );
+		}
+
+		/**
+		 * Filters the permalink for the New Reply button at the top of forum topics.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Permalink for the New Reply button.
+		 */
+		return apply_filters( 'bp_get_forum_topic_new_reply_link', $link . '#post-topic-reply', $link );
+	}
+
+/**
+ * Output the currently viewed tag name.
+ *
+ * @todo Deprecate?
+ */
+function bp_forums_tag_name() {
+	echo bp_get_forums_tag_name();
+}
+	/**
+	 * Return the currently viewed tag name.
+	 *
+	 * @todo Deprecate? Seems unused
+	 */
+	function bp_get_forums_tag_name() {
+		$tag_name = bp_is_directory() && bp_is_forums_component() ? bp_action_variable( 0 ) : false;
+
+		return apply_filters( 'bp_get_forums_tag_name', $tag_name );
+	}
+
+/**
+ * Output the pagination links for the current topic list.
+ */
+function bp_forum_pagination() {
+	echo bp_get_forum_pagination();
+}
+	/**
+	 * Return the pagination links for the current topic list.
+	 *
+	 * @return string HTML pagination links.
+	 */
+	function bp_get_forum_pagination() {
+		global $forum_template;
+
+		/**
+		 * Filters the pagination links for the current topic list.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $pag_links HTML pagination links.
+		 */
+		return apply_filters( 'bp_get_forum_pagination', $forum_template->pag_links );
+	}
+
+/**
+ * Output the pagination count for the current topic list.
+ */
+function bp_forum_pagination_count() {
+	echo bp_get_forum_pagination_count();
+}
+	/**
+	 * Return the pagination count for the current topic list.
+	 *
+	 * The "count" is a string of the form "Viewing x of y topics".
+	 *
+	 * @return string
+	 */
+	function bp_get_forum_pagination_count() {
+		global $forum_template;
+
+		$start_num  = intval( ( $forum_template->pag_page - 1 ) * $forum_template->pag_num ) + 1;
+		$from_num   = bp_core_number_format( $start_num );
+		$to_num     = bp_core_number_format( ( $start_num + ( $forum_template->pag_num - 1  ) > $forum_template->total_topic_count ) ? $forum_template->total_topic_count : $start_num + ( $forum_template->pag_num - 1 ) );
+		$total      = bp_core_number_format( $forum_template->total_topic_count );
+		$pag_filter = false;
+
+		if ( 'tags' == $forum_template->type && !empty( $forum_template->search_terms ) )
+			$pag_filter = sprintf( __( ' matching tag "%s"', 'buddypress' ), $forum_template->search_terms );
+
+		if ( 1 == $forum_template->total_topic_count ) {
+			$message = __( 'Viewing 1 topic', 'buddypress' );
+		} else {
+			$message = sprintf( _n( 'Viewing %1$s - %2$s of %3$s topic', 'Viewing %1$s - %2$s of %3$s topics', (int) $forum_template->total_topic_count, 'buddypress' ), $from_num, $to_num, $total, $pag_filter );
+		}
+
+		/**
+		 * Filters the pagination count for the current topic list.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $message  Pagination count for the current topic list.
+		 * @param string $from_num Low end count in the view.
+		 * @param string $to_num   High end count in the view.
+		 * @param string $total    Total count of topics found.
+		 */
+		return apply_filters( 'bp_get_forum_pagination_count', $message, $from_num, $to_num, $total );
+	}
+
+/**
+ * Are we currently on an Edit Topic screen?
+ *
+ * @return bool True if currently editing a topic, otherwise false.
+ */
+function bp_is_edit_topic() {
+
+	if ( bp_is_action_variable( 'post' ) && bp_is_action_variable( 'edit' ) )
+		return false;
+
+	return true;
+}
+
+/**
+ * The single forum topic template loop class.
+ *
+ * Responsible for loading a topic's posts into a loop for display.
+ */
+class BP_Forums_Template_Topic {
+	/**
+	 * The loop iterator.
+	 *
+	 * @var int
+	 */
+	public $current_post = -1;
+
+	/**
+	 * The number of posts returned by the paged query.
+	 *
+	 * @var int
+	 */
+	public $post_count;
+
+	/**
+	 * Array of posts located by the query.
+	 *
+	 * @var array
+	 */
+	public $posts;
+
+	/**
+	 * The post object currently being iterated on.
+	 *
+	 * @var object
+	 */
+	public $post;
+
+	/**
+	 * The ID of the forum whose topic is being queried.
+	 *
+	 * @var int
+	 */
+	public $forum_id;
+
+	/**
+	 * The ID of the topic whose posts are being queried.
+	 *
+	 * @var int
+	 */
+	public $topic_id;
+
+	/**
+	 * The topic object to which the posts belong.
+	 *
+	 * @var object
+	 */
+	public $topic;
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @var bool
+	 */
+	public $in_the_loop;
+
+	/**
+	 * Contains a 'total_pages' property holding total number of pages in
+	 * this loop.
+	 *
+	 * @since 1.2.0
+	 * @var stdClass
+	 */
+	public $pag;
+
+	/**
+	 * The page number being requested.
+	 *
+	 * @var int
+	 */
+	public $pag_page;
+
+	/**
+	 * The number of items being requested per page.
+	 *
+	 * @var int
+	 */
+	public $pag_num;
+
+	/**
+	 * An HTML string containing pagination links.
+	 *
+	 * @var string
+	 */
+	public $pag_links;
+
+	/**
+	 * The total number of posts matching the query parameters.
+	 *
+	 * @var int
+	 */
+	public $total_post_count;
+
+	/**
+	 * Whether requesting a single topic. Not currently used.
+	 *
+	 * @var bool
+	 */
+	public $single_post = false;
+
+	/**
+	 * Term to sort by.
+	 *
+	 * @var string
+	 */
+	public $sort_by;
+
+	/**
+	 * Sort order.
+	 *
+	 * @var string
+	 */
+	public $order;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @param int    $topic_id ID of the topic whose posts are being requested.
+	 * @param int    $per_page Number of items to return per page.
+	 * @param int    $max Max records to return.
+	 * @param string $order Direction to order results.
+	 */
+	function __construct( $topic_id, $per_page, $max, $order ) {
+		global $forum_template;
+
+		if ( !isset( $forum_template ) ) {
+			$forum_template = new stdClass;
+		}
+
+		$this->pag_page        = isset( $_REQUEST['topic_page'] ) ? intval( $_REQUEST['topic_page'] ) : 1;
+		$this->pag_num         = isset( $_REQUEST['num'] ) ? intval( $_REQUEST['num'] ) : $per_page;
+
+		$this->order           = $order;
+		$this->topic_id        = $topic_id;
+		$forum_template->topic = (object) bp_forums_get_topic_details( $this->topic_id );
+		$this->forum_id        = $forum_template->topic->forum_id;
+
+		$this->posts           = bp_forums_get_topic_posts( array( 'topic_id' => $this->topic_id, 'page' => $this->pag_page, 'per_page' => $this->pag_num, 'order' => $this->order ) );
+
+		if ( !$this->posts ) {
+			$this->post_count       = 0;
+			$this->total_post_count = 0;
+		} else {
+			if ( !$max || $max >= (int) $forum_template->topic->topic_posts ) {
+				$this->total_post_count = (int) $forum_template->topic->topic_posts;
+			} else {
+				$this->total_post_count = (int) $max;
+			}
+
+			if ( $max ) {
+				if ( $max >= count( $this->posts ) ) {
+					$this->post_count = count( $this->posts );
+				} else {
+					$this->post_count = (int) $max;
+				}
+			} else {
+				$this->post_count = count( $this->posts );
+			}
+		}
+
+		// Load topic tags.
+		$this->topic_tags = bb_get_topic_tags( $this->topic_id );
+
+		$this->pag = new stdClass;
+
+		if ( (int) $this->total_post_count && (int) $this->pag_num ) {
+			$this->pag_links = paginate_links( array(
+				'base'      => add_query_arg( array( 'topic_page' => '%#%', 'num' => (int) $this->pag_num ) ),
+				'format'    => '',
+				'total'     => ceil( (int) $this->total_post_count / (int) $this->pag_num ),
+				'current'   => $this->pag_page,
+				'prev_text' => _x( '&larr;', 'Forum thread pagination previous text', 'buddypress' ),
+				'next_text' => _x( '&rarr;', 'Forum thread pagination next text', 'buddypress' ),
+				'mid_size'  => 1,
+				'add_args'  => array(),
+			) );
+
+			$this->pag->total_pages = ceil( (int) $this->total_post_count / (int) $this->pag_num );
+		} else {
+			$this->pag->total_pages = 1;
+		}
+	}
+
+	/**
+	 * Whether there are posts available in the loop.
+	 *
+	 * @see bp_has_forum_topic_posts()
+	 *
+	 * @return bool True if there are items in the loop, otherwise false.
+	 */
+	function has_posts() {
+		if ( $this->post_count )
+			return true;
+
+		return false;
+	}
+
+	/**
+	 * Set up the next post and iterate index.
+	 *
+	 * @return object The next post to iterate over.
+	 */
+	function next_post() {
+		$this->current_post++;
+		$this->post = $this->posts[$this->current_post];
+
+		return $this->post;
+	}
+
+	/**
+	 * Rewind the posts and reset post index.
+	 */
+	function rewind_posts() {
+		$this->current_post = -1;
+		if ( $this->post_count > 0 ) {
+			$this->post = $this->posts[0];
+		}
+	}
+
+	/**
+	 * Whether there are posts left in the loop to iterate over.
+	 *
+	 * This method is used by {@link bp_forum_topic_posts()} as part of
+	 * the while loop that controls iteration inside the blogs loop, eg:
+	 *     while ( bp_forum_topic_posts() ) { ...
+	 *
+	 * @see bp_forum_topic_posts()
+	 *
+	 * @return bool True if there are more posts to show, otherwise false.
+	 */
+	function user_posts() {
+		if ( $this->current_post + 1 < $this->post_count ) {
+			return true;
+		} elseif ( $this->current_post + 1 == $this->post_count ) {
+
+			/**
+			 * Fires right before the rewinding of user posts.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action('topic_loop_end');
+			// Do some cleaning up after the loop.
+			$this->rewind_posts();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	/**
+	 * Set up the current topic in the loop.
+	 *
+	 * @see bp_the_forum_topic_post()
+	 */
+	function the_post() {
+
+		$this->in_the_loop = true;
+		$this->post = $this->next_post();
+		$this->post = (object)$this->post;
+
+		if ( $this->current_post == 0 ) {
+
+			/**
+			 * Fires if the current post item is the first in the topic loop.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'topic_loop_start' );
+		}
+	}
+}
+
+/**
+ * Initiate the loop for a single topic's posts.
+ *
+ * @param array|string $args {
+ *     Arguments for limiting the contents of the topic posts loop.
+ *     @type int    $topic_id ID of the topic to which the posts belong.
+ *     @type int    $per_page Number of items to return per page. Default: 15.
+ *     @type int    $max      Max items to return. Default: false.
+ *     @type string $order   'ASC' or 'DESC'.
+ * }
+ * @return bool True when posts are found corresponding to the args,
+ *              otherwise false.
+ */
+function bp_has_forum_topic_posts( $args = '' ) {
+	global $topic_template;
+
+	$defaults = array(
+		'topic_id' => false,
+		'per_page' => 15,
+		'max'      => false,
+		'order'    => 'ASC'
+	);
+
+	$r = bp_parse_args( $args, $defaults, 'has_forum_topic_posts' );
+	extract( $r, EXTR_SKIP );
+
+	if ( empty( $topic_id ) && bp_is_groups_component() && bp_is_current_action( 'forum' ) && bp_is_action_variable( 'topic', 0 ) && bp_action_variable( 1 ) )
+		$topic_id = bp_forums_get_topic_id_from_slug( bp_action_variable( 1 ) );
+	elseif ( empty( $topic_id ) && bp_is_forums_component() && bp_is_current_action( 'topic' ) && bp_action_variable( 0 ) )
+		$topic_id = bp_forums_get_topic_id_from_slug( bp_action_variable( 0 ) );
+
+	if ( empty( $topic_id ) ) {
+		return false;
+
+	} else {
+		$topic_template = new BP_Forums_Template_Topic( (int) $topic_id, $per_page, $max, $order );
+
+		// Current topic forum_id needs to match current_group forum_id.
+		if ( bp_is_groups_component() && $topic_template->forum_id != groups_get_groupmeta( bp_get_current_group_id(), 'forum_id' ) )
+			return false;
+	}
+
+	/**
+	 * Filters whether or not there are topics to display.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool                     $value          Whether or not there are topics.
+	 * @param BP_Forums_Template_Topic $topic_template Topic template global to use when rendering.
+	 */
+	return apply_filters( 'bp_has_topic_posts', $topic_template->has_posts(), $topic_template );
+}
+
+/**
+ * Determine whether there are posts left in the loop.
+ *
+ * @return bool True when posts are found.
+ */
+function bp_forum_topic_posts() {
+	global $topic_template;
+	return $topic_template->user_posts();
+}
+
+/**
+ * Set up the current post in the loop.
+ *
+ * @return object
+ */
+function bp_the_forum_topic_post() {
+	global $topic_template;
+	return $topic_template->the_post();
+}
+
+/**
+ * Output the ID of the current post in the loop.
+ */
+function bp_the_topic_post_id() {
+	echo bp_get_the_topic_post_id();
+}
+	/**
+	 * Return the ID of the current post in the loop.
+	 *
+	 * @return int ID of the current post in the loop.
+	 */
+	function bp_get_the_topic_post_id() {
+		global $topic_template;
+
+		/**
+		 * Filters the ID of the current post in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $post_id ID of the current post.
+		 */
+		return apply_filters( 'bp_get_the_topic_post_id', $topic_template->post->post_id );
+	}
+
+/**
+ * Output the content of the current post in the loop.
+ */
+function bp_the_topic_post_content() {
+	echo bp_get_the_topic_post_content();
+}
+	/**
+	 * Return the content of the current post in the loop.
+	 *
+	 * @return string Content of the current post.
+	 */
+	function bp_get_the_topic_post_content() {
+		global $topic_template;
+
+		/**
+		 * Filters the content of the current post in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $post_text The content of the current post.
+		 */
+		return apply_filters( 'bp_get_the_topic_post_content', stripslashes( $topic_template->post->post_text ) );
+	}
+
+/**
+ * Output the CSS class of the current post in the loop.
+ */
+function bp_the_topic_post_css_class() {
+	echo bp_get_the_topic_post_css_class();
+}
+	/**
+	 * Return the CSS class of the current post in the loop.
+	 *
+	 * May contain strings 'alt', 'deleted', or 'open', depending on
+	 * context.
+	 *
+	 * @return string String to put in the 'class' attribute of the current
+	 *                post.
+	 */
+	function bp_get_the_topic_post_css_class() {
+		global $topic_template;
+
+		$class = false;
+
+		if ( $topic_template->current_post % 2 == 1 )
+			$class .= 'alt';
+
+		if ( 1 == (int) $topic_template->post->post_status )
+			$class .= ' deleted';
+
+		if ( 0 == (int) $topic_template->post->post_status )
+			$class .= ' open';
+
+		/**
+		 * Filters the CSS class for the current post in the loop.
+		 *
+		 * This class may contain keywords like 'alt', 'deleted', or 'open',
+		 * based on context.
+		 *
+		 * @since 1.2.4
+		 *
+		 * @param string $value Concatenated classes for the current post in the loop.
+		 */
+		return apply_filters( 'bp_get_the_topic_post_css_class', trim( $class ) );
+	}
+
+/**
+ * Output the avatar of the user who posted the current post in the loop.
+ *
+ * @see bp_get_the_topic_post_poster_avatar() for a description of arguments.
+ *
+ * @param array|string $args See {@link bp_get_the_topic_post_poster_avatar()}.
+ */
+function bp_the_topic_post_poster_avatar( $args = '' ) {
+	echo bp_get_the_topic_post_poster_avatar( $args );
+}
+	/**
+	 * Return the avatar of the user who posted the current post in the loop.
+	 *
+	 * @param array|string $args {
+	 *     Arguments for building the avatar.
+	 *     @type string $type   Avatar type. 'thumb' or 'full'. Default:
+	 *                          'thumb'.
+	 *     @type int    $width  Width of the avatar, in pixels. Default: the
+	 *                          width corresponding to $type.
+	 *                          See {@link bp_core_fetch_avatar()}.
+	 *     @type int    $height Height of the avatar, in pixels. Default: the
+	 *                          height corresponding to $type.
+	 *                          See {@link bp_core_fetch_avatar()}.
+	 *     @type string $alt    The text of the image's 'alt' attribute.
+	 *                          Default: 'Profile picture of [user name]'.
+	 * }
+	 * @return string HTML of user avatar.
+	 */
+	function bp_get_the_topic_post_poster_avatar( $args = '' ) {
+		global $topic_template;
+
+		$defaults = array(
+			'type' => 'thumb',
+			'width' => 20,
+			'height' => 20,
+			'alt' => __( 'Profile picture of %s', 'buddypress' )
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		/**
+		 * Filters the avatar of the user who posted the current post in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Avatar of the user who posted the current post.
+		 */
+		return apply_filters( 'bp_get_the_topic_post_poster_avatar', bp_core_fetch_avatar( array( 'item_id' => $topic_template->post->poster_id, 'type' => $type, 'width' => $width, 'height' => $height, 'alt' => $alt ) ) );
+	}
+
+/**
+ * Output the name of the user who posted the current post in the loop.
+ */
+function bp_the_topic_post_poster_name() {
+	echo bp_get_the_topic_post_poster_name();
+}
+	/**
+	 * Return the name of the user who posted the current post in the loop.
+	 *
+	 * @return string Name of the user who posted the current post.
+	 */
+	function bp_get_the_topic_post_poster_name() {
+		global $topic_template;
+
+		if ( empty( $topic_template->post->poster_name ) || ( !$link = bp_core_get_user_domain( $topic_template->post->poster_id ) ) )
+			return __( 'Deleted User', 'buddypress' );
+
+		/**
+		 * Filters the name of the user who posted the current post in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value HTML link for the poster's name.
+		 */
+		return apply_filters( 'bp_get_the_topic_post_poster_name', '<a href="' . $link . '" title="' . $topic_template->post->poster_name . '">' . $topic_template->post->poster_name . '</a>' );
+	}
+
+/**
+ * Output a link to the profile of the user who posted the current post.
+ */
+function bp_the_topic_post_poster_link() {
+	echo bp_get_the_topic_post_poster_link();
+}
+	/**
+	 * Return a link to the profile of the user who posted the current post.
+	 *
+	 * @return string Link to the profile of the user who posted the
+	 *                current post.
+	 */
+	function bp_get_the_topic_post_poster_link() {
+		global $topic_template;
+
+		/**
+		 * Filters a link to the profile of the user who posted the current post.
+		 *
+		 * @since 1.2.1
+		 *
+		 * @param string $value Link for the current poster's profile.
+		 */
+		return apply_filters( 'bp_the_topic_post_poster_link', bp_core_get_user_domain( $topic_template->post->poster_id ) );
+	}
+
+/**
+ * Output a 'since' string describing when the current post in the loop was posted.
+ */
+function bp_the_topic_post_time_since() {
+	echo bp_get_the_topic_post_time_since();
+}
+	/**
+	 * Return a 'since' string describing when the current post in the loop was posted.
+	 *
+	 * @see bp_core_time_since() for a description of return value.
+	 *
+	 * @return string
+	 */
+	function bp_get_the_topic_post_time_since() {
+		global $topic_template;
+
+		/**
+		 * Filters the 'since' string describing when the current post in the loop was posted.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value The 'since' string.
+		 */
+		return apply_filters( 'bp_get_the_topic_post_time_since', bp_core_time_since( strtotime( $topic_template->post->post_time ) ) );
+	}
+
+/**
+ * Output whether the current post in the loop belongs to the logged-in user.
+ */
+function bp_the_topic_post_is_mine() {
+	echo bp_get_the_topic_post_is_mine();
+}
+	/**
+	 * Does the current post belong to the logged-in user?
+	 *
+	 * @return bool True if the current post in the loop was created by
+	 *              the logged-in user, otherwise false.
+	 */
+	function bp_get_the_topic_post_is_mine() {
+		global $topic_template;
+
+		return bp_loggedin_user_id() == $topic_template->post->poster_id;
+	}
+
+/**
+ * Output the admin links for the current post in the loop.
+ *
+ * @see bp_get_the_post_admin_links() for a description of arguments.
+ *
+ * @param array|string $args See {@link bp_get_the_post_admin_links()}.
+ */
+function bp_the_topic_post_admin_links( $args = '' ) {
+	echo bp_get_the_topic_post_admin_links( $args );
+}
+	/**
+	 * Return the admin links for the current post in the loop.
+	 *
+	 * @param array|string $args {
+	 *     @type string $separator The character to use when separating
+	 *                             links. Default: '|'.
+	 * }
+	 * @return string HTML string containing the admin links for the current post.
+	 */
+	function bp_get_the_topic_post_admin_links( $args = '' ) {
+		global $topic_template;
+
+		// Never show for the first post in a topic.
+		if ( 0 == $topic_template->current_post && 1 == $topic_template->pag_page )
+			return;
+
+		$defaults = array(
+			'separator' => ' | '
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		$query_vars = '';
+		if ( $_SERVER['QUERY_STRING'] )
+			$query_vars = '?' . $_SERVER['QUERY_STRING'];
+
+		$links    = array();
+		$links[]  = '<a href="' . wp_nonce_url( bp_get_the_topic_permalink() . 'edit/post/' . $topic_template->post->post_id . '/' . $query_vars, 'bp_forums_edit_post' ) . '">' . __( 'Edit', 'buddypress' ) . '</a>';
+		$links[] .= '<a class="confirm" id="post-delete-link" href="' . wp_nonce_url( bp_get_the_topic_permalink() . 'delete/post/' . $topic_template->post->post_id, 'bp_forums_delete_post' ) . '">' . __( 'Delete', 'buddypress' ) . '</a>';
+
+		/**
+		 * Filters the admin links for the current post in the loop.
+		 *
+		 * @since 1.2.7
+		 *
+		 * @param string $value HTML string containing the admin links for the current post.
+		 */
+		return apply_filters( 'bp_get_the_topic_post_admin_links', implode( $separator, $links ), $links, $r );
+	}
+
+/**
+ * Output the text to edit when editing a post.
+ */
+function bp_the_topic_post_edit_text() {
+	echo bp_get_the_topic_post_edit_text();
+}
+	/**
+	 * Return the text to edit when editing a post.
+	 *
+	 * @return string Editable text.
+	 */
+	function bp_get_the_topic_post_edit_text() {
+		$post = bp_forums_get_post( bp_action_variable( 4 ) );
+
+		/**
+		 * Filters the text to edit when editing a post.
+		 *
+		 * @since 1.2.4
+		 *
+		 * @param string $value The text to edit when editing a post.
+		 */
+		return apply_filters( 'bp_get_the_topic_post_edit_text', esc_attr( $post->post_text ) );
+	}
+
+/**
+ * Output the pagination links for the current topic.
+ */
+function bp_the_topic_pagination() {
+	echo bp_get_the_topic_pagination();
+}
+	/**
+	 * Return the pagination links for the current topic page.
+	 *
+	 * @return string HTML pagination links.
+	 */
+	function bp_get_the_topic_pagination() {
+		global $topic_template;
+
+
+		/**
+		 * Filters the pagination links for the current topic page.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $pag_links HTML pagination links.
+		 */
+		return apply_filters( 'bp_get_the_topic_pagination', $topic_template->pag_links );
+	}
+
+/**
+ * Return the pagination count for the current topic page.
+ *
+ * The "count" is a string of the form "Viewing x of y posts".
+ */
+function bp_the_topic_pagination_count() {
+	global $topic_template;
+
+	$start_num = intval( ( $topic_template->pag_page - 1 ) * $topic_template->pag_num ) + 1;
+	$from_num = bp_core_number_format( $start_num );
+	$to_num = bp_core_number_format( ( $start_num + ( $topic_template->pag_num - 1  ) > $topic_template->total_post_count ) ? $topic_template->total_post_count : $start_num + ( $topic_template->pag_num - 1 ) );
+	$total = bp_core_number_format( $topic_template->total_post_count );
+
+	if ( 1 == $topic_template->total_post_count ) {
+		$message = __( 'Viewing 1 post', 'buddypress' );
+	} else {
+		$message = sprintf( _n( 'Viewing %1$s - %2$s of %3$s post', 'Viewing %1$s - %2$s of %3$s posts', (int) $topic_template->total_post_count, 'buddypress' ), $from_num, $to_num, $total );
+	}
+
+	/**
+	 * Filters the pagination count for the current topic page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $message  Pagination count for the current topic page.
+	 * @param string $from_num Low end count in the view.
+	 * @param string $to_num   High end count in the view.
+	 * @param string $total    Total count of topics found.
+	 */
+	echo apply_filters( 'bp_the_topic_pagination_count', $message, $from_num, $to_num, $total );
+}
+
+/**
+ * Output whether this is the last page in the current topic.
+ */
+function bp_the_topic_is_last_page() {
+	echo bp_get_the_topic_is_last_page();
+}
+	/**
+	 * Is this the last page in the current topic?
+	 *
+	 * @return bool True if this is the last page of posts for the current
+	 *              topic, otherwise false.
+	 */
+	function bp_get_the_topic_is_last_page() {
+		global $topic_template;
+
+		/**
+		 * Filters whether or not a user is on the last page in the current topic.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param bool $value Whether or not user is on last page.
+		 */
+		return apply_filters( 'bp_get_the_topic_is_last_page', $topic_template->pag_page == $topic_template->pag->total_pages );
+	}
+
+/**
+ * Output the forums directory search form.
+ */
+function bp_directory_forums_search_form() {
+	$default_search_value = bp_get_search_default_text( 'forums' );
+	$search_value = !empty( $_REQUEST['fs'] ) ? stripslashes( $_REQUEST['fs'] ) : $default_search_value;
+
+	$search_form_html = '<form action="" method="get" id="search-forums-form">
+		<label for="forums_search"><input type="text" name="s" id="forums_search" placeholder="'. esc_attr( $search_value ) .'" /></label>
+		<input type="submit" id="forums_search_submit" name="forums_search_submit" value="' . __( 'Search', 'buddypress' ) . '" />
+	</form>';
+
+	/**
+	 * Filters the forums directory search form.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $search_form_html HTML search form for the forums directory.
+	 */
+	echo apply_filters( 'bp_directory_forums_search_form', $search_form_html );
+}
+
+/**
+ * Output the link to a given forum.
+ *
+ * @see bp_get_forum_permalink() for a description of arguments.
+ *
+ * @param int $forum_id See {@link bp_get_forum_permalink()}.
+ */
+function bp_forum_permalink( $forum_id = 0 ) {
+	echo bp_get_forum_permalink( $forum_id );
+}
+	/**
+	 * Return the permalink to a given forum.
+	 *
+	 * @param int $forum_id Optional. Defaults to the current forum, if
+	 *                      there is one.
+	 * @return string|bool False on failure, a URL on success.
+	 */
+	function bp_get_forum_permalink( $forum_id = 0 ) {
+
+		if ( bp_is_groups_component() ) {
+			$permalink = trailingslashit( bp_get_groups_directory_permalink() . bp_current_item() . '/forum' );
+		} else {
+			if ( empty( $forum_id ) ) {
+				global $topic_template;
+				if ( isset( $topic_template->forum_id ) )
+					$forum_id = $topic_template->forum_id;
+			}
+
+			if ( $forum = bp_forums_get_forum( $forum_id ) )
+				$permalink = trailingslashit( bp_get_root_domain() . '/' . bp_get_forums_root_slug() . '/forum/' . $forum->forum_slug );
+			else
+				return false;
+		}
+
+		/**
+		 * Filters the permalink to a given forum.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Peramlink to the given forum.
+		 */
+		return apply_filters( 'bp_get_forum_permalink', trailingslashit( $permalink ) );
+	}
+
+/**
+ * Output the name of a given forum.
+ *
+ * @see bp_get_forum_name() for a description of parameters.
+ *
+ * @param int $forum_id See {@link bp_get_forum_name()}.
+ */
+function bp_forum_name( $forum_id = 0 ) {
+	echo bp_get_forum_name( $forum_id );
+}
+	/**
+	 * Return the name of a given forum.
+	 *
+	 * @param int $forum_id Optional. Defaults to the current forum, if
+	 *                      there is one.
+	 * @return string|bool False on failure, a name on success.
+	 */
+	function bp_get_forum_name( $forum_id = 0 ) {
+
+		if ( empty( $forum_id ) ) {
+			global $topic_template;
+			if ( isset( $topic_template->forum_id ) )
+				$forum_id = $topic_template->forum_id;
+		}
+
+		if ( $forum = bp_forums_get_forum( $forum_id ) ) {
+
+			/**
+			 * Filters the name of a given forum.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param string $forum_name Name of the given forum.
+			 * @param string $forum_id   ID of the given forum.
+			 */
+			return apply_filters( 'bp_get_forum_name', $forum->forum_name, $forum->forum_id );
+		} else {
+			return false;
+		}
+	}
+
+/**
+ * Get a heatmap of forum tags for the installation.
+ *
+ * A wrapper for {@link bb_tag_heat_map}, which provides it with BP-friendly
+ * defaults.
+ *
+ * @param array|string $args {
+ *     An array of optional arguments.
+ *     @type int    $smallest Size of the smallest link. Default: 10.
+ *     @type int    $largest  Size of the largest link. Default: 42.
+ *     @type string $sizing   Unit for $largest and $smallest. Default: 'px'.
+ *     @type int    $limit    Max number of tags to display. Default: 50.
+ * }
+ */
+function bp_forums_tag_heat_map( $args = '' ) {
+	$defaults = array(
+		'smallest' => '10',
+		'largest'  => '42',
+		'sizing'   => 'px',
+		'limit'    => '50'
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+	extract( $r, EXTR_SKIP );
+
+	bb_tag_heat_map( $smallest, $largest, $sizing, $limit );
+}
+
+/**
+ * Output the current topic's tag list, comma-separated.
+ *
+ * @since 1.5.0
+ */
+function bp_forum_topic_tag_list() {
+	echo bp_get_forum_topic_tag_list();
+}
+	/**
+	 * Get the current topic's tag list.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $format 'string' returns comma-separated string;
+	 *                       otherwise returns array.
+	 * @return mixed $tags
+	 */
+	function bp_get_forum_topic_tag_list( $format = 'string' ) {
+		global $topic_template;
+
+		$tags_data = !empty( $topic_template->topic_tags ) ? $topic_template->topic_tags : false;
+
+		$tags = array();
+
+		if ( $tags_data ) {
+			foreach( $tags_data as $tag_data ) {
+				$tags[] = $tag_data->name;
+			}
+		}
+
+		if ( 'string' == $format )
+			$tags = implode( ', ', $tags );
+
+		/**
+		 * Filters the current topic's tag list.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string|array $tags   List or array of tags for the current topic.
+		 * @param string       $format Requested format for the tags.
+		 */
+		return apply_filters( 'bp_forum_topic_tag_list', $tags, $format );
+	}
+
+/**
+ * Does the current topic have any tags?
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if the current topic has tags, otherwise false.
+ */
+function bp_forum_topic_has_tags() {
+	global $topic_template;
+
+	$has_tags = false;
+
+	if ( !empty( $topic_template->topic_tags ) )
+		$has_tags = true;
+
+	/**
+	 * Filters whether or not a forum topic has any tags.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $has_tags Whether or not there are any tags.
+	 */
+	return apply_filters( 'bp_forum_topic_has_tags', $has_tags );
+}
+
+/**
+ * Output a URL to use in as a forum form 'action'.
+ */
+function bp_forum_action() {
+	echo bp_get_forum_action();
+}
+	/**
+	 * Get a URL to use in as a forum form 'action'.
+	 *
+	 * @return string URL of the current page, minus query args.
+	 */
+	function bp_get_forum_action() {
+		global $topic_template;
+
+		/**
+		 * Filters the url to use in a forum form 'action'.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL to use in the forum form 'action'.
+		 */
+		return apply_filters( 'bp_get_forum_action', bp_get_root_domain() . esc_attr( $_SERVER['REQUEST_URI'] ) );
+	}
+
+/**
+ * Output a URL to use in as a forum topic form 'action'.
+ */
+function bp_forum_topic_action() {
+	echo bp_get_forum_topic_action();
+}
+	/**
+	 * Get a URL to use in as a forum topic form 'action'.
+	 *
+	 * @return string URL of the current page, minus query args.
+	 */
+	function bp_get_forum_topic_action() {
+
+		/**
+		 * Filters the url to use in a forum topic form 'action'.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL to use in the forum topic form 'action'.
+		 */
+		return apply_filters( 'bp_get_forum_topic_action', $_SERVER['REQUEST_URI'] );
+	}
+
+/**
+ * Output the total topic count for a given user.
+ *
+ * @see bp_get_forum_topic_count_for_user() for description of parameters.
+ *
+ * @param int $user_id See {@link bp_get_forum_topic_count_for_user()}.
+ */
+function bp_forum_topic_count_for_user( $user_id = 0 ) {
+	echo bp_get_forum_topic_count_for_user( $user_id );
+}
+	/**
+	 * Return the total topic count for a given user.
+	 *
+	 * @param int $user_id See {@link bp_forums_total_topic_count_for_user}.
+	 *
+	 * @return int
+	 */
+	function bp_get_forum_topic_count_for_user( $user_id = 0 ) {
+
+		/**
+		 * Filters the total topic count for a given user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $value Total topic count for the given user.
+		 */
+		return apply_filters( 'bp_get_forum_topic_count_for_user', bp_forums_total_topic_count_for_user( $user_id ) );
+	}
+
+/**
+ * Output the total topic count for a given user.
+ *
+ * @see bp_get_forum_topic_count() for description of parameters.
+ *
+ * @param int $user_id See {@link bp_get_forum_topic_count()}.
+ */
+function bp_forum_topic_count( $user_id = 0 ) {
+	echo bp_get_forum_topic_count( $user_id );
+}
+	/**
+	 * Return the total topic count for a given user.
+	 *
+	 * @param int $user_id See {@link bp_forums_total_topic_count()}.
+	 *
+	 * @return int
+	 */
+	function bp_get_forum_topic_count( $user_id = 0 ) {
+
+		/**
+		 * Filters the total topic count for a given user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $value Total topic count for the given user.
+		 */
+		return apply_filters( 'bp_get_forum_topic_count', bp_forums_total_topic_count( $user_id ) );
+	}
Index: /tags/2.4.3/src/bp-forums/deprecated/1.6.php
===================================================================
--- /tags/2.4.3/src/bp-forums/deprecated/1.6.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/deprecated/1.6.php	(revision 10410)
@@ -0,0 +1,254 @@
+<?php
+/**
+ * BuddyPress Forums Deprecated Functions.
+ *
+ * This file contains all the deprecated functions for BuddyPress forums since
+ * version 1.6. This was a major update for the forums component, moving from
+ * bbPress 1.x to bbPress 2.x.
+ *
+ * @package BuddyPress
+ * @subpackage Forums
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Outputs the markup for the bb-forums-admin panel
+ */
+function bp_forums_bbpress_admin() {
+
+	// The text and URL of the Site Wide Forums button differs depending on whether bbPress
+	// is running.
+	if ( is_plugin_active( 'bbpress/bbpress.php' ) ) {
+		// The bbPress admin page will always be on the root blog. switch_to_blog() will
+		// pass through if we're already there.
+		switch_to_blog( bp_get_root_blog_id() );
+		$button_url = admin_url( add_query_arg( array( 'page' => 'bbpress' ), 'options-general.php' ) );
+		restore_current_blog();
+
+		$button_text = __( 'Configure bbPress', 'buddypress' );
+	} else {
+		$button_url = bp_get_admin_url( add_query_arg( array( 'tab' => 'plugin-information', 'plugin' => 'bbpress', 'TB_iframe' => 'true', 'width' => '640', 'height' => '500' ), 'plugin-install.php' ) );
+		$button_text = __( 'Install bbPress', 'buddypress' );
+	}
+
+	$action = bp_get_admin_url( 'admin.php?page=bb-forums-setup&reinstall=1' ); ?>
+
+	<div class="wrap">
+		<h2 class="nav-tab-wrapper"><?php bp_core_admin_tabs( __( 'Forums', 'buddypress' ) ); ?></h2>
+
+		<?php if ( isset( $_POST['submit'] ) ) : ?>
+
+			<div id="message" class="updated fade">
+				<p><?php _e( 'Settings Saved.', 'buddypress' ) ?></p>
+			</div>
+
+		<?php endif; ?>
+
+		<?php
+
+		if ( isset( $_REQUEST['reinstall'] ) || !bp_forums_is_installed_correctly() ) :
+
+			// Delete the bb-config.php location option.
+			bp_delete_option( 'bb-config-location' );
+
+			// Now delete the bb-config.php file.
+			@unlink( ABSPATH . 'bb-config.php' );
+
+			// Show the updated wizard.
+			bp_forums_bbpress_install_wizard();
+
+		else : ?>
+
+			<div style="width: 45%; float: left; margin-top: 20px;">
+				<h3><?php _e( '(Installed)', 'buddypress' ); ?> <?php _e( 'Forums for Groups', 'buddypress' ) ?></h3>
+
+				<p><?php _e( 'Give each individual group its own discussion forum. Choose this if you\'d like to keep your members\' conversations separated into distinct areas.' , 'buddypress' ); ?></p>
+
+				<p><?php _e( 'Note: This component is retired and will not be receiving any updates in the future.  Only use this component if your current site relies on it.' , 'buddypress' ); ?></p>
+
+				<h4 style="margin-bottom: 10px;"><?php _e( 'Features', 'buddypress' ); ?></h4>
+				<ul class="description" style="list-style: square; margin-left: 30px;">
+					<li><?php _e( 'Group Integration',           'buddypress' ); ?></p></li>
+					<li><?php _e( 'Member Profile Integration',  'buddypress' ); ?></p></li>
+					<li><?php _e( 'Activity Stream Integration', 'buddypress' ); ?></p></li>
+					<li><?php _e( '@ Mention Integration',       'buddypress' ); ?></p></li>
+				</ul>
+
+				<div>
+					<a class="button button-primary confirm" href="<?php echo $action ?>"><?php _e( 'Uninstall Group Forums', 'buddypress' ) ?></a> &nbsp;
+				</div>
+			</div>
+
+			<div style="width: 45%; float: left; margin: 20px 0 20px 20px; padding: 0 20px 20px 20px; border: 1px solid #ddd; background-color: #fff;">
+				<h3><?php _e( 'New! bbPress', 'buddypress' ) ?></h3>
+				<p><?php _e( 'bbPress is a brand-new forum plugin from one of the lead developers of BuddyPress.', 'buddypress' ) ?></p>
+
+				<p><?php _e( 'It boasts a bunch of cool features that the BP Legacy Discussion Forums does not have including:', 'buddypress' ) ?></p>
+
+				<ul class="description" style="list-style: square; margin-left: 30px;">
+					<li><?php _e( 'Non-group specific forum creation', 'buddypress' ); ?></p></li>
+					<li><?php _e( 'Moderation via the WP admin dashboard', 'buddypress' ); ?></p></li>
+					<li><?php _e( 'Topic splitting', 'buddypress' ); ?></p></li>
+					<li><?php _e( 'Revisions', 'buddypress' ); ?></p></li>
+					<li><?php _e( 'Spam management', 'buddypress' ); ?></p></li>
+					<li><?php _e( 'Subscriptions', 'buddypress' ); ?></p></li>
+					<li><?php _e( 'And more!', 'buddypress' ); ?></p></li>
+				</ul>
+
+				<p><?php printf( __( 'If you decide to use bbPress, you will need to deactivate the legacy group forum component.  For more info, <a href="%s">read this codex article</a>.', 'buddypress' ), 'https://codex.buddypress.org/legacy/getting-started/using-bbpress-2-2-with-buddypress/' ) ?></p>
+
+				<div>
+					<a class="button thickbox button-primary" href="<?php echo esc_url( $button_url ) ?>"><?php echo esc_html( $button_text ) ?></a> &nbsp;
+				</div>
+			</div>
+
+		<?php endif; ?>
+
+	</div>
+<?php
+}
+
+function bp_forums_bbpress_install_wizard() {
+	$post_url                 = bp_get_admin_url( 'admin.php?page=bb-forums-setup' );
+	$bbpress_plugin_is_active = false;
+
+	$step = isset( $_REQUEST['step'] ) ? $_REQUEST['step'] : '';
+
+	// The text and URL of the Site Wide Forums button differs depending on whether bbPress
+	// is running.
+	if ( is_plugin_active( 'bbpress/bbpress.php' ) ) {
+		$bbpress_plugin_is_active = true;
+
+		// The bbPress admin page will always be on the root blog. switch_to_blog() will
+		// pass through if we're already there.
+		switch_to_blog( bp_get_root_blog_id() );
+		$button_url = admin_url( add_query_arg( array( 'page' => 'bbpress' ), 'options-general.php' ) );
+		restore_current_blog();
+
+		$button_text = __( 'Configure bbPress', 'buddypress' );
+	} else {
+		$button_url = bp_get_admin_url( add_query_arg( array( 'tab' => 'plugin-information', 'plugin' => 'bbpress', 'TB_iframe' => 'true', 'width' => '640', 'height' => '500' ), 'plugin-install.php' ) );
+		$button_text = __( 'Install bbPress', 'buddypress' );
+	}
+
+	switch( $step ) {
+		case 'existing':
+			if ( isset( $_REQUEST['doinstall'] ) && ( 1 == (int) $_REQUEST['doinstall'] ) ) {
+				if ( !bp_forums_configure_existing_install() ) {
+					_e( 'The bb-config.php file was not found at that location. Please try again.', 'buddypress' );
+				} else {
+					?>
+					<h3><?php _e( 'Forums were set up correctly using your existing bbPress install!', 'buddypress' ) ?></h3>
+					<p><?php _e( 'BuddyPress will now use its internal copy of bbPress to run the forums on your site. If you wish, you can remove your old bbPress installation files, as long as you keep the bb-config.php file in the same location.', 'buddypress' ) ?></p><?php
+				}
+			} else { ?>
+
+					<form action="" method="post">
+						<h3><?php _e( 'Existing bbPress Installation', 'buddypress' ) ?></h3>
+						<p><?php _e( "BuddyPress can make use of your existing bbPress install. Just provide the location of your <code>bb-config.php</code> file, and BuddyPress will do the rest.", 'buddypress' ) ?></p>
+						<p><label><code>bb-config.php</code> file location:</label><br /><input style="width: 50%" type="text" name="bbconfigloc" id="bbconfigloc" value="<?php echo str_replace( 'buddypress', '', $_SERVER['DOCUMENT_ROOT'] ) ?>" /></p>
+						<p><input type="submit" class="button-primary" value="<?php esc_attr_e( 'Complete Installation', 'buddypress' ) ?>" /></p>
+						<input type="hidden" name="step" value="existing" />
+						<input type="hidden" name="doinstall" value="1" />
+						<?php wp_nonce_field( 'bp_forums_existing_install_init' ) ?>
+					</form>
+
+				<?php
+			}
+		break;
+
+		case 'new':
+			if ( isset( $_REQUEST['doinstall'] ) && 1 == (int)$_REQUEST['doinstall'] ) {
+				$result = bp_forums_bbpress_install();
+
+				switch ( $result ) {
+					case 1:
+						echo '<p>';
+						_e( 'All done! Configuration settings have been saved to the file <code>bb-config.php</code> in the root of your WordPress install.', 'buddypress' );
+						echo '</p>';
+						break;
+					default:
+						// Just write the contents to screen.
+						echo '<p>';
+						_e( 'A configuration file could not be created. No problem, but you will need to save the text shown below into a file named <code>bb-config.php</code> in the root directory of your WordPress installation before you can start using the forum functionality.', 'buddypress' );
+						echo '</p>';
+						?>
+						<textarea style="display:block; margin-top: 30px; width: 80%;" rows="50"><?php echo esc_textarea( $result ); ?></textarea>
+						<?php
+						break;
+				}
+			} else { ?>
+
+				<h3><?php _e( 'New bbPress Installation', 'buddypress' ) ?></h3>
+				<p><?php _e( "You've decided to set up a new installation of bbPress for forum management in BuddyPress. This is very simple and is usually just a one click
+				process. When you're ready, hit the link below.", 'buddypress' ) ?></p>
+				<p><a class="button-primary" href="<?php echo esc_url( wp_nonce_url( $post_url . '&step=new&doinstall=1', 'bp_forums_new_install_init' ) ); ?>"><?php _e( 'Complete Installation', 'buddypress' ) ?></a></p>
+
+				<?php
+			}
+		break;
+
+		default:
+			if ( !file_exists( buddypress()->plugin_dir . '/bp-forums/bbpress/' ) ) { ?>
+
+				<div id="message" class="error">
+					<p><?php printf( __( 'bbPress files were not found. To install the forums component you must download a copy of bbPress and make sure it is in the folder: "%s"', 'buddypress' ), 'wp-content/plugins/buddypress/bp-forums/bbpress/' ) ?></p>
+				</div>
+
+			<?php } else {
+
+				// Include the plugin install.
+				add_thickbox();
+				wp_enqueue_script( 'plugin-install' );
+				wp_admin_css( 'plugin-install' );
+			?>
+
+				<div style="width: 45%; float: left;  margin-top: 20px;">
+					<h3><?php _e( 'Forums for Groups', 'buddypress' ) ?></h3>
+
+					<p><?php _e( 'Give each individual group its own discussion forum. Choose this if you\'d like to keep your members\' conversations separated into distinct areas.' , 'buddypress' ); ?></p>
+
+					<p><?php _e( 'Note: This component is retired and will not be receiving any updates in the future.  Only use this component if your current site relies on it.' , 'buddypress' ); ?></p>
+
+					<h4 style="margin-bottom: 10px;"><?php _e( 'Features', 'buddypress' ); ?></h4>
+					<ul class="description" style="list-style: square; margin-left: 30px;">
+						<li><?php _e( 'Group Integration',           'buddypress' ); ?></p></li>
+						<li><?php _e( 'Member Profile Integration',  'buddypress' ); ?></p></li>
+						<li><?php _e( 'Activity Stream Integration', 'buddypress' ); ?></p></li>
+						<li><?php _e( '@ Mention Integration',       'buddypress' ); ?></p></li>
+					</ul>
+
+					<div>
+						<a class="button button-primary" href="<?php echo esc_url( $post_url ) . '&step=new' ?>"><?php _e( 'Install Group Forums', 'buddypress' ) ?></a> &nbsp;
+						<a class="button" href="<?php echo esc_url( $post_url ) . '&step=existing' ?>"><?php _e( 'Use Existing Installation', 'buddypress' ) ?></a>
+					</div>
+				</div>
+
+				<div style="width: 45%; float: left; margin: 20px 0 20px 20px; padding: 0 20px 20px 20px; border: 1px solid #ddd; background-color: #fff;">
+					<h3><?php _e( 'New! bbPress', 'buddypress' ) ?></h3>
+					<p><?php _e( 'bbPress is a brand-new forum plugin from one of the lead developers of BuddyPress.', 'buddypress' ) ?></p>
+
+					<p><?php _e( 'It boasts a bunch of cool features that the BP Legacy Discussion Forums does not have including:', 'buddypress' ) ?></p>
+
+					<ul class="description" style="list-style: square; margin-left: 30px;">
+						<li><?php _e( 'Non-group specific forum creation', 'buddypress' ); ?></p></li>
+						<li><?php _e( 'Moderation via the WP admin dashboard', 'buddypress' ); ?></p></li>
+						<li><?php _e( 'Topic splitting', 'buddypress' ); ?></p></li>
+						<li><?php _e( 'Revisions', 'buddypress' ); ?></p></li>
+						<li><?php _e( 'Spam management', 'buddypress' ); ?></p></li>
+						<li><?php _e( 'Subscriptions', 'buddypress' ); ?></p></li>
+						<li><?php _e( 'And more!', 'buddypress' ); ?></p></li>
+					</ul>
+
+					<p><?php printf( __( 'If you decide to use bbPress, you will need to deactivate the legacy group forum component.  For more info, <a href="%s">read this codex article</a>.', 'buddypress' ), 'https://codex.buddypress.org/legacy/getting-started/using-bbpress-2-2-with-buddypress/' ) ?></p>
+					<div>
+						<a class="button button-primary <?php if ( ! $bbpress_plugin_is_active ) { echo esc_attr( 'thickbox' ); }?>" href="<?php echo esc_url( $button_url ) ?>"><?php echo esc_html( $button_text ) ?></a> &nbsp;
+					</div>
+				</div>
+
+			<?php }
+		break;
+	}
+}
Index: /tags/2.4.3/src/bp-forums/deprecated/1.7.php
===================================================================
--- /tags/2.4.3/src/bp-forums/deprecated/1.7.php	(revision 10410)
+++ /tags/2.4.3/src/bp-forums/deprecated/1.7.php	(revision 10410)
@@ -0,0 +1,206 @@
+<?php
+/**
+ * BuddyPress Forums Deprecated Functions.
+ *
+ * This file contains all the deprecated functions for BuddyPress forums since
+ * version 1.7. This was a major update for the forums component, moving from
+ * bbPress 1.x to bbPress 2.x.
+ *
+ * @package BuddyPress
+ * @subpackage Forums
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+function bp_forums_add_admin_menu() {
+
+	if ( !is_super_admin() )
+		return false;
+
+	$page  = bp_core_do_network_admin()  ? 'settings.php' : 'options-general.php';
+
+	// Add the administration tab under the "Site Admin" tab for site administrators.
+	$hook = add_submenu_page( $page, __( 'Forums', 'buddypress' ), __( 'Forums', 'buddypress' ), 'manage_options', 'bb-forums-setup', "bp_forums_bbpress_admin" );
+
+	// Fudge the highlighted subnav item when on the BuddyPress Forums admin page.
+	add_action( "admin_head-$hook", 'bp_core_modify_admin_menu_highlight' );
+}
+add_action( bp_core_admin_hook(), 'bp_forums_add_admin_menu' );
+
+function bp_forums_configure_existing_install() {
+	global $wpdb, $bbdb;
+
+	check_admin_referer( 'bp_forums_existing_install_init' );
+
+	// Sanitize $_REQUEST['bbconfigloc'].
+	$_REQUEST['bbconfigloc'] = apply_filters( 'bp_forums_bbconfig_location', $_REQUEST['bbconfigloc'] );
+
+	if ( false === strpos( $_REQUEST['bbconfigloc'], 'bb-config.php' ) ) {
+		if ( '/' != substr( $_REQUEST['bbconfigloc'], -1, 1 ) )
+			$_REQUEST['bbconfigloc'] .= '/';
+
+		$_REQUEST['bbconfigloc'] .= 'bb-config.php';
+	}
+
+	bp_update_option( 'bb-config-location', $_REQUEST['bbconfigloc'] );
+
+	if ( !file_exists( $_REQUEST['bbconfigloc'] ) ) {
+		return false;
+	}
+
+	return true;
+}
+
+function bp_forums_bbpress_install( $location = '' ) {
+	global $wpdb, $bbdb;
+
+	check_admin_referer( 'bp_forums_new_install_init' );
+
+	if ( empty( $location ) ) {
+		$location = ABSPATH . 'bb-config.php';
+	}
+
+	$bp = buddypress();
+
+	// Create the bb-config.php file.
+	$initial_write = bp_forums_bbpress_write(
+		$bp->plugin_dir . '/bp-forums/bbpress/bb-config-sample.php',
+		$location,
+		array(
+			"define( 'BBDB_NAME',"  => array( "'bbpress'",                     	"'" . DB_NAME . "'" ),
+			"define( 'BBDB_USER',"  => array( "'username'",                    	"'" . DB_USER . "'" ),
+			"define( 'BBDB_PASSWO"  => array( "'password'",                    	"'" . DB_PASSWORD . "'" ),
+			"define( 'BBDB_HOST',"  => array( "'localhost'",                   	"'" . DB_HOST . "'" ),
+			"define( 'BBDB_CHARSE"  => array( "'utf8'",                        	"'" . DB_CHARSET . "'" ),
+			"define( 'BBDB_COLLAT"  => array( "''",                            	"'" . DB_COLLATE . "'" ),
+			"define( 'BB_AUTH_KEY"  => array( "'put your unique phrase here'",  "'" . addslashes( AUTH_KEY ) . "'" ),
+			"define( 'BB_SECURE_A"  => array( "'put your unique phrase here'",  "'" . addslashes( SECURE_AUTH_KEY ) . "'" ),
+			"define( 'BB_LOGGED_I"  => array( "'put your unique phrase here'",  "'" . addslashes( LOGGED_IN_KEY ) . "'" ),
+			"define( 'BB_NONCE_KE"  => array( "'put your unique phrase here'",  "'" . addslashes( NONCE_KEY ) . "'" ),
+			"\$bb_table_prefix = '" => array( "'bb_'",                          "'" . $bp->table_prefix . "bb_'" ),
+			"define( 'BB_LANG', '"  => array( "''",                             "'" . get_locale() . "'" )
+		)
+	);
+
+	// Add the custom user and usermeta entries to the config file.
+	if ( $initial_write == 1 ) {
+		$file = file_get_contents( $location );
+	} else {
+		$file = &$initial_write;
+	}
+
+	$file = trim( $file );
+	if ( '?>' == substr( $file, -2, 2 ) ) {
+		$file = substr( $file, 0, -2 );
+	}
+
+	$file .= "\n" .   '$bb->custom_user_table = \'' . $wpdb->users . '\';';
+	$file .= "\n" .   '$bb->custom_user_meta_table = \'' . $wpdb->usermeta . '\';';
+	$file .= "\n\n" . '$bb->uri = \'' . $bp->plugin_url . '/bp-forums/bbpress/\';';
+	$file .= "\n" .   '$bb->name = \'' . get_blog_option( bp_get_root_blog_id(), 'blogname' ) . ' ' . __( 'Forums', 'buddypress' ) . '\';';
+
+	if ( is_multisite() ) {
+		$file .= "\n" .   '$bb->wordpress_mu_primary_blog_id = ' . bp_get_root_blog_id() . ';';
+	}
+
+	if ( defined( 'AUTH_SALT' ) ) {
+		$file .= "\n\n" . 'define(\'BB_AUTH_SALT\', \'' . addslashes( AUTH_SALT ) . '\');';
+	}
+
+	if ( defined( 'LOGGED_IN_SALT' ) ) {
+		$file .= "\n" .   'define(\'BB_LOGGED_IN_SALT\', \'' . addslashes( LOGGED_IN_SALT ) . '\');';
+	}
+
+	if ( defined( 'SECURE_AUTH_SALT' ) ) {
+		$file .= "\n" .   'define(\'BB_SECURE_AUTH_SALT\', \'' . addslashes( SECURE_AUTH_SALT ) . '\');';
+	}
+
+	$file .= "\n\n" . 'define(\'WP_AUTH_COOKIE_VERSION\', 2);';
+	$file .= "\n\n" . '?>';
+
+	if ( $initial_write == 1 ) {
+		$file_handle = fopen( $location, 'w' );
+		fwrite( $file_handle, $file );
+		fclose( $file_handle );
+	} else {
+		$initial_write = $file;
+	}
+
+	bp_update_option( 'bb-config-location', $location );
+	return $initial_write;
+}
+
+function bp_forums_bbpress_write( $file_source, $file_target, $alterations ) {
+
+	if ( empty( $file_source ) || !file_exists( $file_source ) || !is_file( $file_source ) ) {
+		return -1;
+	}
+
+	if ( empty( $file_target ) ) {
+		$file_target = $file_source;
+	}
+
+	if ( empty( $alterations ) || !is_array( $alterations ) ) {
+		return -2;
+	}
+
+	// Get the existing lines in the file.
+	$lines = file( $file_source );
+
+	// Initialise an array to store the modified lines.
+	$modified_lines = array();
+
+	// Loop through the lines and modify them.
+	foreach ( (array) $lines as $line ) {
+		if ( isset( $alterations[substr( $line, 0, 20 )] ) ) {
+			$alteration = $alterations[substr( $line, 0, 20 )];
+			$modified_lines[] = str_replace( $alteration[0], $alteration[1], $line );
+		} else {
+			$modified_lines[] = $line;
+		}
+	}
+
+	$writable = true;
+	if ( file_exists( $file_target ) ) {
+		if ( !is_writable( $file_target ) ) {
+			$writable = false;
+		}
+	} else {
+		$dir_target = dirname( $file_target );
+
+		if ( file_exists( $dir_target ) ) {
+			if ( !is_writable( $dir_target ) || !is_dir( $dir_target ) ) {
+				$writable = false;
+			}
+		} else {
+			$writable = false;
+		}
+	}
+
+	if ( empty( $writable ) ) {
+		return trim( join( null, $modified_lines ) );
+	}
+
+	// Open the file for writing - rewrites the whole file.
+	$file_handle = fopen( $file_target, 'w' );
+
+	// Write lines one by one to avoid OS specific newline hassles.
+	foreach ( (array) $modified_lines as $modified_line ) {
+		if ( strlen( $modified_line ) - 2 === strrpos( $modified_line, '?>' ) ) {
+			$modified_line = '?>';
+		}
+
+		fwrite( $file_handle, $modified_line );
+		if ( $modified_line == '?>' ) {
+			break;
+		}
+	}
+
+	// Close the config file.
+	fclose( $file_handle );
+
+	@chmod( $file_target, 0666 );
+
+	return 1;
+}
Index: /tags/2.4.3/src/bp-friends/bp-friends-actions.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-actions.php	(revision 10410)
@@ -0,0 +1,90 @@
+<?php
+/**
+ * BuddyPress Friends Actions.
+ *
+ * Action functions are exactly the same as screen functions, however they do
+ * not have a template screen associated with them. Usually they will send the
+ * user back to the default screen after execution.
+ *
+ * @package BuddyPress
+ * @subpackage FriendsActions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Catch and process friendship requests.
+ */
+function friends_action_add_friend() {
+	if ( !bp_is_friends_component() || !bp_is_current_action( 'add-friend' ) )
+		return false;
+
+	if ( !$potential_friend_id = (int)bp_action_variable( 0 ) )
+		return false;
+
+	if ( $potential_friend_id == bp_loggedin_user_id() )
+		return false;
+
+	$friendship_status = BP_Friends_Friendship::check_is_friend( bp_loggedin_user_id(), $potential_friend_id );
+
+	if ( 'not_friends' == $friendship_status ) {
+
+		if ( !check_admin_referer( 'friends_add_friend' ) )
+			return false;
+
+		if ( !friends_add_friend( bp_loggedin_user_id(), $potential_friend_id ) ) {
+			bp_core_add_message( __( 'Friendship could not be requested.', 'buddypress' ), 'error' );
+		} else {
+			bp_core_add_message( __( 'Friendship requested', 'buddypress' ) );
+		}
+
+	} elseif ( 'is_friend' == $friendship_status ) {
+		bp_core_add_message( __( 'You are already friends with this user', 'buddypress' ), 'error' );
+	} else {
+		bp_core_add_message( __( 'You already have a pending friendship request with this user', 'buddypress' ), 'error' );
+	}
+
+	bp_core_redirect( wp_get_referer() );
+
+	return false;
+}
+add_action( 'bp_init', 'friends_action_add_friend' );
+
+/**
+ * Catch and process Remove Friendship requests.
+ */
+function friends_action_remove_friend() {
+	if ( !bp_is_friends_component() || !bp_is_current_action( 'remove-friend' ) )
+		return false;
+
+	if ( !$potential_friend_id = (int)bp_action_variable( 0 ) )
+		return false;
+
+	if ( $potential_friend_id == bp_loggedin_user_id() )
+		return false;
+
+	$friendship_status = BP_Friends_Friendship::check_is_friend( bp_loggedin_user_id(), $potential_friend_id );
+
+	if ( 'is_friend' == $friendship_status ) {
+
+		if ( !check_admin_referer( 'friends_remove_friend' ) )
+			return false;
+
+		if ( !friends_remove_friend( bp_loggedin_user_id(), $potential_friend_id ) ) {
+			bp_core_add_message( __( 'Friendship could not be canceled.', 'buddypress' ), 'error' );
+		} else {
+			bp_core_add_message( __( 'Friendship canceled', 'buddypress' ) );
+		}
+
+	} elseif ( 'is_friends' == $friendship_status ) {
+		bp_core_add_message( __( 'You are not yet friends with this user', 'buddypress' ), 'error' );
+	} else {
+		bp_core_add_message( __( 'You have a pending friendship request with this user', 'buddypress' ), 'error' );
+	}
+
+	bp_core_redirect( wp_get_referer() );
+
+	return false;
+}
+add_action( 'bp_init', 'friends_action_remove_friend' );
Index: /tags/2.4.3/src/bp-friends/bp-friends-activity.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-activity.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-activity.php	(revision 10410)
@@ -0,0 +1,385 @@
+<?php
+/**
+ * BuddyPress Friends Activity Functions.
+ *
+ * These functions handle the recording, deleting and formatting of activity
+ * for the user and for this specific component.
+ *
+ * @package BuddyPress
+ * @subpackage FriendsActivity
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Record an activity item related to the Friends component.
+ *
+ * A wrapper for {@link bp_activity_add()} that provides some Friends-specific
+ * defaults.
+ *
+ * @see bp_activity_add() for more detailed description of parameters and
+ *      return values.
+ *
+ * @param array|string $args {
+ *     An array of arguments for the new activity item. Accepts all parameters
+ *     of {@link bp_activity_add()}. The one difference is the following
+ *     argument, which has a different default here:
+ *     @type string $component Default: the id of your Friends component
+ *                             (usually 'friends').
+ * }
+ * @return bool See {@link bp_activity_add()}.
+ */
+function friends_record_activity( $args = '' ) {
+
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	$r = wp_parse_args( $args, array(
+		'user_id'           => bp_loggedin_user_id(),
+		'action'            => '',
+		'content'           => '',
+		'primary_link'      => '',
+		'component'         => buddypress()->friends->id,
+		'type'              => false,
+		'item_id'           => false,
+		'secondary_item_id' => false,
+		'recorded_time'     => bp_core_current_time(),
+		'hide_sitewide'     => false
+	) );
+
+	return bp_activity_add( $r );
+}
+
+/**
+ * Delete an activity item related to the Friends component.
+ *
+ * @param array $args {
+ *     An array of arguments for the item to delete.
+ *     @type int    $item_id ID of the 'item' associated with the activity item.
+ *                           For Friends activity items, this is usually the user ID of one
+ *                           of the friends.
+ *     @type string $type    The 'type' of the activity item (eg
+ *                           'friendship_accepted').
+ *     @type int    $user_id ID of the user associated with the activity item.
+ * }
+ * @return bool True on success, false on failure.
+ */
+function friends_delete_activity( $args ) {
+	if ( ! bp_is_active( 'activity' ) ) {
+		return;
+	}
+
+	bp_activity_delete_by_item_id( array(
+		'component' => buddypress()->friends->id,
+		'item_id'   => $args['item_id'],
+		'type'      => $args['type'],
+		'user_id'   => $args['user_id']
+	) );
+}
+
+/**
+ * Register the activity actions for bp-friends.
+ */
+function friends_register_activity_actions() {
+
+	if ( !bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	$bp = buddypress();
+
+	// These two added in BP 1.6.
+	bp_activity_set_action(
+		$bp->friends->id,
+		'friendship_accepted',
+		__( 'Friendships accepted', 'buddypress' ),
+		'bp_friends_format_activity_action_friendship_accepted',
+		__( 'Friendships', 'buddypress' ),
+		array( 'activity', 'member' )
+	);
+
+	bp_activity_set_action(
+		$bp->friends->id,
+		'friendship_created',
+		__( 'New friendships', 'buddypress' ),
+		'bp_friends_format_activity_action_friendship_created',
+		__( 'Friendships', 'buddypress' ),
+		array( 'activity', 'member' )
+	);
+
+	// < BP 1.6 backpat.
+	bp_activity_set_action( $bp->friends->id, 'friends_register_activity_action', __( 'New friendship created', 'buddypress' ) );
+
+	/**
+	 * Fires after all default bp-friends activity actions have been registered.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'friends_register_activity_actions' );
+}
+add_action( 'bp_register_activity_actions', 'friends_register_activity_actions' );
+
+/**
+ * Format 'friendship_accepted' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Activity action string.
+ * @param object $activity Activity data.
+ * @return string $action Formatted activity action.
+ */
+function bp_friends_format_activity_action_friendship_accepted( $action, $activity ) {
+	$initiator_link = bp_core_get_userlink( $activity->user_id );
+	$friend_link    = bp_core_get_userlink( $activity->secondary_item_id );
+
+	$action = sprintf( __( '%1$s and %2$s are now friends', 'buddypress' ), $initiator_link, $friend_link );
+
+	// Backward compatibility for legacy filter
+	// The old filter has the $friendship object passed to it. We want to
+	// avoid having to build this object if it's not necessary.
+	if ( has_filter( 'friends_activity_friendship_accepted_action' ) ) {
+		$friendship = new BP_Friends_Friendship( $activity->item_id );
+		$action     = apply_filters( 'friends_activity_friendsip_accepted_action', $action, $friendship );
+	}
+
+	/**
+	 * Filters the 'friendship_accepted' activity action format.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $action   String text for the 'friendship_accepted' action.
+	 * @param object $activity Activity data.
+	 */
+	return apply_filters( 'bp_friends_format_activity_action_friendship_accepted', $action, $activity );
+}
+
+/**
+ * Format 'friendship_created' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity data.
+ * @return string $action Formatted activity action.
+ */
+function bp_friends_format_activity_action_friendship_created( $action, $activity ) {
+	$initiator_link = bp_core_get_userlink( $activity->user_id );
+	$friend_link    = bp_core_get_userlink( $activity->secondary_item_id );
+
+	$action = sprintf( __( '%1$s and %2$s are now friends', 'buddypress' ), $initiator_link, $friend_link );
+
+	// Backward compatibility for legacy filter
+	// The old filter has the $friendship object passed to it. We want to
+	// avoid having to build this object if it's not necessary.
+	if ( has_filter( 'friends_activity_friendship_accepted_action' ) ) {
+		$friendship = new BP_Friends_Friendship( $activity->item_id );
+		$action     = apply_filters( 'friends_activity_friendsip_accepted_action', $action, $friendship );
+	}
+
+	/**
+	 * Filters the 'friendship_created' activity action format.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $action   String text for the 'friendship_created' action.
+	 * @param object $activity Activity data.
+	 */
+	return apply_filters( 'bp_friends_format_activity_action_friendship_created', $action, $activity );
+}
+
+/**
+ * Fetch data related to friended users at the beginning of an activity loop.
+ *
+ * This reduces database overhead during the activity loop.
+ *
+ * @since 2.0.0
+ *
+ * @param array $activities Array of activity items.
+ * @return array
+ */
+function bp_friends_prefetch_activity_object_data( $activities ) {
+	if ( empty( $activities ) ) {
+		return $activities;
+	}
+
+	$friend_ids = array();
+
+	foreach ( $activities as $activity ) {
+		if ( buddypress()->friends->id !== $activity->component ) {
+			continue;
+		}
+
+		$friend_ids[] = $activity->secondary_item_id;
+	}
+
+	if ( ! empty( $friend_ids ) ) {
+		// Fire a user query to prime user caches.
+		new BP_User_Query( array(
+			'user_ids'          => $friend_ids,
+			'populate_extras'   => false,
+			'update_meta_cache' => false,
+		) );
+	}
+
+	return $activities;
+}
+add_filter( 'bp_activity_prefetch_object_data', 'bp_friends_prefetch_activity_object_data' );
+
+/**
+ * Set up activity arguments for use with the 'friends' scope.
+ *
+ * For details on the syntax, see {@link BP_Activity_Query}.
+ *
+ * @since 2.2.0
+ *
+ * @param array $retval Empty array by default.
+ * @param array $filter Current activity arguments.
+ * @return array
+ */
+function bp_friends_filter_activity_scope( $retval = array(), $filter = array() ) {
+
+	// Determine the user_id.
+	if ( ! empty( $filter['user_id'] ) ) {
+		$user_id = $filter['user_id'];
+	} else {
+		$user_id = bp_displayed_user_id()
+			? bp_displayed_user_id()
+			: bp_loggedin_user_id();
+	}
+
+	// Determine friends of user.
+	$friends = friends_get_friend_user_ids( $user_id );
+	if ( empty( $friends ) ) {
+		$friends = array( 0 );
+	}
+
+	$retval = array(
+		'relation' => 'AND',
+		array(
+			'column'  => 'user_id',
+			'compare' => 'IN',
+			'value'   => (array) $friends
+		),
+
+		// We should only be able to view sitewide activity content for friends.
+		array(
+			'column' => 'hide_sitewide',
+			'value'  => 0
+		),
+
+		// Overrides.
+		'override' => array(
+			'filter'      => array( 'user_id' => 0 ),
+			'show_hidden' => true
+		),
+	);
+
+	return $retval;
+}
+add_filter( 'bp_activity_set_friends_scope_args', 'bp_friends_filter_activity_scope', 10, 2 );
+
+/**
+ * Set up activity arguments for use with the 'just-me' scope.
+ *
+ * For details on the syntax, see {@link BP_Activity_Query}.
+ *
+ * @since 2.2.0
+ *
+ * @param array $retval Empty array by default.
+ * @param array $filter Current activity arguments.
+ *
+ * @return array
+ */
+function bp_friends_filter_activity_just_me_scope( $retval = array(), $filter = array() ) {
+
+	// Determine the user_id.
+	if ( ! empty( $filter['user_id'] ) ) {
+		$user_id = $filter['user_id'];
+	} else {
+		$user_id = bp_displayed_user_id()
+			? bp_displayed_user_id()
+			: bp_loggedin_user_id();
+	}
+
+	// Get the requested action.
+	$action = $filter['filter']['action'];
+
+	// Make sure actions are listed in an array.
+	if ( ! is_array( $action ) ) {
+		$action = explode( ',', $filter['filter']['action'] );
+	}
+
+	$action = array_flip( array_filter( $action ) );
+
+	/**
+	 * If filtering activities for something other than the friendship_created
+	 * action return without changing anything
+	 */
+	if ( ! empty( $action ) && ! isset( $action['friendship_created'] ) ) {
+		return $retval;
+	}
+
+	// Juggle existing override value.
+	$override = array();
+	if ( ! empty( $retval['override'] ) ) {
+		$override = $retval['override'];
+		unset( $retval['override'] );
+	}
+
+	/**
+	 * Else make sure to get the friendship_created action, the user is involved in
+	 * - user initiated the friendship
+	 * - user has been requested a friendship
+	 */
+	$retval = array(
+		'relation' => 'OR',
+		$retval,
+		array(
+			'relation' => 'AND',
+			array(
+				'column' => 'component',
+				'value'  => 'friends',
+			),
+			array(
+				'column' => 'secondary_item_id',
+				'value'  => $user_id,
+			),
+		)
+	);
+
+	// Juggle back override value.
+	if ( ! empty( $override ) ) {
+		$retval['override'] = $override;
+	}
+
+	return $retval;
+}
+add_filter( 'bp_activity_set_just-me_scope_args', 'bp_friends_filter_activity_just_me_scope', 20, 2 );
+
+/**
+ * Add activity stream items when one members accepts another members request
+ * for virtual friendship.
+ *
+ * @since 1.9.0
+ *
+ * @param int         $friendship_id       ID of the friendship.
+ * @param int         $initiator_user_id   ID of friendship initiator.
+ * @param int         $friend_user_id      ID of user whose friendship is requested.
+ * @param object|bool $friendship Optional Friendship object.
+ */
+function bp_friends_friendship_accepted_activity( $friendship_id, $initiator_user_id, $friend_user_id, $friendship = false ) {
+	if ( ! bp_is_active( 'activity' ) ) {
+		return;
+	}
+
+	// Record in activity streams for the initiator.
+	friends_record_activity( array(
+		'user_id'           => $initiator_user_id,
+		'type'              => 'friendship_created',
+		'item_id'           => $friendship_id,
+		'secondary_item_id' => $friend_user_id
+	) );
+}
+add_action( 'friends_friendship_accepted', 'bp_friends_friendship_accepted_activity', 10, 4 );
Index: /tags/2.4.3/src/bp-friends/bp-friends-cache.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-cache.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-cache.php	(revision 10410)
@@ -0,0 +1,82 @@
+<?php
+/**
+ * BuddyPress Friends Caching.
+ *
+ * Caching functions handle the clearing of cached objects and pages on specific
+ * actions throughout BuddyPress.
+ *
+ * @package BuddyPress
+ * @subpackage FriendsCaching
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Clear friends-related cache for members of a specific friendship.
+ *
+ * @param int $friendship_id ID of the friendship whose two members should
+ *                           have their friends cache busted.
+ * @return bool
+ */
+function friends_clear_friend_object_cache( $friendship_id ) {
+	if ( !$friendship = new BP_Friends_Friendship( $friendship_id ) )
+		return false;
+
+	wp_cache_delete( 'friends_friend_ids_' .    $friendship->initiator_user_id, 'bp' );
+	wp_cache_delete( 'friends_friend_ids_' .    $friendship->friend_user_id,    'bp' );
+}
+
+// List actions to clear object caches on.
+add_action( 'friends_friendship_accepted', 'friends_clear_friend_object_cache' );
+add_action( 'friends_friendship_deleted',  'friends_clear_friend_object_cache' );
+
+/**
+ * Clear the friend request cache for the user not initiating the friendship.
+ *
+ * @since 2.0.0
+ *
+ * @param int $friend_user_id The user ID not initiating the friendship.
+ */
+function bp_friends_clear_request_cache( $friend_user_id ) {
+	wp_cache_delete( $friend_user_id, 'bp_friends_requests' );
+}
+
+/**
+ * Clear the friend request cache when a friendship is saved.
+ *
+ * A friendship is deemed saved when a friendship is requested or accepted.
+ *
+ * @since 2.0.0
+ *
+ * @param int $friendship_id     The friendship ID.
+ * @param int $initiator_user_id The user ID initiating the friendship.
+ * @param int $friend_user_id    The user ID not initiating the friendship.
+ */
+function bp_friends_clear_request_cache_on_save( $friendship_id, $initiator_user_id, $friend_user_id ) {
+	bp_friends_clear_request_cache( $friend_user_id );
+}
+add_action( 'friends_friendship_requested', 'bp_friends_clear_request_cache_on_save', 10, 3 );
+add_action( 'friends_friendship_accepted',  'bp_friends_clear_request_cache_on_save', 10, 3 );
+
+/**
+ * Clear the friend request cache when a friendship is removed.
+ *
+ * A friendship is deemed removed when a friendship is withdrawn or rejected.
+ *
+ * @since 2.0.0
+ *
+ * @param int                   $friendship_id The friendship ID.
+ * @param BP_Friends_Friendship $friendship Friendship object.
+ */
+function bp_friends_clear_request_cache_on_remove( $friendship_id, BP_Friends_Friendship $friendship ) {
+	bp_friends_clear_request_cache( $friendship->friend_user_id );
+}
+add_action( 'friends_friendship_withdrawn', 'bp_friends_clear_request_cache_on_remove', 10, 2 );
+add_action( 'friends_friendship_rejected',  'bp_friends_clear_request_cache_on_remove', 10, 2 );
+
+// List actions to clear super cached pages on, if super cache is installed.
+add_action( 'friends_friendship_rejected',  'bp_core_clear_cache' );
+add_action( 'friends_friendship_accepted',  'bp_core_clear_cache' );
+add_action( 'friends_friendship_deleted',   'bp_core_clear_cache' );
+add_action( 'friends_friendship_requested', 'bp_core_clear_cache' );
Index: /tags/2.4.3/src/bp-friends/bp-friends-classes.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-classes.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-classes.php	(revision 10410)
@@ -0,0 +1,12 @@
+<?php
+/**
+ * BuddyPress Friends Classes.
+ *
+ * @package BuddyPress
+ * @subpackage FriendsClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+require dirname( __FILE__ ) . '/classes/class-bp-friends-friendship.php';
Index: /tags/2.4.3/src/bp-friends/bp-friends-filters.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-filters.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-filters.php	(revision 10410)
@@ -0,0 +1,72 @@
+<?php
+/**
+ * BuddyPress Friend Filters.
+ *
+ * @package BuddyPress
+ * @subpackage FriendsFilters
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Filter BP_User_Query::populate_extras to add confirmed friendship status.
+ *
+ * Each member in the user query is checked for confirmed friendship status
+ * against the logged-in user.
+ *
+ * @since 1.7.0
+ *
+ * @global WPDB $wpdb WordPress database access object.
+ *
+ * @param BP_User_Query $user_query   The BP_User_Query object.
+ * @param string        $user_ids_sql Comma-separated list of user IDs to fetch extra
+ *                                    data for, as determined by BP_User_Query.
+ */
+function bp_friends_filter_user_query_populate_extras( BP_User_Query $user_query, $user_ids_sql ) {
+	global $wpdb;
+
+	// Stop if user isn't logged in.
+	if ( ! is_user_logged_in() ) {
+		return;
+	}
+
+	$bp = buddypress();
+
+	// Fetch whether or not the user is a friend of the current user.
+	$friend_status = $wpdb->get_results( $wpdb->prepare( "SELECT initiator_user_id, friend_user_id, is_confirmed FROM {$bp->friends->table_name} WHERE (initiator_user_id = %d AND friend_user_id IN ( {$user_ids_sql} ) ) OR (initiator_user_id IN ( {$user_ids_sql} ) AND friend_user_id = %d )", bp_loggedin_user_id(), bp_loggedin_user_id() ) );
+
+	// Keep track of members that have a friendship status with the current user.
+	$friend_user_ids = array();
+
+	// The "friend" is the user ID in the pair who is *not* the logged in user.
+	foreach ( (array) $friend_status as $fs ) {
+		$friend_id = bp_loggedin_user_id() == $fs->initiator_user_id ? $fs->friend_user_id : $fs->initiator_user_id;
+		$friend_user_ids[] = $friend_id;
+
+		if ( isset( $user_query->results[ $friend_id ] ) ) {
+			if ( 0 == $fs->is_confirmed ) {
+				$status = $fs->initiator_user_id == bp_loggedin_user_id() ? 'pending' : 'awaiting_response';
+			} else {
+				$status = 'is_friend';
+			}
+
+			$user_query->results[ $friend_id ]->is_friend         = $fs->is_confirmed;
+			$user_query->results[ $friend_id ]->friendship_status = $status;
+		}
+	}
+
+	// The rest are not friends with the current user, so set status accordingly.
+	$not_friends = array_diff( $user_query->user_ids, $friend_user_ids );
+	foreach ( (array) $not_friends as $nf ) {
+		if ( bp_loggedin_user_id() == $nf ) {
+			continue;
+		}
+
+		if ( isset( $user_query->results[ $nf ] ) ) {
+			$user_query->results[ $nf ]->friendship_status = 'not_friends';
+		}
+	}
+
+}
+add_filter( 'bp_user_query_populate_extras', 'bp_friends_filter_user_query_populate_extras', 4, 2 );
Index: /tags/2.4.3/src/bp-friends/bp-friends-functions.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-functions.php	(revision 10410)
@@ -0,0 +1,748 @@
+<?php
+/**
+ * BuddyPress Friends Functions.
+ *
+ * Functions are where all the magic happens in BuddyPress. They will
+ * handle the actual saving or manipulation of information. Usually they will
+ * hand off to a database class for data access, then return
+ * true or false on success or failure.
+ *
+ * @package BuddyPress
+ * @subpackage FriendsFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Create a new friendship.
+ *
+ * @param int  $initiator_userid ID of the "initiator" user (the user who is
+ *                               sending the friendship request).
+ * @param int  $friend_userid    ID of the "friend" user (the user whose friendship
+ *                               is being requested).
+ * @param bool $force_accept     Optional. Whether to force acceptance. When false,
+ *                               running friends_add_friend() will result in a friendship request.
+ *                               When true, running friends_add_friend() will result in an accepted
+ *                               friendship, with no notifications being sent. Default: false.
+ * @return bool True on success, false on failure.
+ */
+function friends_add_friend( $initiator_userid, $friend_userid, $force_accept = false ) {
+
+	// You cannot be friends with yourself!
+	if ( $initiator_userid == $friend_userid ) {
+		return false;
+	}
+
+	// Check if already friends, and bail if so.
+	if ( friends_check_friendship( $initiator_userid, $friend_userid ) ) {
+		return true;
+	}
+
+	// Setup the friendship data.
+	$friendship = new BP_Friends_Friendship;
+	$friendship->initiator_user_id = $initiator_userid;
+	$friendship->friend_user_id    = $friend_userid;
+	$friendship->is_confirmed      = 0;
+	$friendship->is_limited        = 0;
+	$friendship->date_created      = bp_core_current_time();
+
+	if ( ! empty( $force_accept ) ) {
+		$friendship->is_confirmed = 1;
+	}
+
+	// Bail if friendship could not be saved (how sad!).
+	if ( ! $friendship->save() ) {
+		return false;
+	}
+
+	// Send notifications.
+	if ( empty( $force_accept ) ) {
+		$action = 'requested';
+
+	// Update friend totals.
+	} else {
+		$action = 'accepted';
+		friends_update_friend_totals( $friendship->initiator_user_id, $friendship->friend_user_id, 'add' );
+	}
+
+	/**
+	 * Fires at the end of initiating a new friendship connection.
+	 *
+	 * This is a variable hook, depending on context.
+	 * The two potential hooks are: friends_friendship_requested, friends_friendship_accepted.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int    $id                ID of the pending friendship connection.
+	 * @param int    $initiator_user_id ID of the friendship initiator.
+	 * @param int    $friend_user_id    ID of the friend user.
+	 * @param object $friendship        BuddyPress Friendship Object.
+	 */
+	do_action( 'friends_friendship_' . $action, $friendship->id, $friendship->initiator_user_id, $friendship->friend_user_id, $friendship );
+
+	return true;
+}
+
+/**
+ * Remove a friendship.
+ *
+ * Will also delete the related "friendship_accepted" activity item.
+ *
+ * @param int $initiator_userid ID of the friendship initiator.
+ * @param int $friend_userid    ID of the friend user.
+ * @return bool True on success, false on failure.
+ */
+function friends_remove_friend( $initiator_userid, $friend_userid ) {
+
+	$friendship_id = BP_Friends_Friendship::get_friendship_id( $initiator_userid, $friend_userid );
+	$friendship    = new BP_Friends_Friendship( $friendship_id );
+
+	/**
+	 * Fires before the deletion of a friendship activity item
+	 * for the user who canceled the friendship.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $friendship_id    ID of the friendship object, if any, between a pair of users.
+	 * @param int $initiator_userid ID of the friendship initiator.
+	 * @param int $friend_userid    ID of the friend user.
+	 */
+	do_action( 'friends_before_friendship_delete', $friendship_id, $initiator_userid, $friend_userid );
+
+	// Remove the activity stream items about the friendship id.
+	friends_delete_activity( array( 'item_id' => $friendship_id, 'type' => 'friendship_created', 'user_id' => 0 ) );
+
+	/**
+	 * Fires before the friendship connection is removed.
+	 *
+	 * This hook is misleadingly named - the friendship is not yet deleted.
+	 * This is your last chance to do something while the friendship exists.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $friendship_id    ID of the friendship object, if any, between a pair of users.
+	 * @param int $initiator_userid ID of the friendship initiator.
+	 * @param int $friend_userid    ID of the friend user.
+	 */
+	do_action( 'friends_friendship_deleted', $friendship_id, $initiator_userid, $friend_userid );
+
+	if ( $friendship->delete() ) {
+		friends_update_friend_totals( $initiator_userid, $friend_userid, 'remove' );
+
+		/**
+		 * Fires after the friendship connection is removed.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param int $initiator_userid ID of the friendship initiator.
+		 * @param int $friend_userid    ID of the friend user.
+		 */
+		do_action( 'friends_friendship_post_delete', $initiator_userid, $friend_userid );
+
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Mark a friendship request as accepted.
+ *
+ * Also initiates a "friendship_accepted" activity item.
+ *
+ * @param int $friendship_id ID of the pending friendship object.
+ * @return bool True on success, false on failure.
+ */
+function friends_accept_friendship( $friendship_id ) {
+
+	// Get the friendship data.
+	$friendship = new BP_Friends_Friendship( $friendship_id, true, false );
+
+	// Accepting friendship.
+	if ( empty( $friendship->is_confirmed ) && BP_Friends_Friendship::accept( $friendship_id ) ) {
+
+		// Bump the friendship counts.
+		friends_update_friend_totals( $friendship->initiator_user_id, $friendship->friend_user_id );
+
+		/**
+		 * Fires after a friendship is accepted.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int    $id                ID of the pending friendship object.
+		 * @param int    $initiator_user_id ID of the friendship initiator.
+		 * @param int    $friend_user_id    ID of the user requested friendship with.
+		 * @param object $friendship        BuddyPress Friendship Object.
+		 */
+		do_action( 'friends_friendship_accepted', $friendship->id, $friendship->initiator_user_id, $friendship->friend_user_id, $friendship );
+
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Mark a friendship request as rejected.
+ *
+ * @param int $friendship_id ID of the pending friendship object.
+ * @return bool True on success, false on failure.
+ */
+function friends_reject_friendship( $friendship_id ) {
+	$friendship = new BP_Friends_Friendship( $friendship_id, true, false );
+
+	if ( empty( $friendship->is_confirmed ) && BP_Friends_Friendship::reject( $friendship_id ) ) {
+
+		/**
+		 * Fires after a friendship request is rejected.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int                   $friendship_id ID of the pending friendship.
+		 * @param BP_Friends_Friendship $friendships Friendship object. Passed by reference.
+		 */
+		do_action_ref_array( 'friends_friendship_rejected', array( $friendship_id, &$friendship ) );
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Withdraw a friendship request.
+ *
+ * @param int $initiator_userid ID of the friendship initiator - this is the
+ *                              user who requested the friendship, and is doing the withdrawing.
+ * @param int $friend_userid    ID of the requested friend.
+ * @return bool True on success, false on failure.
+ */
+function friends_withdraw_friendship( $initiator_userid, $friend_userid ) {
+	$friendship_id = BP_Friends_Friendship::get_friendship_id( $initiator_userid, $friend_userid );
+	$friendship    = new BP_Friends_Friendship( $friendship_id, true, false );
+
+	if ( empty( $friendship->is_confirmed ) && BP_Friends_Friendship::withdraw( $friendship_id ) ) {
+
+		// @deprecated Since 1.9
+		do_action_ref_array( 'friends_friendship_whithdrawn', array( $friendship_id, &$friendship ) );
+
+		/**
+		 * Fires after a friendship request has been withdrawn.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param int                   $friendship_id ID of the friendship.
+		 * @param BP_Friends_Friendship $friendship    Friendship object. Passed by reference.
+		 */
+		do_action_ref_array( 'friends_friendship_withdrawn',  array( $friendship_id, &$friendship ) );
+
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Check whether two users are friends.
+ *
+ * @param int $user_id            ID of the first user.
+ * @param int $possible_friend_id ID of the other user.
+ * @return bool Returns true if the two users are friends, otherwise false.
+ */
+function friends_check_friendship( $user_id, $possible_friend_id ) {
+
+	if ( 'is_friend' == BP_Friends_Friendship::check_is_friend( $user_id, $possible_friend_id ) )
+		return true;
+
+	return false;
+}
+
+/**
+ * Get the friendship status of two friends.
+ *
+ * Will return 'is_friends', 'not_friends', 'pending' or 'awaiting_response'.
+ *
+ * @param int $user_id            ID of the first user.
+ * @param int $possible_friend_id ID of the other user.
+ * @return string Friend status of the two users.
+ */
+function friends_check_friendship_status( $user_id, $possible_friend_id ) {
+	global $members_template;
+
+	// Check the BP_User_Query first
+	// @see bp_friends_filter_user_query_populate_extras().
+	if ( ! empty( $members_template->in_the_loop ) ) {
+		if ( isset( $members_template->member->friendship_status ) ) {
+			return $members_template->member->friendship_status;
+		}
+	}
+
+	return BP_Friends_Friendship::check_is_friend( $user_id, $possible_friend_id );
+}
+
+/**
+ * Get the friend count of a given user.
+ *
+ * @param int $user_id ID of the user whose friends are being counted.
+ * @return int Friend count of the user.
+ */
+function friends_get_total_friend_count( $user_id = 0 ) {
+	if ( empty( $user_id ) )
+		$user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+
+	$count = bp_get_user_meta( $user_id, 'total_friend_count', true );
+	if ( empty( $count ) )
+		$count = 0;
+
+	/**
+	 * Filters the total friend count for a given user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param int $count Total friend count for a given user.
+	 */
+	return apply_filters( 'friends_get_total_friend_count', $count );
+}
+
+/**
+ * Check whether a given user has any friends.
+ *
+ * @param int $user_id ID of the user whose friends are being checked.
+ * @return bool True if the user has friends, otherwise false.
+ */
+function friends_check_user_has_friends( $user_id ) {
+	$friend_count = friends_get_total_friend_count( $user_id );
+
+	if ( empty( $friend_count ) )
+		return false;
+
+	if ( !(int) $friend_count )
+		return false;
+
+	return true;
+}
+
+/**
+ * Get the ID of two users' friendship, if it exists.
+ *
+ * @param int $initiator_user_id ID of the first user.
+ * @param int $friend_user_id    ID of the second user.
+ * @return int|bool ID of the friendship if found, otherwise false.
+ */
+function friends_get_friendship_id( $initiator_user_id, $friend_user_id ) {
+	return BP_Friends_Friendship::get_friendship_id( $initiator_user_id, $friend_user_id );
+}
+
+/**
+ * Get the IDs of a given user's friends.
+ *
+ * @param int  $user_id              ID of the user whose friends are being retrieved.
+ * @param bool $friend_requests_only Optional. Whether to fetch unaccepted
+ *                                   requests only. Default: false.
+ * @param bool $assoc_arr            Optional. True to receive an array of arrays keyed as
+ *                                   'user_id' => $user_id; false to get a one-dimensional
+ *                                   array of user IDs. Default: false.
+ * @return array
+ */
+function friends_get_friend_user_ids( $user_id, $friend_requests_only = false, $assoc_arr = false ) {
+	return BP_Friends_Friendship::get_friend_user_ids( $user_id, $friend_requests_only, $assoc_arr );
+}
+
+/**
+ * Search the friends of a user by a search string.
+ *
+ * @param string $search_terms The search string, matched against xprofile fields (if
+ *                             available), or usermeta 'nickname' field.
+ * @param int    $user_id      ID of the user whose friends are being searched.
+ * @param int    $pag_num      Optional. Max number of friends to return.
+ * @param int    $pag_page     Optional. The page of results to return. Default: null (no
+ *                             pagination - return all results).
+ * @return array|bool On success, an array: {
+ *     @type array $friends IDs of friends returned by the query.
+ *     @type int   $count   Total number of friends (disregarding
+ *                          pagination) who match the search.
+ * }. Returns false on failure.
+ */
+function friends_search_friends( $search_terms, $user_id, $pag_num = 10, $pag_page = 1 ) {
+	return BP_Friends_Friendship::search_friends( $search_terms, $user_id, $pag_num, $pag_page );
+}
+
+/**
+ * Get a list of IDs of users who have requested friendship of a given user.
+ *
+ * @param int $user_id The ID of the user who has received the friendship requests.
+ * @return array|bool An array of user IDs, or false if none are found.
+ */
+function friends_get_friendship_request_user_ids( $user_id ) {
+	return BP_Friends_Friendship::get_friendship_request_user_ids( $user_id );
+}
+
+/**
+ * Get a user's most recently active friends.
+ *
+ * @see BP_Core_User::get_users() for a description of return value.
+ *
+ * @param int    $user_id  ID of the user whose friends are being retrieved.
+ * @param int    $per_page Optional. Number of results to return per page.
+ *                         Default: 0 (no pagination; show all results).
+ * @param int    $page     Optional. Number of the page of results to return.
+ *                         Default: 0 (no pagination; show all results).
+ * @param string $filter   Optional. Limit results to those matching a search
+ *                         string.
+ * @return array See {@link BP_Core_User::get_users()}.
+ */
+function friends_get_recently_active( $user_id, $per_page = 0, $page = 0, $filter = '' ) {
+
+	/**
+	 * Filters a user's most recently active friends.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array {
+	 *     @type int   $total_users Total number of users matched by query params.
+	 *     @type array $paged_users The current page of users matched by query params.
+	 * }
+	 */
+	return apply_filters( 'friends_get_recently_active', BP_Core_User::get_users( 'active', $per_page, $page, $user_id, $filter ) );
+}
+
+/**
+ * Get a user's friends, in alphabetical order.
+ *
+ * @see BP_Core_User::get_users() for a description of return value.
+ *
+ * @param int    $user_id  ID of the user whose friends are being retrieved.
+ * @param int    $per_page Optional. Number of results to return per page.
+ *                         Default: 0 (no pagination; show all results).
+ * @param int    $page     Optional. Number of the page of results to return.
+ *                         Default: 0 (no pagination; show all results).
+ * @param string $filter   Optional. Limit results to those matching a search
+ *                         string.
+ * @return array See {@link BP_Core_User::get_users()}.
+ */
+function friends_get_alphabetically( $user_id, $per_page = 0, $page = 0, $filter = '' ) {
+
+	/**
+	 * Filters a user's friends listed in alphabetical order.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @return array {
+	 *     @type int   $total_users Total number of users matched by query params.
+	 *     @type array $paged_users The current page of users matched by query params.
+	 * }
+	 */
+	return apply_filters( 'friends_get_alphabetically', BP_Core_User::get_users( 'alphabetical', $per_page, $page, $user_id, $filter ) );
+}
+
+/**
+ * Get a user's friends, in the order in which they joined the site.
+ *
+ * @see BP_Core_User::get_users() for a description of return value.
+ *
+ * @param int    $user_id  ID of the user whose friends are being retrieved.
+ * @param int    $per_page Optional. Number of results to return per page.
+ *                         Default: 0 (no pagination; show all results).
+ * @param int    $page     Optional. Number of the page of results to return.
+ *                         Default: 0 (no pagination; show all results).
+ * @param string $filter   Optional. Limit results to those matching a search
+ *                         string.
+ * @return array See {@link BP_Core_User::get_users()}.
+ */
+function friends_get_newest( $user_id, $per_page = 0, $page = 0, $filter = '' ) {
+
+	/**
+	 * Filters a user's friends listed from newest to oldest.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array {
+	 *     @type int   $total_users Total number of users matched by query params.
+	 *     @type array $paged_users The current page of users matched by query params.
+	 * }
+	 */
+	return apply_filters( 'friends_get_newest', BP_Core_User::get_users( 'newest', $per_page, $page, $user_id, $filter ) );
+}
+
+/**
+ * Get the last active date of many users at once.
+ *
+ * @see BP_Friends_Friendship::get_bulk_last_active() for a description of
+ *      arguments and return value.
+ *
+ * @param array $friend_ids See BP_Friends_Friendship::get_bulk_last_active().
+ * @return array $user_ids See BP_Friends_Friendship::get_bulk_last_active().
+ */
+function friends_get_bulk_last_active( $friend_ids ) {
+	return BP_Friends_Friendship::get_bulk_last_active( $friend_ids );
+}
+
+/**
+ * Get a list of friends that a user can invite into this group.
+ *
+ * Excludes friends that are already in the group, and banned friends if the
+ * user is not a group admin.
+ *
+ * @since 1.0.0
+ *
+ * @param int $user_id  User ID whose friends to see can be invited. Default:
+ *                      ID of the logged-in user.
+ * @param int $group_id Group to check possible invitations against.
+ * @return mixed False if no friends, array of users if friends.
+ */
+function friends_get_friends_invite_list( $user_id = 0, $group_id = 0 ) {
+
+	// Default to logged in user id.
+	if ( empty( $user_id ) )
+		$user_id = bp_loggedin_user_id();
+
+	// Only group admins can invited previously banned users.
+	$user_is_admin = (bool) groups_is_user_admin( $user_id, $group_id );
+
+	// Assume no friends.
+	$friends = array();
+
+	/**
+	 * Filters default arguments for list of friends a user can invite into this group.
+	 *
+	 * @since 1.5.4
+	 *
+	 * @param array $value Array of default parameters for invite list.
+	 */
+	$args = apply_filters( 'bp_friends_pre_get_invite_list', array(
+		'user_id'  => $user_id,
+		'type'     => 'alphabetical',
+		'per_page' => 0
+	) );
+
+	// User has friends.
+	if ( bp_has_members( $args ) ) {
+
+		/**
+		 * Loop through all friends and try to add them to the invitation list.
+		 *
+		 * Exclude friends that:
+		 *     1. are already members of the group
+		 *     2. are banned from this group if the current user is also not a
+		 *        group admin.
+		 */
+		while ( bp_members() ) :
+
+			// Load the member.
+			bp_the_member();
+
+			// Get the user ID of the friend.
+			$friend_user_id = bp_get_member_user_id();
+
+			// Skip friend if already in the group.
+			if ( groups_is_user_member( $friend_user_id, $group_id ) )
+				continue;
+
+			// Skip friend if not group admin and user banned from group.
+			if ( ( false === $user_is_admin ) && groups_is_user_banned( $friend_user_id, $group_id ) )
+				continue;
+
+			// Friend is safe, so add it to the array of possible friends.
+			$friends[] = array(
+				'id'        => $friend_user_id,
+				'full_name' => bp_get_member_name()
+			);
+
+		endwhile;
+	}
+
+	// If no friends, explicitly set to false.
+	if ( empty( $friends ) )
+		$friends = false;
+
+	/**
+	 * Filters the list of potential friends that can be invited to this group.
+	 *
+	 * @since 1.5.4
+	 *
+	 * @param array|bool $friends  Array friends available to invite or false for no friends.
+	 * @param int        $user_id  ID of the user checked for who they can invite.
+	 * @param int        $group_id ID of the group being checked on.
+	 */
+	return apply_filters( 'bp_friends_get_invite_list', $friends, $user_id, $group_id );
+}
+
+/**
+ * Get a count of a user's friends who can be invited to a given group.
+ *
+ * Users can invite any of their friends except:
+ *
+ * - users who are already in the group
+ * - users who have a pending invite to the group
+ * - users who have been banned from the group
+ *
+ * @param int $user_id  ID of the user whose friends are being counted.
+ * @param int $group_id ID of the group friends are being invited to.
+ * @return int $invitable_count Eligible friend count.
+ */
+function friends_count_invitable_friends( $user_id, $group_id ) {
+	return BP_Friends_Friendship::get_invitable_friend_count( $user_id, $group_id );
+}
+
+/**
+ * Get a total friend count for a given user.
+ *
+ * @param int $user_id Optional. ID of the user whose friendships you are
+ *                     counting. Default: displayed user (if any), otherwise logged-in user.
+ * @return int Friend count for the user.
+ */
+function friends_get_friend_count_for_user( $user_id ) {
+	return BP_Friends_Friendship::total_friend_count( $user_id );
+}
+
+/**
+ * Return a list of a user's friends, filtered by a search term.
+ *
+ * @param string $search_terms Search term to filter on.
+ * @param int    $user_id      ID of the user whose friends are being searched.
+ * @param int    $pag_num      Number of results to return per page. Default: 0 (no
+ *                             pagination - show all results).
+ * @param int    $pag_page     Number of the page being requested. Default: 0 (no
+ *                             pagination - show all results).
+ * @return array Array of BP_Core_User objects corresponding to friends.
+ */
+function friends_search_users( $search_terms, $user_id, $pag_num = 0, $pag_page = 0 ) {
+
+	$user_ids = BP_Friends_Friendship::search_users( $search_terms, $user_id, $pag_num, $pag_page );
+
+	if ( empty( $user_ids ) )
+		return false;
+
+	$users = array();
+	for ( $i = 0, $count = count( $user_ids ); $i < $count; ++$i )
+		$users[] = new BP_Core_User( $user_ids[$i] );
+
+	return array( 'users' => $users, 'count' => BP_Friends_Friendship::search_users_count( $search_terms ) );
+}
+
+/**
+ * Has a friendship been confirmed (accepted)?
+ *
+ * @param int $friendship_id The ID of the friendship being checked.
+ * @return bool True if the friendship is confirmed, otherwise false.
+ */
+function friends_is_friendship_confirmed( $friendship_id ) {
+	$friendship = new BP_Friends_Friendship( $friendship_id );
+	return $friendship->is_confirmed;
+}
+
+/**
+ * Update user friend counts.
+ *
+ * Friend counts are cached in usermeta for performance reasons. After a
+ * friendship event (acceptance, deletion), call this function to regenerate
+ * the cached values.
+ *
+ * @param int    $initiator_user_id ID of the first user.
+ * @param int    $friend_user_id    ID of the second user.
+ * @param string $status            Optional. The friendship event that's been triggered.
+ *                                  'add' will ++ each user's friend counts, while any other string
+ *                                  will --.
+ */
+function friends_update_friend_totals( $initiator_user_id, $friend_user_id, $status = 'add' ) {
+
+	if ( 'add' == $status ) {
+		bp_update_user_meta( $initiator_user_id, 'total_friend_count', (int)bp_get_user_meta( $initiator_user_id, 'total_friend_count', true ) + 1 );
+		bp_update_user_meta( $friend_user_id, 'total_friend_count', (int)bp_get_user_meta( $friend_user_id, 'total_friend_count', true ) + 1 );
+	} else {
+		bp_update_user_meta( $initiator_user_id, 'total_friend_count', (int)bp_get_user_meta( $initiator_user_id, 'total_friend_count', true ) - 1 );
+		bp_update_user_meta( $friend_user_id, 'total_friend_count', (int)bp_get_user_meta( $friend_user_id, 'total_friend_count', true ) - 1 );
+	}
+}
+
+/**
+ * Remove all friends-related data concerning a given user.
+ *
+ * Removes the following:
+ *
+ * - Friendships of which the user is a member.
+ * - Cached friend count for the user.
+ * - Notifications of friendship requests sent by the user.
+ *
+ * @param int $user_id ID of the user whose friend data is being removed.
+ */
+function friends_remove_data( $user_id ) {
+
+	/**
+	 * Fires before deletion of friend-related data for a given user.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $user_id ID for the user whose friend data is being removed.
+	 */
+	do_action( 'friends_before_remove_data', $user_id );
+
+	BP_Friends_Friendship::delete_all_for_user( $user_id );
+
+	// Remove usermeta.
+	bp_delete_user_meta( $user_id, 'total_friend_count' );
+
+	/**
+	 * Fires after deletion of friend-related data for a given user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $user_id ID for the user whose friend data is being removed.
+	 */
+	do_action( 'friends_remove_data', $user_id );
+}
+add_action( 'wpmu_delete_user',  'friends_remove_data' );
+add_action( 'delete_user',       'friends_remove_data' );
+add_action( 'bp_make_spam_user', 'friends_remove_data' );
+
+/**
+ * Used by the Activity component's @mentions to print a JSON list of the current user's friends.
+ *
+ * This is intended to speed up @mentions lookups for a majority of use cases.
+ *
+ * @see bp_activity_mentions_script()
+ */
+function bp_friends_prime_mentions_results() {
+	if ( ! bp_activity_maybe_load_mentions_scripts() ) {
+		return;
+	}
+
+	// Bail out if the site has a ton of users.
+	if ( is_multisite() && wp_is_large_network( 'users' ) ) {
+		return;
+	}
+
+	if ( friends_get_total_friend_count( get_current_user_id() ) > 150 ) {
+		return;
+	}
+
+	$friends_query = array(
+		'count_total'     => '',                    // Prevents total count.
+		'populate_extras' => false,
+
+		'type'            => 'alphabetical',
+		'user_id'         => get_current_user_id(),
+	);
+
+	$friends_query = new BP_User_Query( $friends_query );
+	$results       = array();
+
+	foreach ( $friends_query->results as $user ) {
+		$result        = new stdClass();
+		$result->ID    = $user->user_nicename;
+		$result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
+
+		if ( ! empty( $user->display_name ) && ! bp_disable_profile_sync() ) {
+			$result->name = $user->display_name;
+		} else {
+			$result->name = bp_core_get_user_displayname( $user->ID );
+		}
+
+		$results[] = $result;
+	}
+
+	wp_localize_script( 'bp-mentions', 'BP_Suggestions', array(
+		'friends' => $results,
+	) );
+}
+add_action( 'bp_activity_mentions_prime_results', 'bp_friends_prime_mentions_results' );
Index: /tags/2.4.3/src/bp-friends/bp-friends-loader.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-loader.php	(revision 10410)
@@ -0,0 +1,269 @@
+<?php
+/**
+ * BuddyPress Friends Streams Loader.
+ *
+ * The friends component is for users to create relationships with each other.
+ *
+ * @package BuddyPress
+ * @subpackage Friends
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Defines the BuddyPress Friends Component.
+ */
+class BP_Friends_Component extends BP_Component {
+
+	/**
+	 * Start the friends component creation process.
+	 *
+	 * @since 1.5.0
+	 */
+	public function __construct() {
+		parent::start(
+			'friends',
+			_x( 'Friend Connections', 'Friends screen page <title>', 'buddypress' ),
+			buddypress()->plugin_dir,
+			array(
+				'adminbar_myaccount_order' => 60
+			)
+		);
+	}
+
+	/**
+	 * Include bp-friends files.
+	 *
+	 * @see BP_Component::includes() for description of parameters.
+	 *
+	 * @param array $includes See {@link BP_Component::includes()}.
+	 */
+	public function includes( $includes = array() ) {
+		$includes = array(
+			'cache',
+			'actions',
+			'screens',
+			'filters',
+			'classes',
+			'activity',
+			'template',
+			'functions',
+			'notifications',
+			'widgets',
+		);
+
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Set up bp-friends global settings.
+	 *
+	 * The BP_FRIENDS_SLUG constant is deprecated, and only used here for
+	 * backwards compatibility.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_globals() for description of parameters.
+	 *
+	 * @param array $args See {@link BP_Component::setup_globals()}.
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		// Deprecated. Do not use.
+		// Defined conditionally to support unit tests.
+		if ( ! defined( 'BP_FRIENDS_DB_VERSION' ) ) {
+			define( 'BP_FRIENDS_DB_VERSION', '1800' );
+		}
+
+		// Define a slug, if necessary.
+		if ( ! defined( 'BP_FRIENDS_SLUG' ) ) {
+			define( 'BP_FRIENDS_SLUG', $this->id );
+		}
+
+		// Global tables for the friends component.
+		$global_tables = array(
+			'table_name'      => $bp->table_prefix . 'bp_friends',
+			'table_name_meta' => $bp->table_prefix . 'bp_friends_meta',
+		);
+
+		// All globals for the friends component.
+		// Note that global_tables is included in this array.
+		$args = array(
+			'slug'                  => BP_FRIENDS_SLUG,
+			'has_directory'         => false,
+			'search_string'         => __( 'Search Friends...', 'buddypress' ),
+			'notification_callback' => 'friends_format_notifications',
+			'global_tables'         => $global_tables
+		);
+
+		parent::setup_globals( $args );
+	}
+
+	/**
+	 * Set up component navigation.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_nav() for a description of arguments.
+	 *
+	 * @param array $main_nav Optional. See BP_Component::setup_nav() for
+	 *                        description.
+	 * @param array $sub_nav  Optional. See BP_Component::setup_nav() for
+	 *                        description.
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		// Determine user to use.
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		$access       = bp_core_can_edit_settings();
+		$slug         = bp_get_friends_slug();
+		$friends_link = trailingslashit( $user_domain . $slug );
+
+		// Add 'Friends' to the main navigation.
+		$count    = friends_get_total_friend_count();
+		$class    = ( 0 === $count ) ? 'no-count' : 'count';
+		$main_nav = array(
+			'name'                => sprintf( __( 'Friends <span class="%s">%s</span>', 'buddypress' ), esc_attr( $class ), bp_core_number_format( $count ) ),
+			'slug'                => $slug,
+			'position'            => 60,
+			'screen_function'     => 'friends_screen_my_friends',
+			'default_subnav_slug' => 'my-friends',
+			'item_css_id'         => $this->id
+		);
+
+		// Add the subnav items to the friends nav item.
+		$sub_nav[] = array(
+			'name'            => _x( 'Friendships', 'Friends screen sub nav', 'buddypress' ),
+			'slug'            => 'my-friends',
+			'parent_url'      => $friends_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'friends_screen_my_friends',
+			'position'        => 10,
+			'item_css_id'     => 'friends-my-friends'
+		);
+
+		$sub_nav[] = array(
+			'name'            => _x( 'Requests', 'Friends screen sub nav', 'buddypress' ),
+			'slug'            => 'requests',
+			'parent_url'      => $friends_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'friends_screen_requests',
+			'position'        => 20,
+			'user_has_access' => $access
+		);
+
+		parent::setup_nav( $main_nav, $sub_nav );
+	}
+
+	/**
+	 * Set up bp-friends integration with the WordPress admin bar.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_admin_bar() for a description of arguments.
+	 *
+	 * @param array $wp_admin_nav See BP_Component::setup_admin_bar()
+	 *                            for description.
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		// Menus for logged in user.
+		if ( is_user_logged_in() ) {
+
+			// Setup the logged in user variables.
+			$friends_link = trailingslashit( bp_loggedin_user_domain() . bp_get_friends_slug() );
+
+			// Pending friend requests.
+			$count = count( friends_get_friendship_request_user_ids( bp_loggedin_user_id() ) );
+			if ( !empty( $count ) ) {
+				$title   = sprintf( _x( 'Friends <span class="count">%s</span>',          'My Account Friends menu',         'buddypress' ), bp_core_number_format( $count ) );
+				$pending = sprintf( _x( 'Pending Requests <span class="count">%s</span>', 'My Account Friends menu sub nav', 'buddypress' ), bp_core_number_format( $count ) );
+			} else {
+				$title   = _x( 'Friends',            'My Account Friends menu',         'buddypress' );
+				$pending = _x( 'No Pending Requests','My Account Friends menu sub nav', 'buddypress' );
+			}
+
+			// Add the "My Account" sub menus.
+			$wp_admin_nav[] = array(
+				'parent' => buddypress()->my_account_menu_id,
+				'id'     => 'my-account-' . $this->id,
+				'title'  => $title,
+				'href'   => $friends_link
+			);
+
+			// My Friends.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-friendships',
+				'title'  => _x( 'Friendships', 'My Account Friends menu sub nav', 'buddypress' ),
+				'href'   => $friends_link
+			);
+
+			// Requests.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-requests',
+				'title'  => $pending,
+				'href'   => trailingslashit( $friends_link . 'requests' )
+			);
+		}
+
+		parent::setup_admin_bar( $wp_admin_nav );
+	}
+
+	/**
+	 * Set up the title for pages and <title>.
+	 */
+	public function setup_title() {
+
+		// Adjust title.
+		if ( bp_is_friends_component() ) {
+			$bp = buddypress();
+
+			if ( bp_is_my_profile() ) {
+				$bp->bp_options_title = __( 'Friendships', 'buddypress' );
+			} else {
+				$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+					'item_id' => bp_displayed_user_id(),
+					'type'    => 'thumb',
+					'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_displayed_user_fullname() )
+				) );
+				$bp->bp_options_title = bp_get_displayed_user_fullname();
+			}
+		}
+
+		parent::setup_title();
+	}
+
+	/**
+	 * Setup cache groups.
+	 *
+	 * @since 2.2.0
+	 */
+	public function setup_cache_groups() {
+
+		// Global groups.
+		wp_cache_add_global_groups( array(
+			'bp_friends_requests'
+		) );
+
+		parent::setup_cache_groups();
+	}
+}
+
+/**
+ * Set up the bp-forums component.
+ */
+function bp_setup_friends() {
+	buddypress()->friends = new BP_Friends_Component();
+}
+add_action( 'bp_setup_components', 'bp_setup_friends', 6 );
Index: /tags/2.4.3/src/bp-friends/bp-friends-notifications.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-notifications.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-notifications.php	(revision 10410)
@@ -0,0 +1,425 @@
+<?php
+/**
+ * BuddyPress Friends Activity Functions.
+ *
+ * These functions handle the recording, deleting and formatting of activity
+ * for the user and for this specific component.
+ *
+ * @package BuddyPress
+ * @subpackage FriendsActivity
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/** Emails ********************************************************************/
+
+/**
+ * Send notifications related to a new friendship request.
+ *
+ * When a friendship is requested, an email and a BP notification are sent to
+ * the user of whom friendship has been requested ($friend_id).
+ *
+ * @param int $friendship_id ID of the friendship object.
+ * @param int $initiator_id  ID of the user who initiated the request.
+ * @param int $friend_id     ID of the request recipient.
+ * @return bool
+ */
+function friends_notification_new_request( $friendship_id, $initiator_id, $friend_id ) {
+
+	$initiator_name = bp_core_get_user_displayname( $initiator_id );
+
+	if ( 'no' == bp_get_user_meta( (int) $friend_id, 'notification_friends_friendship_request', true ) )
+		return false;
+
+	$ud                = get_userdata( $friend_id );
+	$all_requests_link = bp_core_get_user_domain( $friend_id ) . bp_get_friends_slug() . '/requests/';
+	$settings_slug     = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+	$settings_link     = trailingslashit( bp_core_get_user_domain( $friend_id ) .  $settings_slug . '/notifications' );
+	$initiator_link    = bp_core_get_user_domain( $initiator_id );
+
+	// Set up and send the message.
+	$to       = $ud->user_email;
+	$subject  = bp_get_email_subject( array( 'text' => sprintf( __( 'New friendship request from %s', 'buddypress' ), $initiator_name ) ) );
+	$message  = sprintf( __(
+'%1$s wants to add you as a friend.
+
+To view all of your pending friendship requests: %2$s
+
+To view %3$s\'s profile: %4$s
+
+---------------------
+', 'buddypress' ), $initiator_name, $all_requests_link, $initiator_name, $initiator_link );
+
+	// Only show the disable notifications line if the settings component is enabled.
+	if ( bp_is_active( 'settings' ) ) {
+		$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+	}
+
+	/**
+	 * Filters the email address for who is getting the friend request.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $to Email address for who is getting the friend request.
+	 */
+	$to      = apply_filters( 'friends_notification_new_request_to', $to );
+
+	/**
+	 * Filters the subject for the friend request email.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $subject        Subject line to be used in friend request email.
+	 * @param string $initiator_name Name of the person requesting friendship.
+	 */
+	$subject = apply_filters( 'friends_notification_new_request_subject', $subject, $initiator_name );
+
+	/**
+	 * Filters the message for the friend request email.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $message           Message to be used in friend request email.
+	 * @param string $initiator_name    Name of the person requesting friendship.
+	 * @param string $initiator_link    Profile link of person requesting friendship.
+	 * @param string $all_requests_link User's friends request management link.
+	 * @param string $settings_link     Email recipient's settings management link.
+	 */
+	$message = apply_filters( 'friends_notification_new_request_message', $message, $initiator_name, $initiator_link, $all_requests_link, $settings_link );
+
+	wp_mail( $to, $subject, $message );
+
+	/**
+	 * Fires after the new friend request email is sent.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int    $friend_id     ID of the request recipient.
+	 * @param string $subject       Text for the friend request subject field.
+	 * @param string $message       Text for the friend request message field.
+	 * @param int    $friendship_id ID of the friendship object.
+	 * @param int    $initiator_id  ID of the friendship requester.
+	 */
+	do_action( 'bp_friends_sent_request_email', $friend_id, $subject, $message, $friendship_id, $initiator_id );
+}
+add_action( 'friends_friendship_requested', 'friends_notification_new_request', 10, 3 );
+
+/**
+ * Send notifications related to the acceptance of a friendship request.
+ *
+ * When a friendship request is accepted, an email and a BP notification are
+ * sent to the user who requested the friendship ($initiator_id).
+ *
+ * @param int $friendship_id ID of the friendship object.
+ * @param int $initiator_id  ID of the user who initiated the request.
+ * @param int $friend_id     ID of the request recipient.
+ * @return bool
+ */
+function friends_notification_accepted_request( $friendship_id, $initiator_id, $friend_id ) {
+
+	$friend_name = bp_core_get_user_displayname( $friend_id );
+
+	if ( 'no' == bp_get_user_meta( (int) $initiator_id, 'notification_friends_friendship_accepted', true ) )
+		return false;
+
+	$ud            = get_userdata( $initiator_id );
+	$friend_link   = bp_core_get_user_domain( $friend_id );
+	$settings_slug = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+	$settings_link = trailingslashit( bp_core_get_user_domain( $initiator_id ) . $settings_slug . '/notifications' );
+
+	// Set up and send the message.
+	$to       = $ud->user_email;
+	$subject  = bp_get_email_subject( array( 'text' => sprintf( __( '%s accepted your friendship request', 'buddypress' ), $friend_name ) ) );
+	$message  = sprintf( __(
+'%1$s accepted your friend request.
+
+To view %2$s\'s profile: %3$s
+
+---------------------
+', 'buddypress' ), $friend_name, $friend_name, $friend_link );
+
+	// Only show the disable notifications line if the settings component is enabled.
+	if ( bp_is_active( 'settings' ) ) {
+		$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+	}
+
+	/**
+	 * Filters the email address for whose friend request got accepted.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $to Email address for whose friend request got accepted.
+	 */
+	$to      = apply_filters( 'friends_notification_accepted_request_to', $to );
+
+	/**
+	 * Filters the subject for the friend request accepted email.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $subject     Subject line to be used in friend request accepted email.
+	 * @param string $friend_name Name of the person who accepted the friendship request.
+	 */
+	$subject = apply_filters( 'friends_notification_accepted_request_subject', $subject, $friend_name );
+
+	/**
+	 * Filters the message for the friend request accepted email.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $message       Message to be used in friend request email.
+	 * @param string $friend_name   Name of the person who accepted the friendship request.
+	 * @param string $friend_link   Profile link of person who accepted the friendship request.
+	 * @param string $settings_link Email recipient's settings management link.
+	 */
+	$message = apply_filters( 'friends_notification_accepted_request_message', $message, $friend_name, $friend_link, $settings_link );
+
+	wp_mail( $to, $subject, $message );
+
+	/**
+	 * Fires after the friend request accepted email is sent.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int    $initiator_id  ID of the friendship requester.
+	 * @param string $subject       Text for the friend request subject field.
+	 * @param string $message       Text for the friend request message field.
+	 * @param int    $friendship_id ID of the friendship object.
+	 * @param int    $friend_id     ID of the request recipient.
+	 */
+	do_action( 'bp_friends_sent_accepted_email', $initiator_id, $subject, $message, $friendship_id, $friend_id );
+}
+add_action( 'friends_friendship_accepted', 'friends_notification_accepted_request', 10, 3 );
+
+/** Notifications *************************************************************/
+
+/**
+ * Notification formatting callback for bp-friends notifications.
+ *
+ * @param string $action            The kind of notification being rendered.
+ * @param int    $item_id           The primary item ID.
+ * @param int    $secondary_item_id The secondary item ID.
+ * @param int    $total_items       The total number of messaging-related notifications
+ *                                  waiting for the user.
+ * @param string $format            'string' for BuddyBar-compatible notifications;
+ *                                  'array' for WP Toolbar. Default: 'string'.
+ * @return array|string
+ */
+function friends_format_notifications( $action, $item_id, $secondary_item_id, $total_items, $format = 'string' ) {
+
+	switch ( $action ) {
+		case 'friendship_accepted':
+			$link = trailingslashit( bp_loggedin_user_domain() . bp_get_friends_slug() . '/my-friends' );
+
+			// $action and $amount are used to generate dynamic filter names.
+			$action = 'accepted';
+
+			// Set up the string and the filter.
+			if ( (int) $total_items > 1 ) {
+				$text = sprintf( __( '%d friends accepted your friendship requests', 'buddypress' ), (int) $total_items );
+				$amount = 'multiple';
+			} else {
+				$text = sprintf( __( '%s accepted your friendship request', 'buddypress' ),  bp_core_get_user_displayname( $item_id ) );
+				$amount = 'single';
+			}
+
+			break;
+
+		case 'friendship_request':
+			$link = bp_loggedin_user_domain() . bp_get_friends_slug() . '/requests/?new';
+
+			$action = 'request';
+
+			// Set up the string and the filter.
+			if ( (int) $total_items > 1 ) {
+				$text = sprintf( __( 'You have %d pending friendship requests', 'buddypress' ), (int) $total_items );
+				$amount = 'multiple';
+			} else {
+				$text = sprintf( __( 'You have a friendship request from %s', 'buddypress' ),  bp_core_get_user_displayname( $item_id ) );
+				$amount = 'single';
+			}
+
+			break;
+	}
+
+	// Return either an HTML link or an array, depending on the requested format.
+	if ( 'string' == $format ) {
+
+		/**
+		 * Filters the format of friendship notifications based on type and amount * of notifications pending.
+		 *
+		 * This is a variable filter that has four possible versions.
+		 * The four possible versions are:
+		 *   - bp_friends_single_friendship_accepted_notification
+		 *   - bp_friends_multiple_friendship_accepted_notification
+		 *   - bp_friends_single_friendship_request_notification
+		 *   - bp_friends_multiple_friendship_request_notification
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string|array $value       Depending on format, an HTML link to new requests profile
+		 *                                  tab or array with link and text.
+		 * @param int          $total_items The total number of messaging-related notifications
+		 *                                  waiting for the user.
+		 * @param int          $item_id     The primary item ID.
+		 */
+		$return = apply_filters( 'bp_friends_' . $amount . '_friendship_' . $action . '_notification', '<a href="' . esc_url( $link ) . '">' . esc_html( $text ) . '</a>', (int) $total_items, $item_id );
+	} else {
+		/** This filter is documented in bp-friends/bp-friends-notifications.php */
+		$return = apply_filters( 'bp_friends_' . $amount . '_friendship_' . $action . '_notification', array(
+			'link' => $link,
+			'text' => $text
+		), (int) $total_items, $item_id );
+	}
+
+	/**
+	 * Fires at the end of the bp-friends notification format callback.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string       $action            The kind of notification being rendered.
+	 * @param int          $item_id           The primary item ID.
+	 * @param int          $secondary_item_id The secondary item ID.
+	 * @param int          $total_items       The total number of messaging-related notifications
+	 *                                        waiting for the user.
+	 * @param array|string $return            Notification text string or array of link and text.
+	 */
+	do_action( 'friends_format_notifications', $action, $item_id, $secondary_item_id, $total_items, $return );
+
+	return $return;
+}
+
+/**
+ * Clear friend-related notifications when ?new=1
+ */
+function friends_clear_friend_notifications() {
+	if ( isset( $_GET['new'] ) && bp_is_active( 'notifications' ) ) {
+		bp_notifications_mark_notifications_by_type( bp_loggedin_user_id(), buddypress()->friends->id, 'friendship_accepted' );
+	}
+}
+add_action( 'bp_activity_screen_my_activity', 'friends_clear_friend_notifications' );
+
+/**
+ * Delete any friendship request notifications for the logged in user.
+ *
+ * @since 1.9.0
+ */
+function bp_friends_mark_friendship_request_notifications_by_type() {
+	if ( isset( $_GET['new'] ) && bp_is_active( 'notifications' ) ) {
+		bp_notifications_mark_notifications_by_type( bp_loggedin_user_id(), buddypress()->friends->id, 'friendship_request' );
+	}
+}
+add_action( 'friends_screen_requests', 'bp_friends_mark_friendship_request_notifications_by_type' );
+
+/**
+ * Delete any friendship acceptance notifications for the logged in user.
+ *
+ * @since 1.9.0
+ */
+function bp_friends_mark_friendship_accepted_notifications_by_type() {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_mark_notifications_by_type( bp_loggedin_user_id(), buddypress()->friends->id, 'friendship_accepted' );
+	}
+}
+add_action( 'friends_screen_my_friends', 'bp_friends_mark_friendship_accepted_notifications_by_type' );
+
+/**
+ * Notify one use that another user has requested their virtual friendship.
+ *
+ * @since 1.9.0
+ *
+ * @param int $friendship_id     The unique ID of the friendship.
+ * @param int $initiator_user_id The friendship initiator user ID.
+ * @param int $friend_user_id    The friendship request receiver user ID.
+ */
+function bp_friends_friendship_requested_notification( $friendship_id, $initiator_user_id, $friend_user_id ) {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_add_notification( array(
+			'user_id'           => $friend_user_id,
+			'item_id'           => $initiator_user_id,
+			'secondary_item_id' => $friendship_id,
+			'component_name'    => buddypress()->friends->id,
+			'component_action'  => 'friendship_request',
+			'date_notified'     => bp_core_current_time(),
+			'is_new'            => 1,
+		) );
+	}
+}
+add_action( 'friends_friendship_requested', 'bp_friends_friendship_requested_notification', 10, 3 );
+
+/**
+ * Remove friend request notice when a member rejects another members
+ *
+ * @since 1.9.0
+ *
+ * @param int    $friendship_id Friendship ID (not used).
+ * @param object $friendship    Friendship object.
+ */
+function bp_friends_mark_friendship_rejected_notifications_by_item_id( $friendship_id, $friendship ) {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_mark_notifications_by_item_id( $friendship->friend_user_id, $friendship->initiator_user_id, buddypress()->friends->id, 'friendship_request' );
+	}
+}
+add_action( 'friends_friendship_rejected', 'bp_friends_mark_friendship_rejected_notifications_by_item_id', 10, 2 );
+
+/**
+ * Notify a member when another member accepts their virtual friendship request.
+ *
+ * @since 1.9.0
+ *
+ * @param int $friendship_id     The unique ID of the friendship.
+ * @param int $initiator_user_id The friendship initiator user ID.
+ * @param int $friend_user_id    The friendship request receiver user ID.
+ */
+function bp_friends_add_friendship_accepted_notification( $friendship_id, $initiator_user_id, $friend_user_id ) {
+
+	// Bail if notifications is not active.
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return;
+	}
+
+	// Remove the friend request notice.
+	bp_notifications_mark_notifications_by_item_id( $friend_user_id, $initiator_user_id, buddypress()->friends->id, 'friendship_request' );
+
+	// Add a friend accepted notice for the initiating user.
+	bp_notifications_add_notification(  array(
+		'user_id'           => $initiator_user_id,
+		'item_id'           => $friend_user_id,
+		'secondary_item_id' => $friendship_id,
+		'component_name'    => buddypress()->friends->id,
+		'component_action'  => 'friendship_accepted',
+		'date_notified'     => bp_core_current_time(),
+		'is_new'            => 1,
+	) );
+}
+add_action( 'friends_friendship_accepted', 'bp_friends_add_friendship_accepted_notification', 10, 3 );
+
+/**
+ * Remove friend request notice when a member withdraws their friend request.
+ *
+ * @since 1.9.0
+ *
+ * @param int    $friendship_id Friendship ID (not used).
+ * @param object $friendship    Friendship Object.
+ */
+function bp_friends_mark_friendship_withdrawn_notifications_by_item_id( $friendship_id, $friendship ) {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_delete_notifications_by_item_id( $friendship->friend_user_id, $friendship->initiator_user_id, buddypress()->friends->id, 'friendship_request' );
+	}
+}
+add_action( 'friends_friendship_withdrawn', 'bp_friends_mark_friendship_withdrawn_notifications_by_item_id', 10, 2 );
+
+/**
+ * Remove friendship requests FROM user, used primarily when a user is deleted.
+ *
+ * @since 1.9.0
+ *
+ * @param int $user_id ID of the user whose notifications are removed.
+ */
+function bp_friends_remove_notifications_data( $user_id = 0 ) {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_delete_notifications_from_user( $user_id, buddypress()->friends->id, 'friendship_request' );
+	}
+}
+add_action( 'friends_remove_data', 'bp_friends_remove_notifications_data', 10, 1 );
Index: /tags/2.4.3/src/bp-friends/bp-friends-screens.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-screens.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-screens.php	(revision 10410)
@@ -0,0 +1,142 @@
+<?php
+/**
+ * BuddyPress Friends Screen Functions.
+ *
+ * Screen functions are the controllers of BuddyPress. They will execute when
+ * their specific URL is caught. They will first save or manipulate data using
+ * business functions, then pass on the user to a template file.
+ *
+ * @package BuddyPress
+ * @subpackage FriendsScreens
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Catch and process the My Friends page.
+ */
+function friends_screen_my_friends() {
+
+	/**
+	 * Fires before the loading of template for the My Friends page.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'friends_screen_my_friends' );
+
+	/**
+	 * Filters the template used to display the My Friends page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the my friends template to load.
+	 */
+	bp_core_load_template( apply_filters( 'friends_template_my_friends', 'members/single/home' ) );
+}
+
+/**
+ * Catch and process the Requests page.
+ */
+function friends_screen_requests() {
+	if ( bp_is_action_variable( 'accept', 0 ) && is_numeric( bp_action_variable( 1 ) ) ) {
+		// Check the nonce.
+		check_admin_referer( 'friends_accept_friendship' );
+
+		if ( friends_accept_friendship( bp_action_variable( 1 ) ) )
+			bp_core_add_message( __( 'Friendship accepted', 'buddypress' ) );
+		else
+			bp_core_add_message( __( 'Friendship could not be accepted', 'buddypress' ), 'error' );
+
+		bp_core_redirect( trailingslashit( bp_loggedin_user_domain() . bp_current_component() . '/' . bp_current_action() ) );
+
+	} elseif ( bp_is_action_variable( 'reject', 0 ) && is_numeric( bp_action_variable( 1 ) ) ) {
+		// Check the nonce.
+		check_admin_referer( 'friends_reject_friendship' );
+
+		if ( friends_reject_friendship( bp_action_variable( 1 ) ) )
+			bp_core_add_message( __( 'Friendship rejected', 'buddypress' ) );
+		else
+			bp_core_add_message( __( 'Friendship could not be rejected', 'buddypress' ), 'error' );
+
+		bp_core_redirect( trailingslashit( bp_loggedin_user_domain() . bp_current_component() . '/' . bp_current_action() ) );
+
+	} elseif ( bp_is_action_variable( 'cancel', 0 ) && is_numeric( bp_action_variable( 1 ) ) ) {
+		// Check the nonce.
+		check_admin_referer( 'friends_withdraw_friendship' );
+
+		if ( friends_withdraw_friendship( bp_loggedin_user_id(), bp_action_variable( 1 ) ) )
+			bp_core_add_message( __( 'Friendship request withdrawn', 'buddypress' ) );
+		else
+			bp_core_add_message( __( 'Friendship request could not be withdrawn', 'buddypress' ), 'error' );
+
+		bp_core_redirect( trailingslashit( bp_loggedin_user_domain() . bp_current_component() . '/' . bp_current_action() ) );
+	}
+
+	/**
+	 * Fires before the loading of template for the friends requests page.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'friends_screen_requests' );
+
+	/**
+	 * Filters the template used to display the My Friends page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the friends request template to load.
+	 */
+	bp_core_load_template( apply_filters( 'friends_template_requests', 'members/single/home' ) );
+}
+
+/**
+ * Add Friends-related settings to the Settings > Notifications page.
+ */
+function friends_screen_notification_settings() {
+
+	if ( !$send_requests = bp_get_user_meta( bp_displayed_user_id(), 'notification_friends_friendship_request', true ) )
+		$send_requests   = 'yes';
+
+	if ( !$accept_requests = bp_get_user_meta( bp_displayed_user_id(), 'notification_friends_friendship_accepted', true ) )
+		$accept_requests = 'yes'; ?>
+
+	<table class="notification-settings" id="friends-notification-settings">
+		<thead>
+			<tr>
+				<th class="icon"></th>
+				<th class="title"><?php _ex( 'Friends', 'Friend settings on notification settings page', 'buddypress' ) ?></th>
+				<th class="yes"><?php _e( 'Yes', 'buddypress' ) ?></th>
+				<th class="no"><?php _e( 'No', 'buddypress' )?></th>
+			</tr>
+		</thead>
+
+		<tbody>
+			<tr id="friends-notification-settings-request">
+				<td></td>
+				<td><?php _ex( 'A member sends you a friendship request', 'Friend settings on notification settings page', 'buddypress' ) ?></td>
+				<td class="yes"><input type="radio" name="notifications[notification_friends_friendship_request]" id="notification-friends-friendship-request-yes" value="yes" <?php checked( $send_requests, 'yes', true ) ?>/><label for="notification-friends-friendship-request-yes" class="bp-screen-reader-text"><?php _e( 'Yes, send email', 'buddypress' ); ?></label></td>
+				<td class="no"><input type="radio" name="notifications[notification_friends_friendship_request]" id="notification-friends-friendship-request-no" value="no" <?php checked( $send_requests, 'no', true ) ?>/><label for="notification-friends-friendship-request-no" class="bp-screen-reader-text"><?php _e( 'No, do not send email', 'buddypress' ); ?></label></td>
+			</tr>
+			<tr id="friends-notification-settings-accepted">
+				<td></td>
+				<td><?php _ex( 'A member accepts your friendship request', 'Friend settings on notification settings page', 'buddypress' ) ?></td>
+				<td class="yes"><input type="radio" name="notifications[notification_friends_friendship_accepted]" id="notification-friends-friendship-accepted-yes" value="yes" <?php checked( $accept_requests, 'yes', true ) ?>/><label for="notification-friends-friendship-accepted-yes" class="bp-screen-reader-text"><?php _e( 'Yes, send email', 'buddypress' ); ?></label></td>
+				<td class="no"><input type="radio" name="notifications[notification_friends_friendship_accepted]" id="notification-friends-friendship-accepted-no" value="no" <?php checked( $accept_requests, 'no', true ) ?>/><label for="notification-friends-friendship-accepted-no" class="bp-screen-reader-text"><?php _e( 'No, do not send email', 'buddypress' ); ?></label></td>
+			</tr>
+
+			<?php
+
+			/**
+			 * Fires after the last table row on the friends notification screen.
+			 *
+			 * @since 1.0.0
+			 */
+			do_action( 'friends_screen_notification_settings' ); ?>
+
+		</tbody>
+	</table>
+
+<?php
+}
+add_action( 'bp_notification_settings', 'friends_screen_notification_settings' );
Index: /tags/2.4.3/src/bp-friends/bp-friends-template.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-template.php	(revision 10410)
@@ -0,0 +1,687 @@
+<?php
+/**
+ * BuddyPress Friends Template Functions.
+ *
+ * @package BuddyPress
+ * @subpackage FriendsTemplate
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the friends component slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_friends_slug()
+ */
+function bp_friends_slug() {
+	echo bp_get_friends_slug();
+}
+	/**
+	 * Return the friends component slug.
+	 *
+	 * @since 1.5.0
+	 */
+	function bp_get_friends_slug() {
+
+		/**
+		 * Filters the friends component slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Friends component slug.
+		 */
+		return apply_filters( 'bp_get_friends_slug', buddypress()->friends->slug );
+	}
+
+/**
+ * Output the friends component root slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_friends_root_slug()
+ */
+function bp_friends_root_slug() {
+	echo bp_get_friends_root_slug();
+}
+	/**
+	 * Return the friends component root slug.
+	 *
+	 * @since 1.5.0
+	 */
+	function bp_get_friends_root_slug() {
+
+		/**
+		 * Filters the friends component root slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Friends component root slug.
+		 */
+		return apply_filters( 'bp_get_friends_root_slug', buddypress()->friends->root_slug );
+	}
+
+/**
+ * Output a block of random friends.
+ *
+ * No longer used in BuddyPress.
+ *
+ * @todo Deprecate
+ */
+function bp_friends_random_friends() {
+
+	if ( !$friend_ids = wp_cache_get( 'friends_friend_ids_' . bp_displayed_user_id(), 'bp' ) ) {
+		$friend_ids = BP_Friends_Friendship::get_random_friends( bp_displayed_user_id() );
+		wp_cache_set( 'friends_friend_ids_' . bp_displayed_user_id(), $friend_ids, 'bp' );
+	} ?>
+
+	<div class="info-group">
+		<h4><?php bp_word_or_name( __( "My Friends", 'buddypress' ), __( "%s's Friends", 'buddypress' ) ) ?>  (<?php echo BP_Friends_Friendship::total_friend_count( bp_displayed_user_id() ) ?>) <span><a href="<?php echo trailingslashit( bp_displayed_user_domain() . bp_get_friends_slug() ) ?>"><?php _e('See All', 'buddypress') ?></a></span></h4>
+
+		<?php if ( $friend_ids ) { ?>
+
+			<ul class="horiz-gallery">
+
+			<?php for ( $i = 0, $count = count( $friend_ids ); $i < $count; ++$i ) { ?>
+
+				<li>
+					<a href="<?php echo bp_core_get_user_domain( $friend_ids[$i] ) ?>"><?php echo bp_core_fetch_avatar( array( 'item_id' => $friend_ids[$i], 'type' => 'thumb' ) ) ?></a>
+					<h5><?php echo bp_core_get_userlink($friend_ids[$i]) ?></h5>
+				</li>
+
+			<?php } ?>
+
+			</ul>
+
+		<?php } else { ?>
+
+			<div id="message" class="info">
+				<p><?php bp_word_or_name( __( "You haven't added any friend connections yet.", 'buddypress' ), __( "%s hasn't created any friend connections yet.", 'buddypress' ) ) ?></p>
+			</div>
+
+		<?php } ?>
+
+		<div class="clear"></div>
+	</div>
+
+<?php
+}
+
+/**
+ * Pull up a group of random members, and display some profile data about them.
+ *
+ * This function is no longer used by BuddyPress core.
+ *
+ * @todo Deprecate
+ *
+ * @param int $total_members The number of members to retrieve.
+ */
+function bp_friends_random_members( $total_members = 5 ) {
+
+	if ( !$user_ids = wp_cache_get( 'friends_random_users', 'bp' ) ) {
+		$user_ids = BP_Core_User::get_users( 'random', $total_members );
+		wp_cache_set( 'friends_random_users', $user_ids, 'bp' );
+	}
+
+	?>
+
+	<?php if ( $user_ids['users'] ) { ?>
+
+		<ul class="item-list" id="random-members-list">
+
+		<?php for ( $i = 0, $count = count( $user_ids['users'] ); $i < $count; ++$i ) { ?>
+
+			<li>
+				<a href="<?php echo bp_core_get_user_domain( $user_ids['users'][$i]->id ) ?>"><?php echo bp_core_fetch_avatar( array( 'item_id' => $user_ids['users'][$i]->id, 'type' => 'thumb' ) ) ?></a>
+				<h5><?php echo bp_core_get_userlink( $user_ids['users'][$i]->id ) ?></h5>
+
+				<?php if ( bp_is_active( 'xprofile' ) ) { ?>
+
+					<?php $random_data = xprofile_get_random_profile_data( $user_ids['users'][$i]->id, true ); ?>
+
+					<div class="profile-data">
+						<p class="field-name"><?php echo $random_data[0]->name ?></p>
+
+						<?php echo $random_data[0]->value ?>
+
+					</div>
+
+				<?php } ?>
+
+				<div class="action">
+
+					<?php if ( bp_is_active( 'friends' ) ) { ?>
+
+						<?php bp_add_friend_button( $user_ids['users'][$i]->id ) ?>
+
+					<?php } ?>
+
+				</div>
+			</li>
+
+		<?php } ?>
+
+		</ul>
+
+	<?php } else { ?>
+
+		<div id="message" class="info">
+			<p><?php _e( "There aren't enough site members to show a random sample just yet.", 'buddypress' ) ?></p>
+		</div>
+
+	<?php } ?>
+<?php
+}
+
+/**
+ * Display a Friends search form.
+ *
+ * No longer used in BuddyPress.
+ *
+ * @todo Deprecate
+ */
+function bp_friend_search_form() {
+
+	$action = bp_displayed_user_domain() . bp_get_friends_slug() . '/my-friends/search/';
+	$label  = __( 'Filter Friends', 'buddypress' ); ?>
+
+		<form action="<?php echo $action ?>" id="friend-search-form" method="post">
+
+			<label for="friend-search-box" id="friend-search-label"><?php echo $label ?></label>
+			<input type="search" name="friend-search-box" id="friend-search-box" value="<?php echo $value ?>"<?php echo $disabled ?> />
+
+			<?php wp_nonce_field( 'friends_search', '_wpnonce_friend_search' ) ?>
+
+			<input type="hidden" name="initiator" id="initiator" value="<?php echo esc_attr( bp_displayed_user_id() ) ?>" />
+
+		</form>
+
+	<?php
+}
+
+/**
+ * Output the "Add Friend" button in the member loop.
+ *
+ * @since 1.2.6
+ */
+function bp_member_add_friend_button() {
+	bp_add_friend_button( bp_get_member_user_id() );
+}
+add_action( 'bp_directory_members_actions', 'bp_member_add_friend_button' );
+
+/**
+ * Output the friend count for the current member in the loop.
+ */
+function bp_member_total_friend_count() {
+	echo bp_get_member_total_friend_count();
+}
+	/**
+	 * Return the friend count for the current member in the loop.
+	 *
+	 * Return value is a string of the form "x friends".
+	 *
+	 * @return string A string of the form "x friends".
+	 */
+	function bp_get_member_total_friend_count() {
+		global $members_template;
+
+		if ( 1 == (int) $members_template->member->total_friend_count ) {
+
+			/**
+			 * Filters text used to denote total friend count.
+			 *
+			 * @since 1.2.0
+			 *
+			 * @param string $value String of the form "x friends".
+			 * @param int    $value Total friend count for current member in the loop.
+			 */
+			return apply_filters( 'bp_get_member_total_friend_count', sprintf( __( '%d friend', 'buddypress' ), (int) $members_template->member->total_friend_count ) );
+		} else {
+
+			/** This filter is documented in bp-friends/bp-friends-template.php */
+			return apply_filters( 'bp_get_member_total_friend_count', sprintf( __( '%d friends', 'buddypress' ), (int) $members_template->member->total_friend_count ) );
+		}
+	}
+
+/**
+ * Output the ID of the current user in the friend request loop.
+ *
+ * @see bp_get_potential_friend_id() for a description of arguments.
+ *
+ * @param int $user_id See {@link bp_get_potential_friend_id()}.
+ */
+function bp_potential_friend_id( $user_id = 0 ) {
+	echo bp_get_potential_friend_id( $user_id );
+}
+	/**
+	 * Return the ID of current user in the friend request loop.
+	 *
+	 * @global object $friends_template
+	 *
+	 * @param int $user_id Optional. If provided, the function will simply
+	 *                     return this value.
+	 * @return int ID of potential friend.
+	 */
+	function bp_get_potential_friend_id( $user_id = 0 ) {
+		global $friends_template;
+
+		if ( empty( $user_id ) && isset( $friends_template->friendship->friend ) )
+			$user_id = $friends_template->friendship->friend->id;
+		elseif ( empty( $user_id ) && !isset( $friends_template->friendship->friend ) )
+			$user_id = bp_displayed_user_id();
+
+		/**
+		 * Filters the ID of current user in the friend request loop.
+		 *
+		 * @since 1.2.10
+		 *
+		 * @param int $user_id ID of current user in the friend request loop.
+		 */
+		return apply_filters( 'bp_get_potential_friend_id', (int) $user_id );
+	}
+
+/**
+ * Check whether a given user is a friend of the logged-in user.
+ *
+ * Returns - 'is_friend', 'not_friends', 'pending'.
+ *
+ * @param int $user_id ID of the potential friend. Default: the value of
+ *                     {@link bp_get_potential_friend_id()}.
+ * @return string 'is_friend', 'not_friends', or 'pending'.
+ */
+function bp_is_friend( $user_id = 0 ) {
+
+	if ( !is_user_logged_in() )
+		return false;
+
+	if ( empty( $user_id ) )
+		$user_id = bp_get_potential_friend_id( $user_id );
+
+	if ( bp_loggedin_user_id() == $user_id )
+		return false;
+
+	/**
+	 * Filters the status of friendship between logged in user and given user.
+	 *
+	 * @since 1.2.10
+	 *
+	 * @param string $value String status of friendship. Possible values are 'is_friend', 'not_friends', 'pending'.
+	 */
+	return apply_filters( 'bp_is_friend', friends_check_friendship_status( bp_loggedin_user_id(), $user_id ), $user_id );
+}
+
+/**
+ * Output the Add Friend button.
+ *
+ * @see bp_get_add_friend_button() for information on arguments.
+ *
+ * @param int      $potential_friend_id See {@link bp_get_add_friend_button()}.
+ * @param int|bool $friend_status       See {@link bp_get_add_friend_button()}.
+ */
+function bp_add_friend_button( $potential_friend_id = 0, $friend_status = false ) {
+	echo bp_get_add_friend_button( $potential_friend_id, $friend_status );
+}
+	/**
+	 * Create the Add Friend button.
+	 *
+	 * @param int  $potential_friend_id ID of the user to whom the button
+	 *                                  applies. Default: value of {@link bp_get_potential_friend_id()}.
+	 * @param bool $friend_status       Not currently used.
+	 * @return string HTML for the Add Friend button.
+	 */
+	function bp_get_add_friend_button( $potential_friend_id = 0, $friend_status = false ) {
+
+		if ( empty( $potential_friend_id ) )
+			$potential_friend_id = bp_get_potential_friend_id( $potential_friend_id );
+
+		$is_friend = bp_is_friend( $potential_friend_id );
+
+		if ( empty( $is_friend ) )
+			return false;
+
+		switch ( $is_friend ) {
+			case 'pending' :
+				$button = array(
+					'id'                => 'pending',
+					'component'         => 'friends',
+					'must_be_logged_in' => true,
+					'block_self'        => true,
+					'wrapper_class'     => 'friendship-button pending_friend',
+					'wrapper_id'        => 'friendship-button-' . $potential_friend_id,
+					'link_href'         => wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/requests/cancel/' . $potential_friend_id . '/', 'friends_withdraw_friendship' ),
+					'link_text'         => __( 'Cancel Friendship Request', 'buddypress' ),
+					'link_title'        => __( 'Cancel Friendship Requested', 'buddypress' ),
+					'link_id'           => 'friend-' . $potential_friend_id,
+					'link_rel'          => 'remove',
+					'link_class'        => 'friendship-button pending_friend requested'
+				);
+				break;
+
+			case 'awaiting_response' :
+				$button = array(
+					'id'                => 'awaiting_response',
+					'component'         => 'friends',
+					'must_be_logged_in' => true,
+					'block_self'        => true,
+					'wrapper_class'     => 'friendship-button awaiting_response_friend',
+					'wrapper_id'        => 'friendship-button-' . $potential_friend_id,
+					'link_href'         => bp_loggedin_user_domain() . bp_get_friends_slug() . '/requests/',
+					'link_text'         => __( 'Friendship Requested', 'buddypress' ),
+					'link_title'        => __( 'Friendship Requested', 'buddypress' ),
+					'link_id'           => 'friend-' . $potential_friend_id,
+					'link_rel'          => 'remove',
+					'link_class'        => 'friendship-button awaiting_response_friend requested'
+				);
+				break;
+
+			case 'is_friend' :
+				$button = array(
+					'id'                => 'is_friend',
+					'component'         => 'friends',
+					'must_be_logged_in' => true,
+					'block_self'        => false,
+					'wrapper_class'     => 'friendship-button is_friend',
+					'wrapper_id'        => 'friendship-button-' . $potential_friend_id,
+					'link_href'         => wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/remove-friend/' . $potential_friend_id . '/', 'friends_remove_friend' ),
+					'link_text'         => __( 'Cancel Friendship', 'buddypress' ),
+					'link_title'        => __( 'Cancel Friendship', 'buddypress' ),
+					'link_id'           => 'friend-' . $potential_friend_id,
+					'link_rel'          => 'remove',
+					'link_class'        => 'friendship-button is_friend remove'
+				);
+				break;
+
+			default:
+				$button = array(
+					'id'                => 'not_friends',
+					'component'         => 'friends',
+					'must_be_logged_in' => true,
+					'block_self'        => true,
+					'wrapper_class'     => 'friendship-button not_friends',
+					'wrapper_id'        => 'friendship-button-' . $potential_friend_id,
+					'link_href'         => wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/add-friend/' . $potential_friend_id . '/', 'friends_add_friend' ),
+					'link_text'         => __( 'Add Friend', 'buddypress' ),
+					'link_title'        => __( 'Add Friend', 'buddypress' ),
+					'link_id'           => 'friend-' . $potential_friend_id,
+					'link_rel'          => 'add',
+					'link_class'        => 'friendship-button not_friends add'
+				);
+				break;
+		}
+
+		/**
+		 * Filters the HTML for the add friend button.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $button HTML markup for add friend button.
+		 */
+		return bp_get_button( apply_filters( 'bp_get_add_friend_button', $button ) );
+	}
+
+/**
+ * Get a comma-separated list of IDs of a user's friends.
+ *
+ * @param int $user_id Optional. Default: the displayed user's ID, or the
+ *                     logged-in user's ID.
+ * @return string|bool A comma-separated list of friend IDs if any are found,
+ *                     otherwise false.
+ */
+function bp_get_friend_ids( $user_id = 0 ) {
+
+	if ( empty( $user_id ) )
+		$user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+
+	$friend_ids = friends_get_friend_user_ids( $user_id );
+
+	if ( empty( $friend_ids ) )
+		return false;
+
+	return implode( ',', friends_get_friend_user_ids( $user_id ) );
+}
+
+/**
+ * Get a user's friendship requests.
+ *
+ * Note that we return a 0 if no pending requests are found. This is necessary
+ * because of the structure of the $include parameter in bp_has_members().
+ *
+ * @param int $user_id ID of the user whose requests are being retrieved.
+ *                     Defaults to displayed user.
+ * @return array|int An array of user IDs if found, or a 0 if none are found.
+ */
+function bp_get_friendship_requests( $user_id = 0 ) {
+	if ( !$user_id ) {
+		$user_id = bp_displayed_user_id();
+	}
+
+	if ( !$user_id ) {
+		return 0;
+	}
+
+	$requests = friends_get_friendship_request_user_ids( $user_id );
+
+	if ( !empty( $requests ) ) {
+		$requests = implode( ',', (array) $requests );
+	} else {
+		$requests = 0;
+	}
+
+	/**
+	 * Filters the total pending friendship requests for a user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array|int An array of user IDs if found, or a 0 if none are found.
+	 */
+	return apply_filters( 'bp_get_friendship_requests', $requests );
+}
+
+/**
+ * Output the ID of the friendship between the logged-in user and the current user in the loop.
+ */
+function bp_friend_friendship_id() {
+	echo bp_get_friend_friendship_id();
+}
+	/**
+	 * Return the ID of the friendship between the logged-in user and the current user in the loop.
+	 *
+	 * @return int ID of the friendship.
+	 */
+	function bp_get_friend_friendship_id() {
+		global $members_template;
+
+		if ( !$friendship_id = wp_cache_get( 'friendship_id_' . $members_template->member->id . '_' . bp_loggedin_user_id() ) ) {
+			$friendship_id = friends_get_friendship_id( $members_template->member->id, bp_loggedin_user_id() );
+			wp_cache_set( 'friendship_id_' . $members_template->member->id . '_' . bp_loggedin_user_id(), $friendship_id, 'bp' );
+		}
+
+		/**
+		 * Filters the ID of the friendship between the logged in user and the current user in the loop.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $friendship_id ID of the friendship.
+		 */
+		return apply_filters( 'bp_get_friend_friendship_id', $friendship_id );
+	}
+
+/**
+ * Output the URL for accepting the current friendship request in the loop.
+ */
+function bp_friend_accept_request_link() {
+	echo bp_get_friend_accept_request_link();
+}
+	/**
+	 * Return the URL for accepting the current friendship request in the loop.
+	 *
+	 * @return string accept-friendship URL.
+	 */
+	function bp_get_friend_accept_request_link() {
+		global $members_template;
+
+		if ( !$friendship_id = wp_cache_get( 'friendship_id_' . $members_template->member->id . '_' . bp_loggedin_user_id() ) ) {
+			$friendship_id = friends_get_friendship_id( $members_template->member->id, bp_loggedin_user_id() );
+			wp_cache_set( 'friendship_id_' . $members_template->member->id . '_' . bp_loggedin_user_id(), $friendship_id, 'bp' );
+		}
+
+		/**
+		 * Filters the URL for accepting the current friendship request in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Accept-friendship URL.
+		 */
+		return apply_filters( 'bp_get_friend_accept_request_link', wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/requests/accept/' . $friendship_id, 'friends_accept_friendship' ) );
+	}
+
+/**
+ * Output the URL for rejecting the current friendship request in the loop.
+ */
+function bp_friend_reject_request_link() {
+	echo bp_get_friend_reject_request_link();
+}
+	/**
+	 * Return the URL for rejecting the current friendship request in the loop.
+	 *
+	 * @return string reject-friendship URL.
+	 */
+	function bp_get_friend_reject_request_link() {
+		global $members_template;
+
+		if ( !$friendship_id = wp_cache_get( 'friendship_id_' . $members_template->member->id . '_' . bp_loggedin_user_id() ) ) {
+			$friendship_id = friends_get_friendship_id( $members_template->member->id, bp_loggedin_user_id() );
+			wp_cache_set( 'friendship_id_' . $members_template->member->id . '_' . bp_loggedin_user_id(), $friendship_id, 'bp' );
+		}
+
+		/**
+		 * Filters the URL for rejecting the current friendship request in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Reject-friendship URL.
+		 */
+		return apply_filters( 'bp_get_friend_reject_request_link', wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/requests/reject/' . $friendship_id, 'friends_reject_friendship' ) );
+	}
+
+/**
+ * Output the total friend count for a given user.
+ *
+ * @param int $user_id See {@link friends_get_total_friend_count()}.
+ */
+function bp_total_friend_count( $user_id = 0 ) {
+	echo bp_get_total_friend_count( $user_id );
+}
+	/**
+	 * Return the total friend count for a given user.
+	 *
+	 * @param int $user_id See {@link friends_get_total_friend_count()}.
+	 * @return int Total friend count.
+	 */
+	function bp_get_total_friend_count( $user_id = 0 ) {
+
+		/**
+		 * Filters the total friend count for a given user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $value Total friend count.
+		 */
+		return apply_filters( 'bp_get_total_friend_count', friends_get_total_friend_count( $user_id ) );
+	}
+	add_filter( 'bp_get_total_friend_count', 'bp_core_number_format' );
+
+/**
+ * Output the total friendship request count for a given user.
+ *
+ * @see bp_friend_get_total_requests_count() for description of arguments.
+ *
+ * @param int $user_id See {@link bp_friend_get_total_requests_count().
+ */
+function bp_friend_total_requests_count( $user_id = 0 ) {
+	echo bp_friend_get_total_requests_count( $user_id );
+}
+	/**
+	 * Return the total friendship request count for a given user.
+	 *
+	 * @param int $user_id ID of the user whose requests are being counted.
+	 *                     Default: ID of the logged-in user.
+	 * @return int Friend count.
+	 */
+	function bp_friend_get_total_requests_count( $user_id = 0 ) {
+		if ( empty( $user_id ) )
+			$user_id = bp_loggedin_user_id();
+
+		/**
+		 * Filters the total friendship request count for a given user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $value Friendship request count.
+		 */
+		return apply_filters( 'bp_friend_get_total_requests_count', count( BP_Friends_Friendship::get_friend_user_ids( $user_id, true ) ) );
+	}
+
+/** Stats **********************************************************************/
+
+/**
+ * Display the number of friends in user's profile.
+ *
+ * @since 2.0.0
+ *
+ * @uses bp_friends_get_profile_stats() to get the stats.
+ *
+ * @param array|string $args before|after|user_id.
+ */
+function bp_friends_profile_stats( $args = '' ) {
+	echo bp_friends_get_profile_stats( $args );
+}
+add_action( 'bp_members_admin_user_stats', 'bp_friends_profile_stats', 7, 1 );
+
+/**
+ * Return the number of friends in user's profile.
+ *
+ * @since 2.0.0
+ *
+ * @param array|string $args before|after|user_id.
+ * @return string HTML for stats output.
+ */
+function bp_friends_get_profile_stats( $args = '' ) {
+
+	// Parse the args.
+	$r = bp_parse_args( $args, array(
+		'before'  => '<li class="bp-friends-profile-stats">',
+		'after'   => '</li>',
+		'user_id' => bp_displayed_user_id(),
+		'friends' => 0,
+		'output'  => ''
+	), 'friends_get_profile_stats' );
+
+	// Allow completely overloaded output.
+	if ( empty( $r['output'] ) ) {
+
+		// Only proceed if a user ID was passed.
+		if ( ! empty( $r['user_id'] ) ) {
+
+			// Get the user's friends.
+			if ( empty( $r['friends'] ) ) {
+				$r['friends'] = absint( friends_get_total_friend_count( $r['user_id'] ) );
+			}
+
+			// If friends exist, show some formatted output.
+			$r['output'] = $r['before'] . sprintf( _n( '%s friend', '%s friends', $r['friends'], 'buddypress' ), '<strong>' . $r['friends'] . '</strong>' ) . $r['after'];
+		}
+	}
+
+	/**
+	 * Filters the number of friends in user's profile.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $value Formatted string displaying total friends count.
+	 * @param array  $r     Array of arguments for string formatting and output.
+	 */
+	return apply_filters( 'bp_friends_get_profile_stats', $r['output'], $r );
+}
Index: /tags/2.4.3/src/bp-friends/bp-friends-widgets.php
===================================================================
--- /tags/2.4.3/src/bp-friends/bp-friends-widgets.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/bp-friends-widgets.php	(revision 10410)
@@ -0,0 +1,267 @@
+<?php
+/**
+ * BuddyPress Widgets.
+ *
+ * @package BuddyPress
+ * @subpackage Friends
+ * @since 1.9.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register the friends widget.
+ *
+ * @since 1.9.0
+ */
+function bp_friends_register_widgets() {
+	if ( ! bp_is_active( 'friends' ) ) {
+		return;
+	}
+
+	// The Friends widget works only when looking an a displayed user,
+	// and the concept of "displayed user" doesn't exist on non-root blogs,
+	// so we don't register the widget there.
+	if ( ! bp_is_root_blog() ) {
+		return;
+	}
+
+	add_action( 'widgets_init', create_function( '', 'return register_widget("BP_Core_Friends_Widget");' ) );
+}
+add_action( 'bp_register_widgets', 'bp_friends_register_widgets' );
+
+/*** MEMBER FRIENDS WIDGET *****************/
+
+/**
+ * The User Friends widget class.
+ *
+ * @since 1.9.0
+ */
+class BP_Core_Friends_Widget extends WP_Widget {
+
+	/**
+	 * Class constructor.
+	 */
+	function __construct() {
+		$widget_ops = array(
+			'description' => __( 'A dynamic list of recently active, popular, and newest Friends of the displayed member.  Widget is only shown when viewing a member profile.', 'buddypress' ),
+			'classname' => 'widget_bp_core_friends_widget buddypress widget',
+		);
+		parent::__construct( false, $name = _x( '(BuddyPress) Friends', 'widget name', 'buddypress' ), $widget_ops );
+
+	}
+
+	/**
+	 * Display the widget.
+	 *
+	 * @param array $args Widget arguments.
+	 * @param array $instance The widget settings, as saved by the user.
+	 */
+	function widget( $args, $instance ) {
+		extract( $args );
+
+		if ( ! bp_displayed_user_id() ) {
+			return;
+		}
+
+		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+		wp_enqueue_script( 'bp_core_widget_friends-js', buddypress()->plugin_url . "bp-friends/js/widget-friends{$min}.js", array( 'jquery' ), bp_get_version() );
+
+		$user_id = bp_displayed_user_id();
+		$link = trailingslashit( bp_displayed_user_domain() . bp_get_friends_slug() );
+		$instance['title'] = sprintf( __( "%s's Friends", 'buddypress' ), bp_get_displayed_user_fullname() );
+
+		if ( empty( $instance['friend_default'] ) ) {
+			$instance['friend_default'] = 'active';
+		}
+
+		/**
+		 * Filters the Friends widget title.
+		 *
+		 * @since 1.8.0
+		 * @since 2.3.0 Added 'instance' and 'id_base' to arguments passed to filter.
+		 *
+		 * @param string $title    The widget title.
+		 * @param array  $instance The settings for the particular instance of the widget.
+		 * @param string $id_base  Root ID for all widgets of this type.
+		 */
+		$title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
+
+		echo $before_widget;
+
+		$title = $instance['link_title'] ? '<a href="' . esc_url( $link ) . '">' . esc_html( $title ) . '</a>' : esc_html( $title );
+
+		echo $before_title . $title . $after_title;
+
+		$members_args = array(
+			'user_id'         => absint( $user_id ),
+			'type'            => sanitize_text_field( $instance['friend_default'] ),
+			'max'             => absint( $instance['max_friends'] ),
+			'populate_extras' => 1,
+		);
+
+		?>
+
+		<?php if ( bp_has_members( $members_args ) ) : ?>
+			<div class="item-options" id="friends-list-options">
+				<a href="<?php bp_members_directory_permalink(); ?>" id="newest-friends" <?php if ( $instance['friend_default'] == 'newest' ) : ?>class="selected"<?php endif; ?>><?php _e( 'Newest', 'buddypress' ); ?></a>
+				| <a href="<?php bp_members_directory_permalink(); ?>" id="recently-active-friends" <?php if ( $instance['friend_default'] == 'active' ) : ?>class="selected"<?php endif; ?>><?php _e( 'Active', 'buddypress' ); ?></a>
+				| <a href="<?php bp_members_directory_permalink(); ?>" id="popular-friends" <?php if ( $instance['friend_default'] == 'popular' ) : ?>class="selected"<?php endif; ?>><?php _e( 'Popular', 'buddypress' ); ?></a>
+			</div>
+
+			<ul id="friends-list" class="item-list">
+				<?php while ( bp_members() ) : bp_the_member(); ?>
+					<li class="vcard">
+						<div class="item-avatar">
+							<a href="<?php bp_member_permalink(); ?>" title="<?php bp_member_name(); ?>"><?php bp_member_avatar(); ?></a>
+						</div>
+
+						<div class="item">
+							<div class="item-title fn"><a href="<?php bp_member_permalink(); ?>" title="<?php bp_member_name(); ?>"><?php bp_member_name(); ?></a></div>
+							<div class="item-meta">
+								<span class="activity">
+								<?php
+									if ( 'newest' == $instance['friend_default'] )
+										bp_member_registered();
+									if ( 'active' == $instance['friend_default'] )
+										bp_member_last_active();
+									if ( 'popular' == $instance['friend_default'] )
+										bp_member_total_friend_count();
+								?>
+								</span>
+							</div>
+						</div>
+					</li>
+
+				<?php endwhile; ?>
+			</ul>
+			<?php wp_nonce_field( 'bp_core_widget_friends', '_wpnonce-friends' ); ?>
+			<input type="hidden" name="friends_widget_max" id="friends_widget_max" value="<?php echo absint( $instance['max_friends'] ); ?>" />
+
+		<?php else: ?>
+
+			<div class="widget-error">
+				<?php _e( 'Sorry, no members were found.', 'buddypress' ); ?>
+			</div>
+
+		<?php endif; ?>
+
+		<?php echo $after_widget; ?>
+	<?php
+	}
+
+	/**
+	 * Process a widget save.
+	 *
+	 * @param array $new_instance The parameters saved by the user.
+	 * @param array $old_instance The parameters as previously saved to the database.
+	 * @return array $instance The processed settings to save.
+	 */
+	function update( $new_instance, $old_instance ) {
+		$instance = $old_instance;
+
+		$instance['max_friends']    = absint( $new_instance['max_friends'] );
+		$instance['friend_default'] = sanitize_text_field( $new_instance['friend_default'] );
+		$instance['link_title']	    = (bool) $new_instance['link_title'];
+
+		return $instance;
+	}
+
+	/**
+	 * Render the widget edit form.
+	 *
+	 * @param array $instance The saved widget settings.
+	 * @return void
+	 */
+	function form( $instance ) {
+		$defaults = array(
+			'max_friends' 	 => 5,
+			'friend_default' => 'active',
+			'link_title' 	 => false
+		);
+		$instance = wp_parse_args( (array) $instance, $defaults );
+
+		$max_friends 	= $instance['max_friends'];
+		$friend_default = $instance['friend_default'];
+		$link_title	= (bool) $instance['link_title'];
+		?>
+
+		<p><label for="<?php echo $this->get_field_id( 'link_title' ); ?>"><input type="checkbox" name="<?php echo $this->get_field_name('link_title'); ?>" id="<?php echo $this->get_field_id( 'link_title' ); ?>" value="1" <?php checked( $link_title ); ?> /> <?php _e( 'Link widget title to Members directory', 'buddypress' ); ?></label></p>
+
+		<p><label for="<?php echo $this->get_field_id( 'max_friends' ); ?>"><?php _e( 'Max friends to show:', 'buddypress' ); ?> <input class="widefat" id="<?php echo $this->get_field_id( 'max_friends' ); ?>" name="<?php echo $this->get_field_name( 'max_friends' ); ?>" type="text" value="<?php echo absint( $max_friends ); ?>" style="width: 30%" /></label></p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'friend_default' ) ?>"><?php _e( 'Default friends to show:', 'buddypress' ); ?></label>
+			<select name="<?php echo $this->get_field_name( 'friend_default' ); ?>" id="<?php echo $this->get_field_id( 'friend_default' ); ?>">
+				<option value="newest" <?php selected( $friend_default, 'newest' ); ?>><?php _e( 'Newest', 'buddypress' ); ?></option>
+				<option value="active" <?php selected( $friend_default, 'active' );?>><?php _e( 'Active', 'buddypress' ); ?></option>
+				<option value="popular"  <?php selected( $friend_default, 'popular' ); ?>><?php _e( 'Popular', 'buddypress' ); ?></option>
+			</select>
+		</p>
+
+	<?php
+	}
+}
+
+/** Widget AJAX ***************************************************************/
+
+/**
+ * Process AJAX pagination or filtering for the Friends widget.
+ *
+ * @since 1.9.0
+ */
+function bp_core_ajax_widget_friends() {
+
+	check_ajax_referer( 'bp_core_widget_friends' );
+
+	switch ( $_POST['filter'] ) {
+		case 'newest-friends':
+			$type = 'newest';
+			break;
+
+		case 'recently-active-friends':
+			$type = 'active';
+			break;
+
+		case 'popular-friends':
+			$type = 'popular';
+			break;
+	}
+
+	$members_args = array(
+		'user_id'         => bp_displayed_user_id(),
+		'type'            => $type,
+		'max'             => absint( $_POST['max-friends'] ),
+		'populate_extras' => 1,
+	);
+
+	if ( bp_has_members( $members_args ) ) : ?>
+		<?php echo '0[[SPLIT]]'; // Return valid result. TODO: remove this. ?>
+		<?php while ( bp_members() ) : bp_the_member(); ?>
+			<li class="vcard">
+				<div class="item-avatar">
+					<a href="<?php bp_member_permalink(); ?>"><?php bp_member_avatar(); ?></a>
+				</div>
+
+				<div class="item">
+					<div class="item-title fn"><a href="<?php bp_member_permalink(); ?>" title="<?php bp_member_name(); ?>"><?php bp_member_name(); ?></a></div>
+					<?php if ( 'active' == $type ) : ?>
+						<div class="item-meta"><span class="activity"><?php bp_member_last_active(); ?></span></div>
+					<?php elseif ( 'newest' == $type ) : ?>
+						<div class="item-meta"><span class="activity"><?php bp_member_registered(); ?></span></div>
+					<?php elseif ( bp_is_active( 'friends' ) ) : ?>
+						<div class="item-meta"><span class="activity"><?php bp_member_total_friend_count(); ?></span></div>
+					<?php endif; ?>
+				</div>
+			</li>
+		<?php endwhile; ?>
+
+	<?php else: ?>
+		<?php echo "-1[[SPLIT]]<li>"; ?>
+		<?php _e( 'There were no members found, please try another filter.', 'buddypress' ); ?>
+		<?php echo "</li>"; ?>
+	<?php endif;
+}
+add_action( 'wp_ajax_widget_friends', 'bp_core_ajax_widget_friends' );
+add_action( 'wp_ajax_nopriv_widget_friends', 'bp_core_ajax_widget_friends' );
Index: /tags/2.4.3/src/bp-friends/classes/class-bp-friends-friendship.php
===================================================================
--- /tags/2.4.3/src/bp-friends/classes/class-bp-friends-friendship.php	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/classes/class-bp-friends-friendship.php	(revision 10410)
@@ -0,0 +1,682 @@
+<?php
+/**
+ * BuddyPress Friends Classes.
+ *
+ * @package BuddyPress
+ * @subpackage FriendsClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BuddyPress Friendship object.
+ */
+class BP_Friends_Friendship {
+
+	/**
+	 * ID of the friendship.
+	 *
+	 * @var int
+	 */
+	public $id;
+
+	/**
+	 * User ID of the friendship initiator.
+	 *
+	 * @var int
+	 */
+	public $initiator_user_id;
+
+	/**
+	 * User ID of the 'friend' - the one invited to the friendship.
+	 *
+	 * @var int
+	 */
+	public $friend_user_id;
+
+	/**
+	 * Has the friendship been confirmed/accepted?
+	 *
+	 * @var int
+	 */
+	public $is_confirmed;
+
+	/**
+	 * Is this a "limited" friendship?
+	 *
+	 * Not currently used by BuddyPress.
+	 *
+	 * @var int
+	 */
+	public $is_limited;
+
+	/**
+	 * Date the friendship was created.
+	 *
+	 * @var string
+	 */
+	public $date_created;
+
+	/**
+	 * Is this a request?
+	 *
+	 * Not currently used in BuddyPress.
+	 *
+	 * @var bool
+	 */
+	public $is_request;
+
+	/**
+	 * Should additional friend details be queried?
+	 *
+	 * @var bool
+	 */
+	public $populate_friend_details;
+
+	/**
+	 * Details about the friend.
+	 *
+	 * @var BP_Core_User
+	 */
+	public $friend;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @param int  $id                      Optional. The ID of an existing friendship.
+	 * @param bool $is_request              Deprecated.
+	 * @param bool $populate_friend_details True if friend details should be queried.
+	 */
+	public function __construct( $id = null, $is_request = false, $populate_friend_details = true ) {
+		$this->is_request = $is_request;
+
+		if ( !empty( $id ) ) {
+			$this->id                      = $id;
+			$this->populate_friend_details = $populate_friend_details;
+			$this->populate( $this->id );
+		}
+	}
+
+	/**
+	 * Set up data about the current friendship.
+	 */
+	public function populate() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( $friendship = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->friends->table_name} WHERE id = %d", $this->id ) ) ) {
+			$this->initiator_user_id = $friendship->initiator_user_id;
+			$this->friend_user_id    = $friendship->friend_user_id;
+			$this->is_confirmed      = $friendship->is_confirmed;
+			$this->is_limited        = $friendship->is_limited;
+			$this->date_created      = $friendship->date_created;
+		}
+
+		if ( !empty( $this->populate_friend_details ) ) {
+			if ( $this->friend_user_id == bp_displayed_user_id() ) {
+				$this->friend = new BP_Core_User( $this->initiator_user_id );
+			} else {
+				$this->friend = new BP_Core_User( $this->friend_user_id );
+			}
+		}
+	}
+
+	/**
+	 * Save the current friendship to the database.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function save() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$this->initiator_user_id = apply_filters( 'friends_friendship_initiator_user_id_before_save', $this->initiator_user_id, $this->id );
+		$this->friend_user_id    = apply_filters( 'friends_friendship_friend_user_id_before_save',    $this->friend_user_id,    $this->id );
+		$this->is_confirmed      = apply_filters( 'friends_friendship_is_confirmed_before_save',      $this->is_confirmed,      $this->id );
+		$this->is_limited        = apply_filters( 'friends_friendship_is_limited_before_save',        $this->is_limited,        $this->id );
+		$this->date_created      = apply_filters( 'friends_friendship_date_created_before_save',      $this->date_created,      $this->id );
+
+		/**
+		 * Fires before processing and saving the current friendship request.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param Object $value Current friendship request object.
+		 */
+		do_action_ref_array( 'friends_friendship_before_save', array( &$this ) );
+
+		// Update.
+		if (!empty( $this->id ) ) {
+			$result = $wpdb->query( $wpdb->prepare( "UPDATE {$bp->friends->table_name} SET initiator_user_id = %d, friend_user_id = %d, is_confirmed = %d, is_limited = %d, date_created = %s WHERE id = %d", $this->initiator_user_id, $this->friend_user_id, $this->is_confirmed, $this->is_limited, $this->date_created, $this->id ) );
+
+		// Save.
+		} else {
+			$result = $wpdb->query( $wpdb->prepare( "INSERT INTO {$bp->friends->table_name} ( initiator_user_id, friend_user_id, is_confirmed, is_limited, date_created ) VALUES ( %d, %d, %d, %d, %s )", $this->initiator_user_id, $this->friend_user_id, $this->is_confirmed, $this->is_limited, $this->date_created ) );
+			$this->id = $wpdb->insert_id;
+		}
+
+		/**
+		 * Fires after processing and saving the current friendship request.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param Object $value Current friendship request object.
+		 */
+		do_action( 'friends_friendship_after_save', array( &$this ) );
+
+		return $result;
+	}
+
+	/**
+	 * Delete the current friendship from the database.
+	 *
+	 * @return bool|int
+	 */
+	public function delete() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->friends->table_name} WHERE id = %d", $this->id ) );
+	}
+
+	/** Static Methods ********************************************************/
+
+	/**
+	 * Get the IDs of a given user's friends.
+	 *
+	 * @param int  $user_id              ID of the user whose friends are being retrieved.
+	 * @param bool $friend_requests_only Optional. Whether to fetch
+	 *                                   unaccepted requests only. Default: false.
+	 * @param bool $assoc_arr            Optional. True to receive an array of arrays
+	 *                                   keyed as 'user_id' => $user_id; false to get a one-dimensional
+	 *                                   array of user IDs. Default: false.
+	 * @return array $fids IDs of friends for provided user.
+	 */
+	public static function get_friend_user_ids( $user_id, $friend_requests_only = false, $assoc_arr = false ) {
+		global $wpdb;
+
+		if ( !empty( $friend_requests_only ) ) {
+			$oc_sql = 'AND is_confirmed = 0';
+			$friend_sql = $wpdb->prepare( " WHERE friend_user_id = %d", $user_id );
+		} else {
+			$oc_sql = 'AND is_confirmed = 1';
+			$friend_sql = $wpdb->prepare( " WHERE (initiator_user_id = %d OR friend_user_id = %d)", $user_id, $user_id );
+		}
+
+		$bp = buddypress();
+		$friends = $wpdb->get_results( "SELECT friend_user_id, initiator_user_id FROM {$bp->friends->table_name} {$friend_sql} {$oc_sql} ORDER BY date_created DESC" );
+		$fids = array();
+
+		for ( $i = 0, $count = count( $friends ); $i < $count; ++$i ) {
+			if ( !empty( $assoc_arr ) ) {
+				$fids[] = array( 'user_id' => ( $friends[$i]->friend_user_id == $user_id ) ? $friends[$i]->initiator_user_id : $friends[$i]->friend_user_id );
+			} else {
+				$fids[] = ( $friends[$i]->friend_user_id == $user_id ) ? $friends[$i]->initiator_user_id : $friends[$i]->friend_user_id;
+			}
+		}
+
+		return $fids;
+	}
+
+	/**
+	 * Get the ID of the friendship object, if any, between a pair of users.
+	 *
+	 * @param int $user_id   The ID of the first user.
+	 * @param int $friend_id The ID of the second user.
+	 * @return int|bool The ID of the friendship object if found, otherwise false.
+	 */
+	public static function get_friendship_id( $user_id, $friend_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->friends->table_name} WHERE ( initiator_user_id = %d AND friend_user_id = %d ) OR ( initiator_user_id = %d AND friend_user_id = %d ) AND is_confirmed = 1", $user_id, $friend_id, $friend_id, $user_id ) );
+	}
+
+	/**
+	 * Get a list of IDs of users who have requested friendship of a given user.
+	 *
+	 * @param int $user_id The ID of the user who has received the
+	 *                     friendship requests.
+	 * @return array|bool An array of user IDs, or false if none are found.
+	 */
+	public static function get_friendship_request_user_ids( $user_id ) {
+		$friend_requests = wp_cache_get( $user_id, 'bp_friends_requests' );
+
+		if ( false === $friend_requests ) {
+			global $wpdb;
+
+			$bp = buddypress();
+
+			$friend_requests = $wpdb->get_col( $wpdb->prepare( "SELECT initiator_user_id FROM {$bp->friends->table_name} WHERE friend_user_id = %d AND is_confirmed = 0", $user_id ) );
+
+			wp_cache_set( $user_id, $friend_requests, 'bp_friends_requests' );
+		}
+
+		return $friend_requests;
+	}
+
+	/**
+	 * Get a total friend count for a given user.
+	 *
+	 * @param int $user_id Optional. ID of the user whose friendships you
+	 *                     are counting. Default: displayed user (if any), otherwise
+	 *                     logged-in user.
+	 * @return int Friend count for the user.
+	 */
+	public static function total_friend_count( $user_id = 0 ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			$user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+
+		$bp = buddypress();
+
+		/*
+		 * This is stored in 'total_friend_count' usermeta.
+		 * This function will recalculate, update and return.
+		 */
+
+		$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->friends->table_name} WHERE (initiator_user_id = %d OR friend_user_id = %d) AND is_confirmed = 1", $user_id, $user_id ) );
+
+		// Do not update meta if user has never had friends.
+		if ( empty( $count ) && !bp_get_user_meta( $user_id, 'total_friend_count', true ) )
+			return 0;
+
+		bp_update_user_meta( $user_id, 'total_friend_count', (int) $count );
+
+		return absint( $count );
+	}
+
+	/**
+	 * Search the friends of a user by a search string.
+	 *
+	 * @param string $filter  The search string, matched against xprofile
+	 *                        fields (if available), or usermeta 'nickname' field.
+	 * @param int    $user_id ID of the user whose friends are being searched.
+	 * @param int    $limit   Optional. Max number of friends to return.
+	 * @param int    $page    Optional. The page of results to return. Default:
+	 *                        null (no pagination - return all results).
+	 *
+	 * @return array|bool On success, an array: {
+	 *     @type array $friends IDs of friends returned by the query.
+	 *     @type int   $count   Total number of friends (disregarding
+	 *                          pagination) who match the search.
+	 * }. Returns false on failure.
+	 */
+	public static function search_friends( $filter, $user_id, $limit = null, $page = null ) {
+		global $wpdb;
+
+		/*
+		 * TODO: Optimize this function.
+		 */
+
+		if ( empty( $user_id ) )
+			$user_id = bp_loggedin_user_id();
+
+		// Only search for matching strings at the beginning of the
+		// name (@todo - figure out why this restriction).
+		$search_terms_like = bp_esc_like( $filter ) . '%';
+
+		$pag_sql = '';
+		if ( !empty( $limit ) && !empty( $page ) )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		if ( !$friend_ids = BP_Friends_Friendship::get_friend_user_ids( $user_id ) )
+			return false;
+
+		// Get all the user ids for the current user's friends.
+		$fids = implode( ',', wp_parse_id_list( $friend_ids ) );
+
+		if ( empty( $fids ) )
+			return false;
+
+		$bp = buddypress();
+
+		// Filter the user_ids based on the search criteria.
+		if ( bp_is_active( 'xprofile' ) ) {
+			$sql       = $wpdb->prepare( "SELECT DISTINCT user_id FROM {$bp->profile->table_name_data} WHERE user_id IN ({$fids}) AND value LIKE %s {$pag_sql}", $search_terms_like );
+			$total_sql = $wpdb->prepare( "SELECT COUNT(DISTINCT user_id) FROM {$bp->profile->table_name_data} WHERE user_id IN ({$fids}) AND value LIKE %s", $search_terms_like );
+		} else {
+			$sql       = $wpdb->prepare( "SELECT DISTINCT user_id FROM {$wpdb->usermeta} WHERE user_id IN ({$fids}) AND meta_key = 'nickname' AND meta_value LIKE %s {$pag_sql}", $search_terms_like );
+			$total_sql = $wpdb->prepare( "SELECT COUNT(DISTINCT user_id) FROM {$wpdb->usermeta} WHERE user_id IN ({$fids}) AND meta_key = 'nickname' AND meta_value LIKE %s", $search_terms_like );
+		}
+
+		$filtered_friend_ids = $wpdb->get_col( $sql );
+		$total_friend_ids    = $wpdb->get_var( $total_sql );
+
+		if ( empty( $filtered_friend_ids ) )
+			return false;
+
+		return array( 'friends' => $filtered_friend_ids, 'total' => (int) $total_friend_ids );
+	}
+
+	/**
+	 * Check friendship status between two users.
+	 *
+	 * Note that 'pending' means that $initiator_userid has sent a friend
+	 * request to $possible_friend_userid that has not yet been approved,
+	 * while 'awaiting_response' is the other way around ($possible_friend_userid
+	 * sent the initial request).
+	 *
+	 * @param int $initiator_userid       The ID of the user who is the initiator
+	 *                                    of the potential friendship/request.
+	 * @param int $possible_friend_userid The ID of the user who is the
+	 *                                    recipient of the potential friendship/request.
+	 * @return string $value The friendship status, from among 'not_friends',
+	 *                       'is_friend', 'pending', and 'awaiting_response'.
+	 */
+	public static function check_is_friend( $initiator_userid, $possible_friend_userid ) {
+		global $wpdb;
+
+		if ( empty( $initiator_userid ) || empty( $possible_friend_userid ) ) {
+			return false;
+		}
+
+		$bp = buddypress();
+
+		$result = $wpdb->get_results( $wpdb->prepare( "SELECT id, initiator_user_id, is_confirmed FROM {$bp->friends->table_name} WHERE (initiator_user_id = %d AND friend_user_id = %d) OR (initiator_user_id = %d AND friend_user_id = %d)", $initiator_userid, $possible_friend_userid, $possible_friend_userid, $initiator_userid ) );
+
+		if ( ! empty( $result ) ) {
+			if ( 0 == (int) $result[0]->is_confirmed ) {
+				$status = $initiator_userid == $result[0]->initiator_user_id ? 'pending' : 'awaiting_response';
+			} else {
+				$status = 'is_friend';
+			}
+		} else {
+			$status = 'not_friends';
+		}
+
+		return $status;
+	}
+
+	/**
+	 * Get the last active date of many users at once.
+	 *
+	 * @todo Why is this in the Friends component?
+	 *
+	 * @param array $user_ids IDs of users whose last_active meta is
+	 *                        being queried.
+	 * @return array $retval Array of last_active values + user_ids.
+	 */
+	public static function get_bulk_last_active( $user_ids ) {
+		global $wpdb;
+
+		$last_activities = BP_Core_User::get_last_activity( $user_ids );
+
+		// Sort and structure as expected in legacy function.
+		usort( $last_activities, create_function( '$a, $b', '
+			if ( $a["date_recorded"] == $b["date_recorded"] ) {
+				return 0;
+			}
+
+			return ( strtotime( $a["date_recorded"] ) < strtotime( $b["date_recorded"] ) ) ? 1 : -1;
+		' ) );
+
+		$retval = array();
+		foreach ( $last_activities as $last_activity ) {
+			$u = new stdClass;
+			$u->last_activity = $last_activity['date_recorded'];
+			$u->user_id       = $last_activity['user_id'];
+
+			$retval[] = $u;
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * Mark a friendship as accepted.
+	 *
+	 * @param int $friendship_id ID of the friendship to be accepted.
+	 * @return int Number of database rows updated.
+	 */
+	public static function accept($friendship_id) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "UPDATE {$bp->friends->table_name} SET is_confirmed = 1, date_created = %s WHERE id = %d AND friend_user_id = %d", bp_core_current_time(), $friendship_id, bp_loggedin_user_id() ) );
+	}
+
+	/**
+	 * Remove a friendship or a friendship request INITIATED BY the logged-in user.
+	 *
+	 * @param int $friendship_id ID of the friendship to be withdrawn.
+	 * @return int Number of database rows deleted.
+	 */
+	public static function withdraw($friendship_id) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->friends->table_name} WHERE id = %d AND initiator_user_id = %d", $friendship_id, bp_loggedin_user_id() ) );
+	}
+
+	/**
+	 * Remove a friendship or a friendship request MADE OF the logged-in user.
+	 *
+	 * @param int $friendship_id ID of the friendship to be rejected.
+	 * @return int Number of database rows deleted.
+	 */
+	public static function reject($friendship_id) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->friends->table_name} WHERE id = %d AND friend_user_id = %d", $friendship_id, bp_loggedin_user_id() ) );
+	}
+
+	/**
+	 * Search users.
+	 *
+	 * @todo Why does this exist, and why is it in bp-friends?
+	 *
+	 * @param string $filter  String to search by.
+	 * @param int    $user_id A user ID param that is unused.
+	 * @param int    $limit   Optional. Max number of records to return.
+	 * @param int    $page    Optional. Number of the page to return. Default:
+	 *                        false (no pagination - return all results).
+	 * @return array $filtered_ids IDs of users who match the query.
+	 */
+	public static function search_users( $filter, $user_id, $limit = null, $page = null ) {
+		global $wpdb;
+
+		// Only search for matching strings at the beginning of the
+		// name (@todo - figure out why this restriction).
+		$search_terms_like = bp_esc_like( $filter ) . '%';
+
+		$usermeta_table = $wpdb->base_prefix . 'usermeta';
+		$users_table    = $wpdb->base_prefix . 'users';
+
+		$pag_sql = '';
+		if ( !empty( $limit ) && !empty( $page ) )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * intval( $limit ) ), intval( $limit ) );
+
+		$bp = buddypress();
+
+		// Filter the user_ids based on the search criteria.
+		if ( bp_is_active( 'xprofile' ) ) {
+			$sql = $wpdb->prepare( "SELECT DISTINCT d.user_id as id FROM {$bp->profile->table_name_data} d, {$users_table} u WHERE d.user_id = u.id AND d.value LIKE %s ORDER BY d.value DESC {$pag_sql}", $search_terms_like );
+		} else {
+			$sql = $wpdb->prepare( "SELECT DISTINCT user_id as id FROM {$usermeta_table} WHERE meta_value LIKE %s ORDER BY d.value DESC {$pag_sql}", $search_terms_like );
+		}
+
+		$filtered_fids = $wpdb->get_col($sql);
+
+		if ( empty( $filtered_fids ) )
+			return false;
+
+		return $filtered_fids;
+	}
+
+	/**
+	 * Get a count of users who match a search term.
+	 *
+	 * @todo Why does this exist, and why is it in bp-friends?
+	 *
+	 * @param string $filter Search term.
+	 * @return int Count of users matching the search term.
+	 */
+	public static function search_users_count( $filter ) {
+		global $wpdb;
+
+		// Only search for matching strings at the beginning of the
+		// name (@todo - figure out why this restriction).
+		$search_terms_like = bp_esc_like( $filter ) . '%';
+
+		$usermeta_table = $wpdb->prefix . 'usermeta';
+		$users_table    = $wpdb->base_prefix . 'users';
+
+		$bp = buddypress();
+
+		// Filter the user_ids based on the search criteria.
+		if ( bp_is_active( 'xprofile' ) ) {
+			$sql = $wpdb->prepare( "SELECT COUNT(DISTINCT d.user_id) FROM {$bp->profile->table_name_data} d, {$users_table} u WHERE d.user_id = u.id AND d.value LIKE %s", $search_terms_like );
+		} else {
+			$sql = $wpdb->prepare( "SELECT COUNT(DISTINCT user_id) FROM {$usermeta_table} WHERE meta_value LIKE %s", $search_terms_like );
+		}
+
+		$user_count = $wpdb->get_col($sql);
+
+		if ( empty( $user_count ) )
+			return false;
+
+		return $user_count[0];
+	}
+
+	/**
+	 * Sort a list of user IDs by their display names.
+	 *
+	 * @todo Why does this exist, and why is it in bp-friends?
+	 *
+	 * @param array $user_ids Array of user IDs.
+	 * @return array User IDs, sorted by the associated display names.
+	 */
+	public static function sort_by_name( $user_ids ) {
+		global $wpdb;
+
+		if ( !bp_is_active( 'xprofile' ) )
+			return false;
+
+		$bp = buddypress();
+
+		$user_ids = implode( ',', wp_parse_id_list( $user_ids ) );
+
+		return $wpdb->get_results( $wpdb->prepare( "SELECT user_id FROM {$bp->profile->table_name_data} pd, {$bp->profile->table_name_fields} pf WHERE pf.id = pd.field_id AND pf.name = %s AND pd.user_id IN ( {$user_ids} ) ORDER BY pd.value ASC", bp_xprofile_fullname_field_name() ) );
+	}
+
+	/**
+	 * Get a list of random friend IDs.
+	 *
+	 * @param int $user_id       ID of the user whose friends are being retrieved.
+	 * @param int $total_friends Optional. Number of random friends to get.
+	 *                           Default: 5.
+	 * @return array|bool An array of random friend user IDs on success;
+	 *                    false if none are found.
+	 */
+	public static function get_random_friends( $user_id, $total_friends = 5 ) {
+		global $wpdb;
+
+		$bp      = buddypress();
+		$fids    = array();
+		$sql     = $wpdb->prepare( "SELECT friend_user_id, initiator_user_id FROM {$bp->friends->table_name} WHERE (friend_user_id = %d || initiator_user_id = %d) && is_confirmed = 1 ORDER BY rand() LIMIT %d", $user_id, $user_id, $total_friends );
+		$results = $wpdb->get_results( $sql );
+
+		for ( $i = 0, $count = count( $results ); $i < $count; ++$i ) {
+			$fids[] = ( $results[$i]->friend_user_id == $user_id ) ? $results[$i]->initiator_user_id : $results[$i]->friend_user_id;
+		}
+
+		// Remove duplicates.
+		if ( count( $fids ) > 0 )
+			return array_flip( array_flip( $fids ) );
+		else
+			return false;
+	}
+
+	/**
+	 * Get a count of a user's friends who can be invited to a given group.
+	 *
+	 * Users can invite any of their friends except:
+	 *
+	 * - users who are already in the group
+	 * - users who have a pending invite to the group
+	 * - users who have been banned from the group
+	 *
+	 * @param int $user_id  ID of the user whose friends are being counted.
+	 * @param int $group_id ID of the group friends are being invited to.
+	 * @return int $invitable_count Eligible friend count.
+	 */
+	public static function get_invitable_friend_count( $user_id, $group_id ) {
+
+		// Setup some data we'll use below.
+		$is_group_admin  = BP_Groups_Member::check_is_admin( $user_id, $group_id );
+		$friend_ids      = BP_Friends_Friendship::get_friend_user_ids( $user_id );
+		$invitable_count = 0;
+
+		for ( $i = 0, $count = count( $friend_ids ); $i < $count; ++$i ) {
+
+			// If already a member, they cannot be invited again.
+			if ( BP_Groups_Member::check_is_member( (int) $friend_ids[$i], $group_id ) )
+				continue;
+
+			// If user already has invite, they cannot be added.
+			if ( BP_Groups_Member::check_has_invite( (int) $friend_ids[$i], $group_id )  )
+				continue;
+
+			// If user is not group admin and friend is banned, they cannot be invited.
+			if ( ( false === $is_group_admin ) && BP_Groups_Member::check_is_banned( (int) $friend_ids[$i], $group_id ) )
+				continue;
+
+			$invitable_count++;
+		}
+
+		return $invitable_count;
+	}
+
+	/**
+	 * Get the friend user IDs for a given friendship.
+	 *
+	 * @param int $friendship_id ID of the friendship.
+	 * @return object friend_user_id and initiator_user_id.
+	 */
+	public static function get_user_ids_for_friendship( $friendship_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_row( $wpdb->prepare( "SELECT friend_user_id, initiator_user_id FROM {$bp->friends->table_name} WHERE id = %d", $friendship_id ) );
+	}
+
+	/**
+	 * Delete all friendships and friend notifications related to a user.
+	 *
+	 * @param int $user_id ID of the user being expunged.
+	 */
+	public static function delete_all_for_user( $user_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		// Get friends of $user_id.
+		$friend_ids = BP_Friends_Friendship::get_friend_user_ids( $user_id );
+
+		// Delete all friendships related to $user_id.
+		$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->friends->table_name} WHERE friend_user_id = %d OR initiator_user_id = %d", $user_id, $user_id ) );
+
+		// Delete friend request notifications for members who have a
+		// notification from this user.
+		if ( bp_is_active( 'notifications' ) ) {
+			$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->notifications->table_name} WHERE component_name = 'friends' AND ( component_action = 'friendship_request' OR component_action = 'friendship_accepted' ) AND item_id = %d", $user_id ) );
+		}
+
+		// Loop through friend_ids and update their counts.
+		foreach ( (array) $friend_ids as $friend_id ) {
+			BP_Friends_Friendship::total_friend_count( $friend_id );
+		}
+	}
+}
Index: /tags/2.4.3/src/bp-friends/js/widget-friends.js
===================================================================
--- /tags/2.4.3/src/bp-friends/js/widget-friends.js	(revision 10410)
+++ /tags/2.4.3/src/bp-friends/js/widget-friends.js	(revision 10410)
@@ -0,0 +1,49 @@
+jQuery(document).ready( function() {
+	jQuery('.widget div#friends-list-options a').on('click',
+		function() {
+			var link = this;
+			jQuery(link).addClass('loading');
+
+			jQuery('.widget div#friends-list-options a').removeClass('selected');
+			jQuery(this).addClass('selected');
+
+			jQuery.post( ajaxurl, {
+				action: 'widget_friends',
+				'cookie': encodeURIComponent(document.cookie),
+				'_wpnonce': jQuery('input#_wpnonce-friends').val(),
+				'max-friends': jQuery('input#friends_widget_max').val(),
+				'filter': jQuery(this).attr('id')
+			},
+			function(response)
+			{
+				jQuery(link).removeClass('loading');
+				friend_widget_response(response);
+			});
+
+			return false;
+		}
+	);
+});
+
+function friend_widget_response(response) {
+	response = response.substr(0, response.length-1);
+	response = response.split('[[SPLIT]]');
+
+	if ( response[0] !== '-1' ) {
+		jQuery('.widget ul#friends-list').fadeOut(200,
+			function() {
+				jQuery('.widget ul#friends-list').html(response[1]);
+				jQuery('.widget ul#friends-list').fadeIn(200);
+			}
+		);
+
+	} else {
+		jQuery('.widget ul#friends-list').fadeOut(200,
+			function() {
+				var message = '<p>' + response[1] + '</p>';
+				jQuery('.widget ul#friends-list').html(message);
+				jQuery('.widget ul#friends-list').fadeIn(200);
+			}
+		);
+	}
+}
Index: /tags/2.4.3/src/bp-groups/admin/css/admin-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-groups/admin/css/admin-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/admin/css/admin-rtl.css	(revision 10410)
@@ -0,0 +1,86 @@
+body.toplevel_page_bp-groups table.groups th#status,
+body.toplevel_page_bp-groups table.groups th#members {
+	width: 10%;
+}
+
+body.toplevel_page_bp-groups table.groups th#last_active {
+	width: 15%;
+}
+
+#bp-groups-form .avatar {
+	float: right;
+	margin-left: 10px;
+	margin-top: 1px;
+}
+#bp-groups-edit-form input {
+	outline: medium none;
+}
+#bp-groups-edit-form input#bp-groups-name {
+	font-size: 1.7em;
+	width: 100%;
+	margin-bottom: 6px;
+}
+#bp-groups-edit-form input#bp-groups-new-members {
+	width: 100%;
+	max-width: 90%;
+	border: 0;
+}
+#bp-groups-new-members-list {
+	margin: 0;
+}
+#bp_group_settings legend {
+	margin: 10px 0;
+	font-weight: bold;
+}
+#bp_group_settings ul {
+	margin: 0;
+}
+.bp-groups-settings-section {
+	margin-top: 10px;
+}
+#bp-groups-permalink-box {
+	line-height: 24px;
+	color: #666;
+}
+.bp-groups-member-type {
+	position: relative;
+}
+.bp-groups-member-type > h4 {
+	margin-bottom: .5em;
+}
+ul.bp-group-delete-list {
+	list-style-type: disc;
+	margin: 4px 26px;
+}
+.bp-group-admin-pagination {
+	position: absolute;
+	text-align: left;
+	width: 100%;
+}
+.bp-group-admin-pagination.table-top {
+	top: 0;
+}
+.bp-group-admin-pagination.table-bottom {
+	bottom: 0;
+}
+.bp-group-admin-pagination-viewing {
+	color: #777;
+	font-size: 12px;
+	font-style: italic;
+}
+.bp-group-admin-pagination-links {
+	white-space: nowrap;
+	padding-right: 15px;
+}
+
+table.bp-group-members .uid-column {
+	padding-right: 20px;
+	padding-left: 20px;
+}
+table.bp-group-members .uname-column {
+	width: 70%;
+}
+table.bp-group-members .urole-column {
+	padding-right: 20px;
+	padding-left: 20px;
+}
Index: /tags/2.4.3/src/bp-groups/admin/css/admin.css
===================================================================
--- /tags/2.4.3/src/bp-groups/admin/css/admin.css	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/admin/css/admin.css	(revision 10410)
@@ -0,0 +1,86 @@
+body.toplevel_page_bp-groups table.groups th#status,
+body.toplevel_page_bp-groups table.groups th#members {
+	width: 10%;
+}
+
+body.toplevel_page_bp-groups table.groups th#last_active {
+	width: 15%;
+}
+
+#bp-groups-form .avatar {
+	float: left;
+	margin-right: 10px;
+	margin-top: 1px;
+}
+#bp-groups-edit-form input {
+	outline: medium none;
+}
+#bp-groups-edit-form input#bp-groups-name {
+	font-size: 1.7em;
+	width: 100%;
+	margin-bottom: 6px;
+}
+#bp-groups-edit-form input#bp-groups-new-members {
+	width: 100%;
+	max-width: 90%;
+	border: 0;
+}
+#bp-groups-new-members-list {
+	margin: 0;
+}
+#bp_group_settings legend {
+	margin: 10px 0;
+	font-weight: bold;
+}
+#bp_group_settings ul {
+	margin: 0;
+}
+.bp-groups-settings-section {
+	margin-top: 10px;
+}
+#bp-groups-permalink-box {
+	line-height: 24px;
+	color: #666;
+}
+.bp-groups-member-type {
+	position: relative;
+}
+.bp-groups-member-type > h4 {
+	margin-bottom: .5em;
+}
+ul.bp-group-delete-list {
+	list-style-type: disc;
+	margin: 4px 26px;
+}
+.bp-group-admin-pagination {
+	position: absolute;
+	text-align: right;
+	width: 100%;
+}
+.bp-group-admin-pagination.table-top {
+	top: 0;
+}
+.bp-group-admin-pagination.table-bottom {
+	bottom: 0;
+}
+.bp-group-admin-pagination-viewing {
+	color: #777;
+	font-size: 12px;
+	font-style: italic;
+}
+.bp-group-admin-pagination-links {
+	white-space: nowrap;
+	padding-left: 15px;
+}
+
+table.bp-group-members .uid-column {
+	padding-left: 20px;
+	padding-right: 20px;
+}
+table.bp-group-members .uname-column {
+	width: 70%;
+}
+table.bp-group-members .urole-column {
+	padding-left: 20px;
+	padding-right: 20px;
+}
Index: /tags/2.4.3/src/bp-groups/admin/js/admin.js
===================================================================
--- /tags/2.4.3/src/bp-groups/admin/js/admin.js	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/admin/js/admin.js	(revision 10410)
@@ -0,0 +1,59 @@
+/* global BP_Group_Admin, group_id, isRtl */
+
+(function($) {
+	function add_member_to_list( e, ui ) {
+		$('#bp-groups-new-members-list').append('<li data-login="' + ui.item.value + '"><a href="#" class="bp-groups-remove-new-member">x</a> ' + ui.item.label + '</li>');
+	}
+
+	var id = 'undefined' !== typeof group_id ? '&group_id=' + group_id : '';
+	$(document).ready( function() {
+		window.warn_on_leave = false;
+
+		/* Initialize autocomplete */
+		$( '.bp-suggest-user' ).autocomplete({
+			source:    ajaxurl + '?action=bp_group_admin_member_autocomplete' + id,
+			delay:     500,
+			minLength: 2,
+			position:  ( 'undefined' !== typeof isRtl && isRtl ) ? { my: 'right top', at: 'right bottom', offset: '0, -1' } : { offset: '0, -1' },
+			open:      function() { $(this).addClass('open'); },
+			close:     function() { $(this).removeClass('open'); $(this).val(''); },
+			select:    function( event, ui ) { add_member_to_list( event, ui ); }
+		});
+
+		/* Replace noscript placeholder */
+		$( '#bp-groups-new-members' ).prop( 'placeholder', BP_Group_Admin.add_member_placeholder );
+
+		/* Remove a member on 'x' click */
+		$( '#bp_group_add_members' ).on( 'click', '.bp-groups-remove-new-member', function( e ) {
+			e.preventDefault();
+			$( e.target.parentNode ).remove();
+		} );
+
+		/* Warn before leaving unsaved changes */
+		$(document).on( 'change', 'input#bp-groups-name, input#bp-groups-description, select.bp-groups-role, #bp-groups-settings-section-status input[type="radio"]', function() {
+			window.warn_on_leave = true;
+		});
+
+		$( 'input#save' ).on( 'click', function() {
+			/* Check for users to add */
+			var users_to_add = [];
+
+			$( '#bp-groups-new-members-list li' ).each( function() {
+				users_to_add.push( $(this).data('login' ) );
+			} );
+
+			/* There are users to add, include a comma separated list of users login in the main field */
+			if ( users_to_add.length ) {
+				$( '#bp-groups-new-members' ).val( '' ).val( users_to_add.join( ', ' ) );
+			}
+
+			window.warn_on_leave = false;
+		});
+
+		window.onbeforeunload = function() {
+			if ( window.warn_on_leave ) {
+				return BP_Group_Admin.warn_on_leave;
+			}
+		};
+	});
+})(jQuery);
Index: /tags/2.4.3/src/bp-groups/bp-groups-actions.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-actions.php	(revision 10410)
@@ -0,0 +1,540 @@
+<?php
+/**
+ * BuddyPress Groups Actions.
+ *
+ * Action functions are exactly the same as screen functions, however they do
+ * not have a template screen associated with them. Usually they will send the
+ * user back to the default screen after execution.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsActions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Protect access to single groups.
+ *
+ * @since 2.1.0
+ */
+function bp_groups_group_access_protection() {
+	if ( ! bp_is_group() ) {
+		return;
+	}
+
+	$current_group   = groups_get_current_group();
+	$user_has_access = $current_group->user_has_access;
+	$no_access_args  = array();
+
+	if ( ! $user_has_access && 'hidden' !== $current_group->status ) {
+		// Always allow access to home and request-membership
+		if ( bp_is_current_action( 'home' ) || bp_is_current_action( 'request-membership' ) ) {
+			$user_has_access = true;
+
+		// User doesn't have access, so set up redirect args
+		} elseif ( is_user_logged_in() ) {
+			$no_access_args = array(
+				'message'  => __( 'You do not have access to this group.', 'buddypress' ),
+				'root'     => bp_get_group_permalink( $current_group ) . 'home/',
+				'redirect' => false
+			);
+		}
+	}
+
+	// Protect the admin tab from non-admins
+	if ( bp_is_current_action( 'admin' ) && ! bp_is_item_admin() ) {
+		$user_has_access = false;
+		$no_access_args  = array(
+			'message'  => __( 'You are not an admin of this group.', 'buddypress' ),
+			'root'     => bp_get_group_permalink( $current_group ),
+			'redirect' => false
+		);
+	}
+
+	/**
+	 * Allow plugins to filter whether the current user has access to this group content.
+	 *
+	 * Note that if a plugin sets $user_has_access to false, it may also
+	 * want to change the $no_access_args, to avoid problems such as
+	 * logged-in users being redirected to wp-login.php.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool  $user_has_access True if the user has access to the
+	 *                               content, otherwise false.
+	 * @param array $no_access_args  Arguments to be passed to bp_core_no_access() in case
+	 *                               of no access. Note that this value is passed by reference,
+	 *                               so it can be modified by the filter callback.
+	 */
+	$user_has_access = apply_filters_ref_array( 'bp_group_user_has_access', array( $user_has_access, &$no_access_args ) );
+
+	// If user has access, we return rather than redirect.
+	if ( $user_has_access ) {
+		return;
+	}
+
+	// Hidden groups should return a 404 for non-members.
+	// Unset the current group so that you're not redirected
+	// to the default group tab.
+	if ( 'hidden' == $current_group->status ) {
+		buddypress()->groups->current_group = 0;
+		buddypress()->is_single_item        = false;
+		bp_do_404();
+		return;
+	} else {
+		bp_core_no_access( $no_access_args );
+	}
+
+}
+add_action( 'bp_actions', 'bp_groups_group_access_protection' );
+
+/**
+ * Catch and process group creation form submissions.
+ */
+function groups_action_create_group() {
+
+	// If we're not at domain.org/groups/create/ then return false
+	if ( !bp_is_groups_component() || !bp_is_current_action( 'create' ) )
+		return false;
+
+	if ( !is_user_logged_in() )
+		return false;
+
+ 	if ( !bp_user_can_create_groups() ) {
+		bp_core_add_message( __( 'Sorry, you are not allowed to create groups.', 'buddypress' ), 'error' );
+		bp_core_redirect( bp_get_groups_directory_permalink() );
+	}
+
+	$bp = buddypress();
+
+	// Make sure creation steps are in the right order
+	groups_action_sort_creation_steps();
+
+	// If no current step is set, reset everything so we can start a fresh group creation
+	$bp->groups->current_create_step = bp_action_variable( 1 );
+	if ( !bp_get_groups_current_create_step() ) {
+		unset( $bp->groups->current_create_step );
+		unset( $bp->groups->completed_create_steps );
+
+		setcookie( 'bp_new_group_id', false, time() - 1000, COOKIEPATH );
+		setcookie( 'bp_completed_create_steps', false, time() - 1000, COOKIEPATH );
+
+		$reset_steps = true;
+		$keys        = array_keys( $bp->groups->group_creation_steps );
+		bp_core_redirect( trailingslashit( bp_get_groups_directory_permalink() . 'create/step/' . array_shift( $keys ) ) );
+	}
+
+	// If this is a creation step that is not recognized, just redirect them back to the first screen
+	if ( bp_get_groups_current_create_step() && empty( $bp->groups->group_creation_steps[bp_get_groups_current_create_step()] ) ) {
+		bp_core_add_message( __('There was an error saving group details. Please try again.', 'buddypress'), 'error' );
+		bp_core_redirect( trailingslashit( bp_get_groups_directory_permalink() . 'create' ) );
+	}
+
+	// Fetch the currently completed steps variable
+	if ( isset( $_COOKIE['bp_completed_create_steps'] ) && !isset( $reset_steps ) )
+		$bp->groups->completed_create_steps = json_decode( base64_decode( stripslashes( $_COOKIE['bp_completed_create_steps'] ) ) );
+
+	// Set the ID of the new group, if it has already been created in a previous step
+	if ( isset( $_COOKIE['bp_new_group_id'] ) ) {
+		$bp->groups->new_group_id = (int) $_COOKIE['bp_new_group_id'];
+		$bp->groups->current_group = groups_get_group( array( 'group_id' => $bp->groups->new_group_id ) );
+
+		// Only allow the group creator to continue to edit the new group
+		if ( ! bp_is_group_creator( $bp->groups->current_group, bp_loggedin_user_id() ) ) {
+			bp_core_add_message( __( 'Only the group creator may continue editing this group.', 'buddypress' ), 'error' );
+			bp_core_redirect( trailingslashit( bp_get_groups_directory_permalink() . 'create' ) );
+		}
+	}
+
+	// If the save, upload or skip button is hit, lets calculate what we need to save
+	if ( isset( $_POST['save'] ) ) {
+
+		// Check the nonce
+		check_admin_referer( 'groups_create_save_' . bp_get_groups_current_create_step() );
+
+		if ( 'group-details' == bp_get_groups_current_create_step() ) {
+			if ( empty( $_POST['group-name'] ) || empty( $_POST['group-desc'] ) || !strlen( trim( $_POST['group-name'] ) ) || !strlen( trim( $_POST['group-desc'] ) ) ) {
+				bp_core_add_message( __( 'Please fill in all of the required fields', 'buddypress' ), 'error' );
+				bp_core_redirect( trailingslashit( bp_get_groups_directory_permalink() . 'create/step/' . bp_get_groups_current_create_step() ) );
+			}
+
+			$new_group_id = isset( $bp->groups->new_group_id ) ? $bp->groups->new_group_id : 0;
+
+			if ( !$bp->groups->new_group_id = groups_create_group( array( 'group_id' => $new_group_id, 'name' => $_POST['group-name'], 'description' => $_POST['group-desc'], 'slug' => groups_check_slug( sanitize_title( esc_attr( $_POST['group-name'] ) ) ), 'date_created' => bp_core_current_time(), 'status' => 'public' ) ) ) {
+				bp_core_add_message( __( 'There was an error saving group details. Please try again.', 'buddypress' ), 'error' );
+				bp_core_redirect( trailingslashit( bp_get_groups_directory_permalink() . 'create/step/' . bp_get_groups_current_create_step() ) );
+			}
+		}
+
+		if ( 'group-settings' == bp_get_groups_current_create_step() ) {
+			$group_status = 'public';
+			$group_enable_forum = 1;
+
+			if ( !isset($_POST['group-show-forum']) ) {
+				$group_enable_forum = 0;
+			} else {
+				// Create the forum if enable_forum = 1
+				if ( bp_is_active( 'forums' ) && !groups_get_groupmeta( $bp->groups->new_group_id, 'forum_id' ) ) {
+					groups_new_group_forum();
+				}
+			}
+
+			if ( 'private' == $_POST['group-status'] )
+				$group_status = 'private';
+			elseif ( 'hidden' == $_POST['group-status'] )
+				$group_status = 'hidden';
+
+			if ( !$bp->groups->new_group_id = groups_create_group( array( 'group_id' => $bp->groups->new_group_id, 'status' => $group_status, 'enable_forum' => $group_enable_forum ) ) ) {
+				bp_core_add_message( __( 'There was an error saving group details. Please try again.', 'buddypress' ), 'error' );
+				bp_core_redirect( trailingslashit( bp_get_groups_directory_permalink() . 'create/step/' . bp_get_groups_current_create_step() ) );
+			}
+
+			/**
+			 * Filters the allowed invite statuses.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param array $value Array of statuses allowed.
+			 *                     Possible values are 'members,
+			 *                     'mods', and 'admins'.
+			 */
+			$allowed_invite_status = apply_filters( 'groups_allowed_invite_status', array( 'members', 'mods', 'admins' ) );
+			$invite_status	       = !empty( $_POST['group-invite-status'] ) && in_array( $_POST['group-invite-status'], (array) $allowed_invite_status ) ? $_POST['group-invite-status'] : 'members';
+
+			groups_update_groupmeta( $bp->groups->new_group_id, 'invite_status', $invite_status );
+		}
+
+		if ( 'group-invites' === bp_get_groups_current_create_step() ) {
+			if ( ! empty( $_POST['friends'] ) ) {
+				foreach ( (array) $_POST['friends'] as $friend ) {
+					groups_invite_user( array(
+						'user_id'  => (int) $friend,
+						'group_id' => $bp->groups->new_group_id,
+					) );
+				}
+			}
+
+			groups_send_invites( bp_loggedin_user_id(), $bp->groups->new_group_id );
+		}
+
+		/**
+		 * Fires before finalization of group creation and cookies are set.
+		 *
+		 * This hook is a variable hook dependent on the current step
+		 * in the creation process.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'groups_create_group_step_save_' . bp_get_groups_current_create_step() );
+
+		/**
+		 * Fires after the group creation step is completed.
+		 *
+		 * Mostly for clearing cache on a generic action name.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'groups_create_group_step_complete' );
+
+		/**
+		 * Once we have successfully saved the details for this step of the creation process
+		 * we need to add the current step to the array of completed steps, then update the cookies
+		 * holding the information
+		 */
+		$completed_create_steps = isset( $bp->groups->completed_create_steps ) ? $bp->groups->completed_create_steps : array();
+		if ( !in_array( bp_get_groups_current_create_step(), $completed_create_steps ) )
+			$bp->groups->completed_create_steps[] = bp_get_groups_current_create_step();
+
+		// Reset cookie info
+		setcookie( 'bp_new_group_id', $bp->groups->new_group_id, time()+60*60*24, COOKIEPATH );
+		setcookie( 'bp_completed_create_steps', base64_encode( json_encode( $bp->groups->completed_create_steps ) ), time()+60*60*24, COOKIEPATH );
+
+		// If we have completed all steps and hit done on the final step we
+		// can redirect to the completed group
+		$keys = array_keys( $bp->groups->group_creation_steps );
+		if ( count( $bp->groups->completed_create_steps ) == count( $keys ) && bp_get_groups_current_create_step() == array_pop( $keys ) ) {
+			unset( $bp->groups->current_create_step );
+			unset( $bp->groups->completed_create_steps );
+
+			setcookie( 'bp_new_group_id', false, time() - 3600, COOKIEPATH );
+			setcookie( 'bp_completed_create_steps', false, time() - 3600, COOKIEPATH );
+
+			// Once we completed all steps, record the group creation in the activity stream.
+			groups_record_activity( array(
+				'type' => 'created_group',
+				'item_id' => $bp->groups->new_group_id
+			) );
+
+			/**
+			 * Fires after the group has been successfully created.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param int $new_group_id ID of the newly created group.
+			 */
+			do_action( 'groups_group_create_complete', $bp->groups->new_group_id );
+
+			bp_core_redirect( bp_get_group_permalink( $bp->groups->current_group ) );
+		} else {
+			/**
+			 * Since we don't know what the next step is going to be (any plugin can insert steps)
+			 * we need to loop the step array and fetch the next step that way.
+			 */
+			foreach ( $keys as $key ) {
+				if ( $key == bp_get_groups_current_create_step() ) {
+					$next = 1;
+					continue;
+				}
+
+				if ( isset( $next ) ) {
+					$next_step = $key;
+					break;
+				}
+			}
+
+			bp_core_redirect( trailingslashit( bp_get_groups_directory_permalink() . 'create/step/' . $next_step ) );
+		}
+	}
+
+	// Remove invitations
+	if ( 'group-invites' === bp_get_groups_current_create_step() && ! empty( $_REQUEST['user_id'] ) && is_numeric( $_REQUEST['user_id'] ) ) {
+		if ( ! check_admin_referer( 'groups_invite_uninvite_user' ) ) {
+			return false;
+		}
+
+		$message = __( 'Invite successfully removed', 'buddypress' );
+		$error   = false;
+
+		if( ! groups_uninvite_user( (int) $_REQUEST['user_id'], $bp->groups->new_group_id ) ) {
+			$message = __( 'There was an error removing the invite', 'buddypress' );
+			$error   = 'error';
+		}
+
+		bp_core_add_message( $message, $error );
+		bp_core_redirect( trailingslashit( bp_get_groups_directory_permalink() . 'create/step/group-invites' ) );
+	}
+
+	// Group avatar is handled separately
+	if ( 'group-avatar' == bp_get_groups_current_create_step() && isset( $_POST['upload'] ) ) {
+		if ( ! isset( $bp->avatar_admin ) ) {
+			$bp->avatar_admin = new stdClass();
+		}
+
+		if ( !empty( $_FILES ) && isset( $_POST['upload'] ) ) {
+			// Normally we would check a nonce here, but the group save nonce is used instead
+
+			// Pass the file to the avatar upload handler
+			if ( bp_core_avatar_handle_upload( $_FILES, 'groups_avatar_upload_dir' ) ) {
+				$bp->avatar_admin->step = 'crop-image';
+
+				// Make sure we include the jQuery jCrop file for image cropping
+				add_action( 'wp_print_scripts', 'bp_core_add_jquery_cropper' );
+			}
+		}
+
+		// If the image cropping is done, crop the image and save a full/thumb version
+		if ( isset( $_POST['avatar-crop-submit'] ) && isset( $_POST['upload'] ) ) {
+			// Normally we would check a nonce here, but the group save nonce is used instead
+
+			if ( !bp_core_avatar_handle_crop( array( 'object' => 'group', 'avatar_dir' => 'group-avatars', 'item_id' => $bp->groups->current_group->id, 'original_file' => $_POST['image_src'], 'crop_x' => $_POST['x'], 'crop_y' => $_POST['y'], 'crop_w' => $_POST['w'], 'crop_h' => $_POST['h'] ) ) )
+				bp_core_add_message( __( 'There was an error saving the group profile photo, please try uploading again.', 'buddypress' ), 'error' );
+			else
+				bp_core_add_message( __( 'The group profile photo was uploaded successfully.', 'buddypress' ) );
+		}
+	}
+
+	/**
+	 * Filters the template to load for the group creation screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to the group creation template to load.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_create_group', 'groups/create' ) );
+}
+add_action( 'bp_actions', 'groups_action_create_group' );
+
+/**
+ * Catch and process "Join Group" button clicks.
+ */
+function groups_action_join_group() {
+
+	if ( !bp_is_single_item() || !bp_is_groups_component() || !bp_is_current_action( 'join' ) )
+		return false;
+
+	// Nonce check
+	if ( !check_admin_referer( 'groups_join_group' ) )
+		return false;
+
+	$bp = buddypress();
+
+	// Skip if banned or already a member
+	if ( !groups_is_user_member( bp_loggedin_user_id(), $bp->groups->current_group->id ) && !groups_is_user_banned( bp_loggedin_user_id(), $bp->groups->current_group->id ) ) {
+
+		// User wants to join a group that is not public
+		if ( $bp->groups->current_group->status != 'public' ) {
+			if ( !groups_check_user_has_invite( bp_loggedin_user_id(), $bp->groups->current_group->id ) ) {
+				bp_core_add_message( __( 'There was an error joining the group.', 'buddypress' ), 'error' );
+				bp_core_redirect( bp_get_group_permalink( $bp->groups->current_group ) );
+			}
+		}
+
+		// User wants to join any group
+		if ( !groups_join_group( $bp->groups->current_group->id ) )
+			bp_core_add_message( __( 'There was an error joining the group.', 'buddypress' ), 'error' );
+		else
+			bp_core_add_message( __( 'You joined the group!', 'buddypress' ) );
+
+		bp_core_redirect( bp_get_group_permalink( $bp->groups->current_group ) );
+	}
+
+	/**
+	 * Filters the template to load for the single group screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to the single group template to load.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_home', 'groups/single/home' ) );
+}
+add_action( 'bp_actions', 'groups_action_join_group' );
+
+/**
+ * Catch and process "Leave Group" button clicks.
+ *
+ * When a group member clicks on the "Leave Group" button from a group's page,
+ * this function is run.
+ *
+ * Note: When leaving a group from the group directory, AJAX is used and
+ * another function handles this. See {@link bp_legacy_theme_ajax_joinleave_group()}.
+ *
+ * @since 1.2.4
+ */
+function groups_action_leave_group() {
+	if ( ! bp_is_single_item() || ! bp_is_groups_component() || ! bp_is_current_action( 'leave-group' ) ) {
+		return false;
+	}
+
+	// Nonce check
+	if ( ! check_admin_referer( 'groups_leave_group' ) ) {
+		return false;
+	}
+
+	// User wants to leave any group
+	if ( groups_is_user_member( bp_loggedin_user_id(), bp_get_current_group_id() ) ) {
+		$bp = buddypress();
+
+		// Stop sole admins from abandoning their group
+		$group_admins = groups_get_group_admins( bp_get_current_group_id() );
+
+		if ( 1 == count( $group_admins ) && $group_admins[0]->user_id == bp_loggedin_user_id() ) {
+			bp_core_add_message( __( 'This group must have at least one admin', 'buddypress' ), 'error' );
+		} elseif ( ! groups_leave_group( $bp->groups->current_group->id ) ) {
+			bp_core_add_message( __( 'There was an error leaving the group.', 'buddypress' ), 'error' );
+		} else {
+			bp_core_add_message( __( 'You successfully left the group.', 'buddypress' ) );
+		}
+
+		$redirect = bp_get_group_permalink( groups_get_current_group() );
+
+		if( 'hidden' == $bp->groups->current_group->status ) {
+			$redirect = trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() );
+		}
+
+		bp_core_redirect( $redirect );
+	}
+
+	/** This filter is documented in bp-groups/bp-groups-actions.php */
+	bp_core_load_template( apply_filters( 'groups_template_group_home', 'groups/single/home' ) );
+}
+add_action( 'bp_actions', 'groups_action_leave_group' );
+
+/**
+ * Sort the group creation steps.
+ *
+ * @return false|null False on failure.
+ */
+function groups_action_sort_creation_steps() {
+
+	if ( !bp_is_groups_component() || !bp_is_current_action( 'create' ) )
+		return false;
+
+	$bp = buddypress();
+
+	if ( !is_array( $bp->groups->group_creation_steps ) )
+		return false;
+
+	foreach ( (array) $bp->groups->group_creation_steps as $slug => $step ) {
+		while ( !empty( $temp[$step['position']] ) )
+			$step['position']++;
+
+		$temp[$step['position']] = array( 'name' => $step['name'], 'slug' => $slug );
+	}
+
+	// Sort the steps by their position key
+	ksort($temp);
+	unset($bp->groups->group_creation_steps);
+
+	foreach( (array) $temp as $position => $step )
+		$bp->groups->group_creation_steps[$step['slug']] = array( 'name' => $step['name'], 'position' => $position );
+
+	/**
+	 * Fires after group creation sets have been sorted.
+	 *
+	 * @since 2.3.0
+	 */
+	do_action( 'groups_action_sort_creation_steps' );
+}
+
+/**
+ * Catch requests for a random group page (example.com/groups/?random-group) and redirect.
+ */
+function groups_action_redirect_to_random_group() {
+
+	if ( bp_is_groups_component() && isset( $_GET['random-group'] ) ) {
+		$group = BP_Groups_Group::get_random( 1, 1 );
+
+		bp_core_redirect( trailingslashit( bp_get_groups_directory_permalink() . $group['groups'][0]->slug ) );
+	}
+}
+add_action( 'bp_actions', 'groups_action_redirect_to_random_group' );
+
+/**
+ * Load the activity feed for the current group.
+ *
+ * @since 1.2.0
+ *
+ * @return false|null False on failure.
+ */
+function groups_action_group_feed() {
+
+	// get current group
+	$group = groups_get_current_group();
+
+	if ( ! bp_is_active( 'activity' ) || ! bp_is_groups_component() || ! $group || ! bp_is_current_action( 'feed' ) )
+		return false;
+
+	// if group isn't public or if logged-in user is not a member of the group, do
+	// not output the group activity feed
+	if ( ! bp_group_is_visible( $group ) ) {
+		return false;
+	}
+
+	// setup the feed
+	buddypress()->activity->feed = new BP_Activity_Feed( array(
+		'id'            => 'group',
+
+		/* translators: Group activity RSS title - "[Site Name] | [Group Name] | Activity" */
+		'title'         => sprintf( __( '%1$s | %2$s | Activity', 'buddypress' ), bp_get_site_name(), bp_get_current_group_name() ),
+
+		'link'          => bp_get_group_permalink( $group ),
+		'description'   => sprintf( __( "Activity feed for the group, %s.", 'buddypress' ), bp_get_current_group_name() ),
+		'activity_args' => array(
+			'object'           => buddypress()->groups->id,
+			'primary_id'       => bp_get_current_group_id(),
+			'display_comments' => 'threaded'
+		)
+	) );
+}
+add_action( 'bp_actions', 'groups_action_group_feed' );
Index: /tags/2.4.3/src/bp-groups/bp-groups-activity.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-activity.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-activity.php	(revision 10410)
@@ -0,0 +1,579 @@
+<?php
+/**
+ * BuddyPress Groups Activity Functions.
+ *
+ * These functions handle the recording, deleting and formatting of activity
+ * for the user and for this specific component.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsActivity
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register activity actions for the Groups component.
+ *
+ * @return bool|null False on failure.
+ */
+function groups_register_activity_actions() {
+	$bp = buddypress();
+
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	bp_activity_set_action(
+		$bp->groups->id,
+		'created_group',
+		__( 'Created a group', 'buddypress' ),
+		'bp_groups_format_activity_action_created_group',
+		__( 'New Groups', 'buddypress' ),
+		array( 'activity', 'member', 'member_groups' )
+	);
+
+	bp_activity_set_action(
+		$bp->groups->id,
+		'joined_group',
+		__( 'Joined a group', 'buddypress' ),
+		'bp_groups_format_activity_action_joined_group',
+		__( 'Group Memberships', 'buddypress' ),
+		array( 'activity', 'group', 'member', 'member_groups' )
+	);
+
+	bp_activity_set_action(
+		$bp->groups->id,
+		'group_details_updated',
+		__( 'Group details edited', 'buddypress' ),
+		'bp_groups_format_activity_action_group_details_updated',
+		__( 'Group Updates', 'buddypress' ),
+		array( 'activity', 'group', 'member', 'member_groups' )
+	);
+
+	// These actions are for the legacy forums
+	// Since the bbPress plugin also shares the same 'forums' identifier, we also
+	// check for the legacy forums loader class to be extra cautious
+	if ( bp_is_active( 'forums' ) && class_exists( 'BP_Forums_Component' ) ) {
+		bp_activity_set_action(
+			$bp->groups->id,
+			'new_forum_topic',
+			__( 'New group forum topic', 'buddypress' ),
+			false,
+			__( 'Forum Topics', 'buddypress' ),
+			array( 'activity', 'group', 'member', 'member_groups' )
+		);
+
+		bp_activity_set_action(
+			$bp->groups->id,
+			'new_forum_post',
+			__( 'New group forum post',  'buddypress' ),
+			false,
+			__( 'Forum Replies', 'buddypress' ),
+			array( 'activity', 'group', 'member', 'member_groups' )
+		);
+	}
+
+	/**
+	 * Fires at end of registration of the default activity actions for the Groups component.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'groups_register_activity_actions' );
+}
+add_action( 'bp_register_activity_actions', 'groups_register_activity_actions' );
+
+/**
+ * Format 'created_group' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity data object.
+ *
+ * @return string
+ */
+function bp_groups_format_activity_action_created_group( $action, $activity ) {
+	$user_link = bp_core_get_userlink( $activity->user_id );
+
+	$group = groups_get_group( array(
+		'group_id'        => $activity->item_id,
+		'populate_extras' => false,
+	) );
+	$group_link = '<a href="' . esc_url( bp_get_group_permalink( $group ) ) . '">' . esc_html( $group->name ) . '</a>';
+
+	$action = sprintf( __( '%1$s created the group %2$s', 'buddypress'), $user_link, $group_link );
+
+	/**
+	 * Filters the 'created_group' activity actions.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $action   The 'created_group' activity action.
+	 * @param object $activity Activity data object.
+	 */
+	return apply_filters( 'groups_activity_created_group_action', $action, $activity );
+}
+
+/**
+ * Format 'joined_group' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity data object.
+ *
+ * @return string
+ */
+function bp_groups_format_activity_action_joined_group( $action, $activity ) {
+	$user_link = bp_core_get_userlink( $activity->user_id );
+
+	$group = groups_get_group( array(
+		'group_id'        => $activity->item_id,
+		'populate_extras' => false,
+	) );
+	$group_link = '<a href="' . esc_url( bp_get_group_permalink( $group ) ) . '">' . esc_html( $group->name ) . '</a>';
+
+	$action = sprintf( __( '%1$s joined the group %2$s', 'buddypress' ), $user_link, $group_link );
+
+	// Legacy filters (do not follow parameter patterns of other activity
+	// action filters, and requires apply_filters_ref_array())
+	if ( has_filter( 'groups_activity_membership_accepted_action' ) ) {
+		$action = apply_filters_ref_array( 'groups_activity_membership_accepted_action', array( $action, $user_link, &$group ) );
+	}
+
+	// Another legacy filter
+	if ( has_filter( 'groups_activity_accepted_invite_action' ) ) {
+		$action = apply_filters_ref_array( 'groups_activity_accepted_invite_action', array( $action, $activity->user_id, &$group ) );
+	}
+
+	/**
+	 * Filters the 'joined_group' activity actions.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $action   The 'joined_group' activity actions.
+	 * @param object $activity Activity data object.
+	 */
+	return apply_filters( 'bp_groups_format_activity_action_joined_group', $action, $activity );
+}
+
+/**
+ * Format 'group_details_updated' activity actions.
+ *
+ * @since 2.2.0
+ *
+ * @param  string $action   Static activity action.
+ * @param  object $activity Activity data object.
+ *
+ * @return string
+ */
+function bp_groups_format_activity_action_group_details_updated( $action, $activity ) {
+	$user_link = bp_core_get_userlink( $activity->user_id );
+
+	$group = groups_get_group( array(
+		'group_id'        => $activity->item_id,
+		'populate_extras' => false,
+	) );
+	$group_link = '<a href="' . esc_url( bp_get_group_permalink( $group ) ) . '">' . esc_html( $group->name ) . '</a>';
+
+	/*
+	 * Changed group details are stored in groupmeta, keyed by the activity
+	 * timestamp. See {@link bp_groups_group_details_updated_add_activity()}.
+	 */
+	$changed = groups_get_groupmeta( $activity->item_id, 'updated_details_' . $activity->date_recorded );
+
+	// No changed details were found, so use a generic message.
+	if ( empty( $changed ) ) {
+		$action = sprintf( __( '%1$s updated details for the group %2$s', 'buddypress' ), $user_link, $group_link );
+
+	// Name and description changed - to keep things short, don't describe changes in detail.
+	} elseif ( isset( $changed['name'] ) && isset( $changed['description'] ) ) {
+		$action = sprintf( __( '%1$s changed the name and description of the group %2$s', 'buddypress' ), $user_link, $group_link );
+
+	// Name only.
+	} elseif ( ! empty( $changed['name']['old'] ) && ! empty( $changed['name']['new'] ) ) {
+		$action = sprintf( __( '%1$s changed the name of the group %2$s from "%3$s" to "%4$s"', 'buddypress' ), $user_link, $group_link, esc_html( $changed['name']['old'] ), esc_html( $changed['name']['new'] ) );
+
+	// Description only.
+	} elseif ( ! empty( $changed['description']['old'] ) && ! empty( $changed['description']['new'] ) ) {
+		$action = sprintf( __( '%1$s changed the description of the group %2$s from "%3$s" to "%4$s"', 'buddypress' ), $user_link, $group_link, esc_html( $changed['description']['old'] ), esc_html( $changed['description']['new'] ) );
+
+	}
+
+	/**
+	 * Filters the 'group_details_updated' activity actions.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $action   The 'group_details_updated' activity actions.
+	 * @param object $activity Activity data object.
+	 */
+	return apply_filters( 'bp_groups_format_activity_action_joined_group', $action, $activity );
+}
+
+/**
+ * Fetch data related to groups at the beginning of an activity loop.
+ *
+ * This reduces database overhead during the activity loop.
+ *
+ * @since 2.0.0
+ *
+ * @param array $activities Array of activity items.
+ *
+ * @return array
+ */
+function bp_groups_prefetch_activity_object_data( $activities ) {
+	$group_ids = array();
+
+	if ( empty( $activities ) ) {
+		return $activities;
+	}
+
+	foreach ( $activities as $activity ) {
+		if ( buddypress()->groups->id !== $activity->component ) {
+			continue;
+		}
+
+		$group_ids[] = $activity->item_id;
+	}
+
+	if ( ! empty( $group_ids ) ) {
+
+		// TEMPORARY - Once the 'populate_extras' issue is solved
+		// in the groups component, we can do this with groups_get_groups()
+		// rather than manually
+		$uncached_ids = array();
+		foreach ( $group_ids as $group_id ) {
+			if ( false === wp_cache_get( $group_id, 'bp_groups' ) ) {
+				$uncached_ids[] = $group_id;
+			}
+		}
+
+		if ( ! empty( $uncached_ids ) ) {
+			global $wpdb;
+			$bp = buddypress();
+			$uncached_ids_sql = implode( ',', wp_parse_id_list( $uncached_ids ) );
+			$groups = $wpdb->get_results( "SELECT * FROM {$bp->groups->table_name} WHERE id IN ({$uncached_ids_sql})" );
+			foreach ( $groups as $group ) {
+				wp_cache_set( $group->id, $group, 'bp_groups' );
+			}
+		}
+	}
+
+	return $activities;
+}
+add_filter( 'bp_activity_prefetch_object_data', 'bp_groups_prefetch_activity_object_data' );
+
+/**
+ * Set up activity arguments for use with the 'groups' scope.
+ *
+ * @since 2.2.0
+ *
+ * @param array $retval Empty array by default
+ * @param array $filter Current activity arguments
+ *
+ * @return array
+ */
+function bp_groups_filter_activity_scope( $retval = array(), $filter = array() ) {
+
+	// Determine the user_id
+	if ( ! empty( $filter['user_id'] ) ) {
+		$user_id = $filter['user_id'];
+	} else {
+		$user_id = bp_displayed_user_id()
+			? bp_displayed_user_id()
+			: bp_loggedin_user_id();
+	}
+
+	// Determine groups of user
+	$groups = groups_get_user_groups( $user_id );
+	if ( empty( $groups['groups'] ) ) {
+		$groups = array( 'groups' => 0 );
+	}
+
+	// Should we show all items regardless of sitewide visibility?
+	$show_hidden = array();
+	if ( ! empty( $user_id ) && ( $user_id !== bp_loggedin_user_id() ) ) {
+		$show_hidden = array(
+			'column' => 'hide_sitewide',
+			'value'  => 0
+		);
+	}
+
+	$retval = array(
+		'relation' => 'AND',
+		array(
+			'relation' => 'AND',
+			array(
+				'column' => 'component',
+				'value'  => buddypress()->groups->id
+			),
+			array(
+				'column'  => 'item_id',
+				'compare' => 'IN',
+				'value'   => (array) $groups['groups']
+			),
+		),
+		$show_hidden,
+
+		// overrides
+		'override' => array(
+			'filter'      => array( 'user_id' => 0 ),
+			'show_hidden' => true
+		),
+	);
+
+	return $retval;
+}
+add_filter( 'bp_activity_set_groups_scope_args', 'bp_groups_filter_activity_scope', 10, 2 );
+
+/**
+ * Record an activity item related to the Groups component.
+ *
+ * A wrapper for {@link bp_activity_add()} that provides some Groups-specific
+ * defaults.
+ *
+ * @see bp_activity_add() for more detailed description of parameters and
+ *      return values.
+ *
+ * @param array|string $args {
+ *     An array of arguments for the new activity item. Accepts all parameters
+ *     of {@link bp_activity_add()}. However, this wrapper provides some
+ *     additional defaults, as described below:
+ *     @type string $component     Default: the id of your Groups component
+ *                                 (usually 'groups').
+ *     @type bool   $hide_sitewide Default: True if the current group is not
+ *                                 public, otherwise false.
+ * }
+ * @return bool See {@link bp_activity_add()}.
+ */
+function groups_record_activity( $args = '' ) {
+
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	// Set the default for hide_sitewide by checking the status of the group
+	$hide_sitewide = false;
+	if ( !empty( $args['item_id'] ) ) {
+		if ( bp_get_current_group_id() == $args['item_id'] ) {
+			$group = groups_get_current_group();
+		} else {
+			$group = groups_get_group( array( 'group_id' => $args['item_id'] ) );
+		}
+
+		if ( isset( $group->status ) && 'public' != $group->status ) {
+			$hide_sitewide = true;
+		}
+	}
+
+	$r = wp_parse_args( $args, array(
+		'id'                => false,
+		'user_id'           => bp_loggedin_user_id(),
+		'action'            => '',
+		'content'           => '',
+		'primary_link'      => '',
+		'component'         => buddypress()->groups->id,
+		'type'              => false,
+		'item_id'           => false,
+		'secondary_item_id' => false,
+		'recorded_time'     => bp_core_current_time(),
+		'hide_sitewide'     => $hide_sitewide
+	) );
+
+	return bp_activity_add( $r );
+}
+
+/**
+ * Update the last_activity meta value for a given group.
+ *
+ * @param int $group_id Optional. The ID of the group whose last_activity is
+ *                      being updated. Default: the current group's ID.
+ *
+ * @return bool|null False on failure.
+ */
+function groups_update_last_activity( $group_id = 0 ) {
+
+	if ( empty( $group_id ) ) {
+		$group_id = buddypress()->groups->current_group->id;
+	}
+
+	if ( empty( $group_id ) ) {
+		return false;
+	}
+
+	groups_update_groupmeta( $group_id, 'last_activity', bp_core_current_time() );
+}
+add_action( 'groups_join_group',           'groups_update_last_activity' );
+add_action( 'groups_leave_group',          'groups_update_last_activity' );
+add_action( 'groups_created_group',        'groups_update_last_activity' );
+add_action( 'groups_new_forum_topic',      'groups_update_last_activity' );
+add_action( 'groups_new_forum_topic_post', 'groups_update_last_activity' );
+
+/**
+ * Add an activity stream item when a member joins a group.
+ *
+ * @since 1.9.0
+ *
+ * @param int $user_id  ID of the user joining the group.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool|null False on failure.
+ */
+function bp_groups_membership_accepted_add_activity( $user_id, $group_id ) {
+
+	// Bail if Activity is not active
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	// Get the group so we can get it's name
+	$group = groups_get_group( array( 'group_id' => $group_id ) );
+
+	/**
+	 * Filters the 'membership_accepted' activity actions.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value    The 'membership_accepted' activity action.
+	 * @param int    $user_id  ID of the user joining the group.
+	 * @param int    $group_id ID of the group. Passed by reference.
+	 */
+	$action = apply_filters_ref_array( 'groups_activity_membership_accepted_action', array( sprintf( __( '%1$s joined the group %2$s', 'buddypress' ), bp_core_get_userlink( $user_id ), '<a href="' . bp_get_group_permalink( $group ) . '">' . esc_attr( $group->name ) . '</a>' ), $user_id, &$group ) );
+
+	// Record in activity streams
+	groups_record_activity( array(
+		'action'  => $action,
+		'type'    => 'joined_group',
+		'item_id' => $group_id,
+		'user_id' => $user_id
+	) );
+}
+add_action( 'groups_membership_accepted', 'bp_groups_membership_accepted_add_activity', 10, 2 );
+
+/**
+ * Add an activity item when a group's details are updated.
+ *
+ * @since 2.2.0
+ *
+ * @param  int             $group_id       ID of the group.
+ * @param  BP_Groups_Group $old_group      Group object before the details had been changed.
+ * @param  bool            $notify_members True if the admin has opted to notify group members, otherwise false.
+ *
+ * @return int|bool The ID of the activity on success. False on error.
+ */
+function bp_groups_group_details_updated_add_activity( $group_id, $old_group, $notify_members ) {
+
+	// Bail if Activity is not active.
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	if ( ! isset( $old_group->name ) || ! isset( $old_group->description ) ) {
+		return false;
+	}
+
+	// If the admin has opted not to notify members, don't post an activity item either
+	if ( empty( $notify_members ) ) {
+		return;
+	}
+
+	$group = groups_get_group( array(
+		'group_id' => $group_id,
+	) );
+
+	/*
+	 * Store the changed data, which will be used to generate the activity
+	 * action. Since we haven't yet created the activity item, we store the
+	 * old group data in groupmeta, keyed by the timestamp that we'll put
+	 * on the activity item.
+	 */
+	$changed = array();
+
+	if ( $group->name !== $old_group->name ) {
+		$changed['name'] = array(
+			'old' => $old_group->name,
+			'new' => $group->name,
+		);
+	}
+
+	if ( $group->description !== $old_group->description ) {
+		$changed['description'] = array(
+			'old' => $old_group->description,
+			'new' => $group->description,
+		);
+	}
+
+	// If there are no changes, don't post an activity item.
+	if ( empty( $changed ) ) {
+		return;
+	}
+
+	$time = bp_core_current_time();
+	groups_update_groupmeta( $group_id, 'updated_details_' . $time, $changed );
+
+	// Record in activity streams.
+	return groups_record_activity( array(
+		'type'          => 'group_details_updated',
+		'item_id'       => $group_id,
+		'user_id'       => bp_loggedin_user_id(),
+		'recorded_time' => $time,
+
+	) );
+
+}
+add_action( 'groups_details_updated', 'bp_groups_group_details_updated_add_activity', 10, 3 );
+
+/**
+ * Delete all activity items related to a specific group.
+ *
+ * @since 1.9.0
+ *
+ * @param int $group_id ID of the group.
+ */
+function bp_groups_delete_group_delete_all_activity( $group_id ) {
+	if ( bp_is_active( 'activity' ) ) {
+		bp_activity_delete_by_item_id( array(
+			'item_id'   => $group_id,
+			'component' => buddypress()->groups->id
+		) );
+	}
+}
+add_action( 'groups_delete_group', 'bp_groups_delete_group_delete_all_activity', 10 );
+
+/**
+ * Delete group member activity if they leave or are removed within 5 minutes of membership modification.
+ *
+ * If the user joined this group less than five minutes ago, remove the
+ * joined_group activity so users cannot flood the activity stream by
+ * joining/leaving the group in quick succession.
+ *
+ * @since 1.9.0
+ *
+ * @param int $group_id ID of the group.
+ * @param int $user_id  ID of the user leaving the group.
+ */
+function bp_groups_leave_group_delete_recent_activity( $group_id, $user_id ) {
+
+	// Bail if Activity component is not active
+	if ( ! bp_is_active( 'activity' ) ) {
+		return;
+	}
+
+	// Get the member's group membership information
+	$membership = new BP_Groups_Member( $user_id, $group_id );
+
+	// Check the time period, and maybe delete their recent group activity
+	if ( time() <= strtotime( '+5 minutes', (int) strtotime( $membership->date_modified ) ) ) {
+		bp_activity_delete( array(
+			'component' => buddypress()->groups->id,
+			'type'      => 'joined_group',
+			'user_id'   => $user_id,
+			'item_id'   => $group_id
+		) );
+	}
+}
+add_action( 'groups_leave_group',   'bp_groups_leave_group_delete_recent_activity', 10, 2 );
+add_action( 'groups_remove_member', 'bp_groups_leave_group_delete_recent_activity', 10, 2 );
+add_action( 'groups_ban_member',    'bp_groups_leave_group_delete_recent_activity', 10, 2 );
Index: /tags/2.4.3/src/bp-groups/bp-groups-admin.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-admin.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-admin.php	(revision 10410)
@@ -0,0 +1,1752 @@
+<?php
+/**
+ * BuddyPress Groups component admin screen.
+ *
+ * Props to WordPress core for the Comments admin screen, and its contextual
+ * help text, on which this implementation is heavily based.
+ *
+ * @since 1.7.0
+ *
+ * @package BuddyPress
+ * @subpackage Groups
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+// Include WP's list table class.
+if ( !class_exists( 'WP_List_Table' ) ) require( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
+
+// per_page screen option. Has to be hooked in extremely early.
+if ( is_admin() && ! empty( $_REQUEST['page'] ) && 'bp-groups' == $_REQUEST['page'] )
+	add_filter( 'set-screen-option', 'bp_groups_admin_screen_options', 10, 3 );
+
+/**
+ * Register the Groups component admin screen.
+ *
+ * @since 1.7.0
+ */
+function bp_groups_add_admin_menu() {
+
+	// Add our screen
+	$hook = add_menu_page(
+		_x( 'Groups', 'Admin Groups page title', 'buddypress' ),
+		_x( 'Groups', 'Admin Groups menu', 'buddypress' ),
+		'bp_moderate',
+		'bp-groups',
+		'bp_groups_admin',
+		'div'
+	);
+
+	// Hook into early actions to load custom CSS and our init handler.
+	add_action( "load-$hook", 'bp_groups_admin_load' );
+}
+add_action( bp_core_admin_hook(), 'bp_groups_add_admin_menu' );
+
+/**
+ * Add groups component to custom menus array.
+ *
+ * This ensures that the Groups menu item appears in the proper order on the
+ * main Dashboard menu.
+ *
+ * @since 1.7.0
+ *
+ * @param array $custom_menus Array of BP top-level menu items.
+ *
+ * @return array Menu item array, with Groups added.
+ */
+function bp_groups_admin_menu_order( $custom_menus = array() ) {
+	array_push( $custom_menus, 'bp-groups' );
+	return $custom_menus;
+}
+add_filter( 'bp_admin_menu_order', 'bp_groups_admin_menu_order' );
+
+/**
+ * Set up the Groups admin page.
+ *
+ * Loaded before the page is rendered, this function does all initial setup,
+ * including: processing form requests, registering contextual help, and
+ * setting up screen options.
+ *
+ * @since 1.7.0
+ *
+ * @global BP_Groups_List_Table $bp_groups_list_table Groups screen list table.
+ */
+function bp_groups_admin_load() {
+	global $bp_groups_list_table;
+
+	// Build redirection URL
+	$redirect_to = remove_query_arg( array( 'action', 'action2', 'gid', 'deleted', 'error', 'updated', 'success_new', 'error_new', 'success_modified', 'error_modified' ), $_SERVER['REQUEST_URI'] );
+
+	// Decide whether to load the dev version of the CSS and JavaScript
+	$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : 'min.';
+
+	$doaction = bp_admin_list_table_current_bulk_action();
+
+	/**
+	 * Fires at top of groups admin page.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $doaction Current $_GET action being performed in admin screen.
+	 */
+	do_action( 'bp_groups_admin_load', $doaction );
+
+	// Edit screen
+	if ( 'do_delete' == $doaction && ! empty( $_GET['gid'] ) ) {
+
+		check_admin_referer( 'bp-groups-delete' );
+
+		$group_ids = wp_parse_id_list( $_GET['gid'] );
+
+		$count = 0;
+		foreach ( $group_ids as $group_id ) {
+			if ( groups_delete_group( $group_id ) ) {
+				$count++;
+			}
+		}
+
+		$redirect_to = add_query_arg( 'deleted', $count, $redirect_to );
+
+		bp_core_redirect( $redirect_to );
+
+	} elseif ( 'edit' == $doaction && ! empty( $_GET['gid'] ) ) {
+		// columns screen option
+		add_screen_option( 'layout_columns', array( 'default' => 2, 'max' => 2, ) );
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'bp-group-edit-overview',
+			'title'   => __( 'Overview', 'buddypress' ),
+			'content' =>
+				'<p>' . __( 'This page is a convenient way to edit the details associated with one of your groups.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'The Name and Description box is 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 hide or unhide, or to choose a 1- or 2-column layout for this screen.', 'buddypress' ) . '</p>'
+		) );
+
+		// Help panel - sidebar links
+		get_current_screen()->set_help_sidebar(
+			'<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
+			'<p><a href="https://buddypress.org/support">' . __( 'Support Forums', 'buddypress' ) . '</a></p>'
+		);
+
+		// Register metaboxes for the edit screen.
+		add_meta_box( 'submitdiv', _x( 'Save', 'group admin edit screen', 'buddypress' ), 'bp_groups_admin_edit_metabox_status', get_current_screen()->id, 'side', 'high' );
+		add_meta_box( 'bp_group_settings', _x( 'Settings', 'group admin edit screen', 'buddypress' ), 'bp_groups_admin_edit_metabox_settings', get_current_screen()->id, 'side', 'core' );
+		add_meta_box( 'bp_group_add_members', _x( 'Add New Members', 'group admin edit screen', 'buddypress' ), 'bp_groups_admin_edit_metabox_add_new_members', get_current_screen()->id, 'normal', 'core' );
+		add_meta_box( 'bp_group_members', _x( 'Manage Members', 'group admin edit screen', 'buddypress' ), 'bp_groups_admin_edit_metabox_members', get_current_screen()->id, 'normal', 'core' );
+
+		/**
+		 * Fires after the registration of all of the default group meta boxes.
+		 *
+		 * @since 1.7.0
+		 */
+		do_action( 'bp_groups_admin_meta_boxes' );
+
+		// Enqueue JavaScript files
+		wp_enqueue_script( 'postbox' );
+		wp_enqueue_script( 'dashboard' );
+
+	// Index screen
+	} else {
+		// Create the Groups screen list table
+		$bp_groups_list_table = new BP_Groups_List_Table();
+
+		// per_page screen option
+		add_screen_option( 'per_page', array( 'label' => _x( 'Groups', 'Groups per page (screen options)', 'buddypress' )) );
+
+		// Help panel - overview text
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'bp-groups-overview',
+			'title'   => __( 'Overview', 'buddypress' ),
+			'content' =>
+				'<p>' . __( 'You can manage groups much like you can manage comments and other content. This screen is customizable in the same ways as other management screens, and you can act on groups by using the on-hover action links or the Bulk Actions.', 'buddypress' ) . '</p>',
+		) );
+
+		get_current_screen()->add_help_tab( array(
+			'id'      => 'bp-groups-overview-actions',
+			'title'   => __( 'Group Actions', 'buddypress' ),
+			'content' =>
+				'<p>' . __( 'Clicking "Visit" will take you to the group&#8217;s public page. Use this link to see what the group looks like on the front end of your site.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'Clicking "Edit" will take you to a Dashboard panel where you can manage various details about the group, such as its name and description, its members, and other settings.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'If you click "Delete" under a specific group, or select a number of groups and then choose Delete from the Bulk Actions menu, you will be led to a page where you&#8217;ll be asked to confirm the permanent deletion of the group(s).', 'buddypress' ) . '</p>',
+		) );
+
+		// Help panel - sidebar links
+		get_current_screen()->set_help_sidebar(
+			'<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
+			'<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
+		);
+	}
+
+	$bp = buddypress();
+
+	// Enqueue CSS and JavaScript
+	wp_enqueue_script( 'bp_groups_admin_js', $bp->plugin_url . "bp-groups/admin/js/admin.{$min}js", array( 'jquery', 'wp-ajax-response', 'jquery-ui-autocomplete' ), bp_get_version(), true );
+	wp_localize_script( 'bp_groups_admin_js', 'BP_Group_Admin', array(
+		'add_member_placeholder' => __( 'Start typing a username to add a new member.', 'buddypress' ),
+		'warn_on_leave'          => __( 'If you leave this page, you will lose any unsaved changes you have made to the group.', 'buddypress' ),
+	) );
+	wp_enqueue_style( 'bp_groups_admin_css', $bp->plugin_url . "bp-groups/admin/css/admin.{$min}css", array(), bp_get_version() );
+
+	wp_style_add_data( 'bp_groups_admin_css', 'rtl', true );
+	if ( $min ) {
+		wp_style_add_data( 'bp_groups_admin_css', 'suffix', $min );
+	}
+
+
+	if ( $doaction && 'save' == $doaction ) {
+		// Get group ID
+		$group_id = isset( $_REQUEST['gid'] ) ? (int) $_REQUEST['gid'] : '';
+
+		$redirect_to = add_query_arg( array(
+			'gid'    => (int) $group_id,
+			'action' => 'edit'
+		), $redirect_to );
+
+		// Check this is a valid form submission
+		check_admin_referer( 'edit-group_' . $group_id );
+
+		// Get the group from the database
+		$group = groups_get_group( 'group_id=' . $group_id );
+
+		// If the group doesn't exist, just redirect back to the index
+		if ( empty( $group->slug ) ) {
+			wp_redirect( $redirect_to );
+			exit;
+		}
+
+		// Check the form for the updated properties
+
+		// Store errors
+		$error = 0;
+		$success_new = $error_new = $success_modified = $error_modified = array();
+
+		// Group name and description are handled with
+		// groups_edit_base_group_details()
+		if ( !groups_edit_base_group_details( $group_id, $_POST['bp-groups-name'], $_POST['bp-groups-description'], 0 ) ) {
+			$error = $group_id;
+
+			// using negative integers for different error messages... eek!
+			if ( empty( $_POST['bp-groups-name'] ) && empty( $_POST['bp-groups-description'] ) ) {
+				$error = -3;
+			} elseif ( empty( $_POST['bp-groups-name'] ) ) {
+				$error = -1;
+			} elseif ( empty( $_POST['bp-groups-description'] ) ) {
+				$error = -2;
+			}
+		}
+
+		// Enable discussion forum
+		$enable_forum   = ( isset( $_POST['group-show-forum'] ) ) ? 1 : 0;
+
+		/**
+		 * Filters the allowed status values for the group.
+		 *
+		 * @since 1.0.2
+		 *
+		 * @param array $value Array of allowed group statuses.
+		 */
+		$allowed_status = apply_filters( 'groups_allowed_status', array( 'public', 'private', 'hidden' ) );
+		$status         = ( in_array( $_POST['group-status'], (array) $allowed_status ) ) ? $_POST['group-status'] : 'public';
+
+		/**
+		 * Filters the allowed invite status values for the group.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param array $value Array of allowed invite statuses.
+		 */
+		$allowed_invite_status = apply_filters( 'groups_allowed_invite_status', array( 'members', 'mods', 'admins' ) );
+		$invite_status	       = in_array( $_POST['group-invite-status'], (array) $allowed_invite_status ) ? $_POST['group-invite-status'] : 'members';
+
+		if ( !groups_edit_group_settings( $group_id, $enable_forum, $status, $invite_status ) ) {
+			$error = $group_id;
+		}
+
+		// Process new members
+		$user_names = array();
+
+		if ( ! empty( $_POST['bp-groups-new-members'] ) ) {
+			$user_names = array_merge( $user_names, explode( ',', $_POST['bp-groups-new-members'] ) );
+		}
+
+		if ( ! empty( $user_names ) ) {
+
+			foreach( array_values( $user_names ) as $user_name ) {
+				$un = trim( $user_name );
+
+				// Make sure the user exists before attempting
+				// to add to the group
+				$user = get_user_by( 'slug', $un );
+
+				if ( empty( $user ) ) {
+					$error_new[] = $un;
+				} else {
+					if ( ! groups_join_group( $group_id, $user->ID ) ) {
+						$error_new[]   = $un;
+					} else {
+						$success_new[] = $un;
+					}
+				}
+			}
+		}
+
+		// Process member role changes
+		if ( ! empty( $_POST['bp-groups-role'] ) && ! empty( $_POST['bp-groups-existing-role'] ) ) {
+
+			// Before processing anything, make sure you're not
+			// attempting to remove the all user admins
+			$admin_count = 0;
+			foreach ( (array) $_POST['bp-groups-role'] as $new_role ) {
+				if ( 'admin' == $new_role ) {
+					$admin_count++;
+					break;
+				}
+			}
+
+			if ( ! $admin_count ) {
+
+				$redirect_to = add_query_arg( 'no_admins', 1, $redirect_to );
+				$error = $group_id;
+
+			} else {
+
+				// Process only those users who have had their roles changed
+				foreach ( (array) $_POST['bp-groups-role'] as $user_id => $new_role ) {
+					$user_id = (int) $user_id;
+
+					$existing_role = isset( $_POST['bp-groups-existing-role'][$user_id] ) ? $_POST['bp-groups-existing-role'][$user_id] : '';
+
+					if ( $existing_role != $new_role ) {
+						$result = false;
+
+						switch ( $new_role ) {
+							case 'mod' :
+								// Admin to mod is a demotion. Demote to
+								// member, then fall through
+								if ( 'admin' == $existing_role ) {
+									groups_demote_member( $user_id, $group_id );
+								}
+
+							case 'admin' :
+								// If the user was banned, we must
+								// unban first
+								if ( 'banned' == $existing_role ) {
+									groups_unban_member( $user_id, $group_id );
+								}
+
+								// At this point, each existing_role
+								// is a member, so promote
+								$result = groups_promote_member( $user_id, $group_id, $new_role );
+
+								break;
+
+							case 'member' :
+
+								if ( 'admin' == $existing_role || 'mod' == $existing_role ) {
+									$result = groups_demote_member( $user_id, $group_id );
+								} elseif ( 'banned' == $existing_role ) {
+									$result = groups_unban_member( $user_id, $group_id );
+								}
+
+								break;
+
+							case 'banned' :
+
+								$result = groups_ban_member( $user_id, $group_id );
+
+								break;
+
+							case 'remove' :
+
+								$result = groups_remove_member( $user_id, $group_id );
+
+								break;
+						}
+
+						// Store the success or failure
+						if ( $result ) {
+							$success_modified[] = $user_id;
+						} else {
+							$error_modified[]   = $user_id;
+						}
+					}
+				}
+			}
+		}
+
+		/**
+		 * Fires before redirect so plugins can do something first on save action.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param int $group_id ID of the group being edited.
+		 */
+		do_action( 'bp_group_admin_edit_after', $group_id );
+
+		// Create the redirect URL
+
+		if ( $error ) {
+			// This means there was an error updating group details
+			$redirect_to = add_query_arg( 'error', (int) $error, $redirect_to );
+		} else {
+			// Group details were update successfully
+			$redirect_to = add_query_arg( 'updated', 1, $redirect_to );
+		}
+
+		if ( !empty( $success_new ) ) {
+			$success_new = implode( ',', array_filter( $success_new, 'urlencode' ) );
+			$redirect_to = add_query_arg( 'success_new', $success_new, $redirect_to );
+		}
+
+		if ( !empty( $error_new ) ) {
+			$error_new = implode( ',', array_filter( $error_new, 'urlencode' ) );
+			$redirect_to = add_query_arg( 'error_new', $error_new, $redirect_to );
+		}
+
+		if ( !empty( $success_modified ) ) {
+			$success_modified = implode( ',', array_filter( $success_modified, 'urlencode' ) );
+			$redirect_to = add_query_arg( 'success_modified', $success_modified, $redirect_to );
+		}
+
+		if ( !empty( $error_modified ) ) {
+			$error_modified = implode( ',', array_filter( $error_modified, 'urlencode' ) );
+			$redirect_to = add_query_arg( 'error_modified', $error_modified, $redirect_to );
+		}
+
+		/**
+		 * Filters the URL to redirect to after successfully editing a group.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $redirect_to URL to redirect user to.
+		 */
+		wp_redirect( apply_filters( 'bp_group_admin_edit_redirect', $redirect_to ) );
+		exit;
+
+
+	// If a referrer and a nonce is supplied, but no action, redirect back.
+	} elseif ( ! empty( $_GET['_wp_http_referer'] ) ) {
+		wp_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), stripslashes( $_SERVER['REQUEST_URI'] ) ) );
+		exit;
+	}
+}
+
+/**
+ * Handle save/update of screen options for the Groups component admin screen.
+ *
+ * @since 1.7.0
+ *
+ * @param string $value     Will always be false unless another plugin filters it first.
+ * @param string $option    Screen option name.
+ * @param string $new_value Screen option form value.
+ *
+ * @return string Option value. False to abandon update.
+ */
+function bp_groups_admin_screen_options( $value, $option, $new_value ) {
+	if ( 'toplevel_page_bp_groups_per_page' != $option && 'toplevel_page_bp_groups_network_per_page' != $option )
+		return $value;
+
+	// Per page
+	$new_value = (int) $new_value;
+	if ( $new_value < 1 || $new_value > 999 )
+		return $value;
+
+	return $new_value;
+}
+
+/**
+ * Select the appropriate Groups admin screen, and output it.
+ *
+ * @since 1.7.0
+ */
+function bp_groups_admin() {
+	// Decide whether to load the index or edit screen
+	$doaction = bp_admin_list_table_current_bulk_action();
+
+	// Display the single group edit screen
+	if ( 'edit' == $doaction && ! empty( $_GET['gid'] ) ) {
+		bp_groups_admin_edit();
+
+	// Display the group deletion confirmation screen
+	} elseif ( 'delete' == $doaction && ! empty( $_GET['gid'] ) ) {
+		bp_groups_admin_delete();
+
+	// Otherwise, display the groups index screen
+	} else {
+		bp_groups_admin_index();
+	}
+}
+
+/**
+ * Display the single groups edit screen.
+ *
+ * @since 1.7.0
+ */
+function bp_groups_admin_edit() {
+
+	if ( ! current_user_can( 'bp_moderate' ) )
+		die( '-1' );
+
+	$messages = array();
+
+	// If the user has just made a change to a group, build status messages
+	if ( !empty( $_REQUEST['no_admins'] ) || ! empty( $_REQUEST['error'] ) || ! empty( $_REQUEST['updated'] ) || ! empty( $_REQUEST['error_new'] ) || ! empty( $_REQUEST['success_new'] ) || ! empty( $_REQUEST['error_modified'] ) || ! empty( $_REQUEST['success_modified'] ) ) {
+		$no_admins        = ! empty( $_REQUEST['no_admins']        ) ? 1                                             : 0;
+		$errors           = ! empty( $_REQUEST['error']            ) ? $_REQUEST['error']                            : '';
+		$updated          = ! empty( $_REQUEST['updated']          ) ? $_REQUEST['updated']                          : '';
+		$error_new        = ! empty( $_REQUEST['error_new']        ) ? explode( ',', $_REQUEST['error_new'] )        : array();
+		$success_new      = ! empty( $_REQUEST['success_new']      ) ? explode( ',', $_REQUEST['success_new'] )      : array();
+		$error_modified   = ! empty( $_REQUEST['error_modified']   ) ? explode( ',', $_REQUEST['error_modified'] )   : array();
+		$success_modified = ! empty( $_REQUEST['success_modified'] ) ? explode( ',', $_REQUEST['success_modified'] ) : array();
+
+		if ( ! empty( $no_admins ) ) {
+			$messages[] = __( 'You cannot remove all administrators from a group.', 'buddypress' );
+		}
+
+		if ( ! empty( $errors ) ) {
+			switch ( $errors ) {
+				case -1 :
+					$messages[] = __( 'Group name cannot be empty.', 'buddypress' );
+					break;
+
+				case -2 :
+					$messages[] = __( 'Group description cannot be empty.', 'buddypress' );
+					break;
+
+				case -3 :
+					$messages[] = __( 'Group name and description cannot be empty.', 'buddypress' );
+					break;
+
+				default :
+					$messages[] = __( 'An error occurred when trying to update your group details.', 'buddypress' );
+					break;
+			}
+
+		} elseif ( ! empty( $updated ) ) {
+			$messages[] = __( 'The group has been updated successfully.', 'buddypress' );
+		}
+
+		if ( ! empty( $error_new ) ) {
+			$messages[] = sprintf( __( 'The following users could not be added to the group: %s', 'buddypress' ), '<em>' . esc_html( implode( ', ', $error_new ) ) . '</em>' );
+		}
+
+		if ( ! empty( $success_new ) ) {
+			$messages[] = sprintf( __( 'The following users were successfully added to the group: %s', 'buddypress' ), '<em>' . esc_html( implode( ', ', $success_new ) ) . '</em>' );
+		}
+
+		if ( ! empty( $error_modified ) ) {
+			$error_modified = bp_groups_admin_get_usernames_from_ids( $error_modified );
+			$messages[] = sprintf( __( 'An error occurred when trying to modify the following members: %s', 'buddypress' ), '<em>' . esc_html( implode( ', ', $error_modified ) ) . '</em>' );
+		}
+
+		if ( ! empty( $success_modified ) ) {
+			$success_modified = bp_groups_admin_get_usernames_from_ids( $success_modified );
+			$messages[] = sprintf( __( 'The following members were successfully modified: %s', 'buddypress' ), '<em>' . esc_html( implode( ', ', $success_modified ) ) . '</em>' );
+		}
+	}
+
+	$is_error = ! empty( $no_admins ) || ! empty( $errors ) || ! empty( $error_new ) || ! empty( $error_modified );
+
+	// Get the group from the database
+	$group      = groups_get_group( 'group_id=' . (int) $_GET['gid'] );
+
+	/** This filter is documented in bp-groups/bp-groups-template.php */
+	$group_name = isset( $group->name ) ? apply_filters( 'bp_get_group_name', $group->name ) : '';
+
+	// Construct URL for form
+	$form_url = remove_query_arg( array( 'action', 'deleted', 'no_admins', 'error', 'error_new', 'success_new', 'error_modified', 'success_modified' ), $_SERVER['REQUEST_URI'] );
+	$form_url = add_query_arg( 'action', 'save', $form_url );
+
+	/**
+	 * Fires before the display of the edit form.
+	 *
+	 * Useful for plugins to modify the group before display.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param BP_Groups_Group $this Instance of the current group being edited. Passed by reference.
+	 */
+	do_action_ref_array( 'bp_groups_admin_edit', array( &$group ) ); ?>
+
+	<div class="wrap">
+		<h2><?php _e( 'Edit Group', 'buddypress' ); ?>
+
+			<?php if ( is_user_logged_in() && bp_user_can_create_groups() ) : ?>
+				<a class="add-new-h2" href="<?php echo trailingslashit( bp_get_groups_directory_permalink() . 'create' ); ?>"><?php _e( 'Add New', 'buddypress' ); ?></a>
+			<?php endif; ?>
+
+		</h2>
+
+		<?php // If the user has just made a change to an group, display the status messages ?>
+		<?php if ( !empty( $messages ) ) : ?>
+			<div id="moderated" class="<?php echo ( $is_error ) ? 'error' : 'updated'; ?>"><p><?php echo implode( "</p><p>", $messages ); ?></p></div>
+		<?php endif; ?>
+
+		<?php if ( ! empty( $group ) ) : ?>
+
+			<form action="<?php echo esc_url( $form_url ); ?>" id="bp-groups-edit-form" method="post">
+				<div id="poststuff">
+
+					<div id="post-body" class="metabox-holder columns-<?php echo 1 == get_current_screen()->get_columns() ? '1' : '2'; ?>">
+						<div id="post-body-content">
+							<div id="postdiv">
+								<div id="bp_groups_name" class="postbox">
+									<h3><?php _e( 'Name and Description', 'buddypress' ); ?></h3>
+									<div class="inside">
+										<label for="bp-groups-name" class="screen-reader-text"><?php _e( 'Group Name', 'buddypress' ); ?></label>
+										<input type="text" name="bp-groups-name" id="bp-groups-name" value="<?php echo esc_attr( stripslashes( $group_name ) ) ?>" />
+										<div id="bp-groups-permalink-box">
+											<strong><?php esc_html_e( 'Permalink:', 'buddypress' ) ?></strong> <span id="sample-permalink"><?php bp_group_permalink( $group ) ?></span> <a href="<?php echo bp_group_permalink( $group ) ?>" class="button button-small" id="bp-groups-visit-group"><?php esc_html_e( 'Visit Group', 'buddypress' ) ?></a>
+										</div>
+
+										<label for="bp-groups-description" class="screen-reader-text"><?php _e( 'Group Description', 'buddypress' ); ?></label>
+										<?php wp_editor( stripslashes( $group->description ), 'bp-groups-description', array( 'media_buttons' => false, 'teeny' => true, 'textarea_rows' => 5, 'quicktags' => array( 'buttons' => 'strong,em,link,block,del,ins,img,code,spell,close' ) ) ); ?>
+									</div>
+								</div>
+							</div>
+						</div><!-- #post-body-content -->
+
+						<div id="postbox-container-1" class="postbox-container">
+							<?php do_meta_boxes( get_current_screen()->id, 'side', $group ); ?>
+						</div>
+
+						<div id="postbox-container-2" class="postbox-container">
+							<?php do_meta_boxes( get_current_screen()->id, 'normal', $group ); ?>
+							<?php do_meta_boxes( get_current_screen()->id, 'advanced', $group ); ?>
+						</div>
+					</div><!-- #post-body -->
+
+				</div><!-- #poststuff -->
+				<?php wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); ?>
+				<?php wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); ?>
+				<?php wp_nonce_field( 'edit-group_' . $group->id ); ?>
+			</form>
+
+		<?php else : ?>
+			<p><?php printf( __( 'No group found with this ID. <a href="%s">Go back and try again</a>.', 'buddypress' ), esc_url( bp_get_admin_url( 'admin.php?page=bp-groups' ) ) ); ?></p>
+		<?php endif; ?>
+
+	</div><!-- .wrap -->
+
+<?php
+}
+
+/**
+ * Display the Group delete confirmation screen.
+ *
+ * We include a separate confirmation because group deletion is truly
+ * irreversible.
+ *
+ * @since 1.7.0
+ */
+function bp_groups_admin_delete() {
+
+	if ( ! bp_current_user_can( 'bp_moderate' ) ) {
+		die( '-1' );
+	}
+
+	$group_ids = isset( $_REQUEST['gid'] ) ? $_REQUEST['gid'] : 0;
+	if ( ! is_array( $group_ids ) ) {
+		$group_ids = explode( ',', $group_ids );
+	}
+	$group_ids = wp_parse_id_list( $group_ids );
+	$groups    = groups_get_groups( array(
+		'include'     => $group_ids,
+		'show_hidden' => true,
+		'per_page'    => null, // Return all results
+	) );
+
+	// Create a new list of group ids, based on those that actually exist
+	$gids = array();
+	foreach ( $groups['groups'] as $group ) {
+		$gids[] = $group->id;
+	}
+
+	$base_url  = remove_query_arg( array( 'action', 'action2', 'paged', 's', '_wpnonce', 'gid' ), $_SERVER['REQUEST_URI'] ); ?>
+
+	<div class="wrap">
+		<h2><?php _e( 'Delete Groups', 'buddypress' ) ?></h2>
+		<p><?php _e( 'You are about to delete the following groups:', 'buddypress' ) ?></p>
+
+		<ul class="bp-group-delete-list">
+		<?php foreach ( $groups['groups'] as $group ) : ?>
+			<li><?php echo esc_html( $group->name ) ?></li>
+		<?php endforeach; ?>
+		</ul>
+
+		<p><strong><?php _e( 'This action cannot be undone.', 'buddypress' ) ?></strong></p>
+
+		<a class="button-primary" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'action' => 'do_delete', 'gid' => implode( ',', $gids ) ), $base_url ), 'bp-groups-delete' ) ); ?>"><?php _e( 'Delete Permanently', 'buddypress' ) ?></a>
+		<a class="button" href="<?php echo esc_attr( $base_url ); ?>"><?php _e( 'Cancel', 'buddypress' ) ?></a>
+	</div>
+
+	<?php
+}
+
+/**
+ * Display the Groups admin index screen.
+ *
+ * This screen contains a list of all BuddyPress groups.
+ *
+ * @since 1.7.0
+ *
+ * @global BP_Groups_List_Table $bp_groups_list_table Group screen list table.
+ * @global string $plugin_page Currently viewed plugin page.
+ */
+function bp_groups_admin_index() {
+	global $bp_groups_list_table, $plugin_page;
+
+	$messages = array();
+
+	// If the user has just made a change to a group, build status messages
+	if ( ! empty( $_REQUEST['deleted'] ) ) {
+		$deleted  = ! empty( $_REQUEST['deleted'] ) ? (int) $_REQUEST['deleted'] : 0;
+
+		if ( $deleted > 0 ) {
+			$messages[] = sprintf( _n( '%s group has been permanently deleted.', '%s groups have been permanently deleted.', $deleted, 'buddypress' ), number_format_i18n( $deleted ) );
+		}
+	}
+
+	// Prepare the group items for display
+	$bp_groups_list_table->prepare_items();
+
+	/**
+	 * Fires before the display of messages for the edit form.
+	 *
+	 * Useful for plugins to modify the messages before display.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $messages Array of messages to be displayed.
+	 */
+	do_action( 'bp_groups_admin_index', $messages ); ?>
+
+	<div class="wrap">
+		<h2>
+			<?php _e( 'Groups', 'buddypress' ); ?>
+
+			<?php if ( is_user_logged_in() && bp_user_can_create_groups() ) : ?>
+				<a class="add-new-h2" href="<?php echo trailingslashit( bp_get_groups_directory_permalink() . 'create' ); ?>"><?php _e( 'Add New', 'buddypress' ); ?></a>
+			<?php endif; ?>
+
+			<?php if ( !empty( $_REQUEST['s'] ) ) : ?>
+				<span class="subtitle"><?php printf( __( 'Search results for &#8220;%s&#8221;', 'buddypress' ), wp_html_excerpt( esc_html( stripslashes( $_REQUEST['s'] ) ), 50 ) ); ?></span>
+			<?php endif; ?>
+		</h2>
+
+		<?php // If the user has just made a change to an group, display the status messages ?>
+		<?php if ( !empty( $messages ) ) : ?>
+			<div id="moderated" class="<?php echo ( ! empty( $_REQUEST['error'] ) ) ? 'error' : 'updated'; ?>"><p><?php echo implode( "<br/>\n", $messages ); ?></p></div>
+		<?php endif; ?>
+
+		<?php // Display each group on its own row ?>
+		<?php $bp_groups_list_table->views(); ?>
+
+		<form id="bp-groups-form" action="" method="get">
+			<?php $bp_groups_list_table->search_box( __( 'Search all Groups', 'buddypress' ), 'bp-groups' ); ?>
+			<input type="hidden" name="page" value="<?php echo esc_attr( $plugin_page ); ?>" />
+			<?php $bp_groups_list_table->display(); ?>
+		</form>
+
+	</div>
+
+<?php
+}
+
+/**
+ * Markup for the single group's Settings metabox.
+ *
+ * @since 1.7.0
+ *
+ * @param object $item Information about the current group.
+ */
+function bp_groups_admin_edit_metabox_settings( $item ) {
+
+	$invite_status = groups_get_groupmeta( $item->id, 'invite_status' ); ?>
+
+	<?php if ( bp_is_active( 'forums' ) ) : ?>
+		<div class="bp-groups-settings-section" id="bp-groups-settings-section-forum">
+			<label for="group-show-forum"><input type="checkbox" name="group-show-forum" id="group-show-forum" <?php checked( $item->enable_forum ) ?> /> <?php _e( 'Enable discussion forum', 'buddypress' ) ?><br />
+		</div>
+	<?php endif; ?>
+
+	<div class="bp-groups-settings-section" id="bp-groups-settings-section-status">
+		<fieldset>
+			<legend><?php _e( 'Privacy', 'buddypress' ); ?></legend>
+
+			<ul>
+				<li><input type="radio" name="group-status" id="bp-group-status-public" value="public" <?php checked( $item->status, 'public' ) ?> /><label for="bp-group-status-public"><?php _e( 'Public', 'buddypress' ) ?></label></li>
+				<li><input type="radio" name="group-status" id="bp-group-status-private" value="private" <?php checked( $item->status, 'private' ) ?> /><label for="bp-group-status-private"><?php _e( 'Private', 'buddypress' ) ?></label></li>
+				<li><input type="radio" name="group-status" id="bp-group-status-hidden" value="hidden" <?php checked( $item->status, 'hidden' ) ?> /><label for="bp-group-status-hidden"><?php _e( 'Hidden', 'buddypress' ) ?></label></li>
+			</ul>
+		</fieldset>
+	</div>
+
+	<div class="bp-groups-settings-section" id="bp-groups-settings-section-invite-status">
+		<fieldset>
+			<legend><?php _e( 'Who can invite others to this group?', 'buddypress' ); ?></legend>
+
+			<ul>
+				<li><input type="radio" name="group-invite-status" id="bp-group-invite-status-members" value="members" <?php checked( $invite_status, 'members' ) ?> /><label for="bp-group-invite-status-members"><?php _e( 'All group members', 'buddypress' ) ?></label></li>
+				<li><input type="radio" name="group-invite-status" id="bp-group-invite-status-mods" value="mods" <?php checked( $invite_status, 'mods' ) ?> /><label for="bp-group-invite-status-mods"><?php _e( 'Group admins and mods only', 'buddypress' ) ?></label></li>
+				<li><input type="radio" name="group-invite-status" id="bp-group-invite-status-admins" value="admins" <?php checked( $invite_status, 'admins' ) ?> /><label for="bp-group-invite-status-admins"><?php _e( 'Group admins only', 'buddypress' ) ?></label></li>
+			</ul>
+		</fieldset>
+	</div>
+
+<?php
+}
+
+/**
+ * Output the markup for a single group's Add New Members metabox.
+ *
+ * @since 1.7.0
+ */
+function bp_groups_admin_edit_metabox_add_new_members( $item ) {
+	?>
+
+	<label for="bp-groups-new-members" class="screen-reader-text"><?php _e( 'Add new members', 'buddypress' ); ?></label>
+	<input name="bp-groups-new-members" id="bp-groups-new-members" class="bp-suggest-user" placeholder="<?php esc_attr_e( 'Enter a comma-separated list of user logins.', 'buddypress' ) ?>" />
+	<ul id="bp-groups-new-members-list"></ul>
+	<?php
+}
+
+/**
+ * Renders the Members metabox on single group pages.
+ *
+ * @since 1.7.0
+ *
+ * @param BP_Groups_Group $item The BP_Groups_Group object for the current group.
+ */
+function bp_groups_admin_edit_metabox_members( $item ) {
+
+	// Pull up a list of group members, so we can separate out the types
+	// We'll also keep track of group members here to place them into a
+	// JavaScript variable, which will help with group member autocomplete
+	$members = array(
+		'admin'  => array(),
+		'mod'    => array(),
+		'member' => array(),
+		'banned' => array(),
+	);
+
+	$pagination = array(
+		'admin'  => array(),
+		'mod'    => array(),
+		'member' => array(),
+		'banned' => array(),
+	);
+
+	foreach ( $members as $type => &$member_type_users ) {
+		$page_qs_key       = $type . '_page';
+		$current_type_page = isset( $_GET[ $page_qs_key ] ) ? absint( $_GET[ $page_qs_key ] ) : 1;
+		$member_type_query = new BP_Group_Member_Query( array(
+			'group_id'   => $item->id,
+			'group_role' => array( $type ),
+			'type'       => 'alphabetical',
+			'per_page'   => 10,
+			'page'       => $current_type_page,
+		) );
+
+		$member_type_users   = $member_type_query->results;
+		$pagination[ $type ] = bp_groups_admin_create_pagination_links( $member_type_query, $type );
+	}
+
+	// Echo out the JavaScript variable
+	echo '<script type="text/javascript">var group_id = "' . esc_js( $item->id ) . '";</script>';
+
+	// Loop through each member type
+	foreach ( $members as $member_type => $type_users ) : ?>
+
+		<div class="bp-groups-member-type" id="bp-groups-member-type-<?php echo esc_attr( $member_type ) ?>">
+
+			<h4><?php switch ( $member_type ) :
+					case 'admin'  : esc_html_e( 'Administrators', 'buddypress' ); break;
+					case 'mod'    : esc_html_e( 'Moderators',     'buddypress' ); break;
+					case 'member' : esc_html_e( 'Members',        'buddypress' ); break;
+					case 'banned' : esc_html_e( 'Banned Members', 'buddypress' ); break;
+			endswitch; ?></h4>
+
+			<div class="bp-group-admin-pagination table-top">
+				<?php echo $pagination[ $member_type ] ?>
+			</div>
+
+		<?php if ( !empty( $type_users ) ) : ?>
+
+			<table class="widefat bp-group-members">
+				<thead>
+					<tr>
+						<th scope="col" class="uid-column"><?php _ex( 'ID', 'Group member user_id in group admin', 'buddypress' ); ?></th>
+						<th scope="col" class="uname-column"><?php _ex( 'Name', 'Group member name in group admin', 'buddypress' ); ?></th>
+						<th scope="col" class="urole-column"><?php _ex( 'Group Role', 'Group member role in group admin', 'buddypress' ); ?></th>
+					</tr>
+				</thead>
+
+				<tbody>
+
+				<?php foreach ( $type_users as $type_user ) : ?>
+					<tr>
+						<th scope="row" class="uid-column"><?php echo esc_html( $type_user->ID ); ?></th>
+
+						<td class="uname-column">
+							<a style="float: left;" href="<?php echo bp_core_get_user_domain( $type_user->ID ); ?>"><?php echo bp_core_fetch_avatar( array(
+								'item_id' => $type_user->ID,
+								'width'   => '32',
+								'height'  => '32'
+							) ); ?></a>
+
+							<span style="margin: 8px; float: left;"><?php echo bp_core_get_userlink( $type_user->ID ); ?></span>
+						</td>
+
+						<td class="urole-column">
+							<label for="bp-groups-role-<?php echo esc_attr( $type_user->ID ); ?>" class="screen-reader-text"><?php _e( 'Select group role for member', 'buddypress' ); ?></label>
+							<select class="bp-groups-role" id="bp-groups-role-<?php echo esc_attr( $type_user->ID ); ?>" name="bp-groups-role[<?php echo esc_attr( $type_user->ID ); ?>]">
+								<optgroup label="<?php esc_attr_e( 'Roles', 'buddypress' ); ?>">
+									<option class="admin"  value="admin"  <?php selected( 'admin',  $member_type ); ?>><?php esc_html_e( 'Administrator', 'buddypress' ); ?></option>
+									<option class="mod"    value="mod"    <?php selected( 'mod',    $member_type ); ?>><?php esc_html_e( 'Moderator',     'buddypress' ); ?></option>
+									<option class="member" value="member" <?php selected( 'member', $member_type ); ?>><?php esc_html_e( 'Member',        'buddypress' ); ?></option>
+									<?php if ( 'banned' === $member_type ) : ?>
+									<option class="banned" value="banned" <?php selected( 'banned', $member_type ); ?>><?php esc_html_e( 'Banned',        'buddypress' ); ?></option>
+									<?php endif; ?>
+								</optgroup>
+								<optgroup label="<?php esc_attr_e( 'Actions', 'buddypress' ); ?>">
+									<option class="remove" value="remove"><?php esc_html_e( 'Remove', 'buddypress' ); ?></option>
+									<?php if ( 'banned' !== $member_type ) : ?>
+										<option class="banned" value="banned"><?php esc_html_e( 'Ban', 'buddypress' ); ?></option>
+									<?php endif; ?>
+								</optgroup>
+							</select>
+
+							<?php
+							/**
+							 * Store the current role for this user,
+							 * so we can easily detect changes.
+							 *
+							 * @todo remove this, and do database detection on save
+							 */
+							?>
+							<input type="hidden" name="bp-groups-existing-role[<?php echo esc_attr( $type_user->ID ); ?>]" value="<?php echo esc_attr( $member_type ); ?>" />
+						</td>
+					</tr>
+
+					<?php if ( has_filter( 'bp_groups_admin_manage_member_row' ) ) : ?>
+						<tr>
+							<td colspan="3">
+								<?php
+
+								/**
+								 * Fires after the listing of a single row for members in a group on the group edit screen.
+								 *
+								 * @since 1.8.0
+								 *
+								 * @param int             $ID   ID of the user being rendered.
+								 * @param BP_Groups_Group $item Object for the current group.
+								 */
+								do_action( 'bp_groups_admin_manage_member_row', $type_user->ID, $item ); ?>
+							</td>
+						</tr>
+					<?php endif; ?>
+
+				<?php endforeach; ?>
+
+				</tbody>
+			</table>
+
+			<div class="bp-group-admin-pagination table-bottom">
+				<?php echo $pagination[ $member_type ]; ?>
+			</div>
+
+		<?php else : ?>
+
+			<p class="bp-groups-no-members description"><?php esc_html_e( 'No members of this type', 'buddypress' ); ?></p>
+
+		<?php endif; ?>
+
+		</div><!-- .bp-groups-member-type -->
+
+	<?php endforeach;
+}
+
+/**
+ * Renders the Status metabox for the Groups admin edit screen.
+ *
+ * @since 1.7.0
+ *
+ * @param object $item Information about the currently displayed group.
+ */
+function bp_groups_admin_edit_metabox_status( $item ) {
+	$base_url = add_query_arg( array(
+		'page' => 'bp-groups',
+		'gid'  => $item->id
+	), bp_get_admin_url( 'admin.php' ) ); ?>
+
+	<div id="submitcomment" class="submitbox">
+		<div id="major-publishing-actions">
+			<div id="delete-action">
+				<a class="submitdelete deletion" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'action', 'delete', $base_url ), 'bp-groups-delete' ) ); ?>"><?php _e( 'Delete Group', 'buddypress' ) ?></a>
+			</div>
+
+			<div id="publishing-action">
+				<?php submit_button( __( 'Save Changes', 'buddypress' ), 'primary', 'save', false ); ?>
+			</div>
+			<div class="clear"></div>
+		</div><!-- #major-publishing-actions -->
+	</div><!-- #submitcomment -->
+
+<?php
+}
+
+/**
+ * Create pagination links out of a BP_Group_Member_Query.
+ *
+ * This function is intended to create pagination links for use under the
+ * Manage Members section of the Groups Admin Dashboard pages. It is a stopgap
+ * measure until a more general pagination solution is in place for BuddyPress.
+ * Plugin authors should not use this function, as it is likely to be
+ * deprecated soon.
+ *
+ * @since 1.8.0
+ *
+ * @param BP_Group_Member_Query $query       A BP_Group_Member_Query object.
+ * @param string                $member_type member|mod|admin|banned.
+ *
+ * @return string Pagination links HTML.
+ */
+function bp_groups_admin_create_pagination_links( BP_Group_Member_Query $query, $member_type ) {
+	$pagination = '';
+
+	if ( ! in_array( $member_type, array( 'admin', 'mod', 'member', 'banned' ) ) ) {
+		return $pagination;
+	}
+
+	// The key used to paginate this member type in the $_GET global
+	$qs_key = $member_type . '_page';
+	$url_base = remove_query_arg( array( $qs_key, 'updated', 'success_modified' ), $_SERVER['REQUEST_URI'] );
+
+	$page     = isset( $_GET[ $qs_key ] ) ? absint( $_GET[ $qs_key ] ) : 1;
+	$per_page = 10; // @todo Make this customizable?
+
+	// Don't show anything if there's no pagination
+	if ( 1 === $page && $query->total_users <= $per_page ) {
+		return $pagination;
+	}
+
+	$current_page_start = ( ( $page - 1 ) * $per_page ) + 1;
+	$current_page_end   = $page * $per_page > intval( $query->total_users ) ? $query->total_users : $page * $per_page;
+
+	$pag_links = paginate_links( array(
+		'base'      => add_query_arg( $qs_key, '%#%', $url_base ),
+		'format'    => '',
+		'prev_text' => __( '&laquo;', 'buddypress' ),
+		'next_text' => __( '&raquo;', 'buddypress' ),
+		'total'     => ceil( $query->total_users / $per_page ),
+		'current'   => $page,
+	) );
+
+	if ( 1 == $query->total_users ) {
+		$viewing_text = __( 'Viewing 1 member', 'buddypress' );
+	} else {
+		$viewing_text = sprintf(
+			_n( 'Viewing %1$s - %2$s of %3$s member', 'Viewing %1$s - %2$s of %3$s members', $query->total_users, 'buddypress' ),
+			bp_core_number_format( $current_page_start ),
+			bp_core_number_format( $current_page_end ),
+			bp_core_number_format( $query->total_users )
+		);
+	}
+
+	$pagination .= '<span class="bp-group-admin-pagination-viewing">' . $viewing_text . '</span>';
+	$pagination .= '<span class="bp-group-admin-pagination-links">' . $pag_links . '</span>';
+
+	return $pagination;
+}
+
+/**
+ * Get a set of usernames corresponding to a set of user IDs.
+ *
+ * @since 1.7.0
+ *
+ * @param array $user_ids Array of user IDs.
+ *
+ * @return array Array of user_logins corresponding to $user_ids.
+ */
+function bp_groups_admin_get_usernames_from_ids( $user_ids = array() ) {
+
+	$usernames = array();
+	$users     = new WP_User_Query( array( 'blog_id' => 0, 'include' => $user_ids ) );
+
+	foreach ( (array) $users->results as $user ) {
+		$usernames[] = $user->user_login;
+	}
+
+	return $usernames;
+}
+
+/**
+ * AJAX handler for group member autocomplete requests.
+ *
+ * @since 1.7.0
+ */
+function bp_groups_admin_autocomplete_handler() {
+
+	// Bail if user user shouldn't be here, or is a large network
+	if ( ! current_user_can( 'bp_moderate' ) || ( is_multisite() && wp_is_large_network( 'users' ) ) ) {
+		wp_die( -1 );
+	}
+
+	$term     = isset( $_GET['term'] )     ? sanitize_text_field( $_GET['term'] ) : '';
+	$group_id = isset( $_GET['group_id'] ) ? absint( $_GET['group_id'] )          : 0;
+
+	if ( ! $term || ! $group_id ) {
+		wp_die( -1 );
+	}
+
+	$suggestions = bp_core_get_suggestions( array(
+		'group_id' => -$group_id,  // A negative value will exclude this group's members from the suggestions.
+		'limit'    => 10,
+		'term'     => $term,
+		'type'     => 'members',
+	) );
+
+	$matches = array();
+
+	if ( $suggestions && ! is_wp_error( $suggestions ) ) {
+		foreach ( $suggestions as $user ) {
+
+			$matches[] = array(
+				// translators: 1: user_login, 2: user_email
+				'label' => sprintf( __( '%1$s (%2$s)', 'buddypress' ), $user->name, $user->ID ),
+				'value' => $user->ID,
+			);
+		}
+	}
+
+	wp_die( json_encode( $matches ) );
+}
+add_action( 'wp_ajax_bp_group_admin_member_autocomplete', 'bp_groups_admin_autocomplete_handler' );
+
+/**
+ * List table class for the Groups component admin page.
+ *
+ * @since 1.7.0
+ */
+class BP_Groups_List_Table extends WP_List_Table {
+
+	/**
+	 * The type of view currently being displayed.
+	 *
+	 * e.g. "All", "Pending", "Approved", "Spam"...
+	 *
+	 * @since 1.7.0
+	 *
+	 * @var string
+	 */
+	public $view = 'all';
+
+	/**
+	 * Group counts for each group type.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @var int
+	 */
+	public $group_counts = 0;
+
+	/**
+	 * Multidimensional array of group visibility types and their groups.
+	 *
+	 * @link https://buddypress.trac.wordpress.org/ticket/6277
+	 * @var array
+	 */
+	public $group_type_ids = array();
+
+	/**
+	 * Constructor
+	 *
+	 * @since 1.7.0
+	 */
+	public function __construct() {
+
+		// Define singular and plural labels, as well as whether we support AJAX.
+		parent::__construct( array(
+			'ajax'     => false,
+			'plural'   => 'groups',
+			'singular' => 'group',
+		) );
+	}
+
+	/**
+	 * Set up items for display in the list table.
+	 *
+	 * Handles filtering of data, sorting, pagination, and any other data
+	 * manipulation required prior to rendering.
+	 *
+	 * @since 1.7.0
+	 */
+	public function prepare_items() {
+		global $groups_template;
+
+		$screen = get_current_screen();
+
+		// Option defaults
+		$include_id   = false;
+		$search_terms = false;
+
+		// Set current page
+		$page = $this->get_pagenum();
+
+		// Set per page from the screen options
+		$per_page = $this->get_items_per_page( str_replace( '-', '_', "{$screen->id}_per_page" ) );
+
+		// Sort order.
+		$order = 'DESC';
+		if ( !empty( $_REQUEST['order'] ) ) {
+			$order = ( 'desc' == strtolower( $_REQUEST['order'] ) ) ? 'DESC' : 'ASC';
+		}
+
+		// Order by - default to newest
+		$orderby = 'last_activity';
+		if ( ! empty( $_REQUEST['orderby'] ) ) {
+			switch ( $_REQUEST['orderby'] ) {
+				case 'name' :
+					$orderby = 'name';
+					break;
+				case 'id' :
+					$orderby = 'date_created';
+					break;
+				case 'members' :
+					$orderby = 'total_member_count';
+					break;
+				case 'last_active' :
+					$orderby = 'last_activity';
+					break;
+			}
+		}
+
+		// Are we doing a search?
+		if ( !empty( $_REQUEST['s'] ) )
+			$search_terms = $_REQUEST['s'];
+
+		// Check if user has clicked on a specific group (if so, fetch only that group).
+		if ( !empty( $_REQUEST['gid'] ) )
+			$include_id = (int) $_REQUEST['gid'];
+
+		// Set the current view
+		if ( isset( $_GET['group_status'] ) && in_array( $_GET['group_status'], array( 'public', 'private', 'hidden' ) ) ) {
+			$this->view = $_GET['group_status'];
+		}
+
+		// We'll use the ids of group types for the 'include' param
+		$this->group_type_ids = BP_Groups_Group::get_group_type_ids();
+
+		// Pass a dummy array if there are no groups of this type
+		$include = false;
+		if ( 'all' != $this->view && isset( $this->group_type_ids[ $this->view ] ) ) {
+			$include = ! empty( $this->group_type_ids[ $this->view ] ) ? $this->group_type_ids[ $this->view ] : array( 0 );
+		}
+
+		// Get group type counts for display in the filter tabs
+		$this->group_counts = array();
+		foreach ( $this->group_type_ids as $group_type => $group_ids ) {
+			$this->group_counts[ $group_type ] = count( $group_ids );
+		}
+
+		// If we're viewing a specific group, flatten all activities into a single array.
+		if ( $include_id ) {
+			$groups = array( (array) groups_get_group( 'group_id=' . $include_id ) );
+		} else {
+			$groups_args = array(
+				'include'  => $include,
+				'per_page' => $per_page,
+				'page'     => $page,
+				'orderby'  => $orderby,
+				'order'    => $order
+			);
+
+			$groups = array();
+			if ( bp_has_groups( $groups_args ) ) {
+				while ( bp_groups() ) {
+					bp_the_group();
+					$groups[] = (array) $groups_template->group;
+				}
+			}
+		}
+
+		// Set raw data to display
+		$this->items = $groups;
+
+		// Store information needed for handling table pagination
+		$this->set_pagination_args( array(
+			'per_page'    => $per_page,
+			'total_items' => $groups_template->total_group_count,
+			'total_pages' => ceil( $groups_template->total_group_count / $per_page )
+		) );
+	}
+
+	/**
+	 * Get an array of all the columns on the page.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @return array Array of column headers.
+	 */
+	public function get_column_info() {
+		$this->_column_headers = array(
+			$this->get_columns(),
+			array(),
+			$this->get_sortable_columns(),
+			$this->get_default_primary_column_name(),
+		);
+
+		return $this->_column_headers;
+	}
+
+	/**
+	 * Get name of default primary column
+	 *
+	 * @since 2.3.3
+	 *
+	 * @return string
+	 */
+	protected function get_default_primary_column_name() {
+		// comment column is mapped to Group's name
+		return 'comment';
+	}
+
+	/**
+	 * Display a message on screen when no items are found ("No groups found").
+	 *
+	 * @since 1.7.0
+	 */
+	public function no_items() {
+		_e( 'No groups found.', 'buddypress' );
+	}
+
+	/**
+	 * Output the Groups data table.
+	 *
+	 * @since 1.7.0
+	*/
+	public function display() {
+		$this->display_tablenav( 'top' ); ?>
+
+		<table class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>" cellspacing="0">
+			<thead>
+				<tr>
+					<?php $this->print_column_headers(); ?>
+				</tr>
+			</thead>
+
+			<tfoot>
+				<tr>
+					<?php $this->print_column_headers( false ); ?>
+				</tr>
+			</tfoot>
+
+			<tbody id="the-comment-list">
+				<?php $this->display_rows_or_placeholder(); ?>
+			</tbody>
+		</table>
+		<?php
+
+		$this->display_tablenav( 'bottom' );
+	}
+
+	/**
+	 * Generate content for a single row of the table.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param object|array $item The current group item in the loop.
+	 */
+	public function single_row( $item = array() ) {
+		static $even = false;
+
+		$row_classes = array();
+
+		if ( $even ) {
+			$row_classes = array( 'even' );
+		} else {
+			$row_classes = array( 'alternate', 'odd' );
+		}
+
+		/**
+		 * Filters the classes applied to a single row in the groups list table.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param array  $row_classes Array of classes to apply to the row.
+		 * @param string $value       ID of the current group being displayed.
+		 */
+		$row_classes = apply_filters( 'bp_groups_admin_row_class', $row_classes, $item['id'] );
+		$row_class = ' class="' . implode( ' ', $row_classes ) . '"';
+
+		echo '<tr' . $row_class . ' id="group-' . esc_attr( $item['id'] ) . '" data-parent_id="' . esc_attr( $item['id'] ) . '" data-root_id="' . esc_attr( $item['id'] ) . '">';
+		echo $this->single_row_columns( $item );
+		echo '</tr>';
+
+		$even = ! $even;
+	}
+
+	/**
+	 * Get the list of views available on this table (e.g. "all", "public").
+	 *
+	 * @since 1.7.0
+	 */
+	public function get_views() {
+		$url_base = bp_get_admin_url( 'admin.php?page=bp-groups' ); ?>
+
+		<ul class="subsubsub">
+			<li class="all"><a href="<?php echo esc_url( $url_base ); ?>" class="<?php if ( 'all' == $this->view ) echo 'current'; ?>"><?php _e( 'All', 'buddypress' ); ?></a> |</li>
+			<li class="public"><a href="<?php echo esc_url( add_query_arg( 'group_status', 'public', $url_base ) ); ?>" class="<?php if ( 'public' == $this->view ) echo 'current'; ?>"><?php printf( _n( 'Public <span class="count">(%s)</span>', 'Public <span class="count">(%s)</span>', $this->group_counts['public'], 'buddypress' ), number_format_i18n( $this->group_counts['public'] ) ); ?></a> |</li>
+			<li class="private"><a href="<?php echo esc_url( add_query_arg( 'group_status', 'private', $url_base ) ); ?>" class="<?php if ( 'private' == $this->view ) echo 'current'; ?>"><?php printf( _n( 'Private <span class="count">(%s)</span>', 'Private <span class="count">(%s)</span>', $this->group_counts['private'], 'buddypress' ), number_format_i18n( $this->group_counts['private'] ) ); ?></a> |</li>
+			<li class="hidden"><a href="<?php echo esc_url( add_query_arg( 'group_status', 'hidden', $url_base ) ); ?>" class="<?php if ( 'hidden' == $this->view ) echo 'current'; ?>"><?php printf( _n( 'Hidden <span class="count">(%s)</span>', 'Hidden <span class="count">(%s)</span>', $this->group_counts['hidden'], 'buddypress' ), number_format_i18n( $this->group_counts['hidden'] ) ); ?></a></li>
+
+			<?php
+
+			/**
+			 * Fires inside listing of views so plugins can add their own.
+			 *
+			 * @since 1.7.0
+			 *
+			 * @param string $url_base Current URL base for view.
+			 * @param string $view     Current view being displayed.
+			 */
+			do_action( 'bp_groups_list_table_get_views', $url_base, $this->view ); ?>
+		</ul>
+	<?php
+	}
+
+	/**
+	 * Get bulk actions for single group row.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @return array Key/value pairs for the bulk actions dropdown.
+	 */
+	public function get_bulk_actions() {
+
+		/**
+		 * Filters the list of bulk actions to display on a single group row.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param array $value Array of bulk actions to display.
+		 */
+		return apply_filters( 'bp_groups_list_table_get_bulk_actions', array(
+			'delete' => __( 'Delete', 'buddypress' )
+		) );
+	}
+
+	/**
+	 * Get the table column titles.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @see WP_List_Table::single_row_columns()
+	 *
+	 * @return array Array of column titles.
+	 */
+	public function get_columns() {
+
+		/**
+		 * Filters the titles for the columns for the groups list table.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $value Array of slugs and titles for the columns.
+		 */
+		return apply_filters( 'bp_groups_list_table_get_columns', array(
+			'cb'          => '<input name type="checkbox" />',
+			'comment'     => _x( 'Name', 'Groups admin Group Name column header',               'buddypress' ),
+			'description' => _x( 'Description', 'Groups admin Group Description column header', 'buddypress' ),
+			'status'      => _x( 'Status', 'Groups admin Privacy Status column header',         'buddypress' ),
+			'members'     => _x( '# Members', 'Groups admin Members column header',             'buddypress' ),
+			'last_active' => _x( 'Last Active', 'Groups admin Last Active column header',       'buddypress' )
+		) );
+	}
+
+	/**
+	 * Get the column names for sortable columns.
+	 *
+	 * Note: It's not documented in WP, but the second item in the
+	 * nested arrays below is $desc_first. Normally, we would set
+	 * last_active to be desc_first (since you're generally interested in
+	 * the *most* recently active group, not the *least*). But because
+	 * the default sort for the Groups admin screen is DESC by last_active,
+	 * we want the first click on the Last Active column header to switch
+	 * the sort order - ie, to make it ASC. Thus last_active is set to
+	 * $desc_first = false.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @return array Array of sortable column names.
+	 */
+	public function get_sortable_columns() {
+		return array(
+			'gid'         => array( 'gid', false ),
+			'comment'     => array( 'name', false ),
+			'members'     => array( 'members', false ),
+			'last_active' => array( 'last_active', false ),
+		);
+	}
+
+	/**
+	 * Override WP_List_Table::row_actions().
+	 *
+	 * Basically a duplicate of the row_actions() method, but removes the
+	 * unnecessary <button> addition.
+	 *
+	 * @since 2.3.3
+	 * @since 2.3.4 Visibility set to public for compatibility with WP < 4.0.0.
+	 *
+	 * @param array $actions The list of actions
+	 * @param bool $always_visible Whether the actions should be always visible
+	 * @return string
+	 */
+	public function row_actions( $actions, $always_visible = false ) {
+		$action_count = count( $actions );
+		$i = 0;
+
+		if ( !$action_count )
+			return '';
+
+		$out = '<div class="' . ( $always_visible ? 'row-actions visible' : 'row-actions' ) . '">';
+		foreach ( $actions as $action => $link ) {
+			++$i;
+			( $i == $action_count ) ? $sep = '' : $sep = ' | ';
+			$out .= "<span class='$action'>$link$sep</span>";
+		}
+		$out .= '</div>';
+
+		return $out;
+	}
+
+	/**
+	 * Markup for the Checkbox column.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @see WP_List_Table::single_row_columns()
+	 *
+	 * @param array $item A singular item (one full row).
+	 */
+	public function column_cb( $item = array() ) {
+		printf( '<label class="screen-reader-text" for="gid-%1$d">' . __( 'Select group %1$d', 'buddypress' ) . '</label><input type="checkbox" name="gid[]" value="%1$d" id="gid-%1$d" />', $item['id'] );
+	}
+
+	/**
+	 * Markup for the Group ID column.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @see WP_List_Table::single_row_columns()
+	 *
+	 * @param array $item A singular item (one full row).
+	 */
+	public function column_gid( $item = array() ) {
+		echo '<strong>' . absint( $item['id'] ) . '</strong>';
+	}
+
+	/**
+	 * Name column, and "quick admin" rollover actions.
+	 *
+	 * Called "comment" in the CSS so we can re-use some WP core CSS.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @see WP_List_Table::single_row_columns()
+	 *
+	 * @param array $item A singular item (one full row).
+	 */
+	public function column_comment( $item = array() ) {
+
+		// Preorder items: Edit | Delete | View
+		$actions = array(
+			'edit'   => '',
+			'delete' => '',
+			'view'   => '',
+		);
+
+		// We need the group object for some BP functions
+		$item_obj = (object) $item;
+
+		// Build actions URLs
+		$base_url   = bp_get_admin_url( 'admin.php?page=bp-groups&amp;gid=' . $item['id'] );
+		$delete_url = wp_nonce_url( $base_url . "&amp;action=delete", 'bp-groups-delete' );
+		$edit_url   = $base_url . '&amp;action=edit';
+		$view_url   = bp_get_group_permalink( $item_obj );
+
+		/**
+		 * Filters the group name for a group's column content.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $value Name of the group being rendered.
+		 * @param array  $item  Array for the current group item.
+		 */
+		$group_name = apply_filters_ref_array( 'bp_get_group_name', array( $item['name'] ), $item );
+
+		// Rollover actions
+
+		// Edit
+		$actions['edit']   = sprintf( '<a href="%s">%s</a>', esc_url( $edit_url   ), __( 'Edit',   'buddypress' ) );
+
+		// Delete
+		$actions['delete'] = sprintf( '<a href="%s">%s</a>', esc_url( $delete_url ), __( 'Delete', 'buddypress' ) );
+
+		// Visit
+		$actions['view']   = sprintf( '<a href="%s">%s</a>', esc_url( $view_url   ), __( 'View',   'buddypress' ) );
+
+		/**
+		 * Filters the actions that will be shown for the column content.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param array $value Array of actions to be displayed for the column content.
+		 * @param array $item  The current group item in the loop.
+		 */
+		$actions = apply_filters( 'bp_groups_admin_comment_row_actions', array_filter( $actions ), $item );
+
+		// Get group name and avatar
+		$avatar = '';
+
+		if ( buddypress()->avatar->show_avatars ) {
+			$avatar  = bp_core_fetch_avatar( array(
+				'item_id'    => $item['id'],
+				'object'     => 'group',
+				'type'       => 'thumb',
+				'avatar_dir' => 'group-avatars',
+				'alt'        => sprintf( __( 'Group logo of %s', 'buddypress' ), $group_name ),
+				'width'      => '32',
+				'height'     => '32',
+				'title'      => $group_name
+			) );
+		}
+
+		$content = sprintf( '<strong><a href="%s">%s</a></strong>', esc_url( $edit_url ), $group_name );
+
+		echo $avatar . ' ' . $content . ' ' . $this->row_actions( $actions );
+	}
+
+	/**
+	 * Markup for the Description column.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $item Information about the current row.
+	 */
+	public function column_description( $item = array() ) {
+
+		/**
+		 * Filters the markup for the Description column.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Markup for the Description column.
+		 * @param array  $item  The current group item in the loop.
+		 */
+		echo apply_filters_ref_array( 'bp_get_group_description', array( $item['description'], $item ) );
+	}
+
+	/**
+	 * Markup for the Status column.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $item Information about the current row.
+	 */
+	public function column_status( $item = array() ) {
+		$status      = $item['status'];
+		$status_desc = '';
+
+		// @todo This should be abstracted out somewhere for the whole
+		// Groups component
+		switch ( $status ) {
+			case 'public' :
+				$status_desc = __( 'Public', 'buddypress' );
+				break;
+			case 'private' :
+				$status_desc = __( 'Private', 'buddypress' );
+				break;
+			case 'hidden' :
+				$status_desc = __( 'Hidden', 'buddypress' );
+				break;
+		}
+
+		/**
+		 * Filters the markup for the Status column.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $status_desc Markup for the Status column.
+		 * @parma array  $item        The current group item in the loop.
+		 */
+		echo apply_filters_ref_array( 'bp_groups_admin_get_group_status', array( $status_desc, $item ) );
+	}
+
+	/**
+	 * Markup for the Number of Members column.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $item Information about the current row.
+	 */
+	public function column_members( $item = array() ) {
+		$count = groups_get_groupmeta( $item['id'], 'total_member_count' );
+
+		/**
+		 * Filters the markup for the number of Members column.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param int   $count Markup for the number of Members column.
+		 * @parma array $item  The current group item in the loop.
+		 */
+		echo apply_filters_ref_array( 'bp_groups_admin_get_group_member_count', array( (int) $count, $item ) );
+	}
+
+	/**
+	 * Markup for the Last Active column.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $item Information about the current row.
+	 */
+	public function column_last_active( $item = array() ) {
+		$last_active = groups_get_groupmeta( $item['id'], 'last_activity' );
+
+		/**
+		 * Filters the markup for the Last Active column.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $last_active Markup for the Last Active column.
+		 * @parma array  $item        The current group item in the loop.
+		 */
+		echo apply_filters_ref_array( 'bp_groups_admin_get_group_last_active', array( $last_active, $item ) );
+	}
+
+	/**
+	 * Allow plugins to add their custom column.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array  $item        Information about the current row.
+	 * @param string $column_name The column name.
+	 *
+	 * @return string
+	 */
+	public function column_default( $item = array(), $column_name = '' ) {
+
+		/**
+		 * Filters a string to allow plugins to add custom column content.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $value       Empty string.
+		 * @param string $column_name Name of the column being rendered.
+		 * @param array  $item        The current group item in the loop.
+		 */
+		return apply_filters( 'bp_groups_admin_get_group_custom_column', '', $column_name, $item );
+	}
+}
Index: /tags/2.4.3/src/bp-groups/bp-groups-adminbar.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-adminbar.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-adminbar.php	(revision 10410)
@@ -0,0 +1,94 @@
+<?php
+/**
+ * BuddyPress Groups Toolbar.
+ *
+ * Handles the groups functions related to the WordPress Toolbar.
+ *
+ * @package BuddyPress
+ * @subpackage Groups
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Add the Group Admin top-level menu when viewing group pages.
+ *
+ * @since 1.5.0
+ *
+ * @todo Add dynamic menu items for group extensions.
+ *
+ * @return false|null False if not on a group page, or if user does not have
+ *                    access to group admin options.
+ */
+function bp_groups_group_admin_menu() {
+	global $wp_admin_bar;
+	$bp = buddypress();
+
+	// Only show if viewing a group
+	if ( ! bp_is_group() || bp_is_group_create() ) {
+		return false;
+	}
+
+	// Only show this menu to group admins and super admins
+	if ( ! bp_current_user_can( 'bp_moderate' ) && ! bp_group_is_admin() ) {
+		return false;
+	}
+
+	// Unique ID for the 'Edit Group' menu
+	$bp->group_admin_menu_id = 'group-admin';
+
+	// Add the top-level Group Admin button
+	$wp_admin_bar->add_menu( array(
+		'id'    => $bp->group_admin_menu_id,
+		'title' => __( 'Edit Group', 'buddypress' ),
+		'href'  => bp_get_group_permalink( $bp->groups->current_group )
+	) );
+
+	// Index of the Manage tabs parent slug
+	$nav_index = $bp->groups->current_group->slug . '_manage';
+
+	// Check if current group has Manage tabs
+	if ( empty( $bp->bp_options_nav[ $nav_index ] ) ) {
+		return;
+	}
+
+	// Build the Group Admin menus
+	foreach ( $bp->bp_options_nav[ $nav_index ] as $menu ) {
+		/**
+		 * Should we add the current manage link in the Group's "Edit" Admin Bar menu ?
+		 *
+		 * All core items will be added, plugins can use a new parameter in the BP Group Extension API
+		 * to also add the link to the "edit screen" of their group component. To do so, set the
+		 * the 'show_in_admin_bar' argument of your edit screen to true
+		 */
+		if ( $menu['show_in_admin_bar'] ) {
+			$title = sprintf( _x( 'Edit Group %s', 'Group WP Admin Bar manage links', 'buddypress' ), $menu['name'] );
+
+			// Title is specific for delete
+			if ( 'delete-group' == $menu['slug'] ) {
+				$title = sprintf( _x( '%s Group', 'Group WP Admin Bar delete link', 'buddypress' ), $menu['name'] );
+			}
+
+			$wp_admin_bar->add_menu( array(
+				'parent' => $bp->group_admin_menu_id,
+				'id'     => $menu['slug'],
+				'title'  => $title,
+				'href'   => bp_get_groups_action_link( 'admin/' . $menu['slug'] )
+			) );
+		}
+	}
+}
+add_action( 'admin_bar_menu', 'bp_groups_group_admin_menu', 99 );
+
+/**
+ * Remove rogue WP core Edit menu when viewing a single group.
+ *
+ * @since 1.6.0
+ */
+function bp_groups_remove_edit_page_menu() {
+	if ( bp_is_group() ) {
+		remove_action( 'admin_bar_menu', 'wp_admin_bar_edit_menu', 80 );
+	}
+}
+add_action( 'add_admin_bar_menus', 'bp_groups_remove_edit_page_menu' );
Index: /tags/2.4.3/src/bp-groups/bp-groups-cache.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-cache.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-cache.php	(revision 10410)
@@ -0,0 +1,227 @@
+<?php
+/**
+ * BuddyPress Groups Caching.
+ *
+ * Caching functions handle the clearing of cached objects and pages on specific
+ * actions throughout BuddyPress.
+ *
+ * @package BuddyPress
+ * @subpackage Groups
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Slurp up metadata for a set of groups.
+ *
+ * This function is called in two places in the BP_Groups_Group class:
+ *   - in the populate() method, when single group objects are populated
+ *   - in the get() method, when multiple groups are queried
+ *
+ * It grabs all groupmeta associated with all of the groups passed in
+ * $group_ids and adds it to WP cache. This improves efficiency when using
+ * groupmeta within a loop context.
+ *
+ *
+ * @param int|string|array|bool $group_ids Accepts a single group_id, or a
+ *                                         comma-separated list or array of
+ *                                         group ids.
+ */
+function bp_groups_update_meta_cache( $group_ids = false ) {
+	$bp = buddypress();
+
+	$cache_args = array(
+		'object_ids' 	   => $group_ids,
+		'object_type' 	   => $bp->groups->id,
+		'cache_group'      => 'group_meta',
+		'object_column'    => 'group_id',
+		'meta_table' 	   => $bp->groups->table_name_groupmeta,
+		'cache_key_prefix' => 'bp_groups_groupmeta'
+	);
+
+	bp_update_meta_cache( $cache_args );
+}
+
+/**
+ * Clear the cached group count.
+ *
+ * @param int $group_id Not used.
+ */
+function groups_clear_group_object_cache( $group_id ) {
+	wp_cache_delete( 'bp_total_group_count', 'bp' );
+}
+add_action( 'groups_group_deleted',              'groups_clear_group_object_cache' );
+add_action( 'groups_settings_updated',           'groups_clear_group_object_cache' );
+add_action( 'groups_details_updated',            'groups_clear_group_object_cache' );
+add_action( 'groups_group_avatar_updated',       'groups_clear_group_object_cache' );
+add_action( 'groups_create_group_step_complete', 'groups_clear_group_object_cache' );
+
+/**
+ * Bust group caches when editing or deleting.
+ *
+ * @since 1.7.0
+ *
+ * @param int $group_id The group being edited.
+ */
+function bp_groups_delete_group_cache( $group_id = 0 ) {
+	wp_cache_delete( $group_id, 'bp_groups' );
+}
+add_action( 'groups_delete_group',     'bp_groups_delete_group_cache' );
+add_action( 'groups_update_group',     'bp_groups_delete_group_cache' );
+add_action( 'groups_details_updated',  'bp_groups_delete_group_cache' );
+add_action( 'groups_settings_updated', 'bp_groups_delete_group_cache' );
+
+/**
+ * Bust group cache when modifying metadata.
+ *
+ * @since 2.0.0
+ *
+ * @param int $meta_id
+ * @param int $group_id
+ */
+function bp_groups_delete_group_cache_on_metadata_change( $meta_id, $group_id ) {
+	wp_cache_delete( $group_id, 'bp_groups' );
+}
+add_action( 'updated_group_meta', 'bp_groups_delete_group_cache_on_metadata_change', 10, 2 );
+add_action( 'added_group_meta', 'bp_groups_delete_group_cache_on_metadata_change', 10, 2 );
+
+/**
+ * Clear caches for the group creator when a group is created.
+ *
+ * @since 1.6.0
+ *
+ * @param int             $group_id  ID of the group.
+ * @param BP_Groups_Group $group_obj Group object.
+ */
+function bp_groups_clear_group_creator_cache( $group_id, $group_obj ) {
+	// Clears the 'total groups' for this user
+	groups_clear_group_user_object_cache( $group_obj->id, $group_obj->creator_id );
+}
+add_action( 'groups_created_group', 'bp_groups_clear_group_creator_cache', 10, 2 );
+
+/**
+ * Clears caches for all members in a group when a group is deleted.
+ *
+ * @since 1.6.0
+ *
+ * @param BP_Groups_Group $group_obj Group object.
+ * @param array           $user_ids  User IDs who were in this group.
+ */
+function bp_groups_clear_group_members_caches( $group_obj, $user_ids ) {
+	// Clears the 'total groups' cache for each member in a group
+	foreach ( (array) $user_ids as $user_id )
+		groups_clear_group_user_object_cache( $group_obj->id, $user_id );
+}
+add_action( 'bp_groups_delete_group', 'bp_groups_clear_group_members_caches', 10, 2 );
+
+/**
+ * Clear a user's cached total group invite count.
+ *
+ * Count is cleared when an invite is accepted, rejected or deleted.
+ *
+ * @since 2.0.0
+ *
+ * @param int $user_id The user ID.
+ */
+function bp_groups_clear_invite_count_for_user( $user_id ) {
+	wp_cache_delete( $user_id, 'bp_group_invite_count' );
+}
+add_action( 'groups_accept_invite', 'bp_groups_clear_invite_count_for_user' );
+add_action( 'groups_reject_invite', 'bp_groups_clear_invite_count_for_user' );
+add_action( 'groups_delete_invite', 'bp_groups_clear_invite_count_for_user' );
+
+/**
+ * Clear a user's cached total group invite count when a user is uninvited.
+ *
+ * Groan. Our API functions are not consistent.
+ *
+ * @since 2.0.0
+ *
+ * @param int $group_id The group ID. Not used in this function.
+ * @param int $user_id  The user ID.
+ */
+function bp_groups_clear_invite_count_on_uninvite( $group_id, $user_id ) {
+	bp_groups_clear_invite_count_for_user( $user_id );
+}
+add_action( 'groups_uninvite_user', 'bp_groups_clear_invite_count_on_uninvite', 10, 2 );
+
+/**
+ * Clear a user's cached total group invite count when a new invite is sent.
+ *
+ * @since 2.0.0
+ *
+ * @param int   $group_id      The group ID. Not used in this function.
+ * @param array $invited_users Array of invited user IDs.
+ */
+function bp_groups_clear_invite_count_on_send( $group_id, $invited_users ) {
+	foreach ( $invited_users as $user_id ) {
+		bp_groups_clear_invite_count_for_user( $user_id );
+	}
+}
+add_action( 'groups_send_invites', 'bp_groups_clear_invite_count_on_send', 10, 2 );
+
+/**
+ * Clear a user's cached group count.
+ *
+ * @param int $group_id The group ID. Not used in this function.
+ * @param int $user_id  The user ID.
+ */
+function groups_clear_group_user_object_cache( $group_id, $user_id ) {
+	wp_cache_delete( 'bp_total_groups_for_user_' . $user_id, 'bp' );
+}
+add_action( 'groups_join_group',    'groups_clear_group_user_object_cache', 10, 2 );
+add_action( 'groups_leave_group',   'groups_clear_group_user_object_cache', 10, 2 );
+add_action( 'groups_ban_member',    'groups_clear_group_user_object_cache', 10, 2 );
+add_action( 'groups_unban_member',  'groups_clear_group_user_object_cache', 10, 2 );
+add_action( 'groups_uninvite_user', 'groups_clear_group_user_object_cache', 10, 2 );
+add_action( 'groups_remove_member', 'groups_clear_group_user_object_cache', 10, 2 );
+
+/**
+ * Clear group administrator cache.
+ *
+ * @since 2.1.0
+ *
+ * @param int $group_id The group ID.
+ */
+function groups_clear_group_administrator_cache( $group_id ) {
+	wp_cache_delete( $group_id, 'bp_group_admins' );
+}
+add_action( 'groups_promote_member', 'groups_clear_group_administrator_cache' );
+add_action( 'groups_demote_member',  'groups_clear_group_administrator_cache' );
+add_action( 'groups_delete_group',   'groups_clear_group_administrator_cache' );
+
+/**
+ * Clear group administrator cache when a group member is saved.
+ *
+ * This accounts for situations where group administrators are added manually
+ * using {@link BP_Groups_Member::save()}.  Usually via a plugin.
+ *
+ * @since 2.1.0
+ *
+ * @param BP_Groups_Member $member
+ */
+function groups_clear_group_administrator_cache_on_member_save( BP_Groups_Member $member ) {
+	groups_clear_group_administrator_cache( $member->group_id );
+}
+add_action( 'groups_member_after_save', 'groups_clear_group_administrator_cache_on_member_save' );
+
+/* List actions to clear super cached pages on, if super cache is installed */
+add_action( 'groups_join_group',                 'bp_core_clear_cache' );
+add_action( 'groups_leave_group',                'bp_core_clear_cache' );
+add_action( 'groups_accept_invite',              'bp_core_clear_cache' );
+add_action( 'groups_reject_invite',              'bp_core_clear_cache' );
+add_action( 'groups_invite_user',                'bp_core_clear_cache' );
+add_action( 'groups_uninvite_user',              'bp_core_clear_cache' );
+add_action( 'groups_details_updated',            'bp_core_clear_cache' );
+add_action( 'groups_settings_updated',           'bp_core_clear_cache' );
+add_action( 'groups_unban_member',               'bp_core_clear_cache' );
+add_action( 'groups_ban_member',                 'bp_core_clear_cache' );
+add_action( 'groups_demote_member',              'bp_core_clear_cache' );
+add_action( 'groups_promote_member',             'bp_core_clear_cache' );
+add_action( 'groups_membership_rejected',        'bp_core_clear_cache' );
+add_action( 'groups_membership_accepted',        'bp_core_clear_cache' );
+add_action( 'groups_membership_requested',       'bp_core_clear_cache' );
+add_action( 'groups_create_group_step_complete', 'bp_core_clear_cache' );
+add_action( 'groups_created_group',              'bp_core_clear_cache' );
+add_action( 'groups_group_avatar_updated',       'bp_core_clear_cache' );
Index: /tags/2.4.3/src/bp-groups/bp-groups-classes.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-classes.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-classes.php	(revision 10410)
@@ -0,0 +1,16 @@
+<?php
+/**
+ * BuddyPress Groups Classes
+ *
+ * @package BuddyPress
+ * @subpackage GroupsClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+require dirname( __FILE__ ) . '/classes/class-bp-group-extension.php';
+require dirname( __FILE__ ) . '/classes/class-bp-group-member-query.php';
+require dirname( __FILE__ ) . '/classes/class-bp-groups-group.php';
+require dirname( __FILE__ ) . '/classes/class-bp-groups-member-suggestions.php';
+require dirname( __FILE__ ) . '/classes/class-bp-groups-member.php';
Index: /tags/2.4.3/src/bp-groups/bp-groups-filters.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-filters.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-filters.php	(revision 10410)
@@ -0,0 +1,278 @@
+<?php
+/**
+ * BuddyPress Groups Filters.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsFilters
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+// Filter bbPress template locations
+
+add_filter( 'bp_groups_get_directory_template', 'bp_add_template_locations' );
+add_filter( 'bp_get_single_group_template',    'bp_add_template_locations' );
+
+/* Apply WordPress defined filters */
+add_filter( 'bp_get_group_description',         'wptexturize' );
+add_filter( 'bp_get_group_description_excerpt', 'wptexturize' );
+add_filter( 'bp_get_group_name',                'wptexturize' );
+
+add_filter( 'bp_get_group_description',         'convert_smilies' );
+add_filter( 'bp_get_group_description_excerpt', 'convert_smilies' );
+
+add_filter( 'bp_get_group_description',         'convert_chars' );
+add_filter( 'bp_get_group_description_excerpt', 'convert_chars' );
+add_filter( 'bp_get_group_name',                'convert_chars' );
+
+add_filter( 'bp_get_group_description',         'wpautop' );
+add_filter( 'bp_get_group_description_excerpt', 'wpautop' );
+
+add_filter( 'bp_get_group_description',         'make_clickable', 9 );
+add_filter( 'bp_get_group_description_excerpt', 'make_clickable', 9 );
+
+add_filter( 'bp_get_group_name',                    'wp_filter_kses',        1 );
+add_filter( 'bp_get_group_permalink',               'wp_filter_kses',        1 );
+add_filter( 'bp_get_group_description',             'bp_groups_filter_kses', 1 );
+add_filter( 'bp_get_group_description_excerpt',     'wp_filter_kses',        1 );
+add_filter( 'groups_group_name_before_save',        'wp_filter_kses',        1 );
+add_filter( 'groups_group_description_before_save', 'wp_filter_kses',        1 );
+
+add_filter( 'bp_get_group_description',         'stripslashes' );
+add_filter( 'bp_get_group_description_excerpt', 'stripslashes' );
+add_filter( 'bp_get_group_name',                'stripslashes' );
+add_filter( 'bp_get_group_member_name',         'stripslashes' );
+add_filter( 'bp_get_group_member_link',         'stripslashes' );
+
+add_filter( 'groups_new_group_forum_desc', 'bp_create_excerpt' );
+
+add_filter( 'groups_group_name_before_save',        'force_balance_tags' );
+add_filter( 'groups_group_description_before_save', 'force_balance_tags' );
+
+// Trim trailing spaces from name and description when saving
+add_filter( 'groups_group_name_before_save',        'trim' );
+add_filter( 'groups_group_description_before_save', 'trim' );
+
+// Escape output of new group creation details
+add_filter( 'bp_get_new_group_id',          'esc_attr'     );
+add_filter( 'bp_get_new_group_name',        'esc_attr'     );
+add_filter( 'bp_get_new_group_description', 'esc_textarea' );
+
+// Format numberical output
+add_filter( 'bp_get_total_group_count',          'bp_core_number_format' );
+add_filter( 'bp_get_group_total_for_member',     'bp_core_number_format' );
+add_filter( 'bp_get_group_total_members',        'bp_core_number_format' );
+add_filter( 'bp_get_total_group_count_for_user', 'bp_core_number_format' );
+
+/**
+ * Filter output of Group Description through WordPress's KSES API.
+ *
+ * @since 1.1.0
+ *
+ * @param string $content
+ *
+ * @return string
+ */
+function bp_groups_filter_kses( $content = '' ) {
+
+	/**
+	 * Note that we don't immediately bail if $content is empty. This is because
+	 * WordPress's KSES API calls several other filters that might be relevant
+	 * to someone's workflow (like `pre_kses`)
+	 */
+
+	// Get allowed tags using core WordPress API allowing third party plugins
+	// to target the specific `buddypress-groups` context.
+	$allowed_tags = wp_kses_allowed_html( 'buddypress-groups' );
+
+	// Add our own tags allowed in group descriptions
+	$allowed_tags['a']['class']    = array();
+	$allowed_tags['img']           = array();
+	$allowed_tags['img']['src']    = array();
+	$allowed_tags['img']['alt']    = array();
+	$allowed_tags['img']['width']  = array();
+	$allowed_tags['img']['height'] = array();
+	$allowed_tags['img']['class']  = array();
+	$allowed_tags['img']['id']     = array();
+	$allowed_tags['code']          = array();
+
+	/**
+	 * Filters the HTML elements allowed for a given context.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $allowed_tags Allowed tags, attributes, and/or entities.
+	 */
+	$tags = apply_filters( 'bp_groups_filter_kses', $allowed_tags );
+
+	// Return KSES'ed content, allowing the above tags
+	return wp_kses( $content, $tags );
+}
+
+/** Legacy group forums (bbPress 1.x) *****************************************/
+
+/**
+ * Filter bbPress query SQL when on group pages or on forums directory.
+ */
+function groups_add_forum_privacy_sql() {
+	add_filter( 'get_topics_fields', 'groups_add_forum_fields_sql' );
+	add_filter( 'get_topics_join', 	 'groups_add_forum_tables_sql' );
+	add_filter( 'get_topics_where',  'groups_add_forum_where_sql'  );
+}
+add_filter( 'bbpress_init', 'groups_add_forum_privacy_sql' );
+
+/**
+ * Add fields to bbPress query for group-specific data.
+ *
+ * @param string $sql
+ *
+ * @return string
+ */
+function groups_add_forum_fields_sql( $sql = '' ) {
+	$sql = 't.*, g.id as object_id, g.name as object_name, g.slug as object_slug';
+	return $sql;
+}
+
+/**
+ * Add JOINed tables to bbPress query for group-specific data.
+ *
+ * @param string $sql
+ *
+ * @return string
+ */
+function groups_add_forum_tables_sql( $sql = '' ) {
+	$bp = buddypress();
+
+	$sql .= 'JOIN ' . $bp->groups->table_name . ' AS g LEFT JOIN ' . $bp->groups->table_name_groupmeta . ' AS gm ON g.id = gm.group_id ';
+
+	return $sql;
+}
+
+/**
+ * Add WHERE clauses to bbPress query for group-specific data and access protection.
+ *
+ * @param string $sql
+ *
+ * @return string
+ */
+function groups_add_forum_where_sql( $sql = '' ) {
+
+	// Define locale variable
+	$parts = array();
+
+	// Set this for groups
+	$parts['groups'] = "(gm.meta_key = 'forum_id' AND gm.meta_value = t.forum_id)";
+
+	// Restrict to public...
+	$parts['private'] = "g.status = 'public'";
+
+	/**
+	 * ...but do some checks to possibly remove public restriction.
+	 *
+	 * Decide if private are visible
+	 */
+	// Are we in our own profile?
+	if ( bp_is_my_profile() )
+		unset( $parts['private'] );
+
+	// Are we a super admin?
+	elseif ( bp_current_user_can( 'bp_moderate' ) )
+		unset( $parts['private'] );
+
+	// No need to filter on a single item
+	elseif ( bp_is_single_item() )
+		unset( $parts['private'] );
+
+	// Check the SQL filter that was passed
+	if ( !empty( $sql ) )
+		$parts['passed'] = $sql;
+
+	// Assemble Voltron
+	$parts_string = implode( ' AND ', $parts );
+
+	$bp = buddypress();
+
+	// Set it to the global filter
+	$bp->groups->filter_sql = $parts_string;
+
+	// Return the global filter
+	return $bp->groups->filter_sql;
+}
+
+/**
+ * Modify bbPress caps for bp-forums.
+ *
+ * @param bool $value
+ * @param string $cap
+ * @param array $args
+ *
+ * @return bool
+ */
+function groups_filter_bbpress_caps( $value, $cap, $args ) {
+
+	if ( bp_current_user_can( 'bp_moderate' ) )
+		return true;
+
+	if ( 'add_tag_to' === $cap ) {
+		$bp = buddypress();
+
+		if ( $bp->groups->current_group->user_has_access ) {
+			return true;
+		}
+	}
+
+	if ( 'manage_forums' == $cap && is_user_logged_in() )
+		return true;
+
+	return $value;
+}
+add_filter( 'bb_current_user_can', 'groups_filter_bbpress_caps', 10, 3 );
+
+/**
+ * Amends the forum directory's "last active" bbPress SQL query to stop it fetching information we aren't going to use.
+ *
+ * This speeds up the query.
+ *
+ * @since 1.5.0
+ *
+ * @see BB_Query::_filter_sql()
+ */
+function groups_filter_forums_root_page_sql( $sql ) {
+
+	/**
+	 * Filters the forum directory's "last active" bbPress SQL query.
+	 *
+	 * This filter is used to prevent fetching information that is not used.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $value SQL string to specify fetching just topic_id.
+	 */
+	return apply_filters( 'groups_filter_bbpress_root_page_sql', 't.topic_id' );
+}
+add_filter( 'get_latest_topics_fields', 'groups_filter_forums_root_page_sql' );
+
+/**
+ * Should BuddyPress load the mentions scripts and related assets, including results to prime the
+ * mentions suggestions?
+ *
+ * @since 2.2.0
+ *
+ * @param bool $load_mentions    True to load mentions assets, false otherwise.
+ * @param bool $mentions_enabled True if mentions are enabled.
+ *
+ * @return bool True if mentions scripts should be loaded.
+ */
+function bp_groups_maybe_load_mentions_scripts( $load_mentions, $mentions_enabled ) {
+	if ( ! $mentions_enabled ) {
+		return $load_mentions;
+	}
+
+	if ( $load_mentions || bp_is_group_activity() ) {
+		return true;
+	}
+
+	return $load_mentions;
+}
+add_filter( 'bp_activity_maybe_load_mentions_scripts', 'bp_groups_maybe_load_mentions_scripts', 10, 2 );
Index: /tags/2.4.3/src/bp-groups/bp-groups-forums.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-forums.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-forums.php	(revision 10410)
@@ -0,0 +1,662 @@
+<?php
+/**
+ * BuddyPress Groups Forums.
+ *
+ * Action functions are exactly the same as screen functions, however they do not
+ * have a template screen associated with them. Usually they will send the user
+ * back to the default screen after execution.
+ *
+ * Note that this file is only used for the retired version of bbPress (1.x) and
+ * will see minimal updates as of BuddyPress 1.9.0.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsForums
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Creates a new forum inside a specific BuddyPress group.
+ *
+ * Uses the bundled version of bbPress packaged with BuddyPress.
+ *
+ * @since 1.0.0
+ *
+ * @param int    $group_id   The group ID that the new forum should be attached to.
+ * @param string $group_name The group name.
+ * @param string $group_desc The group description.
+ */
+function groups_new_group_forum( $group_id = 0, $group_name = '', $group_desc = '' ) {
+
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	if ( empty( $group_name ) ) {
+		$group_name = bp_get_current_group_name();
+	}
+
+	if ( empty( $group_desc ) ) {
+		$group_desc = bp_get_current_group_description();
+	}
+
+	$forum_id = bp_forums_new_forum( array(
+		'forum_name' => $group_name,
+		'forum_desc' => $group_desc
+	) );
+
+	groups_update_groupmeta( $group_id, 'forum_id', $forum_id );
+
+	/**
+	 * Fires after the creation of a new forum inside a specific BuddyPress group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $forum_id ID of the newly created forum.
+	 * @param int $group_id ID of the associated group.
+	 */
+	do_action( 'groups_new_group_forum', $forum_id, $group_id );
+}
+
+/**
+ * Update group forum metadata (title, description, slug) when the group's details are edited.
+ *
+ * @since 1.1.0
+ *
+ * @param int $group_id Group id, passed from groups_details_updated.
+ *
+ * @return mixed
+ */
+function groups_update_group_forum( $group_id ) {
+
+	$group = groups_get_group( array( 'group_id' => $group_id ) );
+
+	/**
+	 * Bail in the following three situations:
+	 *  1. Forums are not enabled for this group
+	 *  2. The BP Forum component is not enabled
+	 *  3. The built-in bbPress forums are not correctly installed (usually means they've been
+	 *     uninstalled)
+	 */
+	if ( empty( $group->enable_forum ) || !bp_is_active( 'forums' ) || ( function_exists( 'bp_forums_is_installed_correctly' ) && !bp_forums_is_installed_correctly() ) ) {
+		return false;
+	}
+
+	/**
+	 * Filters the group forum metadata value argument.
+	 *
+	 * @since 1.2.5
+	 *
+	 * @param array $value Array of metadata values to update the group forum with.
+	 */
+	bp_forums_update_forum( apply_filters( 'groups_update_group_forum', array(
+		'forum_id'   => groups_get_groupmeta( $group_id, 'forum_id' ),
+		'forum_name' => $group->name,
+		'forum_desc' => $group->description,
+		'forum_slug' => $group->slug
+	) ) );
+}
+add_action( 'groups_details_updated', 'groups_update_group_forum' );
+
+/**
+ * Create a new group forum post.
+ *
+ * Uses the bundled version of bbPress packaged with BuddyPress.
+ *
+ * @since 1.0.0
+ *
+ * @param string $post_text The text for the forum post.
+ * @param int    $topic_id  The topic ID used so we can identify where the new
+ *                          forum post should reside.
+ * @param mixed  $page      The page number where the new forum post should reside.
+ *                          Default: false.
+ *
+ * @return mixed The new forum post ID on success. Boolean false on failure.
+ */
+function groups_new_group_forum_post( $post_text, $topic_id, $page = false ) {
+	if ( empty( $post_text ) ) {
+		return false;
+	}
+
+	/**
+	 * Filters the text for the forum post before save.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $post_text Text for the forum post.
+	 */
+	$post_text = apply_filters( 'group_forum_post_text_before_save',     $post_text );
+
+	/**
+	 * Filters the ID for the forum post before save.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param int $topic_id ID of the topic the post will be associated with.
+	 */
+	$topic_id  = apply_filters( 'group_forum_post_topic_id_before_save', $topic_id  );
+	$post_id   = bp_forums_insert_post( array(
+		'post_text' => $post_text,
+		'topic_id'  => $topic_id
+	) );
+
+	if ( empty( $post_id ) ) {
+		return false;
+	}
+
+	$topic            = bp_forums_get_topic_details( $topic_id );
+	$activity_action  = sprintf( __( '%1$s replied to the forum topic %2$s in the group %3$s', 'buddypress'), bp_core_get_userlink( bp_loggedin_user_id() ), '<a href="' . bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic->topic_slug .'/">' . esc_attr( $topic->topic_title ) . '</a>', '<a href="' . bp_get_group_permalink( groups_get_current_group() ) . '">' . esc_attr( bp_get_current_group_name() ) . '</a>' );
+	$activity_content = bp_create_excerpt( $post_text );
+	$primary_link     = bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic->topic_slug . '/';
+
+	if ( !empty( $page ) ) {
+		$primary_link .= "?topic_page=" . $page;
+	}
+
+	/**
+	 * Filters the new groups activity forum post action.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $activity_action Formatted action related to group activity posting.
+	 * @param int    $post_id         ID of the newly created group forum post.
+	 * @param string $post_text       The text for the forum post.
+	 * @param object $topic           Object holding current topic details. Passed by reference.
+	 */
+	$action = apply_filters_ref_array( 'groups_activity_new_forum_post_action',  array( $activity_action,  $post_id, $post_text, &$topic ) );
+
+	/**
+	 * Filters the new groups activity forum post content.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $activity_content Excerpt-length activity content to be posted.
+	 * @param int    $post_id          ID of the newly created group forum post.
+	 * @param string $post_text        The text for the forum post.
+	 * @param object $topic            Object holding current topic details. Passed by reference.
+	 */
+	$content = apply_filters_ref_array( 'groups_activity_new_forum_post_content', array( $activity_content, $post_id, $post_text, &$topic ) );
+
+	/**
+	 * Filters the new groups activity forum post primary link.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string $value URL to the newly posted forum post.
+	 */
+	$filtered_primary_link = apply_filters( 'groups_activity_new_forum_post_primary_link', "{$primary_link}#post-{$post_id}" );
+
+	groups_record_activity( array(
+		'action'            => $action,
+		'content'           => $content,
+		'primary_link'      => $filtered_primary_link,
+		'type'              => 'new_forum_post',
+		'item_id'           => bp_get_current_group_id(),
+		'secondary_item_id' => $post_id
+	) );
+
+	/**
+	 * Fires after the creation of a new group forum topic post.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $value   ID of the current group.
+	 * @param int $post_id ID of the new created forum topic post.
+	 */
+	do_action( 'groups_new_forum_topic_post', bp_get_current_group_id(), $post_id );
+
+	return $post_id;
+}
+
+/**
+ * Create a new group forum topic.
+ *
+ * Uses the bundled version of bbPress packaged with BuddyPress.
+ *
+ * @since 1.0.0
+ *
+ * @param string $topic_title The title for the forum topic.
+ * @param string $topic_text  The text for the forum topic.
+ * @param string $topic_tags  A comma-delimited string of topic tags.
+ * @param int    $forum_id    The forum ID this forum topic resides in.
+ *
+ * @return mixed The new topic object on success. Boolean false on failure.
+ */
+function groups_new_group_forum_topic( $topic_title, $topic_text, $topic_tags, $forum_id ) {
+	if ( empty( $topic_title ) || empty( $topic_text ) )
+		return false;
+
+	/**
+	 * Filters the new groups forum topic title before saving.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $topic_title The title for the forum topic.
+	 */
+	$topic_title = apply_filters( 'group_forum_topic_title_before_save',    $topic_title );
+
+	/**
+	 * Filters the new groups forum topic text before saving.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $topic_text The text for the forum topic.
+	 */
+	$topic_text  = apply_filters( 'group_forum_topic_text_before_save',     $topic_text  );
+
+	/**
+	 * Filters the new groups forum topic tags before saving.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $topic_tags A comma-delimited string of topic tags.
+	 */
+	$topic_tags  = apply_filters( 'group_forum_topic_tags_before_save',     $topic_tags  );
+
+	/**
+	 * Filters the forum ID this forum topic resides in.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $forum_id The forum ID this forum topic resides in.
+	 */
+	$forum_id    = apply_filters( 'group_forum_topic_forum_id_before_save', $forum_id    );
+	$topic_id    = bp_forums_new_topic( array(
+		'topic_title' => $topic_title,
+		'topic_text'  => $topic_text,
+		'topic_tags'  => $topic_tags,
+		'forum_id'    => $forum_id
+	) );
+
+	if ( empty( $topic_id ) ) {
+		return false;
+	}
+
+	$topic            = bp_forums_get_topic_details( $topic_id );
+	$activity_action  = sprintf( __( '%1$s started the forum topic %2$s in the group %3$s', 'buddypress'), bp_core_get_userlink( bp_loggedin_user_id() ), '<a href="' . bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic->topic_slug .'/">' . esc_attr( $topic->topic_title ) . '</a>', '<a href="' . bp_get_group_permalink( groups_get_current_group() ) . '">' . esc_attr( bp_get_current_group_name() ) . '</a>' );
+	$activity_content = bp_create_excerpt( $topic_text );
+
+	/**
+	 * Filters the new groups activity forum topic action.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $activity_action Formatted action related to forum topic.
+	 * @param string $topic_text      New topic text.
+	 * @param object $topic           Object holding current topic details. Passed by reference.
+	 */
+	$action = apply_filters_ref_array( 'groups_activity_new_forum_topic_action',  array( $activity_action,  $topic_text, &$topic ) );
+
+	/**
+	 * Filters the new groups activity forum topic content.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $activity_content Excerpt-length activity content to be posted.
+	 * @param string $topic_text       New topic text.
+	 * @param object $topic            Object holding current topic details. Passed by reference.
+	 */
+	$content = apply_filters_ref_array( 'groups_activity_new_forum_topic_content', array( $activity_content, $topic_text, &$topic ) );
+
+	/**
+	 * Filters the new groups activity forum topic primary link.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string $value Concatenated primary link.
+	 */
+	$primary_link = apply_filters( 'groups_activity_new_forum_topic_primary_link', bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic->topic_slug . '/' );
+
+	groups_record_activity( array(
+		'action'            => $action,
+		'content'           => $content,
+		'primary_link'      => $primary_link,
+		'type'              => 'new_forum_topic',
+		'item_id'           => bp_get_current_group_id(),
+		'secondary_item_id' => $topic->topic_id
+	) );
+
+	/**
+	 * Fires after the creation of a new group forum topic.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int    $value ID of the current group.
+	 * @param object $topic Object holding current topic details. Passed by reference.
+	 */
+	do_action_ref_array( 'groups_new_forum_topic', array( bp_get_current_group_id(), &$topic ) );
+
+	return $topic;
+}
+
+/**
+ * Update an existing group forum topic.
+ *
+ * Uses the bundled version of bbPress packaged with BuddyPress.
+ *
+ * @since 1.1.0
+ *
+ * @param int    $topic_id    The topic ID of the existing forum topic.
+ * @param string $topic_title The title for the forum topic.
+ * @param string $topic_text  The text for the forum topic.
+ * @param mixed  $topic_tags  A comma-delimited string of topic tags. Optional.
+ *
+ * @return mixed The topic object on success. Boolean false on failure.
+ */
+function groups_update_group_forum_topic( $topic_id, $topic_title, $topic_text, $topic_tags = false ) {
+	$bp = buddypress();
+
+	/** This filter is documented in bp-groups/bp-groups-forums.php */
+	$topic_title = apply_filters( 'group_forum_topic_title_before_save', $topic_title );
+
+	/** This filter is documented in bp-groups/bp-groups-forums.php */
+	$topic_text  = apply_filters( 'group_forum_topic_text_before_save',  $topic_text  );
+	$topic       = bp_forums_update_topic( array(
+		'topic_title' => $topic_title,
+		'topic_text' => $topic_text,
+		'topic_id' => $topic_id,
+		'topic_tags' => $topic_tags
+	) );
+
+	if ( empty( $topic ) ) {
+		return false;
+	}
+
+	// Get the corresponding activity item
+	if ( bp_is_active( 'activity' ) ) {
+		$id = bp_activity_get_activity_id( array(
+			'item_id'           => bp_get_current_group_id(),
+			'secondary_item_id' => $topic_id,
+			'component'         => $bp->groups->id,
+			'type'              => 'new_forum_topic'
+		) );
+	}
+
+	$activity_action  = sprintf( __( '%1$s edited the forum topic %2$s in the group %3$s', 'buddypress'), bp_core_get_userlink( $topic->topic_poster ), '<a href="' . bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic->topic_slug .'/">' . esc_attr( $topic->topic_title ) . '</a>', '<a href="' . bp_get_group_permalink( groups_get_current_group() ) . '">' . esc_attr( bp_get_current_group_name() ) . '</a>' );
+	$activity_content = bp_create_excerpt( $topic_text );
+
+	/** This filter is documented in bp-groups/bp-groups-forums.php */
+	$action = apply_filters_ref_array( 'groups_activity_new_forum_topic_action',  array( $activity_action,  $topic_text, &$topic ) );
+
+	/** This filter is documented in bp-groups/bp-groups-forums.php */
+	$content = apply_filters_ref_array( 'groups_activity_new_forum_topic_content', array( $activity_content, $topic_text, &$topic ) );
+
+	/** This filter is documented in bp-groups/bp-groups-forums.php */
+	$primary_link = apply_filters( 'groups_activity_new_forum_topic_primary_link', bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic->topic_slug . '/' );
+
+	groups_record_activity( array(
+		'id'                => $id,
+		'action'            => $action,
+		'content'           => $content,
+		'primary_link'      => $primary_link,
+		'type'              => 'new_forum_topic',
+		'item_id'           => (int) bp_get_current_group_id(),
+		'user_id'           => (int) $topic->topic_poster,
+		'secondary_item_id' => $topic->topic_id,
+		'recorded_time '    => $topic->topic_time
+	) );
+
+	/**
+	 * Fires after the update of a group forum topic.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param object $topic Object holding current topic being updated. Passed by reference.
+	 */
+	do_action_ref_array( 'groups_update_group_forum_topic', array( &$topic ) );
+
+	return $topic;
+}
+
+/**
+ * Update an existing group forum post.
+ *
+ * Uses the bundled version of bbPress packaged with BuddyPress.
+ *
+ * @since 1.1.0
+ *
+ * @param int    $post_id   The post ID of the existing forum post.
+ * @param string $post_text The text for the forum post.
+ * @param int    $topic_id  The topic ID of the existing forum topic.
+ * @param mixed  $page      The page number where the new forum post should reside. Optional.
+ *
+ * @return mixed The forum post ID on success. Boolean false on failure.
+ */
+function groups_update_group_forum_post( $post_id, $post_text, $topic_id, $page = false ) {
+	$bp = buddypress();
+
+	/** This filter is documented in bp-groups/bp-groups-forums.php */
+	$post_text = apply_filters( 'group_forum_post_text_before_save', $post_text );
+
+	/** This filter is documented in bp-groups/bp-groups-forums.php */
+	$topic_id  = apply_filters( 'group_forum_post_topic_id_before_save', $topic_id );
+	$post      = bp_forums_get_post( $post_id );
+	$post_id   = bp_forums_insert_post( array(
+		'post_id'   => $post_id,
+		'post_text' => $post_text,
+		'post_time' => $post->post_time,
+		'topic_id'  => $topic_id,
+		'poster_id' => $post->poster_id
+	) );
+
+	if ( empty( $post_id ) ) {
+		return false;
+	}
+
+	$topic            = bp_forums_get_topic_details( $topic_id );
+	$activity_action  = sprintf( __( '%1$s replied to the forum topic %2$s in the group %3$s', 'buddypress'), bp_core_get_userlink( $post->poster_id ), '<a href="' . bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic->topic_slug .'">' . esc_attr( $topic->topic_title ) . '</a>', '<a href="' . bp_get_group_permalink( groups_get_current_group() ) . '">' . esc_attr( bp_get_current_group_name() ) . '</a>' );
+	$activity_content = bp_create_excerpt( $post_text );
+	$primary_link     = bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic->topic_slug . '/';
+
+	if ( !empty( $page ) ) {
+		$primary_link .= "?topic_page=" . $page;
+	}
+
+	// Get the corresponding activity item
+	if ( bp_is_active( 'activity' ) ) {
+		$id = bp_activity_get_activity_id( array(
+			'user_id'           => $post->poster_id,
+			'component'         => $bp->groups->id,
+			'type'              => 'new_forum_post',
+			'item_id'           => bp_get_current_group_id(),
+			'secondary_item_id' => $post_id
+		 ) );
+	}
+
+	/** This filter is documented in bp-groups/bp-groups-forums.php */
+	$action = apply_filters_ref_array( 'groups_activity_new_forum_post_action',  array( $activity_action,  $post_text, &$topic, &$topic ) );
+
+	/** This filter is documented in bp-groups/bp-groups-forums.php */
+	$content = apply_filters_ref_array( 'groups_activity_new_forum_post_content', array( $activity_content, $post_text, &$topic, &$topic ) );
+
+	/** This filter is documented in bp-groups/bp-groups-forums.php */
+	$filtered_primary_link = apply_filters( 'groups_activity_new_forum_post_primary_link', $primary_link . "#post-" . $post_id );
+
+	groups_record_activity( array(
+		'id'                => $id,
+		'action'            => $action,
+		'content'           => $content,
+		'primary_link'      => $filtered_primary_link,
+		'type'              => 'new_forum_post',
+		'item_id'           => (int) bp_get_current_group_id(),
+		'user_id'           => (int) $post->poster_id,
+		'secondary_item_id' => $post_id,
+		'recorded_time'     => $post->post_time
+	) );
+
+	/**
+	 * Fires after the update of a group forum post.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param object $post  Object holding current post being updated.
+	 * @param object $topic Object holding current topic details. Passed by reference.
+	 */
+	do_action_ref_array( 'groups_update_group_forum_post', array( $post, &$topic ) );
+
+	return $post_id;
+}
+
+/**
+ * Delete a group forum topic and also any corresponding activity items.
+ *
+ * Uses the bundled version of bbPress packaged with BuddyPress.
+ *
+ * @since 1.1.0
+ *
+ * @param int $topic_id The ID of the topic to be deleted.
+ *
+ * @return bool True if the delete routine went through properly.
+ */
+function groups_delete_group_forum_topic( $topic_id ) {
+	$bp = buddypress();
+
+	// Before deleting the thread, get the post ids so that their activity items can be deleted
+	$posts  = bp_forums_get_topic_posts( array( 'topic_id' => $topic_id, 'per_page' => -1 ) );
+	$action = bp_forums_delete_topic( array( 'topic_id' => $topic_id ) );
+
+	if ( !empty( $action ) ) {
+
+		/**
+		 * Fires before the deletion of a group forum topic.
+		 *
+		 * @since 1.2.9
+		 *
+		 * @param int $topic_id ID of the topic to be deleted.
+		 */
+		do_action( 'groups_before_delete_group_forum_topic', $topic_id );
+
+		// Delete the corresponding activity stream items
+		if ( bp_is_active( 'activity' ) ) {
+
+			// The activity item for the initial topic
+			bp_activity_delete( array(
+				'item_id'           => bp_get_current_group_id(),
+				'secondary_item_id' => $topic_id,
+				'component'         => $bp->groups->id,
+				'type'              => 'new_forum_topic'
+			) );
+
+			// The activity item for each post
+			foreach ( (array) $posts as $post ) {
+				bp_activity_delete( array(
+					'item_id'           => bp_get_current_group_id(),
+					'secondary_item_id' => $post->post_id,
+					'component'         => $bp->groups->id,
+					'type'              => 'new_forum_post'
+				) );
+			}
+		}
+
+		/**
+		 * Fires after the deletion of a group forum topic.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int $topic_id ID of the topic that was deleted.
+		 */
+		do_action( 'groups_delete_group_forum_topic', $topic_id );
+	}
+
+	return (bool) $action;
+}
+
+/**
+ * Delete a group forum post and its corresponding activity item.
+ *
+ * Uses the bundled version of bbPress packaged with BuddyPress.
+ *
+ * @since 1.1.0
+ *
+ * @param int      $post_id  The ID of the post you want to delete.
+ * @param int|bool $topic_id Optional. The topic to which the post belongs. This
+ *                           value isn't used in the function but is passed along
+ *                           to do_action() hooks.
+ *
+ * @return bool True on success.
+ */
+function groups_delete_group_forum_post( $post_id, $topic_id = false ) {
+
+	$action = bp_forums_delete_post( array( 'post_id' => $post_id ) );
+
+	if ( !empty( $action ) ) {
+
+		/**
+		 * Fires before the deletion of a group forum post.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int $post_id  ID of the post to be deleted.
+		 * @param int $topic_id ID of the associated topic.
+		 */
+		do_action( 'groups_before_delete_group_forum_post', $post_id, $topic_id );
+
+		// Delete the corresponding activity stream item
+		if ( bp_is_active( 'activity' ) ) {
+			bp_activity_delete( array(
+				'item_id'           => bp_get_current_group_id(),
+				'secondary_item_id' => $post_id,
+				'component'         => buddypress()->groups->id,
+				'type'              => 'new_forum_post'
+			) );
+		}
+
+		/**
+		 * Fires after the deletion of a group forum post.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int $post_id  ID of the post that was deleted.
+		 * @param int $topic_id ID of the associated topic.
+		 */
+		do_action( 'groups_delete_group_forum_post', $post_id, $topic_id );
+	}
+
+	return (bool) $action;
+}
+
+/**
+ * Get a total count of all public topics of a given type, across groups/forums.
+ *
+ * @since 1.5.0
+ *
+ * @param string $type Either 'newest', 'popular', 'unreplied', 'tags'.
+ *                     Default: 'newest'.
+ *
+ * @return int The topic count.
+ */
+function groups_total_public_forum_topic_count( $type = 'newest' ) {
+
+	/**
+	 * Filters the total count of all public topics of a given type, across groups/forums.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param int $value Total count of all public topics.
+	 */
+	return apply_filters( 'groups_total_public_forum_topic_count', BP_Groups_Group::get_global_forum_topic_count( $type ) );
+}
+
+/**
+ * Get a total count of all topics of a given status, across groups/forums.
+ *
+ * @since 1.5.0
+ *
+ * @param string      $status       Which groups to count. 'public', 'private', 'hidden',
+ *                                  'all'. Default: 'public'.
+ * @param string|bool $search_terms Optional. Limit by a search term.
+ *
+ * @return int The topic count.
+ */
+function groups_total_forum_topic_count( $status = 'public', $search_terms = false ) {
+
+	/**
+	 * Filters the total count of all topics of a given status, across groups/forums.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $value Total count of all topics.
+	 */
+	return apply_filters( 'groups_total_forum_topic_count', BP_Groups_Group::get_global_topic_count( $status, $search_terms ) );
+}
Index: /tags/2.4.3/src/bp-groups/bp-groups-functions.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-functions.php	(revision 10410)
@@ -0,0 +1,1787 @@
+<?php
+/**
+ * BuddyPress Groups Functions.
+ *
+ * Functions are where all the magic happens in BuddyPress. They will
+ * handle the actual saving or manipulation of information. Usually they will
+ * hand off to a database class for data access, then return
+ * true or false on success or failure.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Check whether there is a Groups directory page in the $bp global.
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if set, False if empty.
+ */
+function bp_groups_has_directory() {
+	$bp = buddypress();
+
+	return (bool) !empty( $bp->pages->groups->id );
+}
+
+/**
+ * Fetch a single group object.
+ *
+ * When calling up a group object, you should always use this function instead
+ * of instantiating BP_Groups_Group directly, so that you will inherit cache
+ * support and pass through the groups_get_group filter.
+ *
+ * @param array|string $args {
+ *	Array of al arguments.
+ *	@type int  $group_id        ID of the group.
+ *	@type bool $load_users      No longer used.
+ *	@type bool $populate_extras Whether to fetch membership data and other
+ *	                            extra information about the group.
+ *                              Default: false.
+ * }
+ * @return BP_Groups_Group $group The group object.
+ */
+function groups_get_group( $args = '' ) {
+	$r = wp_parse_args( $args, array(
+		'group_id'          => false,
+		'load_users'        => false,
+		'populate_extras'   => false,
+	) );
+
+	$group_args = array(
+		'populate_extras' => $r['populate_extras'],
+	);
+
+	$group = new BP_Groups_Group( $r['group_id'], $group_args );
+
+	/**
+	 * Filters a single group object.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param BP_Groups_Group $group Single group object.
+	 */
+	return apply_filters( 'groups_get_group', $group );
+}
+
+/** Group Creation, Editing & Deletion ****************************************/
+
+/**
+ * Create a group.
+ *
+ * @since 1.0.0
+ *
+ * @param array|string $args {
+ *     An array of arguments.
+ *     @type int|bool $group_id     Pass a group ID to update an existing item, or
+ *                                  0 / false to create a new group. Default: 0.
+ *     @type int      $creator_id   The user ID that creates the group.
+ *     @type string   $name         The group name.
+ *     @type string   $description  Optional. The group's description.
+ *     @type string   $slug         The group slug.
+ *     @type string   $status       The group's status. Accepts 'public', 'private' or
+                                    'hidden'. Defaults to 'public'.
+ *     @type int      $enable_forum Optional. Whether the group has a forum enabled.
+ *                                  If the legacy forums are enabled for this group
+ *                                  or if a bbPress forum is enabled for the group,
+ *                                  set this to 1. Default: 0.
+ *     @type string   $date_created The GMT time, in Y-m-d h:i:s format, when the group
+ *                                  was created. Defaults to the current time.
+ * }
+ * @return int|bool The ID of the group on success. False on error.
+ */
+function groups_create_group( $args = '' ) {
+
+	$defaults = array(
+		'group_id'     => 0,
+		'creator_id'   => 0,
+		'name'         => '',
+		'description'  => '',
+		'slug'         => '',
+		'status'       => 'public',
+		'enable_forum' => 0,
+		'date_created' => bp_core_current_time()
+	);
+
+	$args = wp_parse_args( $args, $defaults );
+	extract( $args, EXTR_SKIP );
+
+	// Pass an existing group ID
+	if ( ! empty( $group_id ) ) {
+		$group = groups_get_group( array( 'group_id' => (int) $group_id ) );
+		$name  = ! empty( $name ) ? $name : $group->name;
+		$slug  = ! empty( $slug ) ? $slug : $group->slug;
+		$description = ! empty( $description ) ? $description : $group->description;
+
+		// Groups need at least a name
+		if ( empty( $name ) ) {
+			return false;
+		}
+
+	// Create a new group
+	} else {
+		// Instantiate new group object
+		$group = new BP_Groups_Group;
+	}
+
+	// Set creator ID
+	if ( $creator_id ) {
+		$group->creator_id = (int) $creator_id;
+	} elseif ( is_user_logged_in() ) {
+		$group->creator_id = bp_loggedin_user_id();
+	}
+
+	if ( ! $group->creator_id ) {
+		return false;
+	}
+
+	// Validate status
+	if ( ! groups_is_valid_status( $status ) ) {
+		return false;
+	}
+
+	// Set group name
+	$group->name         = $name;
+	$group->description  = $description;
+	$group->slug         = $slug;
+	$group->status       = $status;
+	$group->enable_forum = (int) $enable_forum;
+	$group->date_created = $date_created;
+
+	// Save group
+	if ( ! $group->save() ) {
+		return false;
+	}
+
+	// If this is a new group, set up the creator as the first member and admin
+	if ( empty( $group_id ) ) {
+		$member                = new BP_Groups_Member;
+		$member->group_id      = $group->id;
+		$member->user_id       = $group->creator_id;
+		$member->is_admin      = 1;
+		$member->user_title    = __( 'Group Admin', 'buddypress' );
+		$member->is_confirmed  = 1;
+		$member->date_modified = bp_core_current_time();
+		$member->save();
+
+		/**
+		 * Fires after the creation of a new group and a group creator needs to be made.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int              $id     ID of the newly created group.
+		 * @param BP_Groups_Member $member Instance of the member who is assigned
+		 *                                 as group creator.
+		 * @param BP_Groups_Group  $group  Instance of the group being created.
+		 */
+		do_action( 'groups_create_group', $group->id, $member, $group );
+
+	} else {
+
+		/**
+		 * Fires after the update of a group.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int             $id    ID of the updated group.
+		 * @param BP_Groups_Group $group Instance of the group being updated.
+		 */
+		do_action( 'groups_update_group', $group->id, $group );
+	}
+
+	/**
+	 * Fires after the creation or update of a group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int             $id    ID of the newly created group.
+	 * @param BP_Groups_Group $group Instance of the group being updated.
+	 */
+	do_action( 'groups_created_group', $group->id, $group );
+
+	return $group->id;
+}
+
+/**
+ * Edit the base details for a group.
+ *
+ * These are the settings that appear on the first page of the group's Admin
+ * section (Name, Description, and "Notify members...").
+ *
+ * @param int    $group_id       ID of the group.
+ * @param string $group_name     Name of the group.
+ * @param string $group_desc     Description of the group.
+ * @param bool   $notify_members Whether to send an email notification to group
+ *                               members about changes in these details.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_edit_base_group_details( $group_id, $group_name, $group_desc, $notify_members ) {
+
+	if ( empty( $group_name ) || empty( $group_desc ) )
+		return false;
+
+	$group     = groups_get_group( array( 'group_id' => $group_id ) );
+	$old_group = clone $group;
+
+	$group->name        = $group_name;
+	$group->description = $group_desc;
+
+	if ( !$group->save() )
+		return false;
+
+	if ( $notify_members ) {
+		groups_notification_group_updated( $group->id, $old_group );
+	}
+
+	/**
+	 * Fired after a group's details are updated.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param int             $value          ID of the group.
+	 * @param BP_Groups_Group $old_group      Group object, before being modified.
+	 * @param bool            $notify_members Whether to send an email notification to members about the change.
+	 */
+	do_action( 'groups_details_updated', $group->id, $old_group, $notify_members );
+
+	return true;
+}
+
+/**
+ * Edit the base details for a group.
+ *
+ * These are the settings that appear on the Settings page of the group's Admin
+ * section (privacy settings, "enable forum", invitation status).
+ *
+ * @param int         $group_id      ID of the group.
+ * @param bool        $enable_forum  Whether to enable a forum for the group.
+ * @param string      $status        Group status. 'public', 'private', 'hidden'.
+ * @param string|bool $invite_status Optional. Who is allowed to send invitations
+ *                                   to the group. 'members', 'mods', or 'admins'.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_edit_group_settings( $group_id, $enable_forum, $status, $invite_status = false ) {
+
+	$group = groups_get_group( array( 'group_id' => $group_id ) );
+	$group->enable_forum = $enable_forum;
+
+	/***
+	 * Before we potentially switch the group status, if it has been changed to public
+	 * from private and there are outstanding membership requests, auto-accept those requests.
+	 */
+	if ( 'private' == $group->status && 'public' == $status )
+		groups_accept_all_pending_membership_requests( $group->id );
+
+	// Now update the status
+	$group->status = $status;
+
+	if ( !$group->save() )
+		return false;
+
+	// If forums have been enabled, and a forum does not yet exist, we need to create one.
+	if ( $group->enable_forum ) {
+		if ( bp_is_active( 'forums' ) && !groups_get_groupmeta( $group->id, 'forum_id' ) ) {
+			groups_new_group_forum( $group->id, $group->name, $group->description );
+		}
+	}
+
+	// Set the invite status
+	if ( $invite_status )
+		groups_update_groupmeta( $group->id, 'invite_status', $invite_status );
+
+	groups_update_groupmeta( $group->id, 'last_activity', bp_core_current_time() );
+
+	/**
+	 * Fires after the update of a groups settings.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id ID of the group that was updated.
+	 */
+	do_action( 'groups_settings_updated', $group->id );
+
+	return true;
+}
+
+/**
+ * Delete a group and all of its associated metadata.
+ *
+ * @since 1.0.0
+ *
+ * @param int $group_id ID of the group to delete.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_delete_group( $group_id ) {
+
+	/**
+	 * Fires before the deletion of a group.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $group_id ID of the group to be deleted.
+	 */
+	do_action( 'groups_before_delete_group', $group_id );
+
+	// Get the group object
+	$group = groups_get_group( array( 'group_id' => $group_id ) );
+
+	// Bail if group cannot be deleted
+	if ( ! $group->delete() ) {
+		return false;
+	}
+
+	// Remove all outstanding invites for this group
+	groups_delete_all_group_invites( $group_id );
+
+	/**
+	 * Fires after the deletion of a group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $group_id ID of the group that was deleted.
+	 */
+	do_action( 'groups_delete_group', $group_id );
+
+	return true;
+}
+
+/**
+ * Check a group status (eg 'private') against the whitelist of registered statuses.
+ *
+ * @param string $status Status to check.
+ *
+ * @return bool True if status is allowed, otherwise false.
+ */
+function groups_is_valid_status( $status ) {
+	$bp = buddypress();
+
+	return in_array( $status, (array) $bp->groups->valid_status );
+}
+
+/**
+ * Provide a unique, sanitized version of a group slug.
+ *
+ * @param string $slug Group slug to check.
+ *
+ * @return string $slug A unique and sanitized slug.
+ */
+function groups_check_slug( $slug ) {
+	$bp = buddypress();
+
+	if ( 'wp' == substr( $slug, 0, 2 ) )
+		$slug = substr( $slug, 2, strlen( $slug ) - 2 );
+
+	if ( in_array( $slug, (array) $bp->groups->forbidden_names ) )
+		$slug = $slug . '-' . rand();
+
+	if ( BP_Groups_Group::check_slug( $slug ) ) {
+		do {
+			$slug = $slug . '-' . rand();
+		}
+		while ( BP_Groups_Group::check_slug( $slug ) );
+	}
+
+	return $slug;
+}
+
+/**
+ * Get a group slug by its ID.
+ *
+ * @param int $group_id The numeric ID of the group.
+ *
+ * @return string The group's slug.
+ */
+function groups_get_slug( $group_id ) {
+	$group = groups_get_group( array( 'group_id' => $group_id ) );
+	return !empty( $group->slug ) ? $group->slug : '';
+}
+
+/**
+ * Get a group ID by its slug.
+ *
+ * @since 1.6.0
+ *
+ * @param string $group_slug The group's slug.
+ *
+ * @return int The ID.
+ */
+function groups_get_id( $group_slug ) {
+	return (int)BP_Groups_Group::group_exists( $group_slug );
+}
+
+/** User Actions **************************************************************/
+
+/**
+ * Remove a user from a group.
+ *
+ * @param int $group_id ID of the group.
+ * @param int $user_id  Optional. ID of the user. Defaults to the currently
+ *                      logged-in user.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_leave_group( $group_id, $user_id = 0 ) {
+
+	if ( empty( $user_id ) )
+		$user_id = bp_loggedin_user_id();
+
+	// Don't let single admins leave the group.
+	if ( count( groups_get_group_admins( $group_id ) ) < 2 ) {
+		if ( groups_is_user_admin( $user_id, $group_id ) ) {
+			bp_core_add_message( __( 'As the only admin, you cannot leave the group.', 'buddypress' ), 'error' );
+			return false;
+		}
+	}
+
+	if ( ! BP_Groups_Member::delete( $user_id, $group_id ) ) {
+		return false;
+	}
+
+	bp_core_add_message( __( 'You successfully left the group.', 'buddypress' ) );
+
+	/**
+	 * Fires after a user leaves a group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $group_id ID of the group.
+	 * @param int $user_id  ID of the user leaving the group.
+	 */
+	do_action( 'groups_leave_group', $group_id, $user_id );
+
+	return true;
+}
+
+/**
+ * Add a user to a group.
+ *
+ * @param int $group_id ID of the group.
+ * @param int $user_id  Optional. ID of the user. Defaults to the currently
+ *                      logged-in user.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_join_group( $group_id, $user_id = 0 ) {
+
+	if ( empty( $user_id ) )
+		$user_id = bp_loggedin_user_id();
+
+	// Check if the user has an outstanding invite. If so, delete it.
+	if ( groups_check_user_has_invite( $user_id, $group_id ) )
+		groups_delete_invite( $user_id, $group_id );
+
+	// Check if the user has an outstanding request. If so, delete it.
+	if ( groups_check_for_membership_request( $user_id, $group_id ) )
+		groups_delete_membership_request( null, $user_id, $group_id );
+
+	// User is already a member, just return true
+	if ( groups_is_user_member( $user_id, $group_id ) )
+		return true;
+
+	$new_member                = new BP_Groups_Member;
+	$new_member->group_id      = $group_id;
+	$new_member->user_id       = $user_id;
+	$new_member->inviter_id    = 0;
+	$new_member->is_admin      = 0;
+	$new_member->user_title    = '';
+	$new_member->date_modified = bp_core_current_time();
+	$new_member->is_confirmed  = 1;
+
+	if ( !$new_member->save() )
+		return false;
+
+	$bp = buddypress();
+
+	if ( !isset( $bp->groups->current_group ) || !$bp->groups->current_group || $group_id != $bp->groups->current_group->id )
+		$group = groups_get_group( array( 'group_id' => $group_id ) );
+	else
+		$group = $bp->groups->current_group;
+
+	// Record this in activity streams
+	groups_record_activity( array(
+		'type'    => 'joined_group',
+		'item_id' => $group_id,
+		'user_id' => $user_id,
+	) );
+
+	/**
+	 * Fires after a user joins a group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $group_id ID of the group.
+	 * @param int $user_id  ID of the user joining the group.
+	 */
+	do_action( 'groups_join_group', $group_id, $user_id );
+
+	return true;
+}
+
+/** General Group Functions ***************************************************/
+
+/**
+ * Get a list of group administrators.
+ *
+ * @param int $group_id ID of the group.
+ *
+ * @return array Info about group admins (user_id + date_modified).
+ */
+function groups_get_group_admins( $group_id ) {
+	return BP_Groups_Member::get_group_administrator_ids( $group_id );
+}
+
+/**
+ * Get a list of group moderators.
+ *
+ * @param int $group_id ID of the group.
+ *
+ * @return array Info about group admins (user_id + date_modified).
+ */
+function groups_get_group_mods( $group_id ) {
+	return BP_Groups_Member::get_group_moderator_ids( $group_id );
+}
+
+/**
+ * Fetch the members of a group.
+ *
+ * Since BuddyPress 1.8, a procedural wrapper for BP_Group_Member_Query.
+ * Previously called BP_Groups_Member::get_all_for_group().
+ *
+ * To use the legacy query, filter 'bp_use_legacy_group_member_query',
+ * returning true.
+ *
+ * @param array $args {
+ *     An array of optional arguments.
+ *     @type int      $group_id     ID of the group whose members are being queried.
+ *                                  Default: current group ID.
+ *     @type int      $page         Page of results to be queried. Default: 1.
+ *     @type int      $per_page     Number of items to return per page of results.
+ *                                  Default: 20.
+ *     @type int      $max          Optional. Max number of items to return.
+ *     @type array    $exclude      Optional. Array of user IDs to exclude.
+ *     @type bool|int $value        True (or 1) to exclude admins and mods from results.
+ *                                  Default: 1.
+ *     @type bool|int $value        True (or 1) to exclude banned users from results.
+ *                                  Default: 1.
+ *     @type array    $group_role   Optional. Array of group roles to include.
+ *     @type string   $search_terms Optional. Filter results by a search string.
+ *     @type string   $type         Optional. Sort the order of results. 'last_joined',
+ *                                  'first_joined', or any of the $type params available
+ *                                  in {@link BP_User_Query}. Default: 'last_joined'.
+ * }
+ * @return array Multi-d array of 'members' list and 'count'.
+ */
+function groups_get_group_members( $args = array() ) {
+
+	// Backward compatibility with old method of passing arguments
+	if ( ! is_array( $args ) || func_num_args() > 1 ) {
+		_deprecated_argument( __METHOD__, '2.0.0', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+		$old_args_keys = array(
+			0 => 'group_id',
+			1 => 'per_page',
+			2 => 'page',
+			3 => 'exclude_admins_mods',
+			4 => 'exclude_banned',
+			5 => 'exclude',
+			6 => 'group_role',
+		);
+
+		$func_args = func_get_args();
+		$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
+	}
+
+	$r = wp_parse_args( $args, array(
+		'group_id'            => bp_get_current_group_id(),
+		'per_page'            => false,
+		'page'                => false,
+		'exclude_admins_mods' => true,
+		'exclude_banned'      => true,
+		'exclude'             => false,
+		'group_role'          => array(),
+		'search_terms'        => false,
+		'type'                => 'last_joined',
+	) );
+
+	// For legacy users. Use of BP_Groups_Member::get_all_for_group()
+	// is deprecated. func_get_args() can't be passed to a function in PHP
+	// 5.2.x, so we create a variable
+	$func_args = func_get_args();
+	if ( apply_filters( 'bp_use_legacy_group_member_query', false, __FUNCTION__, $func_args ) ) {
+		$retval = BP_Groups_Member::get_all_for_group( $r['group_id'], $r['per_page'], $r['page'], $r['exclude_admins_mods'], $r['exclude_banned'], $r['exclude'] );
+	} else {
+
+		// exclude_admins_mods and exclude_banned are legacy arguments.
+		// Convert to group_role
+		if ( empty( $r['group_role'] ) ) {
+			$r['group_role'] = array( 'member' );
+
+			if ( ! $r['exclude_admins_mods'] ) {
+				$r['group_role'][] = 'mod';
+				$r['group_role'][] = 'admin';
+			}
+
+			if ( ! $r['exclude_banned'] ) {
+				$r['group_role'][] = 'banned';
+			}
+		}
+
+		// Perform the group member query (extends BP_User_Query)
+		$members = new BP_Group_Member_Query( array(
+			'group_id'       => $r['group_id'],
+			'per_page'       => $r['per_page'],
+			'page'           => $r['page'],
+			'group_role'     => $r['group_role'],
+			'exclude'        => $r['exclude'],
+			'search_terms'   => $r['search_terms'],
+			'type'           => $r['type'],
+		) );
+
+		// Structure the return value as expected by the template functions
+		$retval = array(
+			'members' => array_values( $members->results ),
+			'count'   => $members->total_users,
+		);
+	}
+
+	return $retval;
+}
+
+/**
+ * Get the member count for a group.
+ *
+ * @param int $group_id Group ID.
+ *
+ * @return int Count of confirmed members for the group.
+ */
+function groups_get_total_member_count( $group_id ) {
+	return BP_Groups_Group::get_total_member_count( $group_id );
+}
+
+/** Group Fetching, Filtering & Searching  ************************************/
+
+/**
+ * Get a collection of groups, based on the parameters passed.
+ *
+ * @param array|string $args {
+ *     Array of arguments. Supports all arguments of
+ *     {@link BP_Groups_Group::get()}. Where the default values differ, they
+ *     have been described here.
+ *     @type int $per_page Default: 20.
+ *     @type int $page Default: 1.
+ * }
+ * @return array See {@link BP_Groups_Group::get()}.
+ */
+function groups_get_groups( $args = '' ) {
+
+	$defaults = array(
+		'type'              => false,    // active, newest, alphabetical, random, popular, most-forum-topics or most-forum-posts
+		'order'             => 'DESC',   // 'ASC' or 'DESC'
+		'orderby'           => 'date_created', // date_created, last_activity, total_member_count, name, random
+		'user_id'           => false,    // Pass a user_id to limit to only groups that this user is a member of
+		'include'           => false,    // Only include these specific groups (group_ids)
+		'exclude'           => false,    // Do not include these specific groups (group_ids)
+		'search_terms'      => false,    // Limit to groups that match these search terms
+		'meta_query'        => false,    // Filter by groupmeta. See WP_Meta_Query for syntax
+		'show_hidden'       => false,    // Show hidden groups to non-admins
+		'per_page'          => 20,       // The number of results to return per page
+		'page'              => 1,        // The page to return if limiting per page
+		'populate_extras'   => true,     // Fetch meta such as is_banned and is_member
+		'update_meta_cache' => true,   // Pre-fetch groupmeta for queried groups
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+
+	$groups = BP_Groups_Group::get( array(
+		'type'              => $r['type'],
+		'user_id'           => $r['user_id'],
+		'include'           => $r['include'],
+		'exclude'           => $r['exclude'],
+		'search_terms'      => $r['search_terms'],
+		'meta_query'        => $r['meta_query'],
+		'show_hidden'       => $r['show_hidden'],
+		'per_page'          => $r['per_page'],
+		'page'              => $r['page'],
+		'populate_extras'   => $r['populate_extras'],
+		'update_meta_cache' => $r['update_meta_cache'],
+		'order'             => $r['order'],
+		'orderby'           => $r['orderby'],
+	) );
+
+	/**
+	 * Filters the collection of groups based on parsed parameters.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param BP_Groups_Group $groups Object of found groups based on parameters.
+	 *                                Passed by reference.
+	 * @param array           $r      Array of parsed arguments used for group query.
+	 *                                Passed by reference.
+	 */
+	return apply_filters_ref_array( 'groups_get_groups', array( &$groups, &$r ) );
+}
+
+/**
+ * Get the total group count for the site.
+ *
+ * @return int
+ */
+function groups_get_total_group_count() {
+	$count = wp_cache_get( 'bp_total_group_count', 'bp' );
+
+	if ( false === $count ) {
+		$count = BP_Groups_Group::get_total_group_count();
+		wp_cache_set( 'bp_total_group_count', $count, 'bp' );
+	}
+
+	return $count;
+}
+
+/**
+ * Get the IDs of the groups of which a specified user is a member.
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $pag_num  Optional. Max number of results to return.
+ *                      Default: false (no limit).
+ * @param int $pag_page Optional. Page offset of results to return.
+ *                      Default: false (no limit).
+ * @return array {
+ *     @type array $groups Array of groups returned by paginated query.
+ *     @type int   $total Count of groups matching query.
+ * }
+ */
+function groups_get_user_groups( $user_id = 0, $pag_num = 0, $pag_page = 0 ) {
+
+	if ( empty( $user_id ) )
+		$user_id = bp_displayed_user_id();
+
+	return BP_Groups_Member::get_group_ids( $user_id, $pag_num, $pag_page );
+}
+
+/**
+ * Get the count of groups of which the specified user is a member.
+ *
+ * @param int $user_id Optional. Default: ID of the displayed user.
+ *
+ * @return int Group count.
+ */
+function groups_total_groups_for_user( $user_id = 0 ) {
+
+	if ( empty( $user_id ) )
+		$user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+
+	$count = wp_cache_get( 'bp_total_groups_for_user_' . $user_id, 'bp' );
+
+	if ( false === $count ) {
+		$count = BP_Groups_Member::total_group_count( $user_id );
+		wp_cache_set( 'bp_total_groups_for_user_' . $user_id, $count, 'bp' );
+	}
+
+	return $count;
+}
+
+/**
+ * Get the BP_Groups_Group object corresponding to the current group.
+ *
+ * @since 1.5.0
+ *
+ * @return BP_Groups_Group The current group object.
+ */
+function groups_get_current_group() {
+	$bp = buddypress();
+
+	$current_group = isset( $bp->groups->current_group )
+		? $bp->groups->current_group
+		: false;
+
+	/**
+	 * Filters the BP_Groups_Group object corresponding to the current group.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param BP_Groups_Group $current_group Current BP_Groups_Group object.
+	 */
+	return apply_filters( 'groups_get_current_group', $current_group );
+}
+
+/** Group Avatars *************************************************************/
+
+/**
+ * Generate the avatar upload directory path for a given group.
+ *
+ * @param int $group_id Optional. ID of the group. Default: ID of the
+ *                      current group.
+ * @return string
+ */
+function groups_avatar_upload_dir( $group_id = 0 ) {
+
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	$directory = 'group-avatars';
+	$path      = bp_core_avatar_upload_path() . '/' . $directory . '/' . $group_id;
+	$newbdir   = $path;
+	$newurl    = bp_core_avatar_url() . '/' . $directory . '/' . $group_id;
+	$newburl   = $newurl;
+	$newsubdir = '/' . $directory . '/' . $group_id;
+
+	/**
+	 * Filters the avatar upload directory path for a given group.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param array $value Array of parts related to the groups avatar upload directory.
+	 */
+	return apply_filters( 'groups_avatar_upload_dir', array(
+		'path'    => $path,
+		'url'     => $newurl,
+		'subdir'  => $newsubdir,
+		'basedir' => $newbdir,
+		'baseurl' => $newburl,
+		'error'   => false
+	) );
+}
+
+/** Group Member Status Checks ************************************************/
+
+/**
+ * Check whether a user is an admin of a given group.
+ *
+ * @param int $user_id ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool
+ */
+function groups_is_user_admin( $user_id, $group_id ) {
+	return BP_Groups_Member::check_is_admin( $user_id, $group_id );
+}
+
+/**
+ * Check whether a user is a mod of a given group.
+ *
+ * @param int $user_id ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool
+ */
+function groups_is_user_mod( $user_id, $group_id ) {
+	return BP_Groups_Member::check_is_mod( $user_id, $group_id );
+}
+
+/**
+ * Check whether a user is a member of a given group.
+ *
+ * @param int $user_id ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool
+ */
+function groups_is_user_member( $user_id, $group_id ) {
+	return BP_Groups_Member::check_is_member( $user_id, $group_id );
+}
+
+function groups_is_user_banned( $user_id, $group_id ) {
+	return BP_Groups_Member::check_is_banned( $user_id, $group_id );
+}
+
+/**
+ * Is the specified user the creator of the group?
+ *
+ * @since 1.2.6
+ *
+ * @param int $user_id ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool
+ */
+function groups_is_user_creator( $user_id, $group_id ) {
+	return BP_Groups_Member::check_is_creator( $user_id, $group_id );
+}
+
+/** Group Activity Posting ****************************************************/
+
+/**
+ * Post an Activity status update affiliated with a group.
+ *
+ * @todo Should bail out when the Activity component is not active.
+ *
+ * @param array|string $args {
+ *     Array of arguments.
+ *     @type string $content  The content of the update.
+ *     @type int    $user_id  Optional. ID of the user posting the update. Default:
+ *                            ID of the logged-in user.
+ *     @type int    $group_id Optional. ID of the group to be affiliated with the
+ *                            update. Default: ID of the current group.
+ * }
+ * @return int
+ */
+function groups_post_update( $args = '' ) {
+	$bp = buddypress();
+
+	$defaults = array(
+		'content'  => false,
+		'user_id'  => bp_loggedin_user_id(),
+		'group_id' => 0
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+	extract( $r, EXTR_SKIP );
+
+	if ( empty( $group_id ) && !empty( $bp->groups->current_group->id ) )
+		$group_id = $bp->groups->current_group->id;
+
+	if ( empty( $content ) || !strlen( trim( $content ) ) || empty( $user_id ) || empty( $group_id ) )
+		return false;
+
+	$bp->groups->current_group = groups_get_group( array( 'group_id' => $group_id ) );
+
+	// Be sure the user is a member of the group before posting.
+	if ( !bp_current_user_can( 'bp_moderate' ) && !groups_is_user_member( $user_id, $group_id ) )
+		return false;
+
+	// Record this in activity streams
+	$activity_action  = sprintf( __( '%1$s posted an update in the group %2$s', 'buddypress'), bp_core_get_userlink( $user_id ), '<a href="' . bp_get_group_permalink( $bp->groups->current_group ) . '">' . esc_attr( $bp->groups->current_group->name ) . '</a>' );
+	$activity_content = $content;
+
+	/**
+	 * Filters the action for the new group activity update.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $activity_action The new group activity update.
+	 */
+	$action = apply_filters( 'groups_activity_new_update_action',  $activity_action  );
+
+	/**
+	 * Filters the content for the new group activity update.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $activity_content The content of the update.
+	 */
+	$content_filtered = apply_filters( 'groups_activity_new_update_content', $activity_content );
+
+	$activity_id = groups_record_activity( array(
+		'user_id' => $user_id,
+		'action'  => $action,
+		'content' => $content_filtered,
+		'type'    => 'activity_update',
+		'item_id' => $group_id
+	) );
+
+	groups_update_groupmeta( $group_id, 'last_activity', bp_core_current_time() );
+
+	/**
+	 * Fires after posting of an Activity status update affiliated with a group.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $content     The content of the update.
+	 * @param int    $user_id     ID of the user posting the update.
+	 * @param int    $group_id    ID of the group being posted to.
+	 * @param bool   $activity_id Whether or not the activity recording succeeded.
+	 */
+	do_action( 'bp_groups_posted_update', $content, $user_id, $group_id, $activity_id );
+
+	return $activity_id;
+}
+
+/** Group Invitations *********************************************************/
+
+/**
+ * Get IDs of users with outstanding invites to a given group from a specified user.
+ *
+ * @param int               $user_id ID of the inviting user.
+ * @param int|bool          $limit Limit to restrict to.
+ * @param int|bool          $page
+ * @param string|array|bool $exclude
+ *
+ * @return array $value IDs of users who have been invited to the group by the
+ *                      user but have not yet accepted.
+ */
+function groups_get_invites_for_user( $user_id = 0, $limit = false, $page = false, $exclude = false ) {
+
+	if ( empty( $user_id ) )
+		$user_id = bp_loggedin_user_id();
+
+	return BP_Groups_Member::get_invites( $user_id, $limit, $page, $exclude );
+}
+
+/**
+ * Get the total group invite count for a user.
+ *
+ * @since 2.0.0
+ *
+ * @param int $user_id The user ID.
+ *
+ * @return int
+ */
+function groups_get_invite_count_for_user( $user_id = 0 ) {
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	return BP_Groups_Member::get_invite_count_for_user( $user_id );
+}
+
+/**
+ * Invite a user to a group.
+ *
+ * @param array|string $args {
+ *     Array of arguments.
+ *     @type int    $user_id       ID of the user being invited.
+ *     @type int    $group_id      ID of the group to which the user is being invited.
+ *     @type int    $inviter_id    Optional. ID of the inviting user. Default:
+ *                                 ID of the logged-in user.
+ *     @type string $date_modified Optional. Modified date for the invitation.
+ *                                 Default: current date/time.
+ *     @type bool   $is_confirmed  Optional. Whether the invitation should be
+ *                                 marked confirmed. Default: false.
+ * }
+ * @return bool True on success, false on failure.
+ */
+function groups_invite_user( $args = '' ) {
+
+	$defaults = array(
+		'user_id'       => false,
+		'group_id'      => false,
+		'inviter_id'    => bp_loggedin_user_id(),
+		'date_modified' => bp_core_current_time(),
+		'is_confirmed'  => 0
+	);
+
+	$args = wp_parse_args( $args, $defaults );
+	extract( $args, EXTR_SKIP );
+
+	if ( empty( $user_id ) || empty( $group_id ) )
+		return false;
+
+	// if the user has already requested membership, accept the request
+	if ( $membership_id = groups_check_for_membership_request( $user_id, $group_id ) ) {
+		groups_accept_membership_request( $membership_id, $user_id, $group_id );
+
+	// Otherwise, create a new invitation
+	} elseif ( ! groups_is_user_member( $user_id, $group_id ) && ! groups_check_user_has_invite( $user_id, $group_id, 'all' ) ) {
+		$invite                = new BP_Groups_Member;
+		$invite->group_id      = $group_id;
+		$invite->user_id       = $user_id;
+		$invite->date_modified = $date_modified;
+		$invite->inviter_id    = $inviter_id;
+		$invite->is_confirmed  = $is_confirmed;
+
+		if ( !$invite->save() )
+			return false;
+
+		/**
+		 * Fires after the creation of a new group invite.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param array $args Array of parsed arguments for the group invite.
+		 */
+		do_action( 'groups_invite_user', $args );
+	}
+
+	return true;
+}
+
+/**
+ * Uninvite a user from a group.
+ *
+ * Functionally, this is equivalent to removing a user from a group.
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_uninvite_user( $user_id, $group_id ) {
+
+	if ( ! BP_Groups_Member::delete_invite( $user_id, $group_id ) )
+		return false;
+
+	/**
+	 * Fires after uninviting a user from a group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $group_id ID of the group being uninvited from.
+	 * @param int $user_id  ID of the user being uninvited.
+	 */
+	do_action( 'groups_uninvite_user', $group_id, $user_id );
+
+	return true;
+}
+
+/**
+ * Process the acceptance of a group invitation.
+ *
+ * Returns true if a user is already a member of the group.
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool True when the user is a member of the group, otherwise false.
+ */
+function groups_accept_invite( $user_id, $group_id ) {
+
+	// If the user is already a member (because BP at one point allowed two invitations to
+	// slip through), delete all existing invitations/requests and return true
+	if ( groups_is_user_member( $user_id, $group_id ) ) {
+		if ( groups_check_user_has_invite( $user_id, $group_id ) ) {
+			groups_delete_invite( $user_id, $group_id );
+		}
+
+		if ( groups_check_for_membership_request( $user_id, $group_id ) ) {
+			groups_delete_membership_request( null, $user_id, $group_id );
+		}
+
+		return true;
+	}
+
+	$member = new BP_Groups_Member( $user_id, $group_id );
+	$member->accept_invite();
+
+	if ( !$member->save() ) {
+		return false;
+	}
+
+	// Remove request to join
+	if ( $member->check_for_membership_request( $user_id, $group_id ) ) {
+		$member->delete_request( $user_id, $group_id );
+	}
+
+	// Modify group meta
+	groups_update_groupmeta( $group_id, 'last_activity', bp_core_current_time() );
+
+	/**
+	 * Fires after a user has accepted a group invite.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $user_id  ID of the user who accepted the group invite.
+	 * @param int $group_id ID of the group being accepted to.
+	 */
+	do_action( 'groups_accept_invite', $user_id, $group_id );
+
+	return true;
+}
+
+/**
+ * Reject a group invitation.
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_reject_invite( $user_id, $group_id ) {
+	if ( ! BP_Groups_Member::delete_invite( $user_id, $group_id ) )
+		return false;
+
+	/**
+	 * Fires after a user rejects a group invitation.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $user_id  ID of the user rejecting the invite.
+	 * @param int $group_id ID of the group being rejected.
+	 */
+	do_action( 'groups_reject_invite', $user_id, $group_id );
+
+	return true;
+}
+
+/**
+ * Delete a group invitation.
+ *
+ * @param int $user_id  ID of the invited user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_delete_invite( $user_id, $group_id ) {
+	if ( ! BP_Groups_Member::delete_invite( $user_id, $group_id ) )
+		return false;
+
+	/**
+	 * Fires after the deletion of a group invitation.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param int $user_id  ID of the user whose invitation is being deleted.
+	 * @param int $group_id ID of the group whose invitation is being deleted.
+	 */
+	do_action( 'groups_delete_invite', $user_id, $group_id );
+
+	return true;
+}
+
+/**
+ * Send all pending invites by a single user to a specific group.
+ *
+ * @param int $user_id  ID of the inviting user.
+ * @param int $group_id ID of the group.
+ */
+function groups_send_invites( $user_id, $group_id ) {
+
+	if ( empty( $user_id ) )
+		$user_id = bp_loggedin_user_id();
+
+	// Send friend invites.
+	$invited_users = groups_get_invites_for_group( $user_id, $group_id );
+	$group = groups_get_group( array( 'group_id' => $group_id ) );
+
+	for ( $i = 0, $count = count( $invited_users ); $i < $count; ++$i ) {
+		$member = new BP_Groups_Member( $invited_users[$i], $group_id );
+
+		// Send the actual invite
+		groups_notification_group_invites( $group, $member, $user_id );
+
+		$member->invite_sent = 1;
+		$member->save();
+	}
+
+	/**
+	 * Fires after the sending of invites for a group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int   $group_id      ID of the group who's being invited to.
+	 * @param array $invited_users Array of users being invited to the group.
+	 */
+	do_action( 'groups_send_invites', $group_id, $invited_users );
+}
+
+/**
+ * Get IDs of users with outstanding invites to a given group from a specified user.
+ *
+ * @param int $user_id  ID of the inviting user.
+ * @param int $group_id ID of the group.
+ *
+ * @return array $value IDs of users who have been invited to the group by the
+ *                      user but have not yet accepted.
+ */
+function groups_get_invites_for_group( $user_id, $group_id ) {
+	return BP_Groups_Group::get_invites( $user_id, $group_id );
+}
+
+/**
+ * Check to see whether a user has already been invited to a group.
+ *
+ * By default, the function checks for invitations that have been sent.
+ * Entering 'all' as the $type parameter will return unsent invitations as
+ * well (useful to make sure AJAX requests are not duplicated).
+ *
+ * @param int    $user_id  ID of potential group member.
+ * @param int    $group_id ID of potential group.
+ * @param string $type     Optional. Use 'sent' to check for sent invites,
+ *                         'all' to check for all. Default: 'sent'.
+ *
+ * @return bool True if an invitation is found, otherwise false.
+ */
+function groups_check_user_has_invite( $user_id, $group_id, $type = 'sent' ) {
+	return BP_Groups_Member::check_has_invite( $user_id, $group_id, $type );
+}
+
+/**
+ * Delete all invitations to a given group.
+ *
+ * @param int $group_id ID of the group whose invitations are being deleted.
+ *
+ * @return int|null Number of rows records deleted on success, null on failure.
+ */
+function groups_delete_all_group_invites( $group_id ) {
+	return BP_Groups_Group::delete_all_invites( $group_id );
+}
+
+/** Group Promotion & Banning *************************************************/
+
+/**
+ * Promote a member to a new status within a group.
+ *
+ * @param int    $user_id  ID of the user.
+ * @param int    $group_id ID of the group.
+ * @param string $status   The new status. 'mod' or 'admin'.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_promote_member( $user_id, $group_id, $status ) {
+
+	if ( ! bp_is_item_admin() )
+		return false;
+
+	$member = new BP_Groups_Member( $user_id, $group_id );
+
+	// Don't use this action. It's deprecated as of BuddyPress 1.6.
+	do_action( 'groups_premote_member', $group_id, $user_id, $status );
+
+	/**
+	 * Fires before the promotion of a user to a new status.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int    $group_id ID of the group being promoted in.
+	 * @param int    $user_id  ID of the user being promoted.
+	 * @param string $status   New status being promoted to.
+	 */
+	do_action( 'groups_promote_member', $group_id, $user_id, $status );
+
+	return $member->promote( $status );
+}
+
+/**
+ * Demote a user to 'member' status within a group.
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_demote_member( $user_id, $group_id ) {
+
+	if ( ! bp_is_item_admin() )
+		return false;
+
+	$member = new BP_Groups_Member( $user_id, $group_id );
+
+	/**
+	 * Fires before the demotion of a user to 'member'.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $group_id ID of the group being demoted in.
+	 * @param int $user_id  ID of the user being demoted.
+	 */
+	do_action( 'groups_demote_member', $group_id, $user_id );
+
+	return $member->demote();
+}
+
+/**
+ * Ban a member from a group.
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_ban_member( $user_id, $group_id ) {
+
+	if ( ! bp_is_item_admin() )
+		return false;
+
+	$member = new BP_Groups_Member( $user_id, $group_id );
+
+	/**
+	 * Fires before the banning of a member from a group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $group_id ID of the group being banned from.
+	 * @param int $user_id  ID of the user being banned.
+	 */
+	do_action( 'groups_ban_member', $group_id, $user_id );
+
+	return $member->ban();
+}
+
+/**
+ * Unban a member from a group.
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_unban_member( $user_id, $group_id ) {
+
+	if ( ! bp_is_item_admin() )
+		return false;
+
+	$member = new BP_Groups_Member( $user_id, $group_id );
+
+	/**
+	 * Fires before the unbanning of a member from a group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $group_id ID of the group being unbanned from.
+	 * @param int $user_id  ID of the user being unbanned.
+	 */
+	do_action( 'groups_unban_member', $group_id, $user_id );
+
+	return $member->unban();
+}
+
+/** Group Removal *************************************************************/
+
+/**
+ * Remove a member from a group.
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_remove_member( $user_id, $group_id ) {
+
+	if ( ! bp_is_item_admin() ) {
+		return false;
+  	}
+
+	$member = new BP_Groups_Member( $user_id, $group_id );
+
+	/**
+	 * Fires before the removal of a member from a group.
+	 *
+	 * @since 1.2.6
+	 *
+	 * @param int $group_id ID of the group being removed from.
+	 * @param int $user_id  ID of the user being removed.
+	 */
+	do_action( 'groups_remove_member', $group_id, $user_id );
+
+	return $member->remove();
+}
+
+/** Group Membership **********************************************************/
+
+/**
+ * Create a group membership request.
+ *
+ * @param int $requesting_user_id ID of the user requesting membership.
+ * @param int $group_id           ID of the group.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_send_membership_request( $requesting_user_id, $group_id ) {
+
+	// Prevent duplicate requests
+	if ( groups_check_for_membership_request( $requesting_user_id, $group_id ) )
+		return false;
+
+	// Check if the user is already a member or is banned
+	if ( groups_is_user_member( $requesting_user_id, $group_id ) || groups_is_user_banned( $requesting_user_id, $group_id ) )
+		return false;
+
+	// Check if the user is already invited - if so, simply accept invite
+	if ( groups_check_user_has_invite( $requesting_user_id, $group_id ) ) {
+		groups_accept_invite( $requesting_user_id, $group_id );
+		return true;
+	}
+
+	$requesting_user                = new BP_Groups_Member;
+	$requesting_user->group_id      = $group_id;
+	$requesting_user->user_id       = $requesting_user_id;
+	$requesting_user->inviter_id    = 0;
+	$requesting_user->is_admin      = 0;
+	$requesting_user->user_title    = '';
+	$requesting_user->date_modified = bp_core_current_time();
+	$requesting_user->is_confirmed  = 0;
+	$requesting_user->comments      = isset( $_POST['group-request-membership-comments'] ) ? $_POST['group-request-membership-comments'] : '';
+
+	if ( $requesting_user->save() ) {
+		$admins = groups_get_group_admins( $group_id );
+
+		// Saved okay, now send the email notification
+		for ( $i = 0, $count = count( $admins ); $i < $count; ++$i )
+			groups_notification_new_membership_request( $requesting_user_id, $admins[$i]->user_id, $group_id, $requesting_user->id );
+
+		/**
+		 * Fires after the creation of a new membership request.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int   $requesting_user_id  ID of the user requesting membership.
+		 * @param array $admins              Array of group admins.
+		 * @param int   $group_id            ID of the group being requested to.
+		 * @param int   $requesting_user->id ID of the user requesting membership.
+		 */
+		do_action( 'groups_membership_requested', $requesting_user_id, $admins, $group_id, $requesting_user->id );
+
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Accept a pending group membership request.
+ *
+ * @param int $membership_id ID of the membership object.
+ * @param int $user_id       Optional. ID of the user who requested membership.
+ *                           Provide this value along with $group_id to override
+ *                           $membership_id.
+ * @param int $group_id      Optional. ID of the group to which membership is being
+ *                           requested. Provide this value along with $user_id to
+ *                           override $membership_id.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_accept_membership_request( $membership_id, $user_id = 0, $group_id = 0 ) {
+
+	if ( !empty( $user_id ) && !empty( $group_id ) ) {
+		$membership = new BP_Groups_Member( $user_id, $group_id );
+	} else {
+		$membership = new BP_Groups_Member( false, false, $membership_id );
+	}
+
+	$membership->accept_request();
+
+	if ( !$membership->save() ) {
+		return false;
+	}
+
+	// Check if the user has an outstanding invite, if so delete it.
+	if ( groups_check_user_has_invite( $membership->user_id, $membership->group_id ) ) {
+		groups_delete_invite( $membership->user_id, $membership->group_id );
+	}
+
+	/**
+	 * Fires after a group membership request has been accepted.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int  $user_id  ID of the user who accepted membership.
+	 * @param int  $group_id ID of the group that was accepted membership to.
+	 * @param bool $value    If membership was accepted.
+	 */
+	do_action( 'groups_membership_accepted', $membership->user_id, $membership->group_id, true );
+
+	return true;
+}
+
+/**
+ * Reject a pending group membership request.
+ *
+ * @param int $membership_id ID of the membership object.
+ * @param int $user_id       Optional. ID of the user who requested membership.
+ *                           Provide this value along with $group_id to override
+ *                           $membership_id.
+ * @param int $group_id      Optional. ID of the group to which membership is being
+ *                           requested. Provide this value along with $user_id to
+ *                           override $membership_id.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_reject_membership_request( $membership_id, $user_id = 0, $group_id = 0 ) {
+	if ( !$membership = groups_delete_membership_request( $membership_id, $user_id, $group_id ) ) {
+		return false;
+	}
+
+	/**
+	 * Fires after a group membership request has been rejected.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int  $user_id  ID of the user who rejected membership.
+	 * @param int  $group_id ID of the group that was rejected membership to.
+	 * @param bool $value    If membership was accepted.
+	 */
+	do_action( 'groups_membership_rejected', $membership->user_id, $membership->group_id, false );
+
+	return true;
+}
+
+/**
+ * Delete a pending group membership request.
+ *
+ * @param int $membership_id ID of the membership object.
+ * @param int $user_id       Optional. ID of the user who requested membership.
+ *                           Provide this value along with $group_id to override
+ *                           $membership_id.
+ * @param int $group_id      Optional. ID of the group to which membership is being
+ *                           requested. Provide this value along with $user_id to
+ *                           override $membership_id.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_delete_membership_request( $membership_id, $user_id = 0, $group_id = 0 ) {
+	if ( !empty( $user_id ) && !empty( $group_id ) )
+		$membership = new BP_Groups_Member( $user_id, $group_id );
+	else
+		$membership = new BP_Groups_Member( false, false, $membership_id );
+
+	if ( ! BP_Groups_Member::delete_request( $membership->user_id, $membership->group_id ) )
+		return false;
+
+	return $membership;
+}
+
+/**
+ * Check whether a user has an outstanding membership request for a given group.
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return int|null ID of the membership if found, otherwise false.
+ */
+function groups_check_for_membership_request( $user_id, $group_id ) {
+	return BP_Groups_Member::check_for_membership_request( $user_id, $group_id );
+}
+
+/**
+ * Accept all pending membership requests to a group.
+ *
+ * @param int $group_id ID of the group.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_accept_all_pending_membership_requests( $group_id ) {
+	$user_ids = BP_Groups_Member::get_all_membership_request_user_ids( $group_id );
+
+	if ( !$user_ids )
+		return false;
+
+	foreach ( (array) $user_ids as $user_id )
+		groups_accept_membership_request( false, $user_id, $group_id );
+
+	/**
+	 * Fires after the acceptance of all pending membership requests to a group.
+	 *
+	 * @since 1.0.2
+	 *
+	 * @param int $group_id ID of the group whose pending memberships were accepted.
+	 */
+	do_action( 'groups_accept_all_pending_membership_requests', $group_id );
+
+	return true;
+}
+
+/** Group Meta ****************************************************************/
+
+/**
+ * Delete metadata for a group.
+ *
+ * @param int         $group_id   ID of the group.
+ * @param string|bool $meta_key   The key of the row to delete.
+ * @param string|bool $meta_value Optional. Metadata value. If specified, only delete
+ *                                metadata entries with this value.
+ * @param bool        $delete_all Optional. If true, delete matching metadata entries
+ *                                for all groups. Otherwise, only delete matching
+ *                                metadata entries for the specified group.
+ *                                Default: false.
+ *
+ * @return bool True on success, false on failure.
+ */
+function groups_delete_groupmeta( $group_id, $meta_key = false, $meta_value = false, $delete_all = false ) {
+	global $wpdb;
+
+	// Legacy - if no meta_key is passed, delete all for the item
+	if ( empty( $meta_key ) ) {
+		$keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->groupmeta} WHERE group_id = %d", $group_id ) );
+
+		// With no meta_key, ignore $delete_all
+		$delete_all = false;
+	} else {
+		$keys = array( $meta_key );
+	}
+
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	$retval = true;
+	foreach ( $keys as $key ) {
+		$retval = delete_metadata( 'group', $group_id, $key, $meta_value, $delete_all );
+	}
+
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Get a piece of group metadata.
+ *
+ * @param int    $group_id ID of the group.
+ * @param string $meta_key Metadata key.
+ * @param bool   $single   Optional. If true, return only the first value of the
+ *                         specified meta_key. This parameter has no effect if
+ *                         meta_key is empty.
+ *
+ * @return mixed Metadata value.
+ */
+function groups_get_groupmeta( $group_id, $meta_key = '', $single = true ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = get_metadata( 'group', $group_id, $meta_key, $single );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Update a piece of group metadata.
+ *
+ * @param int    $group_id   ID of the group.
+ * @param string $meta_key   Metadata key.
+ * @param mixed  $meta_value Value to store.
+ * @param mixed  $prev_value Optional. If specified, only update existing
+ *                           metadata entries with the specified value.
+ *                           Otherwise, update all entries.
+ *
+ * @return bool|int $retval Returns false on failure. On successful update of existing
+ *                          metadata, returns true. On successful creation of new metadata,
+ *                          returns the integer ID of the new metadata row.
+ */
+function groups_update_groupmeta( $group_id, $meta_key, $meta_value, $prev_value = '' ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = update_metadata( 'group', $group_id, $meta_key, $meta_value, $prev_value );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Add a piece of group metadata.
+ *
+ * @since 2.0.0
+ *
+ * @param int    $group_id   ID of the group.
+ * @param string $meta_key   Metadata key.
+ * @param mixed  $meta_value Metadata value.
+ * @param bool   $unique     Optional. Whether to enforce a single metadata value
+ *                           for the given key. If true, and the object already
+ *                           has a value for the key, no change will be made.
+ *                           Default: false.
+ *
+ * @return int|bool The meta ID on successful update, false on failure.
+ */
+function groups_add_groupmeta( $group_id, $meta_key, $meta_value, $unique = false ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = add_metadata( 'group', $group_id, $meta_key, $meta_value, $unique );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/** Group Cleanup Functions ***************************************************/
+
+/**
+ * Delete all group membership information for the specified user.
+ *
+ * @since 1.0.0
+ *
+ * @param int $user_id ID of the user.
+ */
+function groups_remove_data_for_user( $user_id ) {
+	BP_Groups_Member::delete_all_for_user( $user_id );
+
+	/**
+	 * Fires after the deletion of all data for a user.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param int $user_id ID of the user whose data is being deleted.
+	 */
+	do_action( 'groups_remove_data_for_user', $user_id );
+}
+add_action( 'wpmu_delete_user',  'groups_remove_data_for_user' );
+add_action( 'delete_user',       'groups_remove_data_for_user' );
+add_action( 'bp_make_spam_user', 'groups_remove_data_for_user' );
Index: /tags/2.4.3/src/bp-groups/bp-groups-loader.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-loader.php	(revision 10410)
@@ -0,0 +1,815 @@
+<?php
+/**
+ * BuddyPress Groups Loader.
+ *
+ * A groups component, for users to group themselves together. Includes a
+ * robust sub-component API that allows Groups to be extended.
+ * Comes preconfigured with an activity stream, discussion forums, and settings.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsLoader
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+class BP_Groups_Component extends BP_Component {
+
+	/**
+	 * Auto-join group when non group member performs group activity.
+	 *
+	 * @since 1.5.0
+	 * @var bool
+	 */
+	public $auto_join;
+
+	/**
+	 * The group being currently accessed.
+	 *
+	 * @since 1.5.0
+	 * @var BP_Groups_Group
+	 */
+	public $current_group;
+
+	/**
+	 * Default group extension.
+	 *
+	 * @since 1.6.0
+	 * @todo Is this used anywhere? Is this a duplicate of $default_extension?
+	 */
+	var $default_component;
+
+	/**
+	 * Default group extension.
+	 *
+	 * @since 1.6.0
+	 * @var string
+	 */
+	public $default_extension;
+
+	/**
+	 * Illegal group names/slugs.
+	 *
+	 * @since 1.5.0
+	 * @var array
+	 */
+	public $forbidden_names;
+
+	/**
+	 * Group creation/edit steps (e.g. Details, Settings, Avatar, Invites).
+	 *
+	 * @since 1.5.0
+	 * @var array
+	 */
+	public $group_creation_steps;
+
+	/**
+	 * Types of group statuses (Public, Private, Hidden).
+	 *
+	 * @since 1.5.0
+	 * @var array
+	 */
+	public $valid_status;
+
+	/**
+	 * Start the groups component creation process.
+	 *
+	 * @since 1.5.0
+	 */
+	public function __construct() {
+		parent::start(
+			'groups',
+			_x( 'User Groups', 'Group screen page <title>', 'buddypress' ),
+			buddypress()->plugin_dir,
+			array(
+				'adminbar_myaccount_order' => 70,
+				'search_query_arg' => 'groups_search',
+			)
+		);
+	}
+
+	/**
+	 * Include Groups component files.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::includes() for a description of arguments.
+	 *
+	 * @param array $includes See BP_Component::includes() for a description.
+	 */
+	public function includes( $includes = array() ) {
+		$includes = array(
+			'cache',
+			'forums',
+			'actions',
+			'filters',
+			'screens',
+			'classes',
+			'widgets',
+			'activity',
+			'template',
+			'adminbar',
+			'functions',
+			'notifications'
+		);
+
+		if ( is_admin() ) {
+			$includes[] = 'admin';
+		}
+
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Set up component global data.
+	 *
+	 * The BP_GROUPS_SLUG constant is deprecated, and only used here for
+	 * backwards compatibility.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_globals() for a description of arguments.
+	 *
+	 * @param array $args See BP_Component::setup_globals() for a description.
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		// Define a slug, if necessary
+		if ( ! defined( 'BP_GROUPS_SLUG' ) ) {
+			define( 'BP_GROUPS_SLUG', $this->id );
+		}
+
+		// Global tables for groups component
+		$global_tables = array(
+			'table_name'           => $bp->table_prefix . 'bp_groups',
+			'table_name_members'   => $bp->table_prefix . 'bp_groups_members',
+			'table_name_groupmeta' => $bp->table_prefix . 'bp_groups_groupmeta'
+		);
+
+		// Metadata tables for groups component
+		$meta_tables = array(
+			'group' => $bp->table_prefix . 'bp_groups_groupmeta',
+		);
+
+		// All globals for groups component.
+		// Note that global_tables is included in this array.
+		$args = array(
+			'slug'                  => BP_GROUPS_SLUG,
+			'root_slug'             => isset( $bp->pages->groups->slug ) ? $bp->pages->groups->slug : BP_GROUPS_SLUG,
+			'has_directory'         => true,
+			'directory_title'       => _x( 'Groups', 'component directory title', 'buddypress' ),
+			'notification_callback' => 'groups_format_notifications',
+			'search_string'         => _x( 'Search Groups...', 'Component directory search', 'buddypress' ),
+			'global_tables'         => $global_tables,
+			'meta_tables'           => $meta_tables,
+		);
+
+		parent::setup_globals( $args );
+
+		/** Single Group Globals **********************************************/
+
+		// Are we viewing a single group?
+		if ( bp_is_groups_component() && $group_id = BP_Groups_Group::group_exists( bp_current_action() ) ) {
+
+			$bp->is_single_item  = true;
+
+			/**
+			 * Filters the current PHP Class being used.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param string $value Name of the class being used.
+			 */
+			$current_group_class = apply_filters( 'bp_groups_current_group_class', 'BP_Groups_Group' );
+
+			if ( $current_group_class == 'BP_Groups_Group' ) {
+				$this->current_group = groups_get_group( array(
+					'group_id'        => $group_id,
+					'populate_extras' => true,
+				) );
+
+			} else {
+
+				/**
+				 * Filters the current group object being instantiated from previous filter.
+				 *
+				 * @since 1.5.0
+				 *
+				 * @param object $value Newly instantiated object for the group.
+				 */
+				$this->current_group = apply_filters( 'bp_groups_current_group_object', new $current_group_class( $group_id ) );
+			}
+
+			// When in a single group, the first action is bumped down one because of the
+			// group name, so we need to adjust this and set the group name to current_item.
+			$bp->current_item   = bp_current_action();
+			$bp->current_action = bp_action_variable( 0 );
+			array_shift( $bp->action_variables );
+
+			// Using "item" not "group" for generic support in other components.
+			if ( bp_current_user_can( 'bp_moderate' ) ) {
+				bp_update_is_item_admin( true, 'groups' );
+			} else {
+				bp_update_is_item_admin( groups_is_user_admin( bp_loggedin_user_id(), $this->current_group->id ), 'groups' );
+			}
+
+			// If the user is not an admin, check if they are a moderator
+			if ( ! bp_is_item_admin() ) {
+				bp_update_is_item_mod  ( groups_is_user_mod  ( bp_loggedin_user_id(), $this->current_group->id ), 'groups' );
+			}
+
+			// Is the logged in user a member of the group?
+			if ( ( is_user_logged_in() && groups_is_user_member( bp_loggedin_user_id(), $this->current_group->id ) ) ) {
+				$this->current_group->is_user_member = true;
+			} else {
+				$this->current_group->is_user_member = false;
+			}
+
+			// Should this group be visible to the logged in user?
+			if ( 'public' == $this->current_group->status || $this->current_group->is_user_member ) {
+				$this->current_group->is_visible = true;
+			} else {
+				$this->current_group->is_visible = false;
+			}
+
+			// If this is a private or hidden group, does the user have access?
+			if ( 'private' == $this->current_group->status || 'hidden' == $this->current_group->status ) {
+				if ( $this->current_group->is_user_member && is_user_logged_in() || bp_current_user_can( 'bp_moderate' ) ) {
+					$this->current_group->user_has_access = true;
+				} else {
+					$this->current_group->user_has_access = false;
+				}
+			} else {
+				$this->current_group->user_has_access = true;
+			}
+
+			// Check once if the current group has a custom front template
+			$this->current_group->front_template = bp_groups_get_front_template( $this->current_group );
+
+		// Set current_group to 0 to prevent debug errors
+		} else {
+			$this->current_group = 0;
+		}
+
+		/**
+		 * Filters the list of illegal groups names/slugs.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param array $value Array of illegal group names/slugs.
+		 */
+		$this->forbidden_names = apply_filters( 'groups_forbidden_names', array(
+			'my-groups',
+			'create',
+			'invites',
+			'send-invites',
+			'forum',
+			'delete',
+			'add',
+			'admin',
+			'request-membership',
+			'members',
+			'settings',
+			'avatar',
+			$this->slug,
+			$this->root_slug,
+		) );
+
+		// If the user was attempting to access a group, but no group by that name was found, 404
+		if ( bp_is_groups_component() && empty( $this->current_group ) && bp_current_action() && !in_array( bp_current_action(), $this->forbidden_names ) ) {
+			bp_do_404();
+			return;
+		}
+
+		/**
+		 * Filters the preconfigured groups creation steps.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param array $value Array of preconfigured group creation steps.
+		 */
+		$this->group_creation_steps = apply_filters( 'groups_create_group_steps', array(
+			'group-details'  => array(
+				'name'       => _x( 'Details', 'Group screen nav', 'buddypress' ),
+				'position'   => 0
+			),
+			'group-settings' => array(
+				'name'       => _x( 'Settings', 'Group screen nav', 'buddypress' ),
+				'position'   => 10
+			)
+		) );
+
+		// If avatar uploads are not disabled, add avatar option
+		$disabled_avatar_uploads = (int) bp_disable_group_avatar_uploads();
+		if ( ! $disabled_avatar_uploads && $bp->avatar->show_avatars ) {
+			$this->group_creation_steps['group-avatar'] = array(
+				'name'     => _x( 'Photo', 'Group screen nav', 'buddypress' ),
+				'position' => 20
+			);
+		}
+
+		if ( bp_group_use_cover_image_header() ) {
+			$this->group_creation_steps['group-cover-image'] = array(
+				'name'     => _x( 'Cover Image', 'Group screen nav', 'buddypress' ),
+				'position' => 25
+			);
+		}
+
+		// If friends component is active, add invitations
+		if ( bp_is_active( 'friends' ) ) {
+			$this->group_creation_steps['group-invites'] = array(
+				'name'     => _x( 'Invites',  'Group screen nav', 'buddypress' ),
+				'position' => 30
+			);
+		}
+
+		/**
+		 * Filters the list of valid groups statuses.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param array $value Array of valid group statuses.
+		 */
+		$this->valid_status = apply_filters( 'groups_valid_status', array(
+			'public',
+			'private',
+			'hidden'
+		) );
+
+		// Auto join group when non group member performs group activity
+		$this->auto_join = defined( 'BP_DISABLE_AUTO_GROUP_JOIN' ) && BP_DISABLE_AUTO_GROUP_JOIN ? false : true;
+	}
+
+	/**
+	 * Set up canonical stack for this component.
+	 *
+	 * @since 2.1.0
+	 */
+	public function setup_canonical_stack() {
+		if ( ! bp_is_groups_component() ) {
+			return;
+		}
+
+		if ( empty( $this->current_group ) ) {
+			return;
+		}
+
+		/**
+		 * Filters the default groups extension.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $value BP_GROUPS_DEFAULT_EXTENSION constant if defined,
+		 *                      else 'home'.
+		 */
+		$this->default_extension = apply_filters( 'bp_groups_default_extension', defined( 'BP_GROUPS_DEFAULT_EXTENSION' ) ? BP_GROUPS_DEFAULT_EXTENSION : 'home' );
+
+		$bp = buddypress();
+
+		// If the activity component is not active and the current group has no custom front, members are displayed in the home nav
+		if ( 'members' === $this->default_extension && ! bp_is_active( 'activity' ) && ! $this->current_group->front_template ) {
+			$this->default_extension = 'home';
+		}
+
+		if ( ! bp_current_action() ) {
+			$bp->current_action = $this->default_extension;
+		}
+
+		// Prepare for a redirect to the canonical URL
+		$bp->canonical_stack['base_url'] = bp_get_group_permalink( $this->current_group );
+
+		if ( bp_current_action() ) {
+			$bp->canonical_stack['action'] = bp_current_action();
+		}
+
+		/**
+		 * If there's no custom front.php template for the group, we need to make sure the canonical stack action
+		 * is set to 'home' in these 2 cases:
+		 *
+		 * - the current action is 'activity' (eg: site.url/groups/single/activity) and the Activity component is active
+		 * - the current action is 'members' (eg: site.url/groups/single/members) and the Activity component is *not* active.
+		 */
+		if ( ! $this->current_group->front_template && ( bp_is_current_action( 'activity' ) || ( ! bp_is_active( 'activity' ) && bp_is_current_action( 'members' ) ) ) ) {
+			$bp->canonical_stack['action'] = 'home';
+		}
+
+		if ( ! empty( $bp->action_variables ) ) {
+			$bp->canonical_stack['action_variables'] = bp_action_variables();
+		}
+
+		// When viewing the default extension, the canonical URL should not have
+		// that extension's slug, unless more has been tacked onto the URL via
+		// action variables
+		if ( bp_is_current_action( $this->default_extension ) && empty( $bp->action_variables ) )  {
+			unset( $bp->canonical_stack['action'] );
+		}
+	}
+
+	/**
+	 * Set up component navigation.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_nav() for a description of arguments.
+	 *
+	 * @param array $main_nav Optional. See BP_Component::setup_nav() for description.
+	 * @param array $sub_nav  Optional. See BP_Component::setup_nav() for description.
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		// Determine user to use
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			$user_domain = false;
+		}
+
+		// Only grab count if we're on a user page
+		if ( bp_is_user() ) {
+			$count    = bp_get_total_group_count_for_user();
+			$class    = ( 0 === $count ) ? 'no-count' : 'count';
+			$nav_name = sprintf( _x( 'Groups <span class="%s">%s</span>', 'Group screen nav with counter', 'buddypress' ), esc_attr( $class ), bp_core_number_format( $count ) );
+		} else {
+			$nav_name = _x( 'Groups', 'Group screen nav without counter', 'buddypress' );
+		}
+
+		$slug = bp_get_groups_slug();
+
+		// Add 'Groups' to the main navigation
+		$main_nav = array(
+			'name'                => $nav_name,
+			'slug'                => $slug,
+			'position'            => 70,
+			'screen_function'     => 'groups_screen_my_groups',
+			'default_subnav_slug' => 'my-groups',
+			'item_css_id'         => $this->id
+		);
+
+		if ( ! empty( $user_domain ) ) {
+			$access      = bp_core_can_edit_settings();
+			$groups_link = trailingslashit( $user_domain . $slug );
+
+			// Add the My Groups nav item
+			$sub_nav[] = array(
+				'name'            => __( 'Memberships', 'buddypress' ),
+				'slug'            => 'my-groups',
+				'parent_url'      => $groups_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'groups_screen_my_groups',
+				'position'        => 10,
+				'item_css_id'     => 'groups-my-groups'
+			);
+
+			// Add the Group Invites nav item
+			$sub_nav[] = array(
+				'name'            => __( 'Invitations', 'buddypress' ),
+				'slug'            => 'invites',
+				'parent_url'      => $groups_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'groups_screen_group_invites',
+				'user_has_access' => $access,
+				'position'        => 30
+			);
+
+			parent::setup_nav( $main_nav, $sub_nav );
+		}
+
+		if ( bp_is_groups_component() && bp_is_single_item() ) {
+
+			// Reset sub nav
+			$sub_nav = array();
+
+			// Add 'Groups' to the main navigation
+			$main_nav = array(
+				'name'                => __( 'Memberships', 'buddypress' ),
+				'slug'                => $this->current_group->slug,
+				'position'            => -1, // Do not show in BuddyBar
+				'screen_function'     => 'groups_screen_group_home',
+				'default_subnav_slug' => $this->default_extension,
+				'item_css_id'         => $this->id
+			);
+
+			$group_link = bp_get_group_permalink( $this->current_group );
+
+			// Add the "Home" subnav item, as this will always be present
+			$sub_nav[] = array(
+				'name'            =>  _x( 'Home', 'Group screen navigation title', 'buddypress' ),
+				'slug'            => 'home',
+				'parent_url'      => $group_link,
+				'parent_slug'     => $this->current_group->slug,
+				'screen_function' => 'groups_screen_group_home',
+				'position'        => 10,
+				'item_css_id'     => 'home'
+			);
+
+			// If this is a private group, and the user is not a
+			// member and does not have an outstanding invitation,
+			// show a "Request Membership" nav item.
+			if ( is_user_logged_in() &&
+				 ! $this->current_group->is_user_member &&
+				 ! groups_check_for_membership_request( bp_loggedin_user_id(), $this->current_group->id ) &&
+				 $this->current_group->status == 'private' &&
+				 ! groups_check_user_has_invite( bp_loggedin_user_id(), $this->current_group->id )
+				) {
+
+				$sub_nav[] = array(
+					'name'            => _x( 'Request Membership','Group screen nav', 'buddypress' ),
+					'slug'            => 'request-membership',
+					'parent_url'      => $group_link,
+					'parent_slug'     => $this->current_group->slug,
+					'screen_function' => 'groups_screen_group_request_membership',
+					'position'        => 30
+				);
+			}
+
+			// Forums are enabled and turned on
+			if ( $this->current_group->enable_forum && bp_is_active( 'forums' ) ) {
+				$sub_nav[] = array(
+					'name'            => _x( 'Forum', 'My Group screen nav', 'buddypress' ),
+					'slug'            => 'forum',
+					'parent_url'      => $group_link,
+					'parent_slug'     => $this->current_group->slug,
+					'screen_function' => 'groups_screen_group_forum',
+					'position'        => 40,
+					'user_has_access' => $this->current_group->user_has_access,
+					'item_css_id'     => 'forums'
+				);
+			}
+
+			if ( $this->current_group->front_template || bp_is_active( 'activity' ) ) {
+				/**
+				 * If the theme is using a custom front, create activity subnav.
+				 */
+				if ( $this->current_group->front_template && bp_is_active( 'activity' ) ) {
+					$sub_nav[] = array(
+						'name'            => _x( 'Activity', 'My Group screen nav', 'buddypress' ),
+						'slug'            => 'activity',
+						'parent_url'      => $group_link,
+						'parent_slug'     => $this->current_group->slug,
+						'screen_function' => 'groups_screen_group_activity',
+						'position'        => 11,
+						'user_has_access' => $this->current_group->user_has_access,
+						'item_css_id'     => 'activity',
+						'no_access_url'   => $group_link,
+					);
+				}
+
+				/**
+				 * Only add the members subnav if it's not the home's nav
+				 */
+				$sub_nav[] = array(
+					'name'            => sprintf( _x( 'Members %s', 'My Group screen nav', 'buddypress' ), '<span>' . number_format( $this->current_group->total_member_count ) . '</span>' ),
+					'slug'            => 'members',
+					'parent_url'      => $group_link,
+					'parent_slug'     => $this->current_group->slug,
+					'screen_function' => 'groups_screen_group_members',
+					'position'        => 60,
+					'user_has_access' => $this->current_group->user_has_access,
+					'item_css_id'     => 'members',
+					'no_access_url'   => $group_link,
+				);
+			}
+
+			if ( bp_is_active( 'friends' ) && bp_groups_user_can_send_invites() ) {
+				$sub_nav[] = array(
+					'name'            => _x( 'Send Invites', 'My Group screen nav', 'buddypress' ),
+					'slug'            => 'send-invites',
+					'parent_url'      => $group_link,
+					'parent_slug'     => $this->current_group->slug,
+					'screen_function' => 'groups_screen_group_invite',
+					'item_css_id'     => 'invite',
+					'position'        => 70,
+					'user_has_access' => $this->current_group->user_has_access,
+					'no_access_url'   => $group_link,
+				);
+			}
+
+			// If the user is a group admin, then show the group admin nav item
+			if ( bp_is_item_admin() ) {
+				$sub_nav[] = array(
+					'name'            => _x( 'Manage', 'My Group screen nav', 'buddypress' ),
+					'slug'            => 'admin',
+					'parent_url'      => $group_link,
+					'parent_slug'     => $this->current_group->slug,
+					'screen_function' => 'groups_screen_group_admin',
+					'position'        => 1000,
+					'user_has_access' => true,
+					'item_css_id'     => 'admin',
+					'no_access_url'   => $group_link,
+				);
+
+				$admin_link = trailingslashit( $group_link . 'admin' );
+
+				// Common params to all nav items
+				$default_params = array(
+					'parent_url'        => $admin_link,
+					'parent_slug'       => $this->current_group->slug . '_manage',
+					'screen_function'   => 'groups_screen_group_admin',
+					'user_has_access'   => bp_is_item_admin(),
+					'show_in_admin_bar' => true,
+				);
+
+				$sub_nav[] = array_merge( array(
+					'name'     => __( 'Details', 'buddypress' ),
+					'slug'     => 'edit-details',
+					'position' => 0,
+				), $default_params );
+
+				$sub_nav[] = array_merge( array(
+					'name'     => __( 'Settings', 'buddypress' ),
+					'slug'     => 'group-settings',
+					'position' => 10,
+				), $default_params );
+
+				if ( ! bp_disable_group_avatar_uploads() && buddypress()->avatar->show_avatars ) {
+					$sub_nav[] = array_merge( array(
+						'name'     => __( 'Photo', 'buddypress' ),
+						'slug'     => 'group-avatar',
+						'position' => 20,
+					), $default_params );
+				}
+
+				if ( bp_group_use_cover_image_header() ) {
+					$sub_nav[] = array_merge( array(
+						'name'     => __( 'Cover Image', 'buddypress' ),
+						'slug'     => 'group-cover-image',
+						'position' => 25,
+					), $default_params );
+				}
+
+				$sub_nav[] = array_merge( array(
+					'name'     => __( 'Members', 'buddypress' ),
+					'slug'     => 'manage-members',
+					'position' => 30,
+				), $default_params );
+
+				if ( 'private' == $this->current_group->status ) {
+					$sub_nav[] = array_merge( array(
+						'name'     => __( 'Requests', 'buddypress' ),
+						'slug'     => 'membership-requests',
+						'position' => 40,
+					), $default_params );
+				}
+
+				$sub_nav[] = array_merge( array(
+					'name'     => __( 'Delete', 'buddypress' ),
+					'slug'     => 'delete-group',
+					'position' => 1000,
+				), $default_params );
+			}
+
+			parent::setup_nav( $main_nav, $sub_nav );
+		}
+
+		if ( isset( $this->current_group->user_has_access ) ) {
+
+			/**
+			 * Fires at the end of the groups navigation setup if user has access.
+			 *
+			 * @since 1.0.2
+			 *
+			 * @param bool $user_has_access Whether or not user has access.
+			 */
+			do_action( 'groups_setup_nav', $this->current_group->user_has_access );
+		} else {
+
+			/** This action is documented in bp-groups/bp-groups-loader.php */
+			do_action( 'groups_setup_nav');
+		}
+	}
+
+	/**
+	 * Set up the component entries in the WordPress Admin Bar.
+	 *
+	 * @see BP_Component::setup_nav() for a description of the $wp_admin_nav
+	 *      parameter array.
+	 *
+	 * @param array $wp_admin_nav See BP_Component::setup_admin_bar() for a description.
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		// Menus for logged in user
+		if ( is_user_logged_in() ) {
+
+			// Setup the logged in user variables
+			$groups_link = trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() );
+
+			// Pending group invites
+			$count   = groups_get_invite_count_for_user();
+			$title   = _x( 'Groups', 'My Account Groups', 'buddypress' );
+			$pending = _x( 'No Pending Invites', 'My Account Groups sub nav', 'buddypress' );
+
+			if ( ! empty( $count['total'] ) ) {
+				$title   = sprintf( _x( 'Groups <span class="count">%s</span>',          'My Account Groups nav',     'buddypress' ), bp_core_number_format( $count ) );
+				$pending = sprintf( _x( 'Pending Invites <span class="count">%s</span>', 'My Account Groups sub nav', 'buddypress' ), bp_core_number_format( $count ) );
+			}
+
+			// Add the "My Account" sub menus
+			$wp_admin_nav[] = array(
+				'parent' => buddypress()->my_account_menu_id,
+				'id'     => 'my-account-' . $this->id,
+				'title'  => $title,
+				'href'   => $groups_link
+			);
+
+			// My Groups
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-memberships',
+				'title'  => _x( 'Memberships', 'My Account Groups sub nav', 'buddypress' ),
+				'href'   => $groups_link
+			);
+
+			// Invitations
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-invites',
+				'title'  => $pending,
+				'href'   => trailingslashit( $groups_link . 'invites' )
+			);
+
+			// Create a Group
+			if ( bp_user_can_create_groups() ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-create',
+					'title'  => _x( 'Create a Group', 'My Account Groups sub nav', 'buddypress' ),
+					'href'   => trailingslashit( bp_get_groups_directory_permalink() . 'create' )
+				);
+			}
+		}
+
+		parent::setup_admin_bar( $wp_admin_nav );
+	}
+
+	/**
+	 * Set up the title for pages and <title>.
+	 */
+	public function setup_title() {
+
+		if ( bp_is_groups_component() ) {
+			$bp = buddypress();
+
+			if ( bp_is_my_profile() && !bp_is_single_item() ) {
+				$bp->bp_options_title = _x( 'Memberships', 'My Groups page <title>', 'buddypress' );
+
+			} elseif ( !bp_is_my_profile() && !bp_is_single_item() ) {
+				$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+					'item_id' => bp_displayed_user_id(),
+					'type'    => 'thumb',
+					'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_displayed_user_fullname() )
+				) );
+				$bp->bp_options_title = bp_get_displayed_user_fullname();
+
+			// We are viewing a single group, so set up the
+			// group navigation menu using the $this->current_group global.
+			} elseif ( bp_is_single_item() ) {
+				$bp->bp_options_title  = $this->current_group->name;
+				$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+					'item_id'    => $this->current_group->id,
+					'object'     => 'group',
+					'type'       => 'thumb',
+					'avatar_dir' => 'group-avatars',
+					'alt'        => __( 'Group Profile Photo', 'buddypress' )
+				) );
+
+				if ( empty( $bp->bp_options_avatar ) ) {
+					$bp->bp_options_avatar = '<img src="' . esc_url( bp_core_avatar_default_thumb() ) . '" alt="' . esc_attr__( 'No Group Profile Photo', 'buddypress' ) . '" class="avatar" />';
+				}
+			}
+		}
+
+		parent::setup_title();
+	}
+
+	/**
+	 * Setup cache groups
+	 *
+	 * @since 2.2.0
+	 */
+	public function setup_cache_groups() {
+
+		// Global groups
+		wp_cache_add_global_groups( array(
+			'bp_groups',
+			'bp_group_admins',
+			'bp_group_invite_count',
+			'group_meta'
+		) );
+
+		parent::setup_cache_groups();
+	}
+}
+
+/**
+ * Bootstrap the Notifications component.
+ *
+ * @since 1.5.0
+ */
+function bp_setup_groups() {
+	buddypress()->groups = new BP_Groups_Component();
+}
+add_action( 'bp_setup_components', 'bp_setup_groups', 6 );
Index: /tags/2.4.3/src/bp-groups/bp-groups-notifications.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-notifications.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-notifications.php	(revision 10410)
@@ -0,0 +1,1316 @@
+<?php
+/**
+ * BuddyPress Groups Notification Functions.
+ *
+ * These functions handle the recording, deleting and formatting of notifications
+ * for the user and for this specific component.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsActivity
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/** Emails ********************************************************************/
+
+/**
+ * Notify all group members when a group is updated.
+ *
+ * @since 1.0.0
+ *
+ * @param int                  $group_id  ID of the group.
+ * @param BP_Groups_Group|null $old_group Group before new details were saved.
+ */
+function groups_notification_group_updated( $group_id = 0, $old_group = null ) {
+
+	$group = groups_get_group( array( 'group_id' => $group_id ) );
+
+	if ( $old_group instanceof BP_Groups_Group ) {
+		$changed = array();
+
+		if ( $group->name !== $old_group->name ) {
+			$changed[] = sprintf(
+				_x( '* Name changed from "%s" to "%s"', 'Group update email text', 'buddypress' ),
+				esc_html( $old_group->name ),
+				esc_html( $group->name )
+			);
+		}
+
+		if ( $group->description !== $old_group->description ) {
+			$changed[] = sprintf(
+				_x( '* Description changed from "%s" to "%s"', 'Group update email text', 'buddypress' ),
+				esc_html( $old_group->description ),
+				esc_html( $group->description )
+			);
+		}
+	}
+
+	/**
+	 * Filters the bullet points listing updated items in the email notification after a group is updated.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $changed Array of bullet points.
+	 */
+	$changed = apply_filters( 'groups_notification_group_update_updated_items', $changed );
+
+	$changed_text = '';
+	if ( ! empty( $changed ) ) {
+		$changed_text = "\n\n" . implode( "\n", $changed );
+	}
+
+	$subject  = bp_get_email_subject( array( 'text' => __( 'Group Details Updated', 'buddypress' ) ) );
+	$user_ids = BP_Groups_Member::get_group_member_ids( $group->id );
+
+	foreach ( (array) $user_ids as $user_id ) {
+
+		// Continue if member opted out of receiving this email
+		if ( 'no' === bp_get_user_meta( $user_id, 'notification_groups_group_updated', true ) ) {
+			continue;
+		}
+
+		$ud = bp_core_get_core_userdata( $user_id );
+
+		// Set up and send the message
+		$to = $ud->user_email;
+
+		$group_link    = bp_get_group_permalink( $group );
+		$settings_slug = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+		$settings_link = bp_core_get_user_domain( $user_id ) . $settings_slug . '/notifications/';
+
+		$message = sprintf( __(
+'Group details for the group "%1$s" were updated: %2$s
+
+To view the group: %3$s
+
+---------------------
+', 'buddypress' ), $group->name, $changed_text, $group_link );
+
+		$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+
+		/**
+		 * Filters the user email that the group update notification will be sent to.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $to User email the notification is being sent to.
+		 */
+		$to      = apply_filters( 'groups_notification_group_updated_to', $to );
+
+		/**
+		 * Filters the group update notification subject that will be sent to user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string          $subject Email notification subject text.
+		 * @param BP_Groups_Group $group   Object holding the current group instance. Passed by reference.
+		 */
+		$subject = apply_filters_ref_array( 'groups_notification_group_updated_subject', array( $subject, &$group ) );
+
+		/**
+		 * Filters the group update notification message that will be sent to user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string          $message       Email notification message text.
+		 * @param BP_Groups_Group $group         Object holding the current group instance. Passed by reference.
+		 * @param string          $group_link    URL permalink to the group that was updated.
+		 * @param string          $settings_link URL permalink for the user's notification settings area.
+		 */
+		$message = apply_filters_ref_array( 'groups_notification_group_updated_message', array( $message, &$group, $group_link, $settings_link ) );
+
+		wp_mail( $to, $subject, $message );
+
+		unset( $message, $to );
+	}
+
+	/**
+	 * Fires after the notification is sent that a group has been updated.
+	 *
+	 * See https://buddypress.trac.wordpress.org/ticket/3644 for blank message parameter.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array  $user_ids Array of user IDs to notify about the update.
+	 * @param string $subject  Email notification subject text.
+	 * @param string $value    Empty string preventing PHP error.
+	 * @param int    $group_id ID of the group that was updated.
+	 */
+	do_action( 'bp_groups_sent_updated_email', $user_ids, $subject, '', $group_id );
+}
+
+/**
+ * Notify group admin about new membership request.
+ *
+ * @since 1.0.0
+ *
+ * @param int $requesting_user_id ID of the user requesting group membership.
+ * @param int $admin_id           ID of the group admin.
+ * @param int $group_id           ID of the group.
+ * @param int $membership_id      ID of the group membership object.
+ *
+ * @return false|null False on failure.
+ */
+function groups_notification_new_membership_request( $requesting_user_id = 0, $admin_id = 0, $group_id = 0, $membership_id = 0 ) {
+
+	// Trigger a BuddyPress Notification
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_add_notification( array(
+			'user_id'           => $admin_id,
+			'item_id'           => $group_id,
+			'secondary_item_id' => $requesting_user_id,
+			'component_name'    => buddypress()->groups->id,
+			'component_action'  => 'new_membership_request'
+		) );
+	}
+
+	// Bail if member opted out of receiving this email
+	if ( 'no' === bp_get_user_meta( $admin_id, 'notification_groups_membership_request', true ) ) {
+		return false;
+	}
+
+	// Username of the user requesting a membership: %1$s in mail
+	$requesting_user_name = bp_core_get_user_displayname( $requesting_user_id );
+	$group                = groups_get_group( array( 'group_id' => $group_id ) );
+
+	// Group Administrator user's data
+	$ud             = bp_core_get_core_userdata( $admin_id );
+	$group_requests = bp_get_group_permalink( $group ) . 'admin/membership-requests';
+
+	// Link to the user's profile who's requesting a membership: %3$s in mail
+	$profile_link   = bp_core_get_user_domain( $requesting_user_id );
+
+	$settings_slug  = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+	// Link to the group administrator email settings: %s in "disable notifications" part of the email
+	$settings_link  = bp_core_get_user_domain( $admin_id ) . $settings_slug . '/notifications/';
+
+	// Fetch the message, if there's one to fetch.
+	$membership = new BP_Groups_Member( false, false, $membership_id );
+
+	// Set up and send the message
+	$to       = $ud->user_email;
+	$subject  = bp_get_email_subject( array( 'text' => sprintf( __( 'Membership request for group: %s', 'buddypress' ), $group->name ) ) );
+
+	if ( ! empty( $membership->comments ) ) {
+		$message = sprintf( __(
+'%1$s wants to join the group "%2$s".
+
+Message from %1$s: "%3$s"
+
+Because you are the administrator of this group, you must either accept or reject the membership request.
+
+To view all pending membership requests for this group, please visit:
+%4$s
+
+To view %5$s\'s profile: %6$s
+
+---------------------
+', 'buddypress' ), $requesting_user_name, $group->name, esc_html( $membership->comments ), $group_requests, $requesting_user_name, $profile_link );
+
+	} else {
+
+		$message = sprintf( __(
+'%1$s wants to join the group "%2$s".
+
+Because you are the administrator of this group, you must either accept or reject the membership request.
+
+To view all pending membership requests for this group, please visit:
+%3$s
+
+To view %4$s\'s profile: %5$s
+
+---------------------
+', 'buddypress' ), $requesting_user_name, $group->name, $group_requests, $requesting_user_name, $profile_link );
+	}
+
+	// Only show the disable notifications line if the settings component is enabled
+	if ( bp_is_active( 'settings' ) ) {
+		$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+	}
+
+	/**
+	 * Filters the user email that the group membership request will be sent to.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $to User email the request is being sent to.
+	 */
+	$to      = apply_filters( 'groups_notification_new_membership_request_to', $to );
+
+	/**
+	 * Filters the group membership request subject that will be sent to user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string          $subject Membership request email subject text.
+	 * @param BP_Groups_Group $group   Object holding the current group instance. Passed by reference.
+	 */
+	$subject = apply_filters_ref_array( 'groups_notification_new_membership_request_subject', array( $subject, &$group ) );
+
+	/**
+	 * Filters the group membership request message that will be sent to user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string          $message              Membership request email message text.
+	 * @param BP_Groups_Group $group                Object holding the current group instance. Passed by reference.
+	 * @param string          $requesting_user_name Username of who is requesting membership.
+	 * @param string          $profile_link         URL permalink for the profile for the user requesting membership.
+	 * @param string          $group_requests       URL permalink for the group requests screen for group being requested membership to.
+	 * @param string          $settings_link        URL permalink for the user's notification settings area.
+	 */
+	$message = apply_filters_ref_array( 'groups_notification_new_membership_request_message', array( $message, &$group, $requesting_user_name, $profile_link, $group_requests, $settings_link ) );
+
+	wp_mail( $to, $subject, $message );
+
+	/**
+	 * Fires after the notification is sent that a member has requested group membership.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int    $admin_id           ID of the group administrator.
+	 * @param string $subject            Email notification subject text.
+	 * @param string $message            Email notification message text.
+	 * @param int    $requesting_user_id ID of the user requesting membership.
+	 * @param int    $group_id           ID of the group receiving membership request.
+	 * @param int    $membership_id      ID of the group membership object.
+	 */
+	do_action( 'bp_groups_sent_membership_request_email', $admin_id, $subject, $message, $requesting_user_id, $group_id, $membership_id );
+}
+
+/**
+ * Notify member about their group membership request.
+ *
+ * @since 1.0.0
+ *
+ * @param int  $requesting_user_id ID of the user requesting group membership.
+ * @param int  $group_id           ID of the group.
+ * @param bool $accepted           Optional. Whether the membership request was accepted.
+ *                                 Default: true.
+ *
+ * @return false|null
+ */
+function groups_notification_membership_request_completed( $requesting_user_id = 0, $group_id = 0, $accepted = true ) {
+
+	// Trigger a BuddyPress Notification
+	if ( bp_is_active( 'notifications' ) ) {
+
+		// What type of acknowledgement
+		$type = ! empty( $accepted )
+			? 'membership_request_accepted'
+			: 'membership_request_rejected';
+
+		bp_notifications_add_notification( array(
+			'user_id'           => $requesting_user_id,
+			'item_id'           => $group_id,
+			'component_name'    => buddypress()->groups->id,
+			'component_action'  => $type
+		) );
+	}
+
+	// Bail if member opted out of receiving this email
+	if ( 'no' === bp_get_user_meta( $requesting_user_id, 'notification_membership_request_completed', true ) ) {
+		return false;
+	}
+
+	$group         = groups_get_group( array( 'group_id' => $group_id ) );
+	$ud            = bp_core_get_core_userdata( $requesting_user_id );
+	$group_link    = bp_get_group_permalink( $group );
+	$settings_slug = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+	$settings_link = bp_core_get_user_domain( $requesting_user_id ) . $settings_slug . '/notifications/';
+	$to            = $ud->user_email;
+
+	// Set up and send the message
+	if ( ! empty( $accepted ) ) {
+		$subject = bp_get_email_subject( array( 'text' => sprintf( __( 'Membership request for group "%s" accepted', 'buddypress' ), $group->name ) ) );
+		$message = sprintf( __(
+'Your membership request for the group "%1$s" has been accepted.
+
+To view the group please login and visit: %2$s
+
+---------------------
+', 'buddypress' ), $group->name, $group_link );
+
+	} else {
+		$subject = bp_get_email_subject( array( 'text' => sprintf( __( 'Membership request for group "%s" rejected', 'buddypress' ), $group->name ) ) );
+		$message = sprintf( __(
+'Your membership request for the group "%1$s" has been rejected.
+
+To submit another request please log in and visit: %2$s
+
+---------------------
+', 'buddypress' ), $group->name, $group_link );
+	}
+
+	// Only show the disable notifications line if the settings component is enabled
+	if ( bp_is_active( 'settings' ) ) {
+		$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+	}
+
+	/**
+	 * Filters the user email that the group membership request result will be sent to.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $to User email the request result is being sent to.
+	 */
+	$to      = apply_filters( 'groups_notification_membership_request_completed_to', $to );
+
+	/**
+	 * Filters the group membership request result subject that will be sent to user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string          $subject Membership request result email subject text.
+	 * @param BP_Groups_Group $group   Object holding the current group instance. Passed by reference.
+	 */
+	$subject = apply_filters_ref_array( 'groups_notification_membership_request_completed_subject', array( $subject, &$group ) );
+
+	/**
+	 * Filters the group membership request result message that will be sent to user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string          $message       Membership request result email message text.
+	 * @param BP_Groups_Group $group         Object holding the current group instance. Passed by reference.
+	 * @param string          $group_link    URL permalink for the group that was requested membership for.
+	 * @param string          $settings_link URL permalink for the user's notification settings area.
+	 */
+	$message = apply_filters_ref_array( 'groups_notification_membership_request_completed_message', array( $message, &$group, $group_link, $settings_link ) );
+
+	wp_mail( $to, $subject, $message );
+
+	/**
+	 * Fires after the notification is sent that a membership has been approved.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int    $requesting_user_id ID of the user whose membership was approved.
+	 * @param string $subject            Email notification subject text.
+	 * @param string $message            Email notification message text.
+	 * @param int    $group_id           ID of the group that was joined.
+	 */
+	do_action( 'bp_groups_sent_membership_approved_email', $requesting_user_id, $subject, $message, $group_id );
+}
+add_action( 'groups_membership_accepted', 'groups_notification_membership_request_completed', 10, 3 );
+add_action( 'groups_membership_rejected', 'groups_notification_membership_request_completed', 10, 3 );
+
+/**
+ * Notify group member they have been promoted.
+ *
+ * @since 1.0.0
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ *
+ * @return false|null False on failure.
+ */
+function groups_notification_promoted_member( $user_id = 0, $group_id = 0 ) {
+
+	// What type of promotion is this?
+	if ( groups_is_user_admin( $user_id, $group_id ) ) {
+		$promoted_to = __( 'an administrator', 'buddypress' );
+		$type        = 'member_promoted_to_admin';
+	} else {
+		$promoted_to = __( 'a moderator', 'buddypress' );
+		$type        = 'member_promoted_to_mod';
+	}
+
+	// Trigger a BuddyPress Notification
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_add_notification( array(
+			'user_id'           => $user_id,
+			'item_id'           => $group_id,
+			'component_name'    => buddypress()->groups->id,
+			'component_action'  => $type
+		) );
+	}
+
+	// Bail if admin opted out of receiving this email
+	if ( 'no' === bp_get_user_meta( $user_id, 'notification_groups_admin_promotion', true ) ) {
+		return false;
+	}
+
+	$group         = groups_get_group( array( 'group_id' => $group_id ) );
+	$ud            = bp_core_get_core_userdata($user_id);
+	$group_link    = bp_get_group_permalink( $group );
+	$settings_slug = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+	$settings_link = bp_core_get_user_domain( $user_id ) . $settings_slug . '/notifications/';
+
+	// Set up and send the message
+	$to       = $ud->user_email;
+	$subject  = bp_get_email_subject( array( 'text' => sprintf( __( 'You have been promoted in the group: "%s"', 'buddypress' ), $group->name ) ) );
+	$message  = sprintf( __(
+'You have been promoted to %1$s for the group: "%2$s".
+
+To view the group please visit: %3$s
+
+---------------------
+', 'buddypress' ), $promoted_to, $group->name, $group_link );
+
+	// Only show the disable notifications line if the settings component is enabled
+	if ( bp_is_active( 'settings' ) ) {
+		$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+	}
+
+	/**
+	 * Filters the user email that the group promotion notification will be sent to.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $to User email the promotion notification is being sent to.
+	 */
+	$to      = apply_filters( 'groups_notification_promoted_member_to', $to );
+
+	/**
+	 * Filters the group promotion notification subject that will be sent to user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string          $subject Promotion notification email subject text.
+	 * @param BP_Groups_Group $group   Object holding the current group instance. Passed by reference.
+	 */
+	$subject = apply_filters_ref_array( 'groups_notification_promoted_member_subject', array( $subject, &$group ) );
+
+	/**
+	 * Filters the group promotion notification message that will be sent to user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string          $message       Promotion notification email message text.
+	 * @param BP_Groups_Group $group         Object holding the current group instance. Passed by reference.
+	 * @param string          $promoted_to   Role that the user was promoted to within the group.
+	 * @param string          $group_link    URL permalink for the group that the promotion was related to.
+	 * @param string          $settings_link URL permalink for the user's notification settings area.
+	 */
+	$message = apply_filters_ref_array( 'groups_notification_promoted_member_message', array( $message, &$group, $promoted_to, $group_link, $settings_link ) );
+
+	wp_mail( $to, $subject, $message );
+
+	/**
+	 * Fires after the notification is sent that a member has been promoted.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int    $user_id  ID of the user who was promoted.
+	 * @param string $subject  Email notification subject text.
+	 * @param string $message  Email notification message text.
+	 * @param int    $group_id ID of the group that the user is a member of.
+	 */
+	do_action( 'bp_groups_sent_promoted_email', $user_id, $subject, $message, $group_id );
+}
+add_action( 'groups_promoted_member', 'groups_notification_promoted_member', 10, 2 );
+
+/**
+ * Notify a member they have been invited to a group.
+ *
+ * @since 1.0.0
+ *
+ * @param BP_Groups_Group  $group           Group object.
+ * @param BP_Groups_Member $member          Member object.
+ * @param int              $inviter_user_id ID of the user who sent the invite.
+ *
+ * @return null|false False on failure.
+ */
+function groups_notification_group_invites( &$group, &$member, $inviter_user_id ) {
+
+	// Bail if member has already been invited
+	if ( ! empty( $member->invite_sent ) ) {
+		return;
+	}
+
+	// @todo $inviter_ud may be used for caching, test without it
+	$inviter_ud   = bp_core_get_core_userdata( $inviter_user_id );
+	$inviter_name = bp_core_get_userlink( $inviter_user_id, true, false, true );
+	$inviter_link = bp_core_get_user_domain( $inviter_user_id );
+	$group_link   = bp_get_group_permalink( $group );
+
+	// Setup the ID for the invited user
+	$invited_user_id = $member->user_id;
+
+	// Trigger a BuddyPress Notification
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_add_notification( array(
+			'user_id'          => $invited_user_id,
+			'item_id'          => $group->id,
+			'component_name'   => buddypress()->groups->id,
+			'component_action' => 'group_invite'
+		) );
+	}
+
+	// Bail if member opted out of receiving this email
+	if ( 'no' === bp_get_user_meta( $invited_user_id, 'notification_groups_invite', true ) ) {
+		return false;
+	}
+
+	$invited_ud    = bp_core_get_core_userdata( $invited_user_id );
+	$settings_slug = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+	$settings_link = bp_core_get_user_domain( $invited_user_id ) . $settings_slug . '/notifications/';
+	$invited_link  = bp_core_get_user_domain( $invited_user_id );
+	$invites_link  = trailingslashit( $invited_link . bp_get_groups_slug() . '/invites' );
+
+	// Set up and send the message
+	$to       = $invited_ud->user_email;
+	$subject  = bp_get_email_subject( array( 'text' => sprintf( __( 'You have an invitation to the group: "%s"', 'buddypress' ), $group->name ) ) );
+	$message  = sprintf( __(
+'One of your friends %1$s has invited you to the group: "%2$s".
+
+To view your group invites visit: %3$s
+
+To view the group visit: %4$s
+
+To view %5$s\'s profile visit: %6$s
+
+---------------------
+', 'buddypress' ), $inviter_name, $group->name, $invites_link, $group_link, $inviter_name, $inviter_link );
+
+	// Only show the disable notifications line if the settings component is enabled
+	if ( bp_is_active( 'settings' ) ) {
+		$message .= sprintf( __( 'To disable these notifications please log in and go to: %s', 'buddypress' ), $settings_link );
+	}
+
+	/**
+	 * Filters the user email that the group invite notification will be sent to.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $to User email the invite notification is being sent to.
+	 */
+	$to      = apply_filters( 'groups_notification_group_invites_to', $to );
+
+	/**
+	 * Filters the group invite notification subject that will be sent to user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string          $subject Invite notification email subject text.
+	 * @param BP_Groups_Group $group   Object holding the current group instance. Passed by reference.
+	 */
+	$subject = apply_filters_ref_array( 'groups_notification_group_invites_subject', array( $subject, &$group ) );
+
+	/**
+	 * Filters the group invite notification message that will be sent to user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string          $message       Invite notification email message text.
+	 * @param BP_Groups_Group $group         Object holding the current group instance. Passed by reference.
+	 * @param string          $inviter_name  Username for the person doing the inviting.
+	 * @param string          $inviter_link  Profile link for the person doing the inviting.
+	 * @param string          $invites_link  URL permalink for the invited user's invite management screen.
+	 * @param string          $group_link    URL permalink for the group that the invite was related to.
+	 * @param string          $settings_link URL permalink for the user's notification settings area.
+	 */
+	$message = apply_filters_ref_array( 'groups_notification_group_invites_message', array( $message, &$group, $inviter_name, $inviter_link, $invites_link, $group_link, $settings_link ) );
+
+	wp_mail( $to, $subject, $message );
+
+	/**
+	 * Fires after the notification is sent that a member has been invited to a group.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int             $invited_user_id  ID of the user who was invited.
+	 * @param string          $subject          Email notification subject text.
+	 * @param string          $message          Email notification message text.
+	 * @param BP_Groups_Group $group            Group object.
+	 */
+	do_action( 'bp_groups_sent_invited_email', $invited_user_id, $subject, $message, $group );
+}
+
+/** Notifications *************************************************************/
+
+/**
+ * Format notifications for the Groups component.
+ *
+ * @since 1.0.0
+ *
+ * @param string $action            The kind of notification being rendered.
+ * @param int    $item_id           The primary item ID.
+ * @param int    $secondary_item_id The secondary item ID.
+ * @param int    $total_items       The total number of messaging-related notifications
+ *                                  waiting for the user.
+ * @param string $format            'string' for BuddyBar-compatible notifications; 'array'
+ *                                  for WP Toolbar. Default: 'string'.
+ *
+ * @return string
+ */
+function groups_format_notifications( $action, $item_id, $secondary_item_id, $total_items, $format = 'string' ) {
+
+	switch ( $action ) {
+		case 'new_membership_request':
+			$group_id = $item_id;
+			$requesting_user_id = $secondary_item_id;
+
+			$group = groups_get_group( array( 'group_id' => $group_id ) );
+			$group_link = bp_get_group_permalink( $group );
+			$amount = 'single';
+
+			// Set up the string and the filter
+			// Because different values are passed to the filters, we'll return the
+			// values inline
+			if ( (int) $total_items > 1 ) {
+				$text = sprintf( __( '%1$d new membership requests for the group "%2$s"', 'buddypress' ), (int) $total_items, $group->name );
+				$amount = 'multiple';
+				$notification_link = $group_link . 'admin/membership-requests/?n=1';
+
+				if ( 'string' == $format ) {
+
+					/**
+					 * Filters groups multiple new membership request notification for string format.
+					 *
+					 * This is a dynamic filter that is dependent on item count and action.
+					 * Complete filter - bp_groups_multiple_new_membership_requests_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for request.
+					 * @param string $group_link        The permalink for the group.
+					 * @param int    $total_items       Total number of membership requests.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . 's_notification', '<a href="' . $notification_link . '" title="' . __( 'Group Membership Requests', 'buddypress' ) . '">' . $text . '</a>', $group_link, $total_items, $group->name, $text, $notification_link );
+				} else {
+
+					/**
+					 * Filters groups multiple new membership request notification for any non-string format.
+					 *
+					 * This is a dynamic filter that is dependent on item count and action.
+					 * Complete filter - bp_groups_multiple_new_membership_requests_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param string $group_link        The permalink for the group.
+					 * @param int    $total_items       Total number of membership requests.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . 's_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $group_link, $total_items, $group->name, $text, $notification_link );
+				}
+			} else {
+				$user_fullname = bp_core_get_user_displayname( $requesting_user_id );
+				$text = sprintf( __( '%s requests group membership', 'buddypress' ), $user_fullname );
+				$notification_link = $group_link . 'admin/membership-requests/?n=1';
+
+				if ( 'string' == $format ) {
+
+					/**
+					 * Filters groups single new membership request notification for string format.
+					 *
+					 * This is a dynamic filter that is dependent on item count and action.
+					 * Complete filter - bp_groups_single_new_membership_request_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for request.
+					 * @param string $group_link        The permalink for the group.
+					 * @param string $user_fullname     Full name of requesting user.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '" title="' . sprintf( __( '%s requests group membership', 'buddypress' ), $user_fullname ) . '">' . $text . '</a>', $group_link, $user_fullname, $group->name, $text, $notification_link );
+				} else {
+
+					/**
+					 * Filters groups single new membership request notification for any non-string format.
+					 *
+					 * This is a dynamic filter that is dependent on item count and action.
+					 * Complete filter - bp_groups_single_new_membership_request_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param string $group_link        The permalink for the group.
+					 * @param string $user_fullname     Full name of requesting user.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $group_link, $user_fullname, $group->name, $text, $notification_link );
+				}
+			}
+
+			break;
+
+		case 'membership_request_accepted':
+			$group_id = $item_id;
+
+			$group = groups_get_group( array( 'group_id' => $group_id ) );
+			$group_link = bp_get_group_permalink( $group );
+			$amount = 'single';
+
+			if ( (int) $total_items > 1 ) {
+				$text = sprintf( __( '%d accepted group membership requests', 'buddypress' ), (int) $total_items, $group->name );
+				$amount = 'multiple';
+				$notification_link = trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() ) . '?n=1';
+
+				if ( 'string' == $format ) {
+
+					/**
+					 * Filters multiple accepted group membership requests notification for string format.
+					 * Complete filter - bp_groups_multiple_membership_request_accepted_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for notification.
+					 * @param int    $total_items       Total number of accepted requests.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '" title="' . __( 'Groups', 'buddypress' ) . '">' . $text . '</a>', $total_items, $group->name, $text, $notification_link );
+				} else {
+
+					/**
+					 * Filters multiple accepted group membership requests notification for non-string format.
+					 * Complete filter - bp_groups_multiple_membership_request_accepted_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification
+					 * @param int    $total_items       Total number of accepted requests.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $total_items, $group->name, $text, $notification_link );
+				}
+			} else {
+				$text = sprintf( __( 'Membership for group "%s" accepted', 'buddypress' ), $group->name );
+				$filter = 'bp_groups_single_membership_request_accepted_notification';
+				$notification_link = $group_link . '?n=1';
+
+				if ( 'string' == $format ) {
+
+					/**
+					 * Filters single accepted group membership request notification for string format.
+					 * Complete filter - bp_groups_single_membership_request_accepted_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for notification.
+					 * @param string $group_link        The permalink for the group.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '">' . $text . '</a>', $group_link, $group->name, $text, $notification_link );
+				} else {
+
+					/**
+					 * Filters single accepted group membership request notification for non-string format.
+					 * Complete filter - bp_groups_single_membership_request_accepted_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param string $group_link        The permalink for the group.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( $filter, array(
+						'link' => $notification_link,
+						'text' => $text
+					), $group_link, $group->name, $text, $notification_link );
+				}
+			}
+
+			break;
+
+		case 'membership_request_rejected':
+			$group_id = $item_id;
+
+			$group = groups_get_group( array( 'group_id' => $group_id ) );
+			$group_link = bp_get_group_permalink( $group );
+			$amount = 'single';
+
+			if ( (int) $total_items > 1 ) {
+				$text = sprintf( __( '%d rejected group membership requests', 'buddypress' ), (int) $total_items, $group->name );
+				$amount = 'multiple';
+				$notification_link = trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() ) . '?n=1';
+
+				if ( 'string' == $format ) {
+
+					/**
+					 * Filters multiple rejected group membership requests notification for string format.
+					 * Complete filter - bp_groups_multiple_membership_request_rejected_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for notification.
+					 * @param int    $total_items       Total number of rejected requests.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '" title="' . __( 'Groups', 'buddypress' ) . '">' . $text . '</a>', $total_items, $group->name );
+				} else {
+
+					/**
+					 * Filters multiple rejected group membership requests notification for non-string format.
+					 * Complete filter - bp_groups_multiple_membership_request_rejected_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param int    $total_items       Total number of rejected requests.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $total_items, $group->name, $text, $notification_link );
+				}
+			} else {
+				$text = sprintf( __( 'Membership for group "%s" rejected', 'buddypress' ), $group->name );
+				$notification_link = $group_link . '?n=1';
+
+				if ( 'string' == $format ) {
+
+					/**
+					 * Filters single rejected group membership requests notification for string format.
+					 * Complete filter - bp_groups_single_membership_request_rejected_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for notification.
+					 * @param int    $group_link        The permalink for the group.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '">' . $text . '</a>', $group_link, $group->name, $text, $notification_link );
+				} else {
+
+					/**
+					 * Filters single rejected group membership requests notification for non-string format.
+					 * Complete filter - bp_groups_single_membership_request_rejected_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param int    $group_link        The permalink for the group.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $group_link, $group->name, $text, $notification_link );
+				}
+			}
+
+			break;
+
+		case 'member_promoted_to_admin':
+			$group_id = $item_id;
+
+			$group = groups_get_group( array( 'group_id' => $group_id ) );
+			$group_link = bp_get_group_permalink( $group );
+			$amount = 'single';
+
+			if ( (int) $total_items > 1 ) {
+				$text = sprintf( __( 'You were promoted to an admin in %d groups', 'buddypress' ), (int) $total_items );
+				$amount = 'multiple';
+				$notification_link = trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() ) . '?n=1';
+
+				if ( 'string' == $format ) {
+					/**
+					 * Filters multiple promoted to group admin notification for string format.
+					 * Complete filter - bp_groups_multiple_member_promoted_to_admin_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for notification.
+					 * @param int    $total_items       Total number of rejected requests.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '" title="' . __( 'Groups', 'buddypress' ) . '">' . $text . '</a>', $total_items, $text, $notification_link );
+				} else {
+					/**
+					 * Filters multiple promoted to group admin notification for non-string format.
+					 * Complete filter - bp_groups_multiple_member_promoted_to_admin_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param int    $total_items       Total number of rejected requests.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $total_items, $text, $notification_link );
+				}
+			} else {
+				$text = sprintf( __( 'You were promoted to an admin in the group "%s"', 'buddypress' ), $group->name );
+				$notification_link = $group_link . '?n=1';
+
+				if ( 'string' == $format ) {
+					/**
+					 * Filters single promoted to group admin notification for non-string format.
+					 * Complete filter - bp_groups_single_member_promoted_to_admin_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for notification.
+					 * @param int    $group_link        The permalink for the group.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '">' . $text . '</a>', $group_link, $group->name, $text, $notification_link );
+				} else {
+					/**
+					 * Filters single promoted to group admin notification for non-string format.
+					 * Complete filter - bp_groups_single_member_promoted_to_admin_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param int    $group_link        The permalink for the group.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $group_link, $group->name, $text, $notification_link );
+				}
+			}
+
+			break;
+
+		case 'member_promoted_to_mod':
+			$group_id = $item_id;
+
+			$group = groups_get_group( array( 'group_id' => $group_id ) );
+			$group_link = bp_get_group_permalink( $group );
+			$amount = 'single';
+
+			if ( (int) $total_items > 1 ) {
+				$text = sprintf( __( 'You were promoted to a mod in %d groups', 'buddypress' ), (int) $total_items );
+				$amount = 'multiple';
+				$notification_link = trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() ) . '?n=1';
+
+				if ( 'string' == $format ) {
+					/**
+					 * Filters multiple promoted to group mod notification for string format.
+					 * Complete filter - bp_groups_multiple_member_promoted_to_mod_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for notification.
+					 * @param int    $total_items       Total number of rejected requests.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '" title="' . __( 'Groups', 'buddypress' ) . '">' . $text . '</a>', $total_items, $text, $notification_link );
+				} else {
+					/**
+					 * Filters multiple promoted to group mod notification for non-string format.
+					 * Complete filter - bp_groups_multiple_member_promoted_to_mod_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param int    $total_items       Total number of rejected requests.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $total_items, $text, $notification_link );
+				}
+			} else {
+				$text = sprintf( __( 'You were promoted to a mod in the group "%s"', 'buddypress' ), $group->name );
+				$notification_link = $group_link . '?n=1';
+
+				if ( 'string' == $format ) {
+					/**
+					 * Filters single promoted to group mod notification for string format.
+					 * Complete filter - bp_groups_single_member_promoted_to_mod_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for notification.
+					 * @param int    $group_link        The permalink for the group.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '">' . $text . '</a>', $group_link, $group->name, $text, $notification_link );
+				} else {
+					/**
+					 * Filters single promoted to group admin notification for non-string format.
+					 * Complete filter - bp_groups_single_member_promoted_to_mod_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param int    $group_link        The permalink for the group.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $group_link, $group->name, $text, $notification_link );
+				}
+			}
+
+			break;
+
+		case 'group_invite':
+			$group_id = $item_id;
+			$group = groups_get_group( array( 'group_id' => $group_id ) );
+			$group_link = bp_get_group_permalink( $group );
+			$amount = 'single';
+
+			$notification_link = bp_loggedin_user_domain() . bp_get_groups_slug() . '/invites/?n=1';
+
+			if ( (int) $total_items > 1 ) {
+				$text = sprintf( __( 'You have %d new group invitations', 'buddypress' ), (int) $total_items );
+				$amount = 'multiple';
+
+				if ( 'string' == $format ) {
+					/**
+					 * Filters multiple group invitation notification for string format.
+					 * Complete filter - bp_groups_multiple_group_invite_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for notification.
+					 * @param int    $total_items       Total number of rejected requests.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '" title="' . __( 'Group Invites', 'buddypress' ) . '">' . $text . '</a>', $total_items, $text, $notification_link );
+				} else {
+					/**
+					 * Filters multiple group invitation notification for non-string format.
+					 * Complete filter - bp_groups_multiple_group_invite_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param int    $total_items       Total number of rejected requests.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $total_items, $text, $notification_link );
+				}
+			} else {
+				$text = sprintf( __( 'You have an invitation to the group: %s', 'buddypress' ), $group->name );
+				$filter = 'bp_groups_single_group_invite_notification';
+
+				if ( 'string' == $format ) {
+					/**
+					 * Filters single group invitation notification for string format.
+					 * Complete filter - bp_groups_single_group_invite_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param string $string            HTML anchor tag for notification.
+					 * @param int    $group_link        The permalink for the group.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', '<a href="' . $notification_link . '">' . $text . '</a>', $group_link, $group->name, $text, $notification_link );
+				} else {
+					/**
+					 * Filters single group invitation notification for non-string format.
+					 * Complete filter - bp_groups_single_group_invite_notification.
+					 *
+					 * @since 1.0.0
+					 *
+					 * @param array  $array             Array holding permalink and content for notification.
+					 * @param int    $group_link        The permalink for the group.
+					 * @param string $group->name       Name of the group.
+					 * @param string $text              Notification content.
+					 * @param string $notification_link The permalink for notification.
+					 */
+					return apply_filters( 'bp_groups_' . $amount . '_' . $action . '_notification', array(
+						'link' => $notification_link,
+						'text' => $text
+					), $group_link, $group->name, $text, $notification_link );
+				}
+			}
+
+			break;
+
+		default:
+
+			/**
+			 * Filters plugin-added group-related custom component_actions.
+			 *
+			 * @since 2.4.0
+			 *
+			 * @param string $notification      Null value.
+			 * @param int    $item_id           The primary item ID.
+			 * @param int    $secondary_item_id The secondary item ID.
+			 * @param int    $total_items       The total number of messaging-related notifications
+			 *                                  waiting for the user.
+			 * @param string $format            'string' for BuddyBar-compatible notifications;
+			 *                                  'array' for WP Toolbar.
+			 */
+			$custom_action_notification = apply_filters( 'bp_groups_' . $action . '_notification', null, $item_id, $secondary_item_id, $total_items, $format );
+
+			if ( ! is_null( $custom_action_notification ) ) {
+				return $custom_action_notification;
+			}
+
+			break;
+	}
+
+	/**
+	 * Fires right before returning the formatted group notifications.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $action            The type of notification being rendered.
+	 * @param int    $item_id           The primary item ID.
+	 * @param int    $secondary_item_id The secondary item ID.
+	 * @param int    $total_items       Total amount of items to format.
+	 */
+	do_action( 'groups_format_notifications', $action, $item_id, $secondary_item_id, $total_items );
+
+	return false;
+}
+
+/**
+ * Remove all notifications for any member belonging to a specific group.
+ *
+ * @since 1.9.0
+ *
+ * @param int $group_id ID of the group.
+ */
+function bp_groups_delete_group_delete_all_notifications( $group_id ) {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_delete_all_notifications_by_type( $group_id, buddypress()->groups->id );
+	}
+}
+add_action( 'groups_delete_group', 'bp_groups_delete_group_delete_all_notifications', 10 );
+
+/**
+ * When a demotion takes place, delete any corresponding promotion notifications.
+ *
+ * @since 2.0.0
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ */
+function bp_groups_delete_promotion_notifications( $user_id = 0, $group_id = 0 ) {
+	if ( bp_is_active( 'notifications' ) && ! empty( $group_id ) && ! empty( $user_id ) ) {
+		bp_notifications_delete_notifications_by_item_id( $user_id, $group_id, buddypress()->groups->id, 'member_promoted_to_admin' );
+		bp_notifications_delete_notifications_by_item_id( $user_id, $group_id, buddypress()->groups->id, 'member_promoted_to_mod' );
+	}
+}
+add_action( 'groups_demoted_member', 'bp_groups_delete_promotion_notifications', 10, 2 );
+
+/**
+ * Mark notifications read when a member accepts a group invitation.
+ *
+ * @since 1.9.0
+ *
+ * @param int $user_id  ID of the user.
+ * @param int $group_id ID of the group.
+ */
+function bp_groups_accept_invite_mark_notifications( $user_id, $group_id ) {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_mark_notifications_by_item_id( $user_id, $group_id, buddypress()->groups->id, 'group_invite' );
+	}
+}
+add_action( 'groups_accept_invite', 'bp_groups_accept_invite_mark_notifications', 10, 2 );
+add_action( 'groups_reject_invite', 'bp_groups_accept_invite_mark_notifications', 10, 2 );
+add_action( 'groups_delete_invite', 'bp_groups_accept_invite_mark_notifications', 10, 2 );
+
+/**
+ * Mark notifications read when a member views their group memberships.
+ *
+ * @since 1.9.0
+ */
+function bp_groups_screen_my_groups_mark_notifications() {
+
+	// Delete group request notifications for the user
+	if ( isset( $_GET['n'] ) && bp_is_active( 'notifications' ) ) {
+
+		// Get the necessary ID's
+		$group_id = buddypress()->groups->id;
+		$user_id  = bp_loggedin_user_id();
+
+		// Mark notifications read
+		bp_notifications_mark_notifications_by_type( $user_id, $group_id, 'membership_request_accepted' );
+		bp_notifications_mark_notifications_by_type( $user_id, $group_id, 'membership_request_rejected' );
+		bp_notifications_mark_notifications_by_type( $user_id, $group_id, 'member_promoted_to_mod'      );
+		bp_notifications_mark_notifications_by_type( $user_id, $group_id, 'member_promoted_to_admin'    );
+	}
+}
+add_action( 'groups_screen_my_groups',  'bp_groups_screen_my_groups_mark_notifications', 10 );
+add_action( 'groups_screen_group_home', 'bp_groups_screen_my_groups_mark_notifications', 10 );
+
+/**
+ * Mark group invitation notifications read when a member views their invitations.
+ *
+ * @since 1.9.0
+ */
+function bp_groups_screen_invites_mark_notifications() {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_mark_notifications_by_type( bp_loggedin_user_id(), buddypress()->groups->id, 'group_invite' );
+	}
+}
+add_action( 'groups_screen_group_invites', 'bp_groups_screen_invites_mark_notifications', 10 );
+
+/**
+ * Mark group join requests read when an admin or moderator visits the group administration area.
+ *
+ * @since 1.9.0
+ */
+function bp_groups_screen_group_admin_requests_mark_notifications() {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_mark_notifications_by_type( bp_loggedin_user_id(), buddypress()->groups->id, 'new_membership_request' );
+	}
+}
+add_action( 'groups_screen_group_admin_requests', 'bp_groups_screen_group_admin_requests_mark_notifications', 10 );
+
+/**
+ * Delete new group membership notifications when a user is being deleted.
+ *
+ * @since 1.9.0
+ *
+ * @param int $user_id ID of the user.
+ */
+function bp_groups_remove_data_for_user_notifications( $user_id ) {
+	if ( bp_is_active( 'notifications' ) ) {
+		bp_notifications_delete_notifications_from_user( $user_id, buddypress()->groups->id, 'new_membership_request' );
+	}
+}
+add_action( 'groups_remove_data_for_user', 'bp_groups_remove_data_for_user_notifications', 10 );
Index: /tags/2.4.3/src/bp-groups/bp-groups-screens.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-screens.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-screens.php	(revision 10410)
@@ -0,0 +1,1696 @@
+<?php
+/**
+ * BuddyPress Groups Screen Functions
+ *
+ * Screen functions are the controllers of BuddyPress. They will execute when
+ * their specific URL is caught. They will first save or manipulate data using
+ * business functions, then pass on the user to a template file.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsScreens
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Handle the display of the Groups directory index.
+ */
+function groups_directory_groups_setup() {
+	if ( bp_is_groups_directory() ) {
+		bp_update_is_directory( true, 'groups' );
+
+		/**
+		 * Fires before the loading of the Groups directory index.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'groups_directory_groups_setup' );
+
+		/**
+		 * Filters the template to load for the Groups directory index.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Path to the groups directory index template to load.
+		 */
+		bp_core_load_template( apply_filters( 'groups_template_directory_groups', 'groups/index' ) );
+	}
+}
+add_action( 'bp_screens', 'groups_directory_groups_setup', 2 );
+
+/**
+ * Handle the loading of the My Groups page.
+ */
+function groups_screen_my_groups() {
+
+	/**
+	 * Fires before the loading of the My Groups page.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'groups_screen_my_groups' );
+
+	/**
+	 * Filters the template to load for the My Groups page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to the My Groups page template to load.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_my_groups', 'members/single/home' ) );
+}
+
+/**
+ * Handle the loading of a user's Groups > Invites page.
+ */
+function groups_screen_group_invites() {
+	$group_id = (int)bp_action_variable( 1 );
+
+	if ( bp_is_action_variable( 'accept' ) && is_numeric( $group_id ) ) {
+		// Check the nonce
+		if ( !check_admin_referer( 'groups_accept_invite' ) )
+			return false;
+
+		if ( !groups_accept_invite( bp_loggedin_user_id(), $group_id ) ) {
+			bp_core_add_message( __('Group invite could not be accepted', 'buddypress'), 'error' );
+		} else {
+			bp_core_add_message( __('Group invite accepted', 'buddypress') );
+
+			// Record this in activity streams
+			$group = groups_get_group( array( 'group_id' => $group_id ) );
+
+			groups_record_activity( array(
+				'type'    => 'joined_group',
+				'item_id' => $group->id
+			) );
+		}
+
+		if ( isset( $_GET['redirect_to'] ) ) {
+			$redirect_to = urldecode( $_GET['redirect_to'] );
+		} else {
+			$redirect_to = trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() . '/' . bp_current_action() );
+		}
+
+		bp_core_redirect( $redirect_to );
+
+	} elseif ( bp_is_action_variable( 'reject' ) && is_numeric( $group_id ) ) {
+		// Check the nonce
+		if ( !check_admin_referer( 'groups_reject_invite' ) )
+			return false;
+
+		if ( !groups_reject_invite( bp_loggedin_user_id(), $group_id ) ) {
+			bp_core_add_message( __( 'Group invite could not be rejected', 'buddypress' ), 'error' );
+		} else {
+			bp_core_add_message( __( 'Group invite rejected', 'buddypress' ) );
+		}
+
+		if ( isset( $_GET['redirect_to'] ) ) {
+			$redirect_to = urldecode( $_GET['redirect_to'] );
+		} else {
+			$redirect_to = trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() . '/' . bp_current_action() );
+		}
+
+		bp_core_redirect( $redirect_to );
+	}
+
+	/**
+	 * Fires before the loading of a users Groups > Invites template.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $group_id ID of the group being displayed
+	 */
+	do_action( 'groups_screen_group_invites', $group_id );
+
+	/**
+	 * Filters the template to load for a users Groups > Invites page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to a users Groups > Invites page template.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_invites', 'members/single/home' ) );
+}
+
+/**
+ * Handle the loading of a single group's page.
+ */
+function groups_screen_group_home() {
+
+	if ( ! bp_is_single_item() ) {
+		return false;
+	}
+
+	/**
+	 * Fires before the loading of a single group's page.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'groups_screen_group_home' );
+
+	/**
+	 * Filters the template to load for a single group's page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to a single group's template to load.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_home', 'groups/single/home' ) );
+}
+
+/**
+ * This screen function handles actions related to group forums.
+ */
+function groups_screen_group_forum() {
+
+	if ( !bp_is_active( 'forums' ) || !bp_forums_is_installed_correctly() )
+		return false;
+
+	if ( bp_action_variable( 0 ) && !bp_is_action_variable( 'topic', 0 ) ) {
+		bp_do_404();
+		return;
+	}
+
+	$bp = buddypress();
+
+	if ( !$bp->groups->current_group->user_has_access ) {
+		bp_core_no_access();
+		return;
+	}
+
+	if ( ! bp_is_single_item() )
+		return false;
+
+	// Fetch the details we need
+	$topic_slug	= (string)bp_action_variable( 1 );
+	$topic_id       = bp_forums_get_topic_id_from_slug( $topic_slug );
+	$forum_id       = groups_get_groupmeta( $bp->groups->current_group->id, 'forum_id' );
+	$user_is_banned = false;
+
+	if ( !bp_current_user_can( 'bp_moderate' ) && groups_is_user_banned( bp_loggedin_user_id(), $bp->groups->current_group->id ) )
+		$user_is_banned = true;
+
+	if ( !empty( $topic_slug ) && !empty( $topic_id ) ) {
+
+		// Posting a reply
+		if ( !$user_is_banned && !bp_action_variable( 2 ) && isset( $_POST['submit_reply'] ) ) {
+			// Check the nonce
+			check_admin_referer( 'bp_forums_new_reply' );
+
+			// Auto join this user if they are not yet a member of this group
+			if ( bp_groups_auto_join() && !bp_current_user_can( 'bp_moderate' ) && 'public' == $bp->groups->current_group->status && !groups_is_user_member( bp_loggedin_user_id(), $bp->groups->current_group->id ) ) {
+				groups_join_group( $bp->groups->current_group->id, bp_loggedin_user_id() );
+			}
+
+			$topic_page = isset( $_GET['topic_page'] ) ? $_GET['topic_page'] : false;
+
+			// Don't allow reply flooding
+			if ( bp_forums_reply_exists( $_POST['reply_text'], $topic_id, bp_loggedin_user_id() ) ) {
+				bp_core_add_message( __( 'It looks like you\'ve already said that!', 'buddypress' ), 'error' );
+			} else {
+				if ( !$post_id = groups_new_group_forum_post( $_POST['reply_text'], $topic_id, $topic_page ) ) {
+					bp_core_add_message( __( 'There was an error when replying to that topic', 'buddypress'), 'error' );
+				} else {
+					bp_core_add_message( __( 'Your reply was posted successfully', 'buddypress') );
+				}
+			}
+
+			$query_vars = isset( $_SERVER['QUERY_STRING'] ) ? '?' . $_SERVER['QUERY_STRING'] : '';
+
+			$redirect = bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic_slug . '/' . $query_vars;
+
+			if ( !empty( $post_id ) ) {
+				$redirect .= '#post-' . $post_id;
+			}
+
+			bp_core_redirect( $redirect );
+		}
+
+		// Sticky a topic
+		elseif ( bp_is_action_variable( 'stick', 2 ) && ( bp_is_item_admin() || bp_is_item_mod() ) ) {
+			// Check the nonce
+			check_admin_referer( 'bp_forums_stick_topic' );
+
+			if ( !bp_forums_sticky_topic( array( 'topic_id' => $topic_id ) ) ) {
+				bp_core_add_message( __( 'There was an error when making that topic a sticky', 'buddypress' ), 'error' );
+			} else {
+				bp_core_add_message( __( 'The topic was made sticky successfully', 'buddypress' ) );
+			}
+
+			/**
+			 * Fires after a group forum topic has been stickied.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param int $topic_id ID of the topic being stickied.
+			 */
+			do_action( 'groups_stick_forum_topic', $topic_id );
+			bp_core_redirect( wp_get_referer() );
+		}
+
+		// Un-Sticky a topic
+		elseif ( bp_is_action_variable( 'unstick', 2 ) && ( bp_is_item_admin() || bp_is_item_mod() ) ) {
+			// Check the nonce
+			check_admin_referer( 'bp_forums_unstick_topic' );
+
+			if ( !bp_forums_sticky_topic( array( 'topic_id' => $topic_id, 'mode' => 'unstick' ) ) ) {
+				bp_core_add_message( __( 'There was an error when unsticking that topic', 'buddypress'), 'error' );
+			} else {
+				bp_core_add_message( __( 'The topic was unstuck successfully', 'buddypress') );
+			}
+
+			/**
+			 * Fires after a group forum topic has been un-stickied.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param int $topic_id ID of the topic being un-stickied.
+			 */
+			do_action( 'groups_unstick_forum_topic', $topic_id );
+			bp_core_redirect( wp_get_referer() );
+		}
+
+		// Close a topic
+		elseif ( bp_is_action_variable( 'close', 2 ) && ( bp_is_item_admin() || bp_is_item_mod() ) ) {
+			// Check the nonce
+			check_admin_referer( 'bp_forums_close_topic' );
+
+			if ( !bp_forums_openclose_topic( array( 'topic_id' => $topic_id ) ) ) {
+				bp_core_add_message( __( 'There was an error when closing that topic', 'buddypress'), 'error' );
+			} else {
+				bp_core_add_message( __( 'The topic was closed successfully', 'buddypress') );
+			}
+
+			/**
+			 * Fires after a group forum topic has been closed.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param int $topic_id ID of the topic being closed.
+			 */
+			do_action( 'groups_close_forum_topic', $topic_id );
+			bp_core_redirect( wp_get_referer() );
+		}
+
+		// Open a topic
+		elseif ( bp_is_action_variable( 'open', 2 ) && ( bp_is_item_admin() || bp_is_item_mod() ) ) {
+			// Check the nonce
+			check_admin_referer( 'bp_forums_open_topic' );
+
+			if ( !bp_forums_openclose_topic( array( 'topic_id' => $topic_id, 'mode' => 'open' ) ) ) {
+				bp_core_add_message( __( 'There was an error when opening that topic', 'buddypress'), 'error' );
+			} else {
+				bp_core_add_message( __( 'The topic was opened successfully', 'buddypress') );
+			}
+
+			/**
+			 * Fires after a group forum topic has been opened.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param int $topic_id ID of the topic being opened.
+			 */
+			do_action( 'groups_open_forum_topic', $topic_id );
+			bp_core_redirect( wp_get_referer() );
+		}
+
+		// Delete a topic
+		elseif ( empty( $user_is_banned ) && bp_is_action_variable( 'delete', 2 ) && !bp_action_variable( 3 ) ) {
+			// Fetch the topic
+			$topic = bp_forums_get_topic_details( $topic_id );
+
+			/* Check the logged in user can delete this topic */
+			if ( ! bp_is_item_admin() && ! bp_is_item_mod() && ( (int) bp_loggedin_user_id() != (int) $topic->topic_poster ) ) {
+				bp_core_redirect( wp_get_referer() );
+			}
+
+			// Check the nonce
+			check_admin_referer( 'bp_forums_delete_topic' );
+
+			/**
+			 * Fires before a group forum topic is deleted.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param int $topic_id ID of the topic being deleted.
+			 */
+			do_action( 'groups_before_delete_forum_topic', $topic_id );
+
+			if ( !groups_delete_group_forum_topic( $topic_id ) ) {
+				bp_core_add_message( __( 'There was an error deleting the topic', 'buddypress' ), 'error' );
+			} else {
+				bp_core_add_message( __( 'The topic was deleted successfully', 'buddypress' ) );
+			}
+
+			/**
+			 * Fires after a group forum topic has been deleted.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param int $topic_id ID of the topic being deleted.
+			 */
+			do_action( 'groups_delete_forum_topic', $topic_id );
+			bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'forum/' );
+		}
+
+		// Editing a topic
+		elseif ( empty( $user_is_banned ) && bp_is_action_variable( 'edit', 2 ) && !bp_action_variable( 3 ) ) {
+			// Fetch the topic
+			$topic = bp_forums_get_topic_details( $topic_id );
+
+			// Check the logged in user can edit this topic
+			if ( ! bp_is_item_admin() && ! bp_is_item_mod() && ( (int) bp_loggedin_user_id() != (int) $topic->topic_poster ) ) {
+				bp_core_redirect( wp_get_referer() );
+			}
+
+			if ( isset( $_POST['save_changes'] ) ) {
+				// Check the nonce
+				check_admin_referer( 'bp_forums_edit_topic' );
+
+				$topic_tags = !empty( $_POST['topic_tags'] ) ? $_POST['topic_tags'] : false;
+
+				if ( !groups_update_group_forum_topic( $topic_id, $_POST['topic_title'], $_POST['topic_text'], $topic_tags ) ) {
+					bp_core_add_message( __( 'There was an error when editing that topic', 'buddypress'), 'error' );
+				} else {
+					bp_core_add_message( __( 'The topic was edited successfully', 'buddypress') );
+				}
+
+				/**
+				 * Fires after a group forum topic has been edited.
+				 *
+				 * @since 1.1.0
+				 *
+				 * @param int $topic_id ID of the topic being edited.
+				 */
+				do_action( 'groups_edit_forum_topic', $topic_id );
+				bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'forum/topic/' . $topic_slug . '/' );
+			}
+
+			/**
+			 * Filters the template to load for a topic edit page.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param string $value Path to a topic edit template.
+			 */
+			bp_core_load_template( apply_filters( 'groups_template_group_forum_topic_edit', 'groups/single/home' ) );
+
+		// Delete a post
+		} elseif ( empty( $user_is_banned ) && bp_is_action_variable( 'delete', 2 ) && $post_id = bp_action_variable( 4 ) ) {
+			// Fetch the post
+			$post = bp_forums_get_post( $post_id );
+
+			// Check the logged in user can edit this topic
+			if ( ! bp_is_item_admin() && ! bp_is_item_mod() && ( (int) bp_loggedin_user_id() != (int) $post->poster_id ) ) {
+				bp_core_redirect( wp_get_referer() );
+			}
+
+			// Check the nonce
+			check_admin_referer( 'bp_forums_delete_post' );
+
+			/**
+			 * Fires before the deletion of a group forum post.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param int $post_id ID of the forum post being deleted.
+			 */
+			do_action( 'groups_before_delete_forum_post', $post_id );
+
+			if ( !groups_delete_group_forum_post( $post_id ) ) {
+				bp_core_add_message( __( 'There was an error deleting that post', 'buddypress'), 'error' );
+			} else {
+				bp_core_add_message( __( 'The post was deleted successfully', 'buddypress') );
+			}
+
+			/**
+			 * Fires after the deletion of a group forum post.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param int $post_id ID of the forum post being deleted.
+			 */
+			do_action( 'groups_delete_forum_post', $post_id );
+			bp_core_redirect( wp_get_referer() );
+
+		// Editing a post
+		} elseif ( empty( $user_is_banned ) && bp_is_action_variable( 'edit', 2 ) && $post_id = bp_action_variable( 4 ) ) {
+
+			// Fetch the post
+			$post = bp_forums_get_post( $post_id );
+
+			// Check the logged in user can edit this topic
+			if ( ! bp_is_item_admin() && ! bp_is_item_mod() && ( (int) bp_loggedin_user_id() != (int) $post->poster_id ) ) {
+				bp_core_redirect( wp_get_referer() );
+			}
+
+			if ( isset( $_POST['save_changes'] ) ) {
+				// Check the nonce
+				check_admin_referer( 'bp_forums_edit_post' );
+
+				$topic_page = isset( $_GET['topic_page'] ) ? $_GET['topic_page'] : false;
+
+				if ( !$post_id = groups_update_group_forum_post( $post_id, $_POST['post_text'], $topic_id, $topic_page ) ) {
+					bp_core_add_message( __( 'There was an error when editing that post', 'buddypress'), 'error' );
+				} else {
+					bp_core_add_message( __( 'The post was edited successfully', 'buddypress') );
+				}
+
+				if ( $_SERVER['QUERY_STRING'] ) {
+					$query_vars = '?' . $_SERVER['QUERY_STRING'];
+				}
+
+				/**
+				 * Fires after the editing of a group forum post.
+				 *
+				 * @since 1.1.0
+				 *
+				 * @param int $post_id ID of the forum post being edited.
+				 */
+				do_action( 'groups_edit_forum_post', $post_id );
+				bp_core_redirect( bp_get_group_permalink( $bp->groups->current_group ) . 'forum/topic/' . $topic_slug . '/' . $query_vars . '#post-' . $post_id );
+			}
+
+			/** This filter is documented in bp-groups/bp-groups-screens.php */
+			bp_core_load_template( apply_filters( 'groups_template_group_forum_topic_edit', 'groups/single/home' ) );
+
+		// Standard topic display
+		} else {
+			if ( !empty( $user_is_banned ) ) {
+				bp_core_add_message( __( "You have been banned from this group.", 'buddypress' ) );
+			}
+
+			/**
+			 * Filters the template to load for a topic page.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param string $value Path to a topic template.
+			 */
+			bp_core_load_template( apply_filters( 'groups_template_group_forum_topic', 'groups/single/home' ) );
+		}
+
+	// Forum topic does not exist
+	} elseif ( !empty( $topic_slug ) && empty( $topic_id ) ) {
+		bp_do_404();
+		return;
+
+	} else {
+		// Posting a topic
+		if ( isset( $_POST['submit_topic'] ) && bp_is_active( 'forums' ) ) {
+
+			// Check the nonce
+			check_admin_referer( 'bp_forums_new_topic' );
+
+			if ( $user_is_banned ) {
+				$error_message = __( "You have been banned from this group.", 'buddypress' );
+
+			} elseif ( bp_groups_auto_join() && !bp_current_user_can( 'bp_moderate' ) && 'public' == $bp->groups->current_group->status && !groups_is_user_member( bp_loggedin_user_id(), $bp->groups->current_group->id ) ) {
+				// Auto join this user if they are not yet a member of this group
+				groups_join_group( $bp->groups->current_group->id, bp_loggedin_user_id() );
+			}
+
+			if ( empty( $_POST['topic_title'] ) ) {
+				$error_message = __( 'Please provide a title for your forum topic.', 'buddypress' );
+			} elseif ( empty( $_POST['topic_text'] ) ) {
+				$error_message = __( 'Forum posts cannot be empty. Please enter some text.', 'buddypress' );
+			}
+
+			if ( empty( $forum_id ) ) {
+				$error_message = __( 'This group does not have a forum setup yet.', 'buddypress' );
+			}
+
+			if ( isset( $error_message ) ) {
+				bp_core_add_message( $error_message, 'error' );
+				$redirect = bp_get_group_permalink( $bp->groups->current_group ) . 'forum';
+			} else {
+				if ( !$topic = groups_new_group_forum_topic( $_POST['topic_title'], $_POST['topic_text'], $_POST['topic_tags'], $forum_id ) ) {
+					bp_core_add_message( __( 'There was an error when creating the topic', 'buddypress'), 'error' );
+					$redirect = bp_get_group_permalink( $bp->groups->current_group ) . 'forum';
+				} else {
+					bp_core_add_message( __( 'The topic was created successfully', 'buddypress') );
+					$redirect = bp_get_group_permalink( $bp->groups->current_group ) . 'forum/topic/' . $topic->topic_slug . '/';
+				}
+			}
+
+			bp_core_redirect( $redirect );
+		}
+
+		/**
+		 * Fires at the end of the group forum screen loading process.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $topic_id ID of the topic being displayed.
+		 * @param int $forum_id ID of the forum being displayed.
+		 */
+		do_action( 'groups_screen_group_forum', $topic_id, $forum_id );
+
+		/**
+		 * Filters the template to load for a group forum page.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Path to a group forum template.
+		 */
+		bp_core_load_template( apply_filters( 'groups_template_group_forum', 'groups/single/home' ) );
+	}
+}
+
+/**
+ * Handle the display of a group's Members page.
+ */
+function groups_screen_group_members() {
+
+	if ( !bp_is_single_item() )
+		return false;
+
+	$bp = buddypress();
+
+	// Refresh the group member count meta
+	groups_update_groupmeta( $bp->groups->current_group->id, 'total_member_count', groups_get_total_member_count( $bp->groups->current_group->id ) );
+
+	/**
+	 * Fires before the loading of a group's Members page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id ID of the group whose members are being displayed.
+	 */
+	do_action( 'groups_screen_group_members', $bp->groups->current_group->id );
+
+	/**
+	 * Filters the template to load for a group's Members page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to a group's Members template.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_members', 'groups/single/home' ) );
+}
+
+/**
+ * Handle the display of a group's Send Invites page.
+ */
+function groups_screen_group_invite() {
+
+	if ( !bp_is_single_item() )
+		return false;
+
+	$bp = buddypress();
+
+	if ( bp_is_action_variable( 'send', 0 ) ) {
+
+		if ( !check_admin_referer( 'groups_send_invites', '_wpnonce_send_invites' ) )
+			return false;
+
+		if ( !empty( $_POST['friends'] ) ) {
+			foreach( (array) $_POST['friends'] as $friend ) {
+				groups_invite_user( array( 'user_id' => $friend, 'group_id' => $bp->groups->current_group->id ) );
+			}
+		}
+
+		// Send the invites.
+		groups_send_invites( bp_loggedin_user_id(), $bp->groups->current_group->id );
+		bp_core_add_message( __('Group invites sent.', 'buddypress') );
+
+		/**
+		 * Fires after the sending of a group invite inside the group's Send Invites page.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $id ID of the group whose members are being displayed.
+		 */
+		do_action( 'groups_screen_group_invite', $bp->groups->current_group->id );
+		bp_core_redirect( bp_get_group_permalink( $bp->groups->current_group ) );
+
+	} elseif ( !bp_action_variable( 0 ) ) {
+
+		/**
+		 * Filters the template to load for a group's Send Invites page.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Path to a group's Send Invites template.
+		 */
+		bp_core_load_template( apply_filters( 'groups_template_group_invite', 'groups/single/home' ) );
+
+	} else {
+		bp_do_404();
+	}
+}
+
+/**
+ * Process group invitation removal requests.
+ *
+ * Note that this function is only used when JS is disabled. Normally, clicking
+ * Remove Invite removes the invitation via AJAX.
+ *
+ * @since 2.0.0
+ */
+function groups_remove_group_invite() {
+	if ( ! bp_is_group_invites() ) {
+		return;
+	}
+
+	if ( ! bp_is_action_variable( 'remove', 0 ) || ! is_numeric( bp_action_variable( 1 ) ) ) {
+		return;
+	}
+
+	if ( ! check_admin_referer( 'groups_invite_uninvite_user' ) ) {
+		return false;
+	}
+
+	$friend_id = intval( bp_action_variable( 1 ) );
+	$group_id  = bp_get_current_group_id();
+	$message   = __( 'Invite successfully removed', 'buddypress' );
+	$redirect  = wp_get_referer();
+	$error     = false;
+
+	if ( ! bp_groups_user_can_send_invites( $group_id ) ) {
+		$message = __( 'You are not allowed to send or remove invites', 'buddypress' );
+		$error = 'error';
+	} elseif ( BP_Groups_Member::check_for_membership_request( $friend_id, $group_id ) ) {
+		$message = __( 'The member requested to join the group', 'buddypress' );
+		$error = 'error';
+	} elseif ( ! groups_uninvite_user( $friend_id, $group_id ) ) {
+		$message = __( 'There was an error removing the invite', 'buddypress' );
+		$error = 'error';
+	}
+
+	bp_core_add_message( $message, $error );
+	bp_core_redirect( $redirect );
+}
+add_action( 'bp_screens', 'groups_remove_group_invite' );
+
+/**
+ * Handle the display of a group's Request Membership page.
+ */
+function groups_screen_group_request_membership() {
+
+	if ( !is_user_logged_in() )
+		return false;
+
+	$bp = buddypress();
+
+	if ( 'private' != $bp->groups->current_group->status )
+		return false;
+
+	// If the user is already invited, accept invitation
+	if ( groups_check_user_has_invite( bp_loggedin_user_id(), $bp->groups->current_group->id ) ) {
+		if ( groups_accept_invite( bp_loggedin_user_id(), $bp->groups->current_group->id ) )
+			bp_core_add_message( __( 'Group invite accepted', 'buddypress' ) );
+		else
+			bp_core_add_message( __( 'There was an error accepting the group invitation. Please try again.', 'buddypress' ), 'error' );
+		bp_core_redirect( bp_get_group_permalink( $bp->groups->current_group ) );
+	}
+
+	// If the user has submitted a request, send it.
+	if ( isset( $_POST['group-request-send']) ) {
+
+		// Check the nonce
+		if ( !check_admin_referer( 'groups_request_membership' ) )
+			return false;
+
+		if ( !groups_send_membership_request( bp_loggedin_user_id(), $bp->groups->current_group->id ) ) {
+			bp_core_add_message( __( 'There was an error sending your group membership request. Please try again.', 'buddypress' ), 'error' );
+		} else {
+			bp_core_add_message( __( 'Your membership request was sent to the group administrator successfully. You will be notified when the group administrator responds to your request.', 'buddypress' ) );
+		}
+		bp_core_redirect( bp_get_group_permalink( $bp->groups->current_group ) );
+	}
+
+	/**
+	 * Fires before the loading of a group's Request Memebership page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id ID of the group currently being displayed.
+	 */
+	do_action( 'groups_screen_group_request_membership', $bp->groups->current_group->id );
+
+	/**
+	 * Filters the template to load for a group's Request Membership page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to a group's Request Membership template.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_request_membership', 'groups/single/home' ) );
+}
+
+/**
+ * Handle the loading of a single group's activity.
+ *
+ * @since 2.4.0
+ */
+function groups_screen_group_activity() {
+
+	if ( ! bp_is_single_item() ) {
+		return false;
+	}
+
+	/**
+	 * Fires before the loading of a single group's activity page.
+	 *
+	 * @since 2.4.0
+	 */
+	do_action( 'groups_screen_group_activity' );
+
+	/**
+	 * Filters the template to load for a single group's activity page.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string $value Path to a single group's template to load.
+	 */
+	bp_core_load_template( apply_filters( 'groups_screen_group_activity', 'groups/single/activity' ) );
+}
+
+/**
+ * Handle the display of a single group activity item.
+ */
+function groups_screen_group_activity_permalink() {
+
+	if ( !bp_is_groups_component() || !bp_is_active( 'activity' ) || ( bp_is_active( 'activity' ) && !bp_is_current_action( bp_get_activity_slug() ) ) || !bp_action_variable( 0 ) )
+		return false;
+
+	buddypress()->is_single_item = true;
+
+	/** This filter is documented in bp-groups/bp-groups-screens.php */
+	bp_core_load_template( apply_filters( 'groups_template_group_home', 'groups/single/home' ) );
+}
+add_action( 'bp_screens', 'groups_screen_group_activity_permalink' );
+
+/**
+ * Handle the display of a group's Admin pages.
+ */
+function groups_screen_group_admin() {
+	if ( !bp_is_groups_component() || !bp_is_current_action( 'admin' ) )
+		return false;
+
+	if ( bp_action_variables() )
+		return false;
+
+	bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'admin/edit-details/' );
+}
+
+/**
+ * Handle the display of a group's admin/edit-details page.
+ */
+function groups_screen_group_admin_edit_details() {
+
+	if ( 'edit-details' != bp_get_group_current_admin_tab() )
+		return false;
+
+	if ( bp_is_item_admin() ) {
+
+		$bp = buddypress();
+
+		// If the edit form has been submitted, save the edited details
+		if ( isset( $_POST['save'] ) ) {
+			// Check the nonce
+			if ( !check_admin_referer( 'groups_edit_group_details' ) )
+				return false;
+
+			$group_notify_members = isset( $_POST['group-notify-members'] ) ? (int) $_POST['group-notify-members'] : 0;
+
+			if ( !groups_edit_base_group_details( $_POST['group-id'], $_POST['group-name'], $_POST['group-desc'], $group_notify_members ) ) {
+				bp_core_add_message( __( 'There was an error updating group details. Please try again.', 'buddypress' ), 'error' );
+			} else {
+				bp_core_add_message( __( 'Group details were successfully updated.', 'buddypress' ) );
+			}
+
+			/**
+			 * Fires before the redirect if a group details has been edited and saved.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param int $id ID of the group that was edited.
+			 */
+			do_action( 'groups_group_details_edited', $bp->groups->current_group->id );
+
+			bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'admin/edit-details/' );
+		}
+
+		/**
+		 * Fires before the loading of the group admin/edit-details page template.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $id ID of the group that is being displayed.
+		 */
+		do_action( 'groups_screen_group_admin_edit_details', $bp->groups->current_group->id );
+
+		/**
+		 * Filters the template to load for a group's admin/edit-details page.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Path to a group's admin/edit-details template.
+		 */
+		bp_core_load_template( apply_filters( 'groups_template_group_admin', 'groups/single/home' ) );
+	}
+}
+add_action( 'bp_screens', 'groups_screen_group_admin_edit_details' );
+
+/**
+ * Handle the display of a group's admin/group-settings page.
+ */
+function groups_screen_group_admin_settings() {
+
+	if ( 'group-settings' != bp_get_group_current_admin_tab() )
+		return false;
+
+	if ( ! bp_is_item_admin() )
+		return false;
+
+	$bp = buddypress();
+
+	// If the edit form has been submitted, save the edited details
+	if ( isset( $_POST['save'] ) ) {
+		$enable_forum   = ( isset($_POST['group-show-forum'] ) ) ? 1 : 0;
+
+		// Checked against a whitelist for security
+		/** This filter is documented in bp-groups/bp-groups-admin.php */
+		$allowed_status = apply_filters( 'groups_allowed_status', array( 'public', 'private', 'hidden' ) );
+		$status         = ( in_array( $_POST['group-status'], (array) $allowed_status ) ) ? $_POST['group-status'] : 'public';
+
+		// Checked against a whitelist for security
+		/** This filter is documented in bp-groups/bp-groups-admin.php */
+		$allowed_invite_status = apply_filters( 'groups_allowed_invite_status', array( 'members', 'mods', 'admins' ) );
+		$invite_status	       = isset( $_POST['group-invite-status'] ) && in_array( $_POST['group-invite-status'], (array) $allowed_invite_status ) ? $_POST['group-invite-status'] : 'members';
+
+		// Check the nonce
+		if ( !check_admin_referer( 'groups_edit_group_settings' ) )
+			return false;
+
+		if ( !groups_edit_group_settings( $_POST['group-id'], $enable_forum, $status, $invite_status ) ) {
+			bp_core_add_message( __( 'There was an error updating group settings. Please try again.', 'buddypress' ), 'error' );
+		} else {
+			bp_core_add_message( __( 'Group settings were successfully updated.', 'buddypress' ) );
+		}
+
+		/**
+		 * Fires before the redirect if a group settings has been edited and saved.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $id ID of the group that was edited.
+		 */
+		do_action( 'groups_group_settings_edited', $bp->groups->current_group->id );
+
+		bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'admin/group-settings/' );
+	}
+
+	/**
+	 * Fires before the loading of the group admin/group-settings page template.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id ID of the group that is being displayed.
+	 */
+	do_action( 'groups_screen_group_admin_settings', $bp->groups->current_group->id );
+
+	/**
+	 * Filters the template to load for a group's admin/group-settings page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to a group's admin/group-settings template.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_admin_settings', 'groups/single/home' ) );
+}
+add_action( 'bp_screens', 'groups_screen_group_admin_settings' );
+
+/**
+ * Handle the display of a group's Change Avatar page.
+ */
+function groups_screen_group_admin_avatar() {
+
+	if ( 'group-avatar' != bp_get_group_current_admin_tab() )
+		return false;
+
+	// If the logged-in user doesn't have permission or if avatar uploads are disabled, then stop here
+	if ( ! bp_is_item_admin() || bp_disable_group_avatar_uploads() || ! buddypress()->avatar->show_avatars )
+		return false;
+
+	$bp = buddypress();
+
+	// If the group admin has deleted the admin avatar
+	if ( bp_is_action_variable( 'delete', 1 ) ) {
+
+		// Check the nonce
+		check_admin_referer( 'bp_group_avatar_delete' );
+
+		if ( bp_core_delete_existing_avatar( array( 'item_id' => $bp->groups->current_group->id, 'object' => 'group' ) ) ) {
+			bp_core_add_message( __( 'The group profile photo was deleted successfully!', 'buddypress' ) );
+		} else {
+			bp_core_add_message( __( 'There was a problem deleting the group profile photo. Please try again.', 'buddypress' ), 'error' );
+		}
+	}
+
+	if ( ! isset( $bp->avatar_admin ) ) {
+		$bp->avatar_admin = new stdClass();
+	}
+
+	$bp->avatar_admin->step = 'upload-image';
+
+	if ( !empty( $_FILES ) ) {
+
+		// Check the nonce
+		check_admin_referer( 'bp_avatar_upload' );
+
+		// Pass the file to the avatar upload handler
+		if ( bp_core_avatar_handle_upload( $_FILES, 'groups_avatar_upload_dir' ) ) {
+			$bp->avatar_admin->step = 'crop-image';
+
+			// Make sure we include the jQuery jCrop file for image cropping
+			add_action( 'wp_print_scripts', 'bp_core_add_jquery_cropper' );
+		}
+
+	}
+
+	// If the image cropping is done, crop the image and save a full/thumb version
+	if ( isset( $_POST['avatar-crop-submit'] ) ) {
+
+		// Check the nonce
+		check_admin_referer( 'bp_avatar_cropstore' );
+
+		$args = array(
+			'object'        => 'group',
+			'avatar_dir'    => 'group-avatars',
+			'item_id'       => $bp->groups->current_group->id,
+			'original_file' => $_POST['image_src'],
+			'crop_x'        => $_POST['x'],
+			'crop_y'        => $_POST['y'],
+			'crop_w'        => $_POST['w'],
+			'crop_h'        => $_POST['h']
+		);
+
+		if ( !bp_core_avatar_handle_crop( $args ) ) {
+			bp_core_add_message( __( 'There was a problem cropping the group profile photo.', 'buddypress' ), 'error' );
+		} else {
+			bp_core_add_message( __( 'The new group profile photo was uploaded successfully.', 'buddypress' ) );
+		}
+	}
+
+	/**
+	 * Fires before the loading of the group Change Avatar page template.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id ID of the group that is being displayed.
+	 */
+	do_action( 'groups_screen_group_admin_avatar', $bp->groups->current_group->id );
+
+	/**
+	 * Filters the template to load for a group's Change Avatar page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to a group's Change Avatar template.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_admin_avatar', 'groups/single/home' ) );
+}
+add_action( 'bp_screens', 'groups_screen_group_admin_avatar' );
+
+/**
+ * Handle the display of a group's Change cover image page.
+ *
+ * @since 2.4.0
+ */
+function groups_screen_group_admin_cover_image() {
+	if ( 'group-cover-image' != bp_get_group_current_admin_tab() ) {
+		return false;
+	}
+
+	// If the logged-in user doesn't have permission or if cover image uploads are disabled, then stop here
+	if ( ! bp_is_item_admin() || ! bp_group_use_cover_image_header() ) {
+		return false;
+	}
+
+	/**
+	 * Fires before the loading of the group Change cover image page template.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param int $id ID of the group that is being displayed.
+	 */
+	do_action( 'groups_screen_group_admin_cover_image', bp_get_current_group_id() );
+
+	/**
+	 * Filters the template to load for a group's Change cover image page.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string $value Path to a group's Change cover image template.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_admin_cover_image', 'groups/single/home' ) );
+}
+add_action( 'bp_screens', 'groups_screen_group_admin_cover_image' );
+
+/**
+ * This function handles actions related to member management on the group admin.
+ */
+function groups_screen_group_admin_manage_members() {
+
+	if ( 'manage-members' != bp_get_group_current_admin_tab() )
+		return false;
+
+	if ( ! bp_is_item_admin() )
+		return false;
+
+	$bp = buddypress();
+
+	if ( bp_action_variable( 1 ) && bp_action_variable( 2 ) && bp_action_variable( 3 ) ) {
+		if ( bp_is_action_variable( 'promote', 1 ) && ( bp_is_action_variable( 'mod', 2 ) || bp_is_action_variable( 'admin', 2 ) ) && is_numeric( bp_action_variable( 3 ) ) ) {
+			$user_id = bp_action_variable( 3 );
+			$status  = bp_action_variable( 2 );
+
+			// Check the nonce first.
+			if ( !check_admin_referer( 'groups_promote_member' ) )
+				return false;
+
+			// Promote a user.
+			if ( !groups_promote_member( $user_id, $bp->groups->current_group->id, $status ) )
+				bp_core_add_message( __( 'There was an error when promoting that user. Please try again.', 'buddypress' ), 'error' );
+			else
+				bp_core_add_message( __( 'User promoted successfully', 'buddypress' ) );
+
+			/**
+			 * Fires before the redirect after a group member has been promoted.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param int $user_id ID of the user being promoted.
+			 * @param int $id      ID of the group user is promoted within.
+			 */
+			do_action( 'groups_promoted_member', $user_id, $bp->groups->current_group->id );
+
+			bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'admin/manage-members/' );
+		}
+	}
+
+	if ( bp_action_variable( 1 ) && bp_action_variable( 2 ) ) {
+		if ( bp_is_action_variable( 'demote', 1 ) && is_numeric( bp_action_variable( 2 ) ) ) {
+			$user_id = bp_action_variable( 2 );
+
+			// Check the nonce first.
+			if ( !check_admin_referer( 'groups_demote_member' ) )
+				return false;
+
+			// Stop sole admins from abandoning their group
+			$group_admins = groups_get_group_admins( $bp->groups->current_group->id );
+			if ( 1 == count( $group_admins ) && $group_admins[0]->user_id == $user_id )
+				bp_core_add_message( __( 'This group must have at least one admin', 'buddypress' ), 'error' );
+
+			// Demote a user.
+			elseif ( !groups_demote_member( $user_id, $bp->groups->current_group->id ) )
+				bp_core_add_message( __( 'There was an error when demoting that user. Please try again.', 'buddypress' ), 'error' );
+			else
+				bp_core_add_message( __( 'User demoted successfully', 'buddypress' ) );
+
+			/**
+			 * Fires before the redirect after a group member has been demoted.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param int $user_id ID of the user being demoted.
+			 * @param int $id      ID of the group user is demoted within.
+			 */
+			do_action( 'groups_demoted_member', $user_id, $bp->groups->current_group->id );
+
+			bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'admin/manage-members/' );
+		}
+
+		if ( bp_is_action_variable( 'ban', 1 ) && is_numeric( bp_action_variable( 2 ) ) ) {
+			$user_id = bp_action_variable( 2 );
+
+			// Check the nonce first.
+			if ( !check_admin_referer( 'groups_ban_member' ) )
+				return false;
+
+			// Ban a user.
+			if ( !groups_ban_member( $user_id, $bp->groups->current_group->id ) )
+				bp_core_add_message( __( 'There was an error when banning that user. Please try again.', 'buddypress' ), 'error' );
+			else
+				bp_core_add_message( __( 'User banned successfully', 'buddypress' ) );
+
+			/**
+			 * Fires before the redirect after a group member has been banned.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param int $user_id ID of the user being banned.
+			 * @param int $id      ID of the group user is banned from.
+			 */
+			do_action( 'groups_banned_member', $user_id, $bp->groups->current_group->id );
+
+			bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'admin/manage-members/' );
+		}
+
+		if ( bp_is_action_variable( 'unban', 1 ) && is_numeric( bp_action_variable( 2 ) ) ) {
+			$user_id = bp_action_variable( 2 );
+
+			// Check the nonce first.
+			if ( !check_admin_referer( 'groups_unban_member' ) )
+				return false;
+
+			// Remove a ban for user.
+			if ( !groups_unban_member( $user_id, $bp->groups->current_group->id ) )
+				bp_core_add_message( __( 'There was an error when unbanning that user. Please try again.', 'buddypress' ), 'error' );
+			else
+				bp_core_add_message( __( 'User ban removed successfully', 'buddypress' ) );
+
+			/**
+			 * Fires before the redirect after a group member has been unbanned.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param int $user_id ID of the user being unbanned.
+			 * @param int $id      ID of the group user is unbanned from.
+			 */
+			do_action( 'groups_unbanned_member', $user_id, $bp->groups->current_group->id );
+
+			bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'admin/manage-members/' );
+		}
+
+		if ( bp_is_action_variable( 'remove', 1 ) && is_numeric( bp_action_variable( 2 ) ) ) {
+			$user_id = bp_action_variable( 2 );
+
+			// Check the nonce first.
+			if ( !check_admin_referer( 'groups_remove_member' ) )
+				return false;
+
+			// Remove a user.
+			if ( !groups_remove_member( $user_id, $bp->groups->current_group->id ) )
+				bp_core_add_message( __( 'There was an error removing that user from the group. Please try again.', 'buddypress' ), 'error' );
+			else
+				bp_core_add_message( __( 'User removed successfully', 'buddypress' ) );
+
+			/**
+			 * Fires before the redirect after a group member has been removed.
+			 *
+			 * @since 1.2.6
+			 *
+			 * @param int $user_id ID of the user being removed.
+			 * @param int $id      ID of the group the user is removed from.
+			 */
+			do_action( 'groups_removed_member', $user_id, $bp->groups->current_group->id );
+
+			bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'admin/manage-members/' );
+		}
+	}
+
+	/**
+	 * Fires before the loading of a group's manage members template.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id ID of the group whose manage members page is being displayed.
+	 */
+	do_action( 'groups_screen_group_admin_manage_members', $bp->groups->current_group->id );
+
+	/**
+	 * Filters the template to load for a group's manage members page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to a group's manage members template.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_admin_manage_members', 'groups/single/home' ) );
+}
+add_action( 'bp_screens', 'groups_screen_group_admin_manage_members' );
+
+/**
+ * Handle the display of Admin > Membership Requests.
+ */
+function groups_screen_group_admin_requests() {
+	$bp = buddypress();
+
+	if ( 'membership-requests' != bp_get_group_current_admin_tab() ) {
+		return false;
+	}
+
+	if ( ! bp_is_item_admin() || ( 'public' == $bp->groups->current_group->status ) ) {
+		return false;
+	}
+
+	$request_action = (string) bp_action_variable( 1 );
+	$membership_id  = (int) bp_action_variable( 2 );
+
+	if ( !empty( $request_action ) && !empty( $membership_id ) ) {
+		if ( 'accept' == $request_action && is_numeric( $membership_id ) ) {
+
+			// Check the nonce first.
+			if ( !check_admin_referer( 'groups_accept_membership_request' ) )
+				return false;
+
+			// Accept the membership request
+			if ( !groups_accept_membership_request( $membership_id ) )
+				bp_core_add_message( __( 'There was an error accepting the membership request. Please try again.', 'buddypress' ), 'error' );
+			else
+				bp_core_add_message( __( 'Group membership request accepted', 'buddypress' ) );
+
+		} elseif ( 'reject' == $request_action && is_numeric( $membership_id ) ) {
+			/* Check the nonce first. */
+			if ( !check_admin_referer( 'groups_reject_membership_request' ) )
+				return false;
+
+			// Reject the membership request
+			if ( !groups_reject_membership_request( $membership_id ) )
+				bp_core_add_message( __( 'There was an error rejecting the membership request. Please try again.', 'buddypress' ), 'error' );
+			else
+				bp_core_add_message( __( 'Group membership request rejected', 'buddypress' ) );
+		}
+
+		/**
+		 * Fires before the redirect if a group membership request has been handled.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int    $id             ID of the group that was edited.
+		 * @param string $request_action Membership request action being performed.
+		 * @param int    $membership_id  The key of the action_variables array that you want.
+		 */
+		do_action( 'groups_group_request_managed', $bp->groups->current_group->id, $request_action, $membership_id );
+		bp_core_redirect( bp_get_group_permalink( groups_get_current_group() ) . 'admin/membership-requests/' );
+	}
+
+	/**
+	 * Fires before the loading of the group membership request page template.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id ID of the group that is being displayed.
+	 */
+	do_action( 'groups_screen_group_admin_requests', $bp->groups->current_group->id );
+
+	/**
+	 * Filters the template to load for a group's membership request page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to a group's membership request template.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_admin_requests', 'groups/single/home' ) );
+}
+add_action( 'bp_screens', 'groups_screen_group_admin_requests' );
+
+/**
+ * Handle the display of the Delete Group page.
+ */
+function groups_screen_group_admin_delete_group() {
+
+	if ( 'delete-group' != bp_get_group_current_admin_tab() )
+		return false;
+
+	if ( ! bp_is_item_admin() && !bp_current_user_can( 'bp_moderate' ) )
+		return false;
+
+	$bp = buddypress();
+
+	if ( isset( $_REQUEST['delete-group-button'] ) && isset( $_REQUEST['delete-group-understand'] ) ) {
+
+		// Check the nonce first.
+		if ( !check_admin_referer( 'groups_delete_group' ) ) {
+			return false;
+		}
+
+		/**
+		 * Fires before the deletion of a group from the Delete Group page.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int $id ID of the group being deleted.
+		 */
+		do_action( 'groups_before_group_deleted', $bp->groups->current_group->id );
+
+		// Group admin has deleted the group, now do it.
+		if ( !groups_delete_group( $bp->groups->current_group->id ) ) {
+			bp_core_add_message( __( 'There was an error deleting the group. Please try again.', 'buddypress' ), 'error' );
+		} else {
+			bp_core_add_message( __( 'The group was deleted successfully.', 'buddypress' ) );
+
+			/**
+			 * Fires after the deletion of a group from the Delete Group page.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param int $id ID of the group being deleted.
+			 */
+			do_action( 'groups_group_deleted', $bp->groups->current_group->id );
+
+			bp_core_redirect( trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() ) );
+		}
+
+		bp_core_redirect( trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() ) );
+	}
+
+	/**
+	 * Fires before the loading of the Delete Group page template.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id ID of the group that is being displayed.
+	 */
+	do_action( 'groups_screen_group_admin_delete_group', $bp->groups->current_group->id );
+
+	/**
+	 * Filters the template to load for the Delete Group page.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Path to the Delete Group template.
+	 */
+	bp_core_load_template( apply_filters( 'groups_template_group_admin_delete_group', 'groups/single/home' ) );
+}
+add_action( 'bp_screens', 'groups_screen_group_admin_delete_group' );
+
+/**
+ * Render the group settings fields on the Notification Settings page.
+ */
+function groups_screen_notification_settings() {
+
+	if ( !$group_invite = bp_get_user_meta( bp_displayed_user_id(), 'notification_groups_invite', true ) )
+		$group_invite  = 'yes';
+
+	if ( !$group_update = bp_get_user_meta( bp_displayed_user_id(), 'notification_groups_group_updated', true ) )
+		$group_update  = 'yes';
+
+	if ( !$group_promo = bp_get_user_meta( bp_displayed_user_id(), 'notification_groups_admin_promotion', true ) )
+		$group_promo   = 'yes';
+
+	if ( !$group_request = bp_get_user_meta( bp_displayed_user_id(), 'notification_groups_membership_request', true ) )
+		$group_request = 'yes'; ?>
+
+	<table class="notification-settings" id="groups-notification-settings">
+		<thead>
+			<tr>
+				<th class="icon"></th>
+				<th class="title"><?php _ex( 'Groups', 'Group settings on notification settings page', 'buddypress' ) ?></th>
+				<th class="yes"><?php _e( 'Yes', 'buddypress' ) ?></th>
+				<th class="no"><?php _e( 'No', 'buddypress' )?></th>
+			</tr>
+		</thead>
+
+		<tbody>
+			<tr id="groups-notification-settings-invitation">
+				<td></td>
+				<td><?php _ex( 'A member invites you to join a group', 'group settings on notification settings page','buddypress' ) ?></td>
+				<td class="yes"><input type="radio" name="notifications[notification_groups_invite]" id="notification-groups-invite-yes" value="yes" <?php checked( $group_invite, 'yes', true ) ?>/><label for="notification-groups-invite-yes" class="bp-screen-reader-text"><?php _e( 'Yes, send email', 'buddypress' ); ?></label></td>
+				<td class="no"><input type="radio" name="notifications[notification_groups_invite]" id="notification-groups-invite-no" value="no" <?php checked( $group_invite, 'no', true ) ?>/><label for="notification-groups-invite-no" class="bp-screen-reader-text"><?php _e( 'No, do not send email', 'buddypress' ); ?></label></td>
+			</tr>
+			<tr id="groups-notification-settings-info-updated">
+				<td></td>
+				<td><?php _ex( 'Group information is updated', 'group settings on notification settings page', 'buddypress' ) ?></td>
+				<td class="yes"><input type="radio" name="notifications[notification_groups_group_updated]" id="notification-groups-group-updated-yes" value="yes" <?php checked( $group_update, 'yes', true ) ?>/><label for="notification-groups-group-updated-yes" class="bp-screen-reader-text"><?php _e( 'Yes, send email', 'buddypress' ); ?></label></td>
+				<td class="no"><input type="radio" name="notifications[notification_groups_group_updated]" id="notification-groups-group-updated-no" value="no" <?php checked( $group_update, 'no', true ) ?>/><label for="notification-groups-group-updated-no" class="bp-screen-reader-text"><?php _e( 'No, do not send email', 'buddypress' ); ?></label></td>
+			</tr>
+			<tr id="groups-notification-settings-promoted">
+				<td></td>
+				<td><?php _ex( 'You are promoted to a group administrator or moderator', 'group settings on notification settings page', 'buddypress' ) ?></td>
+				<td class="yes"><input type="radio" name="notifications[notification_groups_admin_promotion]" id="notification-groups-admin-promotion-yes" value="yes" <?php checked( $group_promo, 'yes', true ) ?>/><label for="notification-groups-admin-promotion-yes" class="bp-screen-reader-text"><?php _e( 'Yes, send email', 'buddypress' ); ?></label></td>
+				<td class="no"><input type="radio" name="notifications[notification_groups_admin_promotion]" id="notification-groups-admin-promotion-no" value="no" <?php checked( $group_promo, 'no', true ) ?>/><label for="notification-groups-admin-promotion-no" class="bp-screen-reader-text"><?php _e( 'No, do not send email', 'buddypress' ); ?></label></td>
+			</tr>
+			<tr id="groups-notification-settings-request">
+				<td></td>
+				<td><?php _ex( 'A member requests to join a private group for which you are an admin', 'group settings on notification settings page', 'buddypress' ) ?></td>
+				<td class="yes"><input type="radio" name="notifications[notification_groups_membership_request]" id="notification-groups-membership-request-yes" value="yes" <?php checked( $group_request, 'yes', true ) ?>/><label for="notification-groups-membership-request-yes" class="bp-screen-reader-text"><?php _e( 'Yes, send email', 'buddypress' ); ?></label></td>
+				<td class="no"><input type="radio" name="notifications[notification_groups_membership_request]" id="notification-groups-membership-request-no" value="no" <?php checked( $group_request, 'no', true ) ?>/><label for="notification-groups-membership-request-no" class="bp-screen-reader-text"><?php _e( 'No, do not send email', 'buddypress' ); ?></label></td>
+			</tr>
+
+			<?php
+
+			/**
+			 * Fires at the end of the available group settings fields on Notification Settings page.
+			 *
+			 * @since 1.0.0
+			 */
+			do_action( 'groups_screen_notification_settings' ); ?>
+
+		</tbody>
+	</table>
+
+<?php
+}
+add_action( 'bp_notification_settings', 'groups_screen_notification_settings' );
+
+/** Theme Compatibility *******************************************************/
+
+/**
+ * The main theme compat class for BuddyPress Groups.
+ *
+ * This class sets up the necessary theme compatibility actions to safely output
+ * group template parts to the_title and the_content areas of a theme.
+ *
+ * @since 1.7.0
+ */
+class BP_Groups_Theme_Compat {
+
+	/**
+	 * Set up theme compatibility for the Groups component.
+	 *
+	 * @since 1.7.0
+	 */
+	public function __construct() {
+		add_action( 'bp_setup_theme_compat', array( $this, 'is_group' ) );
+	}
+
+	/**
+	 * Are we looking at something that needs group theme compatibility?
+	 *
+	 * @since 1.7.0
+	 */
+	public function is_group() {
+
+		// Bail if not looking at a group
+		if ( ! bp_is_groups_component() )
+			return;
+
+		// Group Directory
+		if ( ! bp_current_action() && ! bp_current_item() ) {
+			bp_update_is_directory( true, 'groups' );
+
+			/**
+			 * Fires at the start of the group theme compatibility setup.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'groups_directory_groups_setup' );
+
+			add_filter( 'bp_get_buddypress_template',                array( $this, 'directory_template_hierarchy' ) );
+			add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'directory_dummy_post' ) );
+			add_filter( 'bp_replace_the_content',                    array( $this, 'directory_content'    ) );
+
+		// Creating a group
+		} elseif ( bp_is_groups_component() && bp_is_current_action( 'create' ) ) {
+			add_filter( 'bp_get_buddypress_template',                array( $this, 'create_template_hierarchy' ) );
+			add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'create_dummy_post' ) );
+			add_filter( 'bp_replace_the_content',                    array( $this, 'create_content'    ) );
+
+		// Group page
+		} elseif ( bp_is_single_item() ) {
+			add_filter( 'bp_get_buddypress_template',                array( $this, 'single_template_hierarchy' ) );
+			add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'single_dummy_post' ) );
+			add_filter( 'bp_replace_the_content',                    array( $this, 'single_content'    ) );
+
+		}
+	}
+
+	/** Directory *********************************************************/
+
+	/**
+	 * Add template hierarchy to theme compat for the group directory page.
+	 *
+	 * This is to mirror how WordPress has
+	 * {@link https://codex.wordpress.org/Template_Hierarchy template hierarchy}.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $templates The templates from bp_get_theme_compat_templates().
+	 * @return array $templates Array of custom templates to look for.
+	 */
+	public function directory_template_hierarchy( $templates ) {
+
+		/**
+		 * Filters the Groups directory page template hierarchy based on priority.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param array $value Array of default template files to use.
+		 */
+		$new_templates = apply_filters( 'bp_template_hierarchy_groups_directory', array(
+			'groups/index-directory.php'
+		) );
+
+		// Merge new templates with existing stack
+		// @see bp_get_theme_compat_templates()
+		$templates = array_merge( (array) $new_templates, $templates );
+
+		return $templates;
+	}
+
+	/**
+	 * Update the global $post with directory data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function directory_dummy_post() {
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => bp_get_directory_title( 'groups' ),
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with the groups index template part.
+	 *
+	 * @since 1.7.0
+	 */
+	public function directory_content() {
+		return bp_buffer_template_part( 'groups/index', null, false );
+	}
+
+	/** Create ************************************************************/
+
+	/**
+	 * Add custom template hierarchy to theme compat for the group create page.
+	 *
+	 * This is to mirror how WordPress has
+	 * {@link https://codex.wordpress.org/Template_Hierarchy template hierarchy}.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $templates The templates from bp_get_theme_compat_templates().
+	 *
+	 * @return array $templates Array of custom templates to look for.
+	 */
+	public function create_template_hierarchy( $templates ) {
+
+		/**
+		 * Filters the Groups create page template hierarchy based on priority.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param array $value Array of default template files to use.
+		 */
+		$new_templates = apply_filters( 'bp_template_hierarchy_groups_create', array(
+			'groups/index-create.php'
+		) );
+
+		// Merge new templates with existing stack
+		// @see bp_get_theme_compat_templates()
+		$templates = array_merge( $new_templates, $templates );
+
+		return $templates;
+	}
+
+	/**
+	 * Update the global $post with create screen data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function create_dummy_post() {
+
+		$title = _x( 'Groups', 'Group creation page', 'buddypress' );
+
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => $title,
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with the create screen template part.
+	 *
+	 * @since 1.7.0
+	 */
+	public function create_content() {
+		return bp_buffer_template_part( 'groups/create', null, false );
+	}
+
+	/** Single ************************************************************/
+
+	/**
+	 * Add custom template hierarchy to theme compat for group pages.
+	 *
+	 * This is to mirror how WordPress has
+	 * {@link https://codex.wordpress.org/Template_Hierarchy template hierarchy}.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $templates The templates from bp_get_theme_compat_templates().
+	 * @return array $templates Array of custom templates to look for.
+	 */
+	public function single_template_hierarchy( $templates ) {
+		// Setup some variables we're going to reference in our custom templates
+		$group = groups_get_current_group();
+
+		/**
+		 * Filters the Groups single pages template hierarchy based on priority.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param array $value Array of default template files to use.
+		 */
+		$new_templates = apply_filters( 'bp_template_hierarchy_groups_single_item', array(
+			'groups/single/index-id-'     . sanitize_file_name( bp_get_current_group_id() )   . '.php',
+			'groups/single/index-slug-'   . sanitize_file_name( bp_get_current_group_slug() ) . '.php',
+			'groups/single/index-action-' . sanitize_file_name( bp_current_action() )         . '.php',
+			'groups/single/index-status-' . sanitize_file_name( $group->status )              . '.php',
+			'groups/single/index.php'
+		) );
+
+		// Merge new templates with existing stack
+		// @see bp_get_theme_compat_templates()
+		$templates = array_merge( (array) $new_templates, $templates );
+
+		return $templates;
+	}
+
+	/**
+	 * Update the global $post with single group data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function single_dummy_post() {
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => bp_get_current_group_name(),
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with the single group template part.
+	 *
+	 * @since 1.7.0
+	 */
+	public function single_content() {
+		return bp_buffer_template_part( 'groups/single/home', null, false );
+	}
+}
+new BP_Groups_Theme_Compat();
Index: /tags/2.4.3/src/bp-groups/bp-groups-template.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-template.php	(revision 10410)
@@ -0,0 +1,6340 @@
+<?php
+/**
+ * BuddyPress Groups Template Functions.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsTemplates
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the groups component slug.
+ *
+ * @since 1.5.0
+ */
+function bp_groups_slug() {
+	echo bp_get_groups_slug();
+}
+	/**
+	 * Return the groups component slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string
+	 */
+	function bp_get_groups_slug() {
+
+		/**
+		 * Filters the groups component slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $slug Groups component slug.
+		 */
+		return apply_filters( 'bp_get_groups_slug', buddypress()->groups->slug );
+	}
+
+/**
+ * Output the groups component root slug.
+ *
+ * @since 1.5.0
+ */
+function bp_groups_root_slug() {
+	echo bp_get_groups_root_slug();
+}
+	/**
+	 * Return the groups component root slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string
+	 */
+	function bp_get_groups_root_slug() {
+
+		/**
+		 * Filters the groups component root slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $root_slug Groups component root slug.
+		 */
+		return apply_filters( 'bp_get_groups_root_slug', buddypress()->groups->root_slug );
+	}
+
+/**
+ * Output group directory permalink.
+ *
+ * @since 1.5.0
+ */
+function bp_groups_directory_permalink() {
+	echo esc_url( bp_get_groups_directory_permalink() );
+}
+	/**
+	 * Return group directory permalink.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string
+	 */
+	function bp_get_groups_directory_permalink() {
+
+		/**
+		 * Filters the group directory permalink.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Permalink for the group directory.
+		 */
+		return apply_filters( 'bp_get_groups_directory_permalink', trailingslashit( bp_get_root_domain() . '/' . bp_get_groups_root_slug() ) );
+	}
+
+/**
+ * The main Groups template loop class.
+ *
+ * Responsible for loading a group of groups into a loop for display.
+ */
+class BP_Groups_Template {
+
+	/**
+	 * The loop iterator.
+	 *
+	 * @var int
+	 */
+	public $current_group = -1;
+
+	/**
+	 * The number of groups returned by the paged query.
+	 *
+	 * @var int
+	 */
+	public $group_count;
+
+	/**
+	 * Array of groups located by the query.
+	 *
+	 * @var array
+	 */
+	public $groups;
+
+	/**
+	 * The group object currently being iterated on.
+	 *
+	 * @var object
+	 */
+	public $group;
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @var bool
+	 */
+	public $in_the_loop;
+
+	/**
+	 * The page number being requested.
+	 *
+	 * @var string
+	 */
+	public $pag_page;
+
+	/**
+	 * The number of items being requested per page.
+	 *
+	 * @var string
+	 */
+	public $pag_num;
+
+	/**
+	 * An HTML string containing pagination links.
+	 *
+	 * @var string
+	 */
+	public $pag_links;
+
+	/**
+	 * The total number of groups matching the query parameters.
+	 *
+	 * @var int
+	 */
+	public $total_group_count;
+
+	/**
+	 * Whether the template loop is for a single group page.
+	 *
+	 * @var bool
+	 */
+	public $single_group = false;
+
+	/**
+	 * Field to sort by.
+	 *
+	 * @var string
+	 */
+	public $sort_by;
+
+	/**
+	 * Sort order.
+	 *
+	 * @var string
+	 */
+	public $order;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @see BP_Groups_Group::get() for an in-depth description of arguments.
+	 *
+	 * @param array $args {
+	 *     Array of arguments. Accepts all arguments accepted by
+	 *     {@link BP_Groups_Group::get()}. In cases where the default
+	 *     values of the params differ, they have been discussed below.
+	 *     @type int $per_page Default: 20.
+	 *     @type int $page Default: 1.
+	 * }
+	 */
+	function __construct( $args = array() ){
+
+		// Backward compatibility with old method of passing arguments
+		if ( ! is_array( $args ) || func_num_args() > 1 ) {
+			_deprecated_argument( __METHOD__, '1.7', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+			$old_args_keys = array(
+				0  => 'user_id',
+				1  => 'type',
+				2  => 'page',
+				3  => 'per_page',
+				4  => 'max',
+				5  => 'slug',
+				6  => 'search_terms',
+				7  => 'populate_extras',
+				8  => 'include',
+				9  => 'exclude',
+				10 => 'show_hidden',
+				11 => 'page_arg',
+			);
+
+			$func_args = func_get_args();
+			$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
+		}
+
+		$defaults = array(
+			'page'              => 1,
+			'per_page'          => 20,
+			'page_arg'          => 'grpage',
+			'max'               => false,
+			'type'              => 'active',
+			'order'             => 'DESC',
+			'orderby'           => 'date_created',
+			'show_hidden'       => false,
+			'user_id'           => 0,
+			'slug'              => false,
+			'include'           => false,
+			'exclude'           => false,
+			'search_terms'      => '',
+			'meta_query'        => false,
+			'populate_extras'   => true,
+			'update_meta_cache' => true,
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r );
+
+		$this->pag_arg  = sanitize_key( $r['page_arg'] );
+		$this->pag_page = bp_sanitize_pagination_arg( $this->pag_arg, $r['page']     );
+		$this->pag_num  = bp_sanitize_pagination_arg( 'num',          $r['per_page'] );
+
+		if ( bp_current_user_can( 'bp_moderate' ) || ( is_user_logged_in() && $user_id == bp_loggedin_user_id() ) ) {
+			$show_hidden = true;
+		}
+
+		if ( 'invites' == $type ) {
+			$this->groups = groups_get_invites_for_user( $user_id, $this->pag_num, $this->pag_page, $exclude );
+		} elseif ( 'single-group' == $type ) {
+			$this->single_group = true;
+
+			if ( groups_get_current_group() ) {
+				$group = groups_get_current_group();
+
+			} else {
+				$group = groups_get_group( array(
+					'group_id'        => BP_Groups_Group::get_id_from_slug( $r['slug'] ),
+					'populate_extras' => $r['populate_extras'],
+				) );
+			}
+
+			// backwards compatibility - the 'group_id' variable is not part of the
+			// BP_Groups_Group object, but we add it here for devs doing checks against it
+			//
+			// @see https://buddypress.trac.wordpress.org/changeset/3540
+			//
+			// this is subject to removal in a future release; devs should check against
+			// $group->id instead
+			$group->group_id = $group->id;
+
+			$this->groups = array( $group );
+
+		} else {
+			$this->groups = groups_get_groups( array(
+				'type'              => $type,
+				'order'             => $order,
+				'orderby'           => $orderby,
+				'per_page'          => $this->pag_num,
+				'page'              => $this->pag_page,
+				'user_id'           => $user_id,
+				'search_terms'      => $search_terms,
+				'meta_query'        => $meta_query,
+				'include'           => $include,
+				'exclude'           => $exclude,
+				'populate_extras'   => $populate_extras,
+				'update_meta_cache' => $update_meta_cache,
+				'show_hidden'       => $show_hidden
+			) );
+		}
+
+		if ( 'invites' == $type ) {
+			$this->total_group_count = (int) $this->groups['total'];
+			$this->group_count       = (int) $this->groups['total'];
+			$this->groups            = $this->groups['groups'];
+		} elseif ( 'single-group' == $type ) {
+			if ( empty( $group->id ) ) {
+				$this->total_group_count = 0;
+				$this->group_count       = 0;
+			} else {
+				$this->total_group_count = 1;
+				$this->group_count       = 1;
+			}
+		} else {
+			if ( empty( $max ) || $max >= (int) $this->groups['total'] ) {
+				$this->total_group_count = (int) $this->groups['total'];
+			} else {
+				$this->total_group_count = (int) $max;
+			}
+
+			$this->groups = $this->groups['groups'];
+
+			if ( !empty( $max ) ) {
+				if ( $max >= count( $this->groups ) ) {
+					$this->group_count = count( $this->groups );
+				} else {
+					$this->group_count = (int) $max;
+				}
+			} else {
+				$this->group_count = count( $this->groups );
+			}
+		}
+
+		// Build pagination links
+		if ( (int) $this->total_group_count && (int) $this->pag_num ) {
+			$pag_args = array(
+				$this->pag_arg => '%#%'
+			);
+
+			if ( defined( 'DOING_AJAX' ) && true === (bool) DOING_AJAX ) {
+				$base = remove_query_arg( 's', wp_get_referer() );
+			} else {
+				$base = '';
+			}
+
+			$add_args = array(
+				'num'     => $this->pag_num,
+				'sortby'  => $this->sort_by,
+				'order'   => $this->order,
+			);
+
+			if ( ! empty( $search_terms ) ) {
+				$query_arg = bp_core_get_component_search_query_arg( 'groups' );
+				$add_args[ $query_arg ] = urlencode( $search_terms );
+			}
+
+			$this->pag_links = paginate_links( array(
+				'base'      => add_query_arg( $pag_args, $base ),
+				'format'    => '',
+				'total'     => ceil( (int) $this->total_group_count / (int) $this->pag_num ),
+				'current'   => $this->pag_page,
+				'prev_text' => _x( '&larr;', 'Group pagination previous text', 'buddypress' ),
+				'next_text' => _x( '&rarr;', 'Group pagination next text', 'buddypress' ),
+				'mid_size'  => 1,
+				'add_args'  => $add_args,
+			) );
+		}
+	}
+
+	/**
+	 * Whether there are groups available in the loop.
+	 *
+	 * @see bp_has_groups()
+	 *
+	 * @return bool True if there are items in the loop, otherwise false.
+	 */
+	function has_groups() {
+		if ( $this->group_count ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Set up the next group and iterate index.
+	 *
+	 * @return object The next group to iterate over.
+	 */
+	function next_group() {
+		$this->current_group++;
+		$this->group = $this->groups[$this->current_group];
+
+		return $this->group;
+	}
+
+	/**
+	 * Rewind the groups and reset member index.
+	 */
+	function rewind_groups() {
+		$this->current_group = -1;
+		if ( $this->group_count > 0 ) {
+			$this->group = $this->groups[0];
+		}
+	}
+
+	/**
+	 * Whether there are groups left in the loop to iterate over.
+	 *
+	 * This method is used by {@link bp_groups()} as part of the while loop
+	 * that controls iteration inside the groups loop, eg:
+	 *     while ( bp_groups() ) { ...
+	 *
+	 * @see bp_groups()
+	 *
+	 * @return bool True if there are more groups to show, otherwise false.
+	 */
+	function groups() {
+		if ( $this->current_group + 1 < $this->group_count ) {
+			return true;
+		} elseif ( $this->current_group + 1 == $this->group_count ) {
+
+			/**
+			 * Fires right before the rewinding of groups list.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action('group_loop_end');
+			// Do some cleaning up after the loop
+			$this->rewind_groups();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	/**
+	 * Set up the current group inside the loop.
+	 *
+	 * Used by {@link bp_the_group()} to set up the current group data
+	 * while looping, so that template tags used during that iteration make
+	 * reference to the current member.
+	 *
+	 * @see bp_the_group()
+	 */
+	function the_group() {
+		$this->in_the_loop = true;
+		$this->group       = $this->next_group();
+
+		if ( 0 == $this->current_group ) {
+
+			/**
+			 * Fires if the current group item is the first in the loop.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'group_loop_start' );
+		}
+	}
+}
+
+/**
+ * Start the Groups Template Loop.
+ *
+ * @since 1.0.0
+ *
+ * @param array|string $args {
+ *     Array of parameters. All items are optional.
+ *     @type string       $type            Shorthand for certain orderby/
+ *                                         order combinations. 'newest',
+ *                                         'active', 'popular', 'alphabetical',
+ *                                         'random'. When present, will override
+ *                                         orderby and order params. Default: null.
+ *     @type string       $orderby         Property to sort by.
+ *                                         'date_created', 'last_activity', 'total_member_count',
+ *                                         'name', 'random'. Default: 'date_created'.
+ *     @type string       $order           Sort order. 'ASC' or 'DESC'.
+ *                                         Default: 'DESC'.
+ *     @type int          $per_page        Number of items to return per page
+ *                                         of results. Default: null (no limit).
+ *     @type int          $page            Page offset of results to return.
+ *                                         Default: null (no limit).
+ *     @type int          $user_id         If provided, results will be limited
+ *                                         to groups of which the specified user
+ *                                         is a member. Default: null.
+ *     @type string       $search_terms    If provided, only groups whose names or descriptions match the search terms
+ *                                         will be returned. Default: value of `$_REQUEST['groups_search']` or
+ *                                         `$_REQUEST['s']`, if present. Otherwise false.
+ *     @type array        $meta_query      An array of meta_query conditions.
+ *                                         See {@link WP_Meta_Query::queries} for description.
+ *     @type array|string $include         Array or comma-separated list of
+ *                                         group IDs. Results will be limited
+ *                                         to groups within the list. Default: false.
+ *     @type bool         $populate_extras Whether to fetch additional information
+ *                                         (such as member count) about groups.
+ *                                         Default: true.
+ *     @type array|string $exclude         Array or comma-separated list of group IDs.
+ *                                         Results will exclude the listed groups.
+ *                                         Default: false.
+ *     @type bool         $show_hidden     Whether to include hidden groups in
+ *                                         results. Default: false.
+ * }
+ * @return bool True if there are groups to display that match the params
+ */
+function bp_has_groups( $args = '' ) {
+	global $groups_template;
+
+	/***
+	 * Defaults based on the current page & overridden by parsed $args
+	 */
+	$slug         = false;
+	$type         = '';
+	$search_terms = false;
+
+	// When looking your own groups, check for two action variables
+	if ( bp_is_current_action( 'my-groups' ) ) {
+		if ( bp_is_action_variable( 'most-popular', 0 ) ) {
+			$type = 'popular';
+		} elseif ( bp_is_action_variable( 'alphabetically', 0 ) ) {
+			$type = 'alphabetical';
+		}
+
+	// When looking at invites, set type to invites
+	} elseif ( bp_is_current_action( 'invites' ) ) {
+		$type = 'invites';
+
+	// When looking at a single group, set the type and slug
+	} elseif ( bp_get_current_group_slug() ) {
+		$type = 'single-group';
+		$slug = bp_get_current_group_slug();
+	}
+
+	// Default search string (too soon to escape here)
+	$search_query_arg = bp_core_get_component_search_query_arg( 'groups' );
+	if ( ! empty( $_REQUEST[ $search_query_arg ] ) ) {
+		$search_terms = stripslashes( $_REQUEST[ $search_query_arg ] );
+	} elseif ( ! empty( $_REQUEST['group-filter-box'] ) ) {
+		$search_terms = $_REQUEST['group-filter-box'];
+	} elseif ( !empty( $_REQUEST['s'] ) ) {
+		$search_terms = $_REQUEST['s'];
+	}
+
+	// Parse defaults and requested arguments
+	$r = bp_parse_args( $args, array(
+		'type'              => $type,
+		'order'             => 'DESC',
+		'orderby'           => 'last_activity',
+		'page'              => 1,
+		'per_page'          => 20,
+		'max'               => false,
+		'show_hidden'       => false,
+		'page_arg'          => 'grpage',
+		'user_id'           => bp_displayed_user_id(),
+		'slug'              => $slug,
+		'search_terms'      => $search_terms,
+		'meta_query'        => false,
+		'include'           => false,
+		'exclude'           => false,
+		'populate_extras'   => true,
+		'update_meta_cache' => true,
+	), 'has_groups' );
+
+	// Setup the Groups template global
+	$groups_template = new BP_Groups_Template( array(
+		'type'              => $r['type'],
+		'order'             => $r['order'],
+		'orderby'           => $r['orderby'],
+		'page'              => (int) $r['page'],
+		'per_page'          => (int) $r['per_page'],
+		'max'               => (int) $r['max'],
+		'show_hidden'       => $r['show_hidden'],
+		'page_arg'          => $r['page_arg'],
+		'user_id'           => (int) $r['user_id'],
+		'slug'              => $r['slug'],
+		'search_terms'      => $r['search_terms'],
+		'meta_query'        => $r['meta_query'],
+		'include'           => $r['include'],
+		'exclude'           => $r['exclude'],
+		'populate_extras'   => (bool) $r['populate_extras'],
+		'update_meta_cache' => (bool) $r['update_meta_cache'],
+	) );
+
+	/**
+	 * Filters whether or not there are groups to iterate over for the groups loop.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool               $value           Whether or not there are groups to iterate over.
+	 * @param BP_Groups_Template $groups_template BP_Groups_Template object based on parsed arguments.
+	 * @param array              $r               Array of parsed arguments for the query.
+	 */
+	return apply_filters( 'bp_has_groups', $groups_template->has_groups(), $groups_template, $r );
+}
+
+/**
+ * Check whether there are more groups to iterate over.
+ *
+ * @return bool
+ */
+function bp_groups() {
+	global $groups_template;
+	return $groups_template->groups();
+}
+
+/**
+ * Set up the current group inside the loop.
+ *
+ * @return object
+ */
+function bp_the_group() {
+	global $groups_template;
+	return $groups_template->the_group();
+}
+
+/**
+ * Is the group visible to the currently logged-in user?
+ *
+ * @param object|bool $group Optional. Group object. Default: current group in loop.
+ *
+ * @return bool
+ */
+function bp_group_is_visible( $group = false ) {
+	global $groups_template;
+
+	if ( bp_current_user_can( 'bp_moderate' ) ) {
+		return true;
+	}
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	if ( 'public' == $group->status ) {
+		return true;
+	} else {
+		if ( groups_is_user_member( bp_loggedin_user_id(), $group->id ) ) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/**
+ * Output the ID of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object. Default: current group in loop.
+ */
+function bp_group_id( $group = false ) {
+	echo bp_get_group_id( $group );
+}
+	/**
+	 * Get the ID of the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return int
+	 */
+	function bp_get_group_id( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the ID of the current group in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $id ID of the current group in the loop.
+		 */
+		return apply_filters( 'bp_get_group_id', $group->id );
+	}
+
+/**
+ * Output the row class of the current group in the loop.
+ *
+ * @since 1.7.0
+ *
+ * @param array $classes Array of custom classes.
+ */
+function bp_group_class( $classes = array() ) {
+	echo bp_get_group_class( $classes );
+}
+	/**
+	 * Get the row class of the current group in the loop.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $classes Array of custom classes.
+	 *
+	 * @return string Row class of the group.
+	 */
+	function bp_get_group_class( $classes = array() ) {
+		global $groups_template;
+
+		// Add even/odd classes, but only if there's more than 1 group
+		if ( $groups_template->group_count > 1 ) {
+			$pos_in_loop = (int) $groups_template->current_group;
+			$classes[]   = ( $pos_in_loop % 2 ) ? 'even' : 'odd';
+
+		// If we've only one group in the loop, don't bother with odd and even
+		} else {
+			$classes[] = 'bp-single-group';
+		}
+
+		// Group type - public, private, hidden.
+		$classes[] = sanitize_key( $groups_template->group->status );
+
+		// User's group role
+		if ( bp_is_user_active() ) {
+
+			// Admin
+			if ( bp_group_is_admin() ) {
+				$classes[] = 'is-admin';
+			}
+
+			// Moderator
+			if ( bp_group_is_mod() ) {
+				$classes[] = 'is-mod';
+			}
+
+			// Member
+			if ( bp_group_is_member() ) {
+				$classes[] = 'is-member';
+			}
+		}
+
+		// Whether a group avatar will appear.
+		if ( bp_disable_group_avatar_uploads() || ! buddypress()->avatar->show_avatars ) {
+			$classes[] = 'group-no-avatar';
+		} else {
+			$classes[] = 'group-has-avatar';
+		}
+
+		/**
+		 * Filters classes that will be applied to row class of the current group in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param array $classes Array of determined classes for the row.
+		 */
+		$classes = apply_filters( 'bp_get_group_class', $classes );
+		$classes = array_merge( $classes, array() );
+		$retval = 'class="' . join( ' ', $classes ) . '"';
+
+		return $retval;
+	}
+
+/**
+ * Output the name of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_name( $group = false ) {
+	echo bp_get_group_name( $group );
+}
+	/**
+	 * Get the name of the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_name( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the name of the current group in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $name Name of the current group in the loop.
+		 */
+		return apply_filters( 'bp_get_group_name', $group->name );
+	}
+
+/**
+ * Output the type of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_type( $group = false ) {
+	echo bp_get_group_type( $group );
+}
+
+/**
+ * Get the type of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ *
+ * @return string
+ */
+function bp_get_group_type( $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	if ( 'public' == $group->status ) {
+		$type = __( "Public Group", "buddypress" );
+	} elseif ( 'hidden' == $group->status ) {
+		$type = __( "Hidden Group", "buddypress" );
+	} elseif ( 'private' == $group->status ) {
+		$type = __( "Private Group", "buddypress" );
+	} else {
+		$type = ucwords( $group->status ) . ' ' . __( 'Group', 'buddypress' );
+	}
+
+	/**
+	 * Filters the type for the current group in the loop.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $type Type for the current group in the loop.
+	 */
+	return apply_filters( 'bp_get_group_type', $type );
+}
+/**
+ * Output the status of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_status( $group = false ) {
+	echo bp_get_group_status( $group );
+}
+	/**
+	 * Get the status of the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_status( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the status of the current group in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $status Status of the current group in the loop.
+		 */
+		return apply_filters( 'bp_get_group_status', $group->status );
+	}
+
+/**
+ * Output the group avatar while in the groups loop.
+ *
+ * @since 1.0.0
+ *
+ * @param array|string $args {
+ *      See {@link bp_get_group_avatar()} for description of arguments.
+ * }
+ */
+function bp_group_avatar( $args = '' ) {
+	echo bp_get_group_avatar( $args );
+}
+	/**
+	 * Get a group's avatar.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @see bp_core_fetch_avatar() For a description of arguments and return values.
+
+	 * @param array $args  {
+	 *     Arguments are listed here with an explanation of their defaults.
+	 *     For more information about the arguments, see {@link bp_core_fetch_avatar()}.
+	 *
+	 *     @type string   $alt     Default: 'Group logo of [group name]'.
+	 *     @type string   $class   Default: 'avatar'.
+	 *     @type string   $type    Default: 'full'.
+	 *     @type int|bool $width   Default: false.
+	 *     @type int|bool $height  Default: false.
+	 *     @type bool     $id      Passed to `$css_id` parameter.
+	 * }
+	 * @return string Group avatar string.
+	 */
+	function bp_get_group_avatar( $args = '' ) {
+		global $groups_template;
+
+		// Bail if avatars are turned off.
+		if ( bp_disable_group_avatar_uploads() || ! buddypress()->avatar->show_avatars ) {
+			return false;
+		}
+
+		// Parse the arguments
+		$r = bp_parse_args( $args, array(
+			'type'   => 'full',
+			'width'  => false,
+			'height' => false,
+			'class'  => 'avatar',
+			'id'     => false,
+			'alt'    => sprintf( __( 'Group logo of %s', 'buddypress' ), $groups_template->group->name )
+		) );
+
+		// Fetch the avatar from the folder
+		$avatar = bp_core_fetch_avatar( array(
+			'item_id'    => $groups_template->group->id,
+			'title'      => $groups_template->group->name,
+			'avatar_dir' => 'group-avatars',
+			'object'     => 'group',
+			'type'       => $r['type'],
+			'alt'        => $r['alt'],
+			'css_id'     => $r['id'],
+			'class'      => $r['class'],
+			'width'      => $r['width'],
+			'height'     => $r['height']
+		) );
+
+		// If No avatar found, provide some backwards compatibility
+		if ( empty( $avatar ) ) {
+			$avatar = '<img src="' . esc_url( $groups_template->group->avatar_thumb ) . '" class="avatar" alt="' . esc_attr( $groups_template->group->name ) . '" />';
+		}
+
+		/**
+		 * Filters the group avatar while in the groups loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $avatar HTML image element holding the group avatar.
+		 * @param array  $r      Array of parsed arguments for the group avatar.
+		 */
+		return apply_filters( 'bp_get_group_avatar', $avatar, $r );
+	}
+
+/**
+ * Output the group avatar thumbnail while in the groups loop.
+ *
+ * @since 1.0.0
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_avatar_thumb( $group = false ) {
+	echo bp_get_group_avatar_thumb( $group );
+}
+	/**
+	 * Return the group avatar thumbnail while in the groups loop.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_avatar_thumb( $group = false ) {
+		return bp_get_group_avatar( array(
+			'type' => 'thumb',
+			'id'   => ! empty( $group->id ) ? $group->id : false
+		) );
+	}
+
+/**
+ * Output the miniature group avatar thumbnail while in the groups loop.
+ *
+ * @since 1.0.0
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_avatar_mini( $group = false ) {
+	echo bp_get_group_avatar_mini( $group );
+}
+	/**
+	 * Return the miniature group avatar thumbnail while in the groups loop.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_avatar_mini( $group = false ) {
+		return bp_get_group_avatar( array(
+			'type'   => 'thumb',
+			'width'  => 30,
+			'height' => 30,
+			'id'     => ! empty( $group->id ) ? $group->id : false
+		) );
+	}
+
+/** Group cover image *********************************************************/
+
+/**
+ * Should we use the group's cover image header
+ *
+ * @since 2.4.0
+ *
+ * @return bool True if the displayed user has a cover image,
+ *              False otherwise
+ */
+function bp_group_use_cover_image_header() {
+	return (bool) bp_is_active( 'groups', 'cover_image' ) && ! bp_disable_group_cover_image_uploads() && bp_attachments_is_wp_version_supported();
+}
+
+/**
+ * Output the 'last active' string for the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_last_active( $group = false ) {
+	echo bp_get_group_last_active( $group );
+}
+	/**
+	 * Return the 'last active' string for the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_last_active( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		$last_active = $group->last_activity;
+
+		if ( !$last_active ) {
+			$last_active = groups_get_groupmeta( $group->id, 'last_activity' );
+		}
+
+		if ( empty( $last_active ) ) {
+			return __( 'not yet active', 'buddypress' );
+		} else {
+
+			/**
+			 * Filters the 'last active' string for the current gorup in the loop.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param string $value Determined last active value for the current group.
+			 */
+			return apply_filters( 'bp_get_group_last_active', bp_core_time_since( $last_active ) );
+		}
+	}
+
+/**
+ * Output the permalink for the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_permalink( $group = false ) {
+	echo bp_get_group_permalink( $group );
+}
+	/**
+	 * Return the permalink for the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_permalink( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the permalink for the current group in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Permalink for the current group in the loop.
+		 */
+		return apply_filters( 'bp_get_group_permalink', trailingslashit( bp_get_groups_directory_permalink() . $group->slug . '/' ) );
+	}
+
+/**
+ * Output the permalink for the admin section of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_admin_permalink( $group = false ) {
+	echo bp_get_group_admin_permalink( $group );
+}
+	/**
+	 * Return the permalink for the admin section of the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_admin_permalink( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the permalink for the admin section of the current group in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Permalink for the admin section of the current group in the loop.
+		 */
+		return apply_filters( 'bp_get_group_admin_permalink', trailingslashit( bp_get_group_permalink( $group ) . 'admin' ) );
+	}
+
+/**
+ * Return the slug for the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_slug( $group = false ) {
+	echo bp_get_group_slug( $group );
+}
+	/**
+	 * Return the slug for the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_slug( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the slug for the current group in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $slug Slug for the current group in the loop.
+		 */
+		return apply_filters( 'bp_get_group_slug', $group->slug );
+	}
+
+/**
+ * Output the description for the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_description( $group = false ) {
+	echo bp_get_group_description( $group );
+}
+	/**
+	 * Return the description for the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_description( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the description for the current group in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Description for the current group.
+		 */
+		return apply_filters( 'bp_get_group_description', stripslashes($group->description) );
+	}
+
+/**
+ * Output the description for the current group in the loop, for use in a textarea.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_description_editable( $group = false ) {
+	echo bp_get_group_description_editable( $group );
+}
+	/**
+	 * Return the permalink for the current group in the loop, for use in a textarea.
+	 *
+	 * 'bp_get_group_description_editable' does not have the formatting
+	 * filters that 'bp_get_group_description' has, which makes it
+	 * appropriate for "raw" editing.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_description_editable( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the permalink for the current group in the loop, for use in a textarea.
+		 *
+		 * 'bp_get_group_description_editable' does not have the formatting
+	     * filters that 'bp_get_group_description' has, which makes it
+	     * appropriate for "raw" editing.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $description Description for the current group in the loop.
+		 */
+		return apply_filters( 'bp_get_group_description_editable', $group->description );
+	}
+
+/**
+ * Output an excerpt of the group description.
+ *
+ * @param object|bool $group Optional. The group being referenced.
+ *                           Defaults to the group currently being
+ *                           iterated on in the groups loop.
+ */
+function bp_group_description_excerpt( $group = false ) {
+	echo bp_get_group_description_excerpt( $group );
+}
+	/**
+	 * Get an excerpt of a group description.
+	 *
+	 * @param object|bool $group Optional. The group being referenced.
+	 *                           Defaults to the group currently being
+	 *                           iterated on in the groups loop.
+	 *
+	 * @return string Excerpt.
+	 */
+	function bp_get_group_description_excerpt( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the excerpt of a group description.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Excerpt of a group description.
+		 * @param object $group Object for group whose description is made into an excerpt.
+		 */
+		return apply_filters( 'bp_get_group_description_excerpt', bp_create_excerpt( $group->description ), $group );
+	}
+
+/**
+ * Output the status of the current group in the loop.
+ *
+ * Either 'Public' or 'Private'.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_public_status( $group = false ) {
+	echo bp_get_group_public_status( $group );
+}
+	/**
+	 * Return the status of the current group in the loop.
+	 *
+	 * Either 'Public' or 'Private'.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_public_status( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		if ( $group->is_public ) {
+			return __( 'Public', 'buddypress' );
+		} else {
+			return __( 'Private', 'buddypress' );
+		}
+	}
+
+/**
+ * Output whether the current group in the loop is public.
+ *
+ * No longer used in BuddyPress.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_is_public( $group = false ) {
+	echo bp_get_group_is_public( $group );
+}
+	/**
+	 * Return whether the current group in the loop is public.
+	 *
+	 * No longer used in BuddyPress.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return unknown
+	 */
+	function bp_get_group_is_public( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		return apply_filters( 'bp_get_group_is_public', $group->is_public );
+	}
+
+/**
+ * Output the created date of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_date_created( $group = false ) {
+	echo bp_get_group_date_created( $group );
+}
+	/**
+	 * Return the created date of the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_date_created( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the created date of the current group in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Created date for the current group.
+		 */
+		return apply_filters( 'bp_get_group_date_created', bp_core_time_since( strtotime( $group->date_created ) ) );
+	}
+
+/**
+ * Output the username of the creator of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_creator_username( $group = false ) {
+	echo bp_get_group_creator_username( $group );
+}
+	/**
+	 * Return the username of the creator of the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_creator_username( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the username of the creator of the current group in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $value Username of the group creator.
+		 */
+		return apply_filters( 'bp_get_group_creator_username', bp_core_get_user_displayname( $group->creator_id ) );
+	}
+
+/**
+ * Output the user ID of the creator of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_creator_id( $group = false ) {
+	echo bp_get_group_creator_id( $group );
+}
+	/**
+	 * Return the user ID of the creator of the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return int
+	 */
+	function bp_get_group_creator_id( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the user ID of the creator of the current group in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param int $creator_id User ID of the group creator.
+		 */
+		return apply_filters( 'bp_get_group_creator_id', $group->creator_id );
+	}
+
+/**
+ * Output the permalink of the creator of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_creator_permalink( $group = false ) {
+	echo bp_get_group_creator_permalink( $group );
+}
+	/**
+	 * Return the permalink of the creator of the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_creator_permalink( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the permalink of the creator of the current group in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $value Permalink of the group creator.
+		 */
+		return apply_filters( 'bp_get_group_creator_permalink', bp_core_get_user_domain( $group->creator_id ) );
+	}
+
+/**
+ * Determine whether a user is the creator of the current group in the loop.
+ *
+ * @param object|bool $group   Optional. Group object.
+ *                             Default: current group in loop.
+ * @param int         $user_id ID of the user.
+ *
+ * @return bool
+ */
+function bp_is_group_creator( $group = false, $user_id = 0 ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	return (bool) ( $group->creator_id == $user_id );
+}
+
+/**
+ * Output the avatar of the creator of the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ * @param array       $args {
+ *     Array of optional arguments. See {@link bp_get_group_creator_avatar()}
+ *     for description.
+ * }
+ */
+function bp_group_creator_avatar( $group = false, $args = array() ) {
+	echo bp_get_group_creator_avatar( $group, $args );
+}
+	/**
+	 * Return the avatar of the creator of the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 * @param array       $args {
+	 *     Array of optional arguments. See {@link bp_core_fetch_avatar()}
+	 *     for detailed description of arguments.
+	 *     @type string $type   Default: 'full'.
+	 *     @type int    $width  Default: false.
+	 *     @type int    $height Default: false.
+	 *     @type int    $class  Default: 'avatar'.
+	 *     @type string $id     Passed to 'css_id'. Default: false.
+	 *     @type string $alt    Alt text. Default: 'Group creator profile
+	 *                          photo of [user display name]'.
+	 * }
+	 * @return string
+	 */
+	function bp_get_group_creator_avatar( $group = false, $args = array() ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		$defaults = array(
+			'type'   => 'full',
+			'width'  => false,
+			'height' => false,
+			'class'  => 'avatar',
+			'id'     => false,
+			'alt'    => sprintf( __( 'Group creator profile photo of %s', 'buddypress' ),  bp_core_get_user_displayname( $group->creator_id ) )
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		$avatar = bp_core_fetch_avatar( array( 'item_id' => $group->creator_id, 'type' => $type, 'css_id' => $id, 'class' => $class, 'width' => $width, 'height' => $height, 'alt' => $alt ) );
+
+		/**
+		 * Filters the avatar of the creator of the current group in the loop.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $avatar Avatar of the group creator.
+		 */
+		return apply_filters( 'bp_get_group_creator_avatar', $avatar );
+	}
+
+/**
+ * Determine whether the current user is the admin of the current group.
+ *
+ * Alias of {@link bp_is_item_admin()}.
+ *
+ * @return bool
+ */
+function bp_group_is_admin() {
+	return bp_is_item_admin();
+}
+
+/**
+ * Determine whether the current user is a mod of the current group.
+ *
+ * Alias of {@link bp_is_item_mod()}.
+ *
+ * @return bool
+ */
+function bp_group_is_mod() {
+	return bp_is_item_mod();
+}
+
+/**
+ * Output markup listing group admins.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_list_admins( $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	// fetch group admins if 'populate_extras' flag is false
+	if ( empty( $group->args['populate_extras'] ) ) {
+		$query = new BP_Group_Member_Query( array(
+			'group_id'   => $group->id,
+			'group_role' => 'admin',
+			'type'       => 'first_joined',
+		) );
+
+		if ( ! empty( $query->results ) ) {
+			$group->admins = $query->results;
+		}
+	}
+
+	if ( ! empty( $group->admins ) ) { ?>
+		<ul id="group-admins">
+			<?php foreach( (array) $group->admins as $admin ) { ?>
+				<li>
+					<a href="<?php echo bp_core_get_user_domain( $admin->user_id, $admin->user_nicename, $admin->user_login ) ?>"><?php echo bp_core_fetch_avatar( array( 'item_id' => $admin->user_id, 'email' => $admin->user_email, 'alt' => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_core_get_user_displayname( $admin->user_id ) ) ) ) ?></a>
+				</li>
+			<?php } ?>
+		</ul>
+	<?php } else { ?>
+		<span class="activity"><?php _e( 'No Admins', 'buddypress' ) ?></span>
+	<?php } ?>
+<?php
+}
+
+/**
+ * Output markup listing group mod.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in loop.
+ */
+function bp_group_list_mods( $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	// fetch group mods if 'populate_extras' flag is false
+	if ( empty( $group->args['populate_extras'] ) ) {
+		$query = new BP_Group_Member_Query( array(
+			'group_id'   => $group->id,
+			'group_role' => 'mod',
+			'type'       => 'first_joined',
+		) );
+
+		if ( ! empty( $query->results ) ) {
+			$group->mods = $query->results;
+		}
+	}
+
+	if ( ! empty( $group->mods ) ) : ?>
+
+		<ul id="group-mods">
+
+			<?php foreach( (array) $group->mods as $mod ) { ?>
+
+				<li>
+					<a href="<?php echo bp_core_get_user_domain( $mod->user_id, $mod->user_nicename, $mod->user_login ) ?>"><?php echo bp_core_fetch_avatar( array( 'item_id' => $mod->user_id, 'email' => $mod->user_email, 'alt' => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_core_get_user_displayname( $mod->user_id ) ) ) ) ?></a>
+				</li>
+
+			<?php } ?>
+
+		</ul>
+
+<?php else : ?>
+
+		<span class="activity"><?php _e( 'No Mods', 'buddypress' ) ?></span>
+
+<?php endif;
+
+}
+
+/**
+ * Return a list of user IDs for a group's admins.
+ *
+ * @since 1.5.0
+ *
+ * @param BP_Groups_Group|bool $group     Optional. The group being queried. Defaults
+ *                                        to the current group in the loop.
+ * @param string               $format    Optional. 'string' to get a comma-separated string,
+ *                                        'array' to get an array.
+ * @return mixed               $admin_ids A string or array of user IDs.
+ */
+function bp_group_admin_ids( $group = false, $format = 'string' ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	$admin_ids = array();
+
+	if ( $group->admins ) {
+		foreach( $group->admins as $admin ) {
+			$admin_ids[] = $admin->user_id;
+		}
+	}
+
+	if ( 'string' == $format ) {
+		$admin_ids = implode( ',', $admin_ids );
+	}
+
+	/**
+	 * Filters a list of user IDs for a group's admins.
+	 *
+	 * This filter may return either an array or a comma separated string.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array|string $admin_ids List of user IDs for a group's admins.
+	 */
+	return apply_filters( 'bp_group_admin_ids', $admin_ids );
+}
+
+/**
+ * Return a list of user IDs for a group's moderators.
+ *
+ * @since 1.5.0
+ *
+ * @param BP_Groups_Group|bool $group   Optional. The group being queried.
+ *                                      Defaults to the current group in the loop.
+ * @param string               $format  Optional. 'string' to get a comma-separated string,
+ *                                      'array' to get an array.
+ * @return mixed               $mod_ids A string or array of user IDs.
+ */
+function bp_group_mod_ids( $group = false, $format = 'string' ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	$mod_ids = array();
+
+	if ( $group->mods ) {
+		foreach( $group->mods as $mod ) {
+			$mod_ids[] = $mod->user_id;
+		}
+	}
+
+	if ( 'string' == $format ) {
+		$mod_ids = implode( ',', $mod_ids );
+	}
+
+	/**
+	 * Filters a list of user IDs for a group's moderators.
+	 *
+	 * This filter may return either an array or a comma separated string.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array|string $admin_ids List of user IDs for a group's moderators.
+	 */
+	return apply_filters( 'bp_group_mod_ids', $mod_ids );
+}
+
+/**
+ * Output the permalink of the current group's Members page.
+ */
+function bp_group_all_members_permalink() {
+	echo bp_get_group_all_members_permalink();
+}
+	/**
+	 * Return the permalink of the Members page of the current group in the loop.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_all_members_permalink( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the permalink of the Members page for the current group in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Permalink of the Members page for the current group.
+		 */
+		return apply_filters( 'bp_get_group_all_members_permalink', bp_get_group_permalink( $group ) . 'members' );
+	}
+
+/**
+ * Display a Groups search form.
+ *
+ * No longer used in BuddyPress.
+ *
+ * @todo Deprecate
+ */
+function bp_group_search_form() {
+
+	$action = bp_displayed_user_domain() . bp_get_groups_slug() . '/my-groups/search/';
+	$label = __('Filter Groups', 'buddypress');
+	$name = 'group-filter-box';
+
+	$search_form_html = '<form action="' . $action . '" id="group-search-form" method="post">
+		<label for="'. $name .'" id="'. $name .'-label">'. $label .'</label>
+		<input type="search" name="'. $name . '" id="'. $name .'" value="'. $value .'"'.  $disabled .' />
+
+		'. wp_nonce_field( 'group-filter-box', '_wpnonce_group_filter', true, false ) .'
+		</form>';
+
+	echo apply_filters( 'bp_group_search_form', $search_form_html );
+}
+
+/**
+ * Determine whether the displayed user has no groups.
+ *
+ * No longer used in BuddyPress.
+ *
+ * @todo Deprecate
+ *
+ * @return bool True if the displayed user has no groups, otherwise false.
+ */
+function bp_group_show_no_groups_message() {
+	if ( !groups_total_groups_for_user( bp_displayed_user_id() ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Determine whether the current page is a group activity permalink.
+ *
+ * No longer used in BuddyPress.
+ *
+ * @todo Deprecate.
+ *
+ * @return bool True if this is a group activity permalink, otherwise false.
+ */
+function bp_group_is_activity_permalink() {
+
+	if ( !bp_is_single_item() || !bp_is_groups_component() || !bp_is_current_action( bp_get_activity_slug() ) ) {
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Output the pagination HTML for a group loop.
+ */
+function bp_groups_pagination_links() {
+	echo bp_get_groups_pagination_links();
+}
+	/**
+	 * Get the pagination HTML for a group loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_groups_pagination_links() {
+		global $groups_template;
+
+		/**
+		 * Filters the pagination HTML for a group loop.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $pag_links HTML markup for the pagination links.
+		 */
+		return apply_filters( 'bp_get_groups_pagination_links', $groups_template->pag_links );
+	}
+
+/**
+ * Output the "Viewing x-y of z groups" pagination message.
+ */
+function bp_groups_pagination_count() {
+	echo bp_get_groups_pagination_count();
+}
+	/**
+	 * Generate the "Viewing x-y of z groups" pagination message.
+	 *
+	 * @return string
+	 */
+	function bp_get_groups_pagination_count() {
+		global $groups_template;
+
+		$start_num = intval( ( $groups_template->pag_page - 1 ) * $groups_template->pag_num ) + 1;
+		$from_num  = bp_core_number_format( $start_num );
+		$to_num    = bp_core_number_format( ( $start_num + ( $groups_template->pag_num - 1 ) > $groups_template->total_group_count ) ? $groups_template->total_group_count : $start_num + ( $groups_template->pag_num - 1 ) );
+		$total     = bp_core_number_format( $groups_template->total_group_count );
+
+		if ( 1 == $groups_template->total_group_count ) {
+			$message = __( 'Viewing 1 group', 'buddypress' );
+		} else {
+			$message = sprintf( _n( 'Viewing %1$s - %2$s of %3$s group', 'Viewing %1$s - %2$s of %3$s groups', $groups_template->total_group_count, 'buddypress' ), $from_num, $to_num, $total );
+		}
+
+		/**
+		 * Filters the "Viewing x-y of z groups" pagination message.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $message  "Viewing x-y of z groups" text.
+		 * @param string $from_num Total amount for the low value in the range.
+		 * @param string $to_num   Total amount for the high value in the range.
+		 * @param string $total    Total amount of groups found.
+		 */
+		return apply_filters( 'bp_get_groups_pagination_count', $message, $from_num, $to_num, $total );
+	}
+
+/**
+ * Determine whether groups auto-join is enabled.
+ *
+ * "Auto-join" is the toggle that determines whether users are joined to a
+ * public group automatically when creating content in that group.
+ *
+ * @return bool
+ */
+function bp_groups_auto_join() {
+
+	/**
+	 * Filters whether groups auto-join is enabled.
+	 *
+	 * @since BuddyPres (1.2.6)
+	 *
+	 * @param bool $value Enabled status.
+	 */
+	return apply_filters( 'bp_groups_auto_join', (bool) buddypress()->groups->auto_join );
+}
+
+/**
+ * Output the total member count for a group.
+ *
+ * @param object|bool $group Optional. Group object. Default: current group in loop.
+ */
+function bp_group_total_members( $group = false ) {
+	echo bp_get_group_total_members( $group );
+}
+	/**
+	 * Get the total member count for a group.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return int
+	 */
+	function bp_get_group_total_members( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the total member count for a group.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $total_member_count Total member count for a group.
+		 */
+		return apply_filters( 'bp_get_group_total_members', $group->total_member_count );
+	}
+
+/**
+ * Output the "x members" count string for a group.
+ */
+function bp_group_member_count() {
+	echo bp_get_group_member_count();
+}
+	/**
+	 * Generate the "x members" count string for a group.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_member_count() {
+		global $groups_template;
+
+		if ( isset( $groups_template->group->total_member_count ) ) {
+			$count = (int) $groups_template->group->total_member_count;
+		} else {
+			$count = 0;
+		}
+
+		$count_string = sprintf( _n( '%s member', '%s members', $count, 'buddypress' ), bp_core_number_format( $count ) );
+
+		/**
+		 * Filters the "x members" count string for a group.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $count_string The "x members" count string for a group.
+		 */
+		return apply_filters( 'bp_get_group_member_count', $count_string );
+	}
+
+/**
+ * Output the URL of the Forum page of the current group in the loop.
+ */
+function bp_group_forum_permalink() {
+	echo bp_get_group_forum_permalink();
+}
+	/**
+	 * Generate the URL of the Forum page of a group.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_forum_permalink( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the URL of the Forum page of a group.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL permalink for the Forum Page.
+		 */
+		return apply_filters( 'bp_get_group_forum_permalink', bp_get_group_permalink( $group ) . 'forum' );
+	}
+
+/**
+ * Output the topic count for a group forum.
+ *
+ * @param array|string $args See {@link bp_get_group_forum_topic_count()}.
+ */
+function bp_group_forum_topic_count( $args = '' ) {
+	echo bp_get_group_forum_topic_count( $args );
+}
+	/**
+	 * Generate the topic count string for a group forum.
+	 *
+	 * @param array|string $args {
+	 *     Array of arguments.
+	 *     @type bool $showtext Optional. If true, result will be formatted as "x topics".
+	 *                          If false, just a number will be returned.
+	 *                          Default: false.
+	 * }
+	 * @return string|int
+	 */
+	function bp_get_group_forum_topic_count( $args = '' ) {
+		global $groups_template;
+
+		$defaults = array(
+			'showtext' => false
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		if ( !$forum_id = groups_get_groupmeta( $groups_template->group->id, 'forum_id' ) ) {
+			return false;
+		}
+
+		if ( !bp_is_active( 'forums' ) ) {
+			return false;
+		}
+
+		if ( !$groups_template->group->forum_counts ) {
+			$groups_template->group->forum_counts = bp_forums_get_forum_topicpost_count( (int) $forum_id );
+		}
+
+		if ( (bool) $showtext ) {
+			if ( 1 == (int) $groups_template->group->forum_counts[0]->topics ) {
+				$total_topics = sprintf( __( '%d topic', 'buddypress' ), (int) $groups_template->group->forum_counts[0]->topics );
+			} else {
+				$total_topics = sprintf( __( '%d topics', 'buddypress' ), (int) $groups_template->group->forum_counts[0]->topics );
+			}
+		} else {
+			$total_topics = (int) $groups_template->group->forum_counts[0]->topics;
+		}
+
+		/**
+		 * Filters the topic count string for a group forum.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $total_topics Total topic count string.
+		 * @param bool   $showtext     Whether or not to return as formatted string.
+		 */
+		return apply_filters( 'bp_get_group_forum_topic_count', $total_topics, (bool)$showtext );
+	}
+
+/**
+ * Output the post count for a group forum.
+ *
+ * @param array|string $args See {@link bp_get_group_forum_post_count()}.
+ */
+function bp_group_forum_post_count( $args = '' ) {
+	echo bp_get_group_forum_post_count( $args );
+}
+	/**
+	 * Generate the post count string for a group forum.
+	 *
+	 * @param array|string $args {
+	 *     Array of arguments.
+	 *     @type bool $showtext Optional. If true, result will be formatted as "x posts".
+	 *                          If false, just a number will be returned.
+	 *                          Default: false.
+	 * }
+	 * @return string|int
+	 */
+	function bp_get_group_forum_post_count( $args = '' ) {
+		global $groups_template;
+
+		$defaults = array(
+			'showtext' => false
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		if ( !$forum_id = groups_get_groupmeta( $groups_template->group->id, 'forum_id' ) ) {
+			return false;
+		}
+
+		if ( !bp_is_active( 'forums' ) ) {
+			return false;
+		}
+
+		if ( !$groups_template->group->forum_counts ) {
+			$groups_template->group->forum_counts = bp_forums_get_forum_topicpost_count( (int) $forum_id );
+		}
+
+		if ( (bool) $showtext ) {
+			if ( 1 == (int) $groups_template->group->forum_counts[0]->posts ) {
+				$total_posts = sprintf( __( '%d post', 'buddypress' ), (int) $groups_template->group->forum_counts[0]->posts );
+			} else {
+				$total_posts = sprintf( __( '%d posts', 'buddypress' ), (int) $groups_template->group->forum_counts[0]->posts );
+			}
+		} else {
+			$total_posts = (int) $groups_template->group->forum_counts[0]->posts;
+		}
+
+		/**
+		 * Filters the post count string for a group forum.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $total_posts Total post count string.
+		 * @param bool   $showtext    Whether or not to return as formatted string.
+		 */
+		return apply_filters( 'bp_get_group_forum_post_count', $total_posts, (bool)$showtext );
+	}
+
+/**
+ * Determine whether forums are enabled for a group.
+ *
+ * @param object|bool $group Optional. Group object. Default: current group in loop.
+ *
+ * @return bool
+ */
+function bp_group_is_forum_enabled( $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	if ( ! empty( $group->enable_forum ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Output the 'checked' attribute for the group forums settings UI.
+ *
+ * @param object|bool $group Optional. Group object. Default: current group in loop.
+ */
+function bp_group_show_forum_setting( $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	if ( $group->enable_forum ) {
+		echo ' checked="checked"';
+	}
+}
+
+/**
+ * Output the 'checked' attribute for a given status in the settings UI.
+ *
+ * @param string      $setting Group status. 'public', 'private', 'hidden'.
+ * @param object|bool $group   Optional. Group object. Default: current group in loop.
+ */
+function bp_group_show_status_setting( $setting, $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	if ( $setting == $group->status ) {
+		echo ' checked="checked"';
+	}
+}
+
+/**
+ * Output the 'checked' value, if needed, for a given invite_status on the group create/admin screens
+ *
+ * @since 1.5.0
+ *
+ * @param string      $setting The setting you want to check against ('members',
+ *	                           'mods', or 'admins').
+ * @param object|bool $group   Optional. Group object. Default: current group in loop.
+ */
+function bp_group_show_invite_status_setting( $setting, $group = false ) {
+	$group_id = isset( $group->id ) ? $group->id : false;
+
+	$invite_status = bp_group_get_invite_status( $group_id );
+
+	if ( $setting == $invite_status ) {
+		echo ' checked="checked"';
+	}
+}
+
+/**
+ * Get the invite status of a group.
+ *
+ * 'invite_status' became part of BuddyPress in BP 1.5. In order to provide
+ * backward compatibility with earlier installations, groups without a status
+ * set will default to 'members', ie all members in a group can send
+ * invitations. Filter 'bp_group_invite_status_fallback' to change this
+ * fallback behavior.
+ *
+ * This function can be used either in or out of the loop.
+ *
+ * @since 1.5.0
+ *
+ * @param int|bool $group_id Optional. The ID of the group whose status you want to
+ *                           check. Default: the displayed group, or the current group
+ *                           in the loop.
+ *
+ * @return bool|string Returns false when no group can be found. Otherwise
+ *                     returns the group invite status, from among 'members',
+ *                     'mods', and 'admins'.
+ */
+function bp_group_get_invite_status( $group_id = false ) {
+	global $groups_template;
+
+	if ( !$group_id ) {
+		$bp = buddypress();
+
+		if ( isset( $bp->groups->current_group->id ) ) {
+			// Default to the current group first
+			$group_id = $bp->groups->current_group->id;
+		} elseif ( isset( $groups_template->group->id ) ) {
+			// Then see if we're in the loop
+			$group_id = $groups_template->group->id;
+		} else {
+			return false;
+		}
+	}
+
+	$invite_status = groups_get_groupmeta( $group_id, 'invite_status' );
+
+	// Backward compatibility. When 'invite_status' is not set, fall back to a default value
+	if ( !$invite_status ) {
+		$invite_status = apply_filters( 'bp_group_invite_status_fallback', 'members' );
+	}
+
+	/**
+	 * Filters the invite status of a group.
+	 *
+	 * Invite status in this case means who from the group can send invites.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $invite_status Membership level needed to send an invite.
+	 * @param int    $group_id      ID of the group whose status is being checked.
+	 */
+	return apply_filters( 'bp_group_get_invite_status', $invite_status, $group_id );
+}
+
+/**
+ * Can a user send invitations in the specified group?
+ *
+ * @since 1.5.0
+ * @since 2.2.0 Added the $user_id parameter.
+ *
+ * @param int $group_id The group ID to check.
+ * @param int $user_id  The user ID to check.
+ * @return bool
+ */
+function bp_groups_user_can_send_invites( $group_id = 0, $user_id = 0 ) {
+	$can_send_invites = false;
+	$invite_status    = false;
+
+	// If $user_id isn't specified, we check against the logged-in user.
+	if ( ! $user_id ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	// If $group_id isn't specified, use existing one if available.
+	if ( ! $group_id ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	if ( $user_id ) {
+		// Users with the 'bp_moderate' cap can always send invitations
+		if ( user_can( $user_id, 'bp_moderate' ) ) {
+			$can_send_invites = true;
+		} else {
+			$invite_status = bp_group_get_invite_status( $group_id );
+
+			switch ( $invite_status ) {
+				case 'admins' :
+					if ( groups_is_user_admin( $user_id, $group_id ) ) {
+						$can_send_invites = true;
+					}
+					break;
+
+				case 'mods' :
+					if ( groups_is_user_mod( $user_id, $group_id ) || groups_is_user_admin( $user_id, $group_id ) ) {
+						$can_send_invites = true;
+					}
+					break;
+
+				case 'members' :
+					if ( groups_is_user_member( $user_id, $group_id ) ) {
+						$can_send_invites = true;
+					}
+					break;
+			}
+		}
+	}
+
+	/**
+	 * Filters whether a user can send invites in a group.
+	 *
+	 * @since 1.5.0
+	 * @since 2.2.0 Added the $user_id parameter.
+	 *
+	 * @param bool $can_send_invites Whether the user can send invites
+	 * @param int  $group_id         The group ID being checked
+	 * @param bool $invite_status    The group's current invite status
+	 * @param int  $user_id          The user ID being checked
+	 */
+	return apply_filters( 'bp_groups_user_can_send_invites', $can_send_invites, $group_id, $invite_status, $user_id );
+}
+
+/**
+ * Since BuddyPress 1.0, this generated the group settings admin/member screen.
+ * As of BuddyPress 1.5 (r4489), and because this function outputs HTML, it was moved into /bp-default/groups/single/admin.php.
+ *
+ * @deprecated 1.5
+ * @deprecated No longer used.
+ * @since 1.0.0
+ * @todo Remove in 1.4
+ *
+ * @param bool $admin_list
+ * @param bool $group
+ */
+function bp_group_admin_memberlist( $admin_list = false, $group = false ) {
+	global $groups_template;
+
+	_deprecated_function( __FUNCTION__, '1.5', 'No longer used. See /bp-default/groups/single/admin.php' );
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+
+	if ( $admins = groups_get_group_admins( $group->id ) ) : ?>
+
+		<ul id="admins-list" class="item-list<?php if ( !empty( $admin_list ) ) : ?> single-line<?php endif; ?>">
+
+		<?php foreach ( (array) $admins as $admin ) { ?>
+
+			<?php if ( !empty( $admin_list ) ) : ?>
+
+			<li>
+
+				<?php echo bp_core_fetch_avatar( array( 'item_id' => $admin->user_id, 'type' => 'thumb', 'width' => 30, 'height' => 30, 'alt' => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_core_get_user_displayname( $admin->user_id ) ) ) ) ?>
+
+				<h5>
+
+					<?php echo bp_core_get_userlink( $admin->user_id ); ?>
+
+					<span class="small">
+						<a class="button confirm admin-demote-to-member" href="<?php bp_group_member_demote_link($admin->user_id) ?>"><?php _e( 'Demote to Member', 'buddypress' ) ?></a>
+					</span>
+				</h5>
+			</li>
+
+			<?php else : ?>
+
+			<li>
+
+				<?php echo bp_core_fetch_avatar( array( 'item_id' => $admin->user_id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_core_get_user_displayname( $admin->user_id ) ) ) ) ?>
+
+				<h5><?php echo bp_core_get_userlink( $admin->user_id ) ?></h5>
+				<span class="activity">
+					<?php echo bp_core_get_last_activity( strtotime( $admin->date_modified ), __( 'joined %s', 'buddypress') ); ?>
+				</span>
+
+				<?php if ( bp_is_active( 'friends' ) ) : ?>
+
+					<div class="action">
+
+						<?php bp_add_friend_button( $admin->user_id ); ?>
+
+					</div>
+
+				<?php endif; ?>
+
+			</li>
+
+			<?php endif;
+		} ?>
+
+		</ul>
+
+	<?php else : ?>
+
+		<div id="message" class="info">
+			<p><?php _e( 'This group has no administrators', 'buddypress' ); ?></p>
+		</div>
+
+	<?php endif;
+}
+
+/**
+ * Generate the HTML for a list of group moderators.
+ *
+ * No longer used.
+ *
+ * @todo Deprecate.
+ *
+ * @param bool $admin_list
+ * @param bool $group
+ */
+function bp_group_mod_memberlist( $admin_list = false, $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	if ( $group_mods = groups_get_group_mods( $group->id ) ) { ?>
+
+		<ul id="mods-list" class="item-list<?php if ( $admin_list ) { ?> single-line<?php } ?>">
+
+		<?php foreach ( (array) $group_mods as $mod ) { ?>
+
+			<?php if ( !empty( $admin_list ) ) { ?>
+
+			<li>
+
+				<?php echo bp_core_fetch_avatar( array( 'item_id' => $mod->user_id, 'type' => 'thumb', 'width' => 30, 'height' => 30, 'alt' => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_core_get_user_displayname( $mod->user_id ) ) ) ) ?>
+
+				<h5>
+					<?php echo bp_core_get_userlink( $mod->user_id ); ?>
+
+					<span class="small">
+						<a href="<?php bp_group_member_promote_admin_link( array( 'user_id' => $mod->user_id ) ) ?>" class="button confirm mod-promote-to-admin" title="<?php esc_attr_e( 'Promote to Admin', 'buddypress' ); ?>"><?php _e( 'Promote to Admin', 'buddypress' ); ?></a>
+						<a class="button confirm mod-demote-to-member" href="<?php bp_group_member_demote_link($mod->user_id) ?>"><?php _e( 'Demote to Member', 'buddypress' ) ?></a>
+					</span>
+				</h5>
+			</li>
+
+			<?php } else { ?>
+
+			<li>
+
+				<?php echo bp_core_fetch_avatar( array( 'item_id' => $mod->user_id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_core_get_user_displayname( $mod->user_id ) ) ) ) ?>
+
+				<h5><?php echo bp_core_get_userlink( $mod->user_id ) ?></h5>
+
+				<span class="activity"><?php echo bp_core_get_last_activity( strtotime( $mod->date_modified ), __( 'joined %s', 'buddypress') ); ?></span>
+
+				<?php if ( bp_is_active( 'friends' ) ) : ?>
+
+					<div class="action">
+						<?php bp_add_friend_button( $mod->user_id ) ?>
+					</div>
+
+				<?php endif; ?>
+
+			</li>
+
+			<?php } ?>
+		<?php } ?>
+
+		</ul>
+
+	<?php } else { ?>
+
+		<div id="message" class="info">
+			<p><?php _e( 'This group has no moderators', 'buddypress' ); ?></p>
+		</div>
+
+	<?php }
+}
+
+/**
+ * Determine whether a group has moderators.
+ *
+ * @param object|bool $group Optional. Group object. Default: current group in loop.
+ *
+ * @return array Info about group admins (user_id + date_modified).
+ */
+function bp_group_has_moderators( $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	/**
+	 * Filters whether a group has moderators.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param array $value Array of user IDs who are a moderator of the provided group.
+	 */
+	return apply_filters( 'bp_group_has_moderators', groups_get_group_mods( $group->id ) );
+}
+
+/**
+ * Output a URL for promoting a user to moderator.
+ *
+ * @param array|string $args See {@link bp_get_group_member_promote_mod_link()}.
+ */
+function bp_group_member_promote_mod_link( $args = '' ) {
+	echo bp_get_group_member_promote_mod_link( $args );
+}
+	/**
+	 * Generate a URL for promoting a user to moderator.
+	 *
+	 * @param array|string $args {
+	 *     @type int    $user_id ID of the member to promote. Default:
+	 *                           current member in a group member loop.
+	 *     @type object $group   Group object. Default: current group.
+	 * }
+	 * @return string
+	 */
+	function bp_get_group_member_promote_mod_link( $args = '' ) {
+		global $members_template, $groups_template;
+
+		$defaults = array(
+			'user_id' => $members_template->member->user_id,
+			'group'   => &$groups_template->group
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		/**
+		 * Filters a URL for promoting a user to moderator.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value URL to use for promoting a user to moderator.
+		 */
+		return apply_filters( 'bp_get_group_member_promote_mod_link', wp_nonce_url( bp_get_group_permalink( $group ) . 'admin/manage-members/promote/mod/' . $user_id, 'groups_promote_member' ) );
+	}
+
+/**
+ * Output a URL for promoting a user to admin.
+ *
+ * @param array|string $args See {@link bp_get_group_member_promote_admin_link()}.
+ */
+function bp_group_member_promote_admin_link( $args = '' ) {
+	echo bp_get_group_member_promote_admin_link( $args );
+}
+	/**
+	 * Generate a URL for promoting a user to admin.
+	 *
+	 * @param array|string $args {
+	 *     @type int    $user_id ID of the member to promote. Default:
+	 *                           current member in a group member loop.
+	 *     @type object $group   Group object. Default: current group.
+	 * }
+	 * @return string
+	 */
+	function bp_get_group_member_promote_admin_link( $args = '' ) {
+		global $members_template, $groups_template;
+
+		$defaults = array(
+			'user_id' => !empty( $members_template->member->user_id ) ? $members_template->member->user_id : false,
+			'group'   => &$groups_template->group
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		/**
+		 * Filters a URL for promoting a user to admin.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value URL to use for promoting a user to admin.
+		 */
+		return apply_filters( 'bp_get_group_member_promote_admin_link', wp_nonce_url( bp_get_group_permalink( $group ) . 'admin/manage-members/promote/admin/' . $user_id, 'groups_promote_member' ) );
+	}
+
+/**
+ * Output a URL for demoting a user to member.
+ *
+ * @param int $user_id ID of the member to demote. Default: current member in
+ *                     a member loop.
+ */
+function bp_group_member_demote_link( $user_id = 0 ) {
+	global $members_template;
+
+	if ( !$user_id ) {
+		$user_id = $members_template->member->user_id;
+	}
+
+	echo bp_get_group_member_demote_link( $user_id );
+}
+	/**
+	 * Generate a URL for demoting a user to member.
+	 *
+	 * @param int         $user_id ID of the member to demote. Default: current
+	 *                             member in a member loop.
+	 * @param object|bool $group   Optional. Group object. Default: current group.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_member_demote_link( $user_id = 0, $group = false ) {
+		global $members_template, $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		if ( !$user_id ) {
+			$user_id = $members_template->member->user_id;
+		}
+
+		/**
+		 * Filters a URL for demoting a user to member.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL to use for demoting a user to member.
+		 */
+		return apply_filters( 'bp_get_group_member_demote_link', wp_nonce_url( bp_get_group_permalink( $group ) . 'admin/manage-members/demote/' . $user_id, 'groups_demote_member' ) );
+	}
+
+/**
+ * Output a URL for banning a member from a group.
+ *
+ * @param int $user_id ID of the member to ban.
+ *                     Default: current member in a member loop.
+ */
+function bp_group_member_ban_link( $user_id = 0 ) {
+	global $members_template;
+
+	if ( !$user_id ) {
+		$user_id = $members_template->member->user_id;
+	}
+
+	echo bp_get_group_member_ban_link( $user_id );
+}
+	/**
+	 * Generate a URL for banning a member from a group.
+	 *
+	 * @param int         $user_id ID of the member to ban.
+	 *                             Default: current member in a member loop.
+	 * @param object|bool $group   Optional. Group object. Default: current group.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_member_ban_link( $user_id = 0, $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters a URL for banning a member from a group.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL to use for banning a member.
+		 */
+		return apply_filters( 'bp_get_group_member_ban_link', wp_nonce_url( bp_get_group_permalink( $group ) . 'admin/manage-members/ban/' . $user_id, 'groups_ban_member' ) );
+	}
+
+/**
+ * Output a URL for unbanning a member from a group.
+ *
+ * @param int $user_id ID of the member to unban.
+ *                     Default: current member in a member loop.
+ */
+function bp_group_member_unban_link( $user_id = 0 ) {
+	global $members_template;
+
+	if ( !$user_id ) {
+		$user_id = $members_template->member->user_id;
+	}
+
+	echo bp_get_group_member_unban_link( $user_id );
+}
+	/**
+	 * Generate a URL for unbanning a member from a group.
+	 *
+	 * @param int         $user_id ID of the member to unban.
+	 *                             Default: current member in a member loop.
+	 * @param object|bool $group   Optional. Group object. Default: current group.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_member_unban_link( $user_id = 0, $group = false ) {
+		global $members_template, $groups_template;
+
+		if ( !$user_id ) {
+			$user_id = $members_template->member->user_id;
+		}
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters a URL for unbanning a member from a group.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL to use for unbanning a member.
+		 */
+		return apply_filters( 'bp_get_group_member_unban_link', wp_nonce_url( bp_get_group_permalink( $group ) . 'admin/manage-members/unban/' . $user_id, 'groups_unban_member' ) );
+	}
+
+/**
+ * Output a URL for removing a member from a group.
+ *
+ * @param int $user_id ID of the member to remove.
+ *                     Default: current member in a member loop.
+ */
+function bp_group_member_remove_link( $user_id = 0 ) {
+	global $members_template;
+
+	if ( !$user_id ) {
+		$user_id = $members_template->member->user_id;
+	}
+
+	echo bp_get_group_member_remove_link( $user_id );
+}
+	/**
+	 * Generate a URL for removing a member from a group.
+	 *
+	 * @param int         $user_id ID of the member to remove.
+	 *                             Default: current member in a member loop.
+	 * @param object|bool $group   Optional. Group object. Default: current group.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_member_remove_link( $user_id = 0, $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters a URL for removing a member from a group.
+		 *
+		 * @since 1.2.6
+		 *
+		 * @param string $value URL to use for removing a member.
+		 */
+		return apply_filters( 'bp_get_group_member_remove_link', wp_nonce_url( bp_get_group_permalink( $group ) . 'admin/manage-members/remove/' . $user_id, 'groups_remove_member' ) );
+	}
+
+/**
+ * HTML admin subnav items for group pages.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in the loop.
+ */
+function bp_group_admin_tabs( $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group = ( $groups_template->group ) ? $groups_template->group : groups_get_current_group();
+	}
+
+	$css_id = 'manage-members';
+
+	if ( 'private' == $group->status ) {
+		$css_id = 'membership-requests';
+	}
+
+	add_filter( "bp_get_options_nav_{$css_id}", 'bp_group_admin_tabs_backcompat', 10, 3 );
+
+	bp_get_options_nav( $group->slug . '_manage' );
+
+	remove_filter( "bp_get_options_nav_{$css_id}", 'bp_group_admin_tabs_backcompat', 10, 3 );
+}
+
+/**
+ * BackCompat for plugins/themes directly hooking groups_admin_tabs
+ * without using the Groups Extension API.
+ *
+ * @param  string $subnav_output Subnav item output.
+ * @param  string $subnav_item   subnav item params.
+ * @param  string $selected_item Surrent selected tab.
+ *
+ * @return string HTML output
+ */
+function bp_group_admin_tabs_backcompat( $subnav_output = '', $subnav_item = '', $selected_item = '' ) {
+	if ( ! has_action( 'groups_admin_tabs' ) ) {
+		return $subnav_output;
+	}
+
+	$group = groups_get_current_group();
+
+	ob_start();
+
+	do_action( 'groups_admin_tabs', $selected_item, $group->slug );
+
+	$admin_tabs_backcompat = trim( ob_get_contents() );
+	ob_end_clean();
+
+	if ( ! empty( $admin_tabs_backcompat ) ) {
+		_doing_it_wrong( "do_action( 'groups_admin_tabs' )", __( 'This action should not be used directly. Please use the BuddyPress Group Extension API to generate Manage tabs.', 'buddypress' ), '2.2.0' );
+		$subnav_output .= $admin_tabs_backcompat;
+	}
+
+	return $subnav_output;
+}
+
+/**
+ * Output the group count for the displayed user.
+ */
+function bp_group_total_for_member() {
+	echo bp_get_group_total_for_member();
+}
+	/**
+	 * Get the group count for the displayed user.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_total_for_member() {
+
+		/**
+		 * FIlters the group count for a displayed user.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int $value Total group count for a displayed user.
+		 */
+		return apply_filters( 'bp_get_group_total_for_member', BP_Groups_Member::total_group_count() );
+	}
+
+/**
+ * Output the 'action' attribute for a group form.
+ *
+ * @param string $page Page slug.
+ */
+function bp_group_form_action( $page ) {
+	echo bp_get_group_form_action( $page );
+}
+	/**
+	 * Generate the 'action' attribute for a group form.
+	 *
+	 * @param string      $page  Page slug.
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_form_action( $page, $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the 'action' attribute for a group form.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Action attribute for a group form.
+		 */
+		return apply_filters( 'bp_group_form_action', bp_get_group_permalink( $group ) . $page );
+	}
+
+/**
+ * Output the 'action' attribute for a group admin form.
+ *
+ * @param string|bool $page Optional. Page slug.
+ */
+function bp_group_admin_form_action( $page = false ) {
+	echo bp_get_group_admin_form_action( $page );
+}
+	/**
+	 * Generate the 'action' attribute for a group admin form.
+	 *
+	 * @param string|bool $page  Optional. Page slug.
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_admin_form_action( $page = false, $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		if ( empty( $page ) ) {
+			$page = bp_action_variable( 0 );
+		}
+
+		/**
+		 * Filters the 'action' attribute for a group admin form.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Action attribute for a group admin form.
+		 */
+		return apply_filters( 'bp_group_admin_form_action', bp_get_group_permalink( $group ) . 'admin/' . $page );
+	}
+
+/**
+ * Determine whether the logged-in user has requested membership to a group.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in the loop.
+ *
+ * @return bool
+ */
+function bp_group_has_requested_membership( $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	if ( groups_check_for_membership_request( bp_loggedin_user_id(), $group->id ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * Check if current user is member of a group.
+ *
+ * @global object $groups_template
+ *
+ * @param object|bool $group Optional. Group to check is_member.
+ *                           Default: current group in the loop.
+ *
+ * @return bool If user is member of group or not.
+ */
+function bp_group_is_member( $group = false ) {
+	global $groups_template;
+
+	// Site admins always have access
+	if ( bp_current_user_can( 'bp_moderate' ) ) {
+		return true;
+	}
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	/**
+	 * Filters whether current user is member of a group.
+	 *
+	 * @since 1.2.4
+	 *
+	 * @param bool $is_member If user is a member of group or not.
+	 */
+	return apply_filters( 'bp_group_is_member', !empty( $group->is_member ) );
+}
+
+/**
+ * Check whether the current user has an outstanding invite to the current group in the loop.
+ *
+ * @param object|bool $group Optional. Group data object.
+ *                           Default: the current group in the groups loop.
+ *
+ * @return bool True if the user has an outstanding invite, otherwise false.
+ */
+function bp_group_is_invited( $group = false ) {
+	global $groups_template;
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	/**
+	 * Filters whether current user has an outstanding invite to current group in loop.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $is_invited If user has an outstanding group invite.
+	 */
+	return apply_filters( 'bp_group_is_invited', ! empty( $group->is_invited ) );
+}
+
+/**
+ * Check if a user is banned from a group.
+ *
+ * If this function is invoked inside the groups template loop, then we check
+ * $groups_template->group->is_banned instead of using {@link groups_is_user_banned()}
+ * and making another SQL query.
+ *
+ * In BuddyPress 2.1, to standardize this function, we are defaulting the
+ * return value to a boolean.  In previous versions, using this function would
+ * return either a string of the integer (0 or 1) or null if a result couldn't
+ * be found from the database.  If the logged-in user had the 'bp_moderate'
+ * capability, the return value would be boolean false.
+ *
+ * @since 1.5.0
+ *
+ * @global BP_Groups_Template $groups_template Group template loop object.
+ *
+ * @param BP_Groups_Group|bool $group   Group to check if user is banned.
+ * @param int                  $user_id The user ID to check.
+ *
+ * @return bool True if user is banned.  False if user isn't banned.
+ */
+function bp_group_is_user_banned( $group = false, $user_id = 0 ) {
+	global $groups_template;
+
+	// Site admins always have access
+	if ( bp_current_user_can( 'bp_moderate' ) ) {
+		return false;
+	}
+
+	// check groups loop first
+	// @see BP_Groups_Group::get_group_extras()
+	if ( ! empty( $groups_template->in_the_loop ) && isset( $groups_template->group->is_banned ) ) {
+		$retval = $groups_template->group->is_banned;
+
+	// not in loop
+	} else {
+		// Default to not banned
+		$retval = false;
+
+		if ( empty( $group ) ) {
+			$group = $groups_template->group;
+		}
+
+		if ( empty( $user_id ) ) {
+			$user_id = bp_loggedin_user_id();
+		}
+
+		if ( ! empty( $user_id ) && ! empty( $group->id ) ) {
+			$retval = groups_is_user_banned( $user_id, $group->id );
+		}
+	}
+
+	/**
+	 * Filters whether current user has been banned from current group in loop.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $is_invited If user has been from current group.
+	 */
+	return (bool) apply_filters( 'bp_group_is_user_banned', $retval );
+}
+
+/**
+ * Output the URL for accepting an invitation to the current group in the loop.
+ */
+function bp_group_accept_invite_link() {
+	echo bp_get_group_accept_invite_link();
+}
+	/**
+	 * Generate the URL for accepting an invitation to a group.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: Current group in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_accept_invite_link( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		$bp = buddypress();
+
+		/**
+		 * Filters the URL for accepting an invitation to a group.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL for accepting an invitation to a group.
+		 */
+		return apply_filters( 'bp_get_group_accept_invite_link', wp_nonce_url( trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() . '/invites/accept/' . $group->id ), 'groups_accept_invite' ) );
+	}
+
+/**
+ * Output the URL for accepting an invitation to the current group in the loop.
+ */
+function bp_group_reject_invite_link() {
+	echo bp_get_group_reject_invite_link();
+}
+	/**
+	 * Generate the URL for rejecting an invitation to a group.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: Current group in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_reject_invite_link( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		$bp = buddypress();
+
+		/**
+		 * Filters the URL for rejecting an invitation to a group.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL for rejecting an invitation to a group.
+		 */
+		return apply_filters( 'bp_get_group_reject_invite_link', wp_nonce_url( trailingslashit( bp_loggedin_user_domain() . bp_get_groups_slug() . '/invites/reject/' . $group->id ), 'groups_reject_invite' ) );
+	}
+
+/**
+ * Output the URL for confirming a request to leave a group.
+ */
+function bp_group_leave_confirm_link() {
+	echo bp_get_group_leave_confirm_link();
+}
+	/**
+	 * Generate the URL for confirming a request to leave a group.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: Current group in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_leave_confirm_link( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the URL for confirming a request to leave a group.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL for confirming a request to leave a group.
+		 */
+		return apply_filters( 'bp_group_leave_confirm_link', wp_nonce_url( bp_get_group_permalink( $group ) . 'leave-group/yes', 'groups_leave_group' ) );
+	}
+
+/**
+ * Output the URL for rejecting a request to leave a group.
+ */
+function bp_group_leave_reject_link() {
+	echo bp_get_group_leave_reject_link();
+}
+	/**
+	 * Generate the URL for rejecting a request to leave a group.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: Current group in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_leave_reject_link( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the URL for rejecting a request to leave a group.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL for rejecting a request to leave a group.
+		 */
+		return apply_filters( 'bp_get_group_leave_reject_link', bp_get_group_permalink( $group ) );
+	}
+
+/**
+ * Output the 'action' attribute for a group send invite form.
+ */
+function bp_group_send_invite_form_action() {
+	echo bp_get_group_send_invite_form_action();
+}
+	/**
+	 * Output the 'action' attribute for a group send invite form.
+	 *
+	 * @param object|bool $group Optional. Group object.
+	 *                           Default: current group in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_send_invite_form_action( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		/**
+		 * Filters the 'action' attribute for a group send invite form.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Action attribute for a group send invite form.
+		 */
+		return apply_filters( 'bp_group_send_invite_form_action', bp_get_group_permalink( $group ) . 'send-invites/send' );
+	}
+
+/**
+ * Determine whether the current user has friends to invite to a group.
+ *
+ * @param object|bool $group Optional. Group object.
+ *                           Default: current group in the loop.
+ *
+ * @return bool
+ */
+function bp_has_friends_to_invite( $group = false ) {
+	global $groups_template;
+
+	if ( !bp_is_active( 'friends' ) ) {
+		return false;
+	}
+
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	if ( !friends_check_user_has_friends( bp_loggedin_user_id() ) || !friends_count_invitable_friends( bp_loggedin_user_id(), $group->id ) ) {
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Output a 'New Topic' button for a group.
+ *
+ * @since 1.2.7
+ *
+ * @param BP_Groups_Group|bool $group The BP Groups_Group object if passed,
+ *                                    boolean false if not passed.
+ */
+function bp_group_new_topic_button( $group = false ) {
+	echo bp_get_group_new_topic_button( $group );
+}
+
+	/**
+	 * Returns a 'New Topic' button for a group.
+	 *
+	 * @since 1.2.7
+	 *
+	 * @param BP_Groups_Group|bool $group The BP Groups_Group object if
+	 *                                    passed, boolean false if not passed.
+	 *
+	 * @return string HTML code for the button.
+	 */
+	function bp_get_group_new_topic_button( $group = false ) {
+		global $groups_template;
+
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		if ( !is_user_logged_in() || bp_group_is_user_banned() || !bp_is_group_forum() || bp_is_group_forum_topic() ) {
+			return false;
+		}
+
+		$button = array(
+			'id'                => 'new_topic',
+			'component'         => 'groups',
+			'must_be_logged_in' => true,
+			'block_self'        => true,
+			'wrapper_class'     => 'group-button',
+			'link_href'         => '#post-new',
+			'link_class'        => 'group-button show-hide-new',
+			'link_id'           => 'new-topic-button',
+			'link_text'         => __( 'New Topic', 'buddypress' ),
+			'link_title'        => __( 'New Topic', 'buddypress' ),
+		);
+
+		/**
+		 * Filters the HTML button for creating a new topic in a group.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $button HTML button for a new topic.
+		 */
+		return bp_get_button( apply_filters( 'bp_get_group_new_topic_button', $button ) );
+	}
+
+/**
+ * Output button to join a group.
+ *
+ * @since 1.0.0
+ *
+ * @param object|bool $group Single group object.
+ */
+function bp_group_join_button( $group = false ) {
+	echo bp_get_group_join_button( $group );
+}
+	/**
+	 * Return button to join a group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param object|bool $group Single group object.
+	 *
+	 * @return mixed
+	 */
+	function bp_get_group_join_button( $group = false ) {
+		global $groups_template;
+
+		// Set group to current loop group if none passed
+		if ( empty( $group ) ) {
+			$group =& $groups_template->group;
+		}
+
+		// Don't show button if not logged in or previously banned
+		if ( ! is_user_logged_in() || bp_group_is_user_banned( $group ) ) {
+			return false;
+		}
+
+		// Group creation was not completed or status is unknown
+		if ( empty( $group->status ) ) {
+			return false;
+		}
+
+		// Already a member
+		if ( ! empty( $group->is_member ) ) {
+
+			// Stop sole admins from abandoning their group
+			$group_admins = groups_get_group_admins( $group->id );
+			if ( ( 1 == count( $group_admins ) ) && ( bp_loggedin_user_id() === (int) $group_admins[0]->user_id ) ) {
+				return false;
+			}
+
+			// Setup button attributes
+			$button = array(
+				'id'                => 'leave_group',
+				'component'         => 'groups',
+				'must_be_logged_in' => true,
+				'block_self'        => false,
+				'wrapper_class'     => 'group-button ' . $group->status,
+				'wrapper_id'        => 'groupbutton-' . $group->id,
+				'link_href'         => wp_nonce_url( bp_get_group_permalink( $group ) . 'leave-group', 'groups_leave_group' ),
+				'link_text'         => __( 'Leave Group', 'buddypress' ),
+				'link_title'        => __( 'Leave Group', 'buddypress' ),
+				'link_class'        => 'group-button leave-group',
+			);
+
+		// Not a member
+		} else {
+
+			// Show different buttons based on group status
+			switch ( $group->status ) {
+				case 'hidden' :
+					return false;
+
+				case 'public':
+					$button = array(
+						'id'                => 'join_group',
+						'component'         => 'groups',
+						'must_be_logged_in' => true,
+						'block_self'        => false,
+						'wrapper_class'     => 'group-button ' . $group->status,
+						'wrapper_id'        => 'groupbutton-' . $group->id,
+						'link_href'         => wp_nonce_url( bp_get_group_permalink( $group ) . 'join', 'groups_join_group' ),
+						'link_text'         => __( 'Join Group', 'buddypress' ),
+						'link_title'        => __( 'Join Group', 'buddypress' ),
+						'link_class'        => 'group-button join-group',
+					);
+					break;
+
+				case 'private' :
+
+					// Member has outstanding invitation -
+					// show an "Accept Invitation" button
+					if ( $group->is_invited ) {
+						$button = array(
+							'id'                => 'accept_invite',
+							'component'         => 'groups',
+							'must_be_logged_in' => true,
+							'block_self'        => false,
+							'wrapper_class'     => 'group-button ' . $group->status,
+							'wrapper_id'        => 'groupbutton-' . $group->id,
+							'link_href'         => add_query_arg( 'redirect_to', bp_get_group_permalink( $group ), bp_get_group_accept_invite_link( $group ) ),
+							'link_text'         => __( 'Accept Invitation', 'buddypress' ),
+							'link_title'        => __( 'Accept Invitation', 'buddypress' ),
+							'link_class'        => 'group-button accept-invite',
+						);
+
+					// Member has requested membership but request is pending -
+					// show a "Request Sent" button
+					} elseif ( $group->is_pending ) {
+						$button = array(
+							'id'                => 'membership_requested',
+							'component'         => 'groups',
+							'must_be_logged_in' => true,
+							'block_self'        => false,
+							'wrapper_class'     => 'group-button pending ' . $group->status,
+							'wrapper_id'        => 'groupbutton-' . $group->id,
+							'link_href'         => bp_get_group_permalink( $group ),
+							'link_text'         => __( 'Request Sent', 'buddypress' ),
+							'link_title'        => __( 'Request Sent', 'buddypress' ),
+							'link_class'        => 'group-button pending membership-requested',
+						);
+
+					// Member has not requested membership yet -
+					// show a "Request Membership" button
+					} else {
+						$button = array(
+							'id'                => 'request_membership',
+							'component'         => 'groups',
+							'must_be_logged_in' => true,
+							'block_self'        => false,
+							'wrapper_class'     => 'group-button ' . $group->status,
+							'wrapper_id'        => 'groupbutton-' . $group->id,
+							'link_href'         => wp_nonce_url( bp_get_group_permalink( $group ) . 'request-membership', 'groups_request_membership' ),
+							'link_text'         => __( 'Request Membership', 'buddypress' ),
+							'link_title'        => __( 'Request Membership', 'buddypress' ),
+							'link_class'        => 'group-button request-membership',
+						);
+					}
+
+					break;
+			}
+		}
+
+		/**
+		 * Filters the HTML button for joining a group.
+		 *
+		 * @since 1.2.6
+		 * @since 2.4.0 Added $group parameter to filter args.
+		 *
+		 * @param string $button HTML button for joining a group.
+		 * @param object $group BuddyPress group object
+		 */
+		return bp_get_button( apply_filters( 'bp_get_group_join_button', $button, $group ) );
+	}
+
+/**
+ * Output the Create a Group button.
+ *
+ * @since 2.0.0
+ */
+function bp_group_create_button() {
+	echo bp_get_group_create_button();
+}
+	/**
+	 * Get the Create a Group button.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @return string
+	 */
+	function bp_get_group_create_button() {
+		if ( ! is_user_logged_in() ) {
+			return false;
+		}
+
+		if ( ! bp_user_can_create_groups() ) {
+			return false;
+		}
+
+		$button_args = array(
+			'id'         => 'create_group',
+			'component'  => 'groups',
+			'link_text'  => __( 'Create a Group', 'buddypress' ),
+			'link_title' => __( 'Create a Group', 'buddypress' ),
+			'link_class' => 'group-create no-ajax',
+			'link_href'  => trailingslashit( bp_get_groups_directory_permalink() . 'create' ),
+			'wrapper'    => false,
+			'block_self' => false,
+		);
+
+		/**
+		 * Filters the HTML button for creating a group.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $button HTML button for creating a group.
+		 */
+		return bp_get_button( apply_filters( 'bp_get_group_create_button', $button_args ) );
+	}
+
+/**
+ * Output the Create a Group nav item.
+ *
+ * @since 2.2.0
+ */
+function bp_group_create_nav_item() {
+	echo bp_get_group_create_nav_item();
+}
+
+	/**
+	 * Get the Create a Group nav item.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @return string
+	 */
+	function bp_get_group_create_nav_item() {
+		// Get the create a group button
+		$create_group_button = bp_get_group_create_button();
+
+		// Make sure the button is available
+		if ( empty( $create_group_button ) ) {
+			return;
+		}
+
+		$output = '<li id="group-create-nav">' . $create_group_button . '</li>';
+
+		/**
+		 * Filters the Create a Group nav item.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param string $output HTML output for nav item.
+		 */
+		return apply_filters( 'bp_get_group_create_nav_item', $output );
+	}
+
+/**
+ * Checks if a specific theme is still filtering the Groups directory title
+ * if so, transform the title button into a Groups directory nav item.
+ *
+ * @since 2.2.0
+ *
+ * @uses   bp_group_create_nav_item() to output the create a Group nav item.
+ * @return string HTML Output
+ */
+function bp_group_backcompat_create_nav_item() {
+	// Bail if the Groups nav item is already used by bp-legacy
+	if ( has_action( 'bp_groups_directory_group_filter', 'bp_legacy_theme_group_create_nav', 999 ) ) {
+		return;
+	}
+
+	// Bail if the theme is not filtering the Groups directory title
+	if ( ! has_filter( 'bp_groups_directory_header' ) ) {
+		return;
+	}
+
+	bp_group_create_nav_item();
+}
+add_action( 'bp_groups_directory_group_filter', 'bp_group_backcompat_create_nav_item', 1000 );
+
+/**
+ * Prints a message if the group is not visible to the current user (it is a
+ * hidden or private group, and the user does not have access).
+ *
+ * @since 1.0.0
+ *
+ * @global BP_Groups_Template $groups_template Groups template object.
+ * @param object $group Group to get status message for. Optional; defaults to current group.
+ */
+function bp_group_status_message( $group = null ) {
+	global $groups_template;
+
+	// Group not passed so look for loop
+	if ( empty( $group ) ) {
+		$group =& $groups_template->group;
+	}
+
+	// Group status is not set (maybe outside of group loop?)
+	if ( empty( $group->status ) ) {
+		$message = __( 'This group is not currently accessible.', 'buddypress' );
+
+	// Group has a status
+	} else {
+		switch( $group->status ) {
+
+			// Private group
+			case 'private' :
+				if ( ! bp_group_has_requested_membership( $group ) ) {
+					if ( is_user_logged_in() ) {
+						if ( bp_group_is_invited( $group ) ) {
+							$message = __( 'You must accept your pending invitation before you can access this private group.', 'buddypress' );
+						} else {
+							$message = __( 'This is a private group and you must request group membership in order to join.', 'buddypress' );
+						}
+					} else {
+						$message = __( 'This is a private group. To join you must be a registered site member and request group membership.', 'buddypress' );
+					}
+				} else {
+					$message = __( 'This is a private group. Your membership request is awaiting approval from the group administrator.', 'buddypress' );
+				}
+
+				break;
+
+			// Hidden group
+			case 'hidden' :
+			default :
+				$message = __( 'This is a hidden group and only invited members can join.', 'buddypress' );
+				break;
+		}
+	}
+
+	/**
+	 * Filters a message if the group is not visible to the current user.
+	 *
+	 * This will be true if it is a hidden or private group, and the user does not have access.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $message Message to display to the current user.
+	 * @param object $group   Group to get status message for.
+	 */
+	echo apply_filters( 'bp_group_status_message', $message, $group );
+}
+
+/**
+ * Output hidden form fields for group.
+ *
+ * This function is no longer used, but may still be used by older themes.
+ *
+ * @since 1.0.0
+ */
+function bp_group_hidden_fields() {
+	$query_arg = bp_core_get_component_search_query_arg( 'groups' );
+
+	if ( isset( $_REQUEST[ $query_arg ] ) ) {
+		echo '<input type="hidden" id="search_terms" value="' . esc_attr( $_REQUEST[ $query_arg ] ) . '" name="search_terms" />';
+	}
+
+	if ( isset( $_REQUEST['letter'] ) ) {
+		echo '<input type="hidden" id="selected_letter" value="' . esc_attr( $_REQUEST['letter'] ) . '" name="selected_letter" />';
+	}
+
+	if ( isset( $_REQUEST['groups_search'] ) ) {
+		echo '<input type="hidden" id="search_terms" value="' . esc_attr( $_REQUEST['groups_search'] ) . '" name="search_terms" />';
+	}
+}
+
+/**
+ * Output the total number of groups.
+ *
+ * @since 1.0.0
+ */
+function bp_total_group_count() {
+	echo bp_get_total_group_count();
+}
+	/**
+	 * Return the total number of groups.
+	 *
+	 * @since 1.0.0
+	 * @return type
+	 */
+	function bp_get_total_group_count() {
+
+		/**
+		 * Filters the total number of groups.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $value Total number of groups found.
+		 */
+		return apply_filters( 'bp_get_total_group_count', groups_get_total_group_count() );
+	}
+
+/**
+ * Output the total number of groups a user belongs to.
+ *
+ * @since 1.0.0
+ *
+ * @param int $user_id User ID to get group membership count.
+ */
+function bp_total_group_count_for_user( $user_id = 0 ) {
+	echo bp_get_total_group_count_for_user( $user_id );
+}
+	/**
+	 * Return the total number of groups a user belongs to.
+	 *
+	 * Filtered by `bp_core_number_format()` by default
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $user_id User ID to get group membership count.
+	 *
+	 * @return int
+	 */
+	function bp_get_total_group_count_for_user( $user_id = 0 ) {
+		$count = groups_total_groups_for_user( $user_id );
+
+		/**
+		 * Filters the total number of groups a user belongs to.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $count   Total number of groups for the user.
+		 * @param int $user_id ID of the user being checked.
+		 */
+		return apply_filters( 'bp_get_total_group_count_for_user', $count, $user_id );
+	}
+
+/** Group Members *************************************************************/
+
+class BP_Groups_Group_Members_Template {
+	public $current_member = -1;
+	public $member_count;
+	public $members;
+	public $member;
+
+	public $in_the_loop;
+
+	public $pag_page;
+	public $pag_num;
+	public $pag_links;
+	public $total_group_count;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param array $args {
+	 *     An array of optional arguments.
+	 *     @type int      $group_id           ID of the group whose members are being
+	 *	                                      queried. Default: current group ID.
+	 *     @type int      $page               Page of results to be queried. Default: 1.
+	 *     @type int      $per_page           Number of items to return per page of
+	 *                                        results. Default: 20.
+	 *     @type int      $max                Optional. Max number of items to return.
+	 *     @type array    $exclude            Optional. Array of user IDs to exclude.
+	 *     @type bool|int $exclude_admin_mods True (or 1) to exclude admins and mods from
+	 *                                        results. Default: 1.
+	 *     @type bool|int $exclude_banned     True (or 1) to exclude banned users from results.
+	 *                                        Default: 1.
+	 *     @type array    $group_role         Optional. Array of group roles to include.
+	 *     @type string   $search_terms       Optional. Search terms to match.
+	 * }
+	 */
+	public function __construct( $args = array() ) {
+
+		// Backward compatibility with old method of passing arguments
+		if ( ! is_array( $args ) || func_num_args() > 1 ) {
+			_deprecated_argument( __METHOD__, '2.0.0', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+			$old_args_keys = array(
+				0 => 'group_id',
+				1 => 'per_page',
+				2 => 'max',
+				3 => 'exclude_admins_mods',
+				4 => 'exclude_banned',
+				5 => 'exclude',
+				6 => 'group_role',
+			);
+
+			$func_args = func_get_args();
+			$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
+		}
+
+		$r = wp_parse_args( $args, array(
+			'group_id'            => bp_get_current_group_id(),
+			'page'                => 1,
+			'per_page'            => 20,
+			'page_arg'            => 'mlpage',
+			'max'                 => false,
+			'exclude'             => false,
+			'exclude_admins_mods' => 1,
+			'exclude_banned'      => 1,
+			'group_role'          => false,
+			'search_terms'        => false,
+			'type'                => 'last_joined',
+		) );
+
+		$this->pag_arg  = sanitize_key( $r['page_arg'] );
+		$this->pag_page = bp_sanitize_pagination_arg( $this->pag_arg, $r['page']     );
+		$this->pag_num  = bp_sanitize_pagination_arg( 'num',          $r['per_page'] );
+
+		/**
+		 * Check the current group is the same as the supplied group ID.
+		 * It can differ when using {@link bp_group_has_members()} outside the Groups screens.
+		 */
+		$current_group = groups_get_current_group();
+		if ( empty( $current_group ) || ( $current_group && $current_group->id !== bp_get_current_group_id() ) ) {
+			$current_group = groups_get_group( array( 'group_id' => $r['group_id'] ) );
+		}
+
+		// Assemble the base URL for pagination
+		$base_url = trailingslashit( bp_get_group_permalink( $current_group ) . bp_current_action() );
+		if ( bp_action_variable() ) {
+			$base_url = trailingslashit( $base_url . bp_action_variable() );
+		}
+
+		$members_args = $r;
+
+		$members_args['page']     = $this->pag_page;
+		$members_args['per_page'] = $this->pag_num;
+
+		// Get group members for this loop
+		$this->members = groups_get_group_members( $members_args );
+
+		if ( empty( $r['max'] ) || ( $r['max'] >= (int) $this->members['count'] ) ) {
+			$this->total_member_count = (int) $this->members['count'];
+		} else {
+			$this->total_member_count = (int) $r['max'];
+		}
+
+		// Reset members array for subsequent looping
+		$this->members = $this->members['members'];
+
+		if ( empty( $r['max'] ) || ( $r['max'] >= count( $this->members ) ) ) {
+			$this->member_count = (int) count( $this->members );
+		} else {
+			$this->member_count = (int) $r['max'];
+		}
+
+		$this->pag_links = paginate_links( array(
+			'base'      => add_query_arg( array( $this->pag_arg => '%#%' ), $base_url ),
+			'format'    => '',
+			'total'     => ! empty( $this->pag_num ) ? ceil( $this->total_member_count / $this->pag_num ) : $this->total_member_count,
+			'current'   => $this->pag_page,
+			'prev_text' => '&larr;',
+			'next_text' => '&rarr;',
+			'mid_size'  => 1,
+			'add_args'  => array(),
+		) );
+	}
+
+	public function has_members() {
+		if ( ! empty( $this->member_count ) ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	public function next_member() {
+		$this->current_member++;
+		$this->member = $this->members[ $this->current_member ];
+
+		return $this->member;
+	}
+
+	public function rewind_members() {
+		$this->current_member = -1;
+		if ( $this->member_count > 0 ) {
+			$this->member = $this->members[0];
+		}
+	}
+
+	public function members() {
+		$tick = intval( $this->current_member + 1 );
+		if ( $tick < $this->member_count ) {
+			return true;
+		} elseif ( $tick == $this->member_count ) {
+
+			/**
+			 * Fires right before the rewinding of members list.
+			 *
+			 * @since 1.0.0
+			 * @since 2.3.0 `$this` parameter added.
+			 *
+			 * @param BP_Groups_Group_Members_Template $this Instance of the current Members template.
+			 */
+			do_action( 'loop_end', $this );
+
+			// Do some cleaning up after the loop
+			$this->rewind_members();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	public function the_member() {
+		$this->in_the_loop = true;
+		$this->member      = $this->next_member();
+
+		// loop has just started
+		if ( 0 == $this->current_member ) {
+
+			/**
+			 * Fires if the current member item is the first in the members list.
+			 *
+			 * @since 1.0.0
+			 * @since 2.3.0 `$this` parameter added.
+			 *
+			 * @param BP_Groups_Group_Members_Template $this Instance of the current Members template.
+			 */
+			do_action( 'loop_start', $this );
+		}
+	}
+}
+
+/**
+ * Initialize a group member query loop.
+ *
+ * @param array|string $args {
+ *     An array of optional arguments.
+ *     @type int      $group_id           ID of the group whose members are being queried.
+ *                                        Default: current group ID.
+ *     @type int      $page               Page of results to be queried. Default: 1.
+ *     @type int      $per_page           Number of items to return per page of results.
+ *                                        Default: 20.
+ *     @type int      $max                Optional. Max number of items to return.
+ *     @type array    $exclude            Optional. Array of user IDs to exclude.
+ *     @type bool|int $exclude_admin_mods True (or 1) to exclude admins and mods from results.
+ *                                        Default: 1.
+ *     @type bool|int $exclude_banned     True (or 1) to exclude banned users from results.
+ *                                        Default: 1.
+ *     @type array    $group_role         Optional. Array of group roles to include.
+ *     @type string   $type               Optional. Sort order of results. 'last_joined',
+ *                                        'first_joined', or any of the $type params available in
+ *                                        {@link BP_User_Query}. Default: 'last_joined'.
+ *     @type string   $search_terms       Optional. Search terms to match. Pass an
+ *                                        empty string to force-disable search, even in
+ *                                        the presence of $_REQUEST['s']. Default: null.
+ * }
+ *
+ * @return bool
+ */
+function bp_group_has_members( $args = '' ) {
+	global $members_template;
+
+	$exclude_admins_mods = 1;
+
+	if ( bp_is_group_members() ) {
+		$exclude_admins_mods = 0;
+	}
+
+	$r = wp_parse_args( $args, array(
+		'group_id'            => bp_get_current_group_id(),
+		'page'                => 1,
+		'per_page'            => 20,
+		'max'                 => false,
+		'exclude'             => false,
+		'exclude_admins_mods' => $exclude_admins_mods,
+		'exclude_banned'      => 1,
+		'group_role'          => false,
+		'search_terms'        => null,
+		'type'                => 'last_joined',
+	) );
+
+	if ( is_null( $r['search_terms'] ) && ! empty( $_REQUEST['s'] ) ) {
+		$r['search_terms'] = $_REQUEST['s'];
+	}
+
+	$members_template = new BP_Groups_Group_Members_Template( $r );
+
+	/**
+	 * Filters whether or not a group member query has members to display.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool                             $value            Whether there are members to display.
+	 * @param BP_Groups_Group_Members_Template $members_template Object holding the member query results.
+	 */
+	return apply_filters( 'bp_group_has_members', $members_template->has_members(), $members_template );
+}
+
+function bp_group_members() {
+	global $members_template;
+
+	return $members_template->members();
+}
+
+function bp_group_the_member() {
+	global $members_template;
+
+	return $members_template->the_member();
+}
+
+/**
+ * Output the group member avatar while in the groups members loop.
+ *
+ * @since 1.0.0
+ *
+ * @param array|string $args {@see bp_core_fetch_avatar()}
+ */
+function bp_group_member_avatar( $args = '' ) {
+	echo bp_get_group_member_avatar( $args );
+}
+	/**
+	 * Return the group member avatar while in the groups members loop.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param array|string $args {@see bp_core_fetch_avatar()}
+	 *
+	 * @return string
+	 */
+	function bp_get_group_member_avatar( $args = '' ) {
+		global $members_template;
+
+		$r = bp_parse_args( $args, array(
+			'item_id' => $members_template->member->user_id,
+			'type'    => 'full',
+			'email'   => $members_template->member->user_email,
+			'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), $members_template->member->display_name )
+		) );
+
+		/**
+		 * Filters the group member avatar while in the groups members loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value HTML markup for group member avatar.
+		 * @param array  $r     Parsed args used for the avatar query.
+		 */
+		return apply_filters( 'bp_get_group_member_avatar', bp_core_fetch_avatar( $r ), $r );
+	}
+
+/**
+ * Output the group member avatar while in the groups members loop.
+ *
+ * @since 1.0.0
+ *
+ * @param array|string $args {@see bp_core_fetch_avatar()}
+ */
+
+function bp_group_member_avatar_thumb( $args = '' ) {
+	echo bp_get_group_member_avatar_thumb( $args );
+}
+	/**
+	 * Return the group member avatar while in the groups members loop.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param array|string $args {@see bp_core_fetch_avatar()}
+	 *
+	 * @return string
+	 */
+	function bp_get_group_member_avatar_thumb( $args = '' ) {
+		global $members_template;
+
+		$r = bp_parse_args( $args, array(
+			'item_id' => $members_template->member->user_id,
+			'type'    => 'thumb',
+			'email'   => $members_template->member->user_email,
+			'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), $members_template->member->display_name )
+		) );
+
+		/**
+		 * Filters the group member avatar thumb while in the groups members loop.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value HTML markup for group member avatar thumb.
+		 * @param array  $r     Parsed args used for the avatar query.
+		 */
+		return apply_filters( 'bp_get_group_member_avatar_thumb', bp_core_fetch_avatar( $r ), $r );
+	}
+
+/**
+ * Output the group member avatar while in the groups members loop.
+ *
+ * @since 1.0.0
+ *
+ * @param int $width  Width of avatar to fetch.
+ * @param int $height Height of avatar to fetch.
+ */
+function bp_group_member_avatar_mini( $width = 30, $height = 30 ) {
+	echo bp_get_group_member_avatar_mini( $width, $height );
+}
+	/**
+	 * Output the group member avatar while in the groups members loop.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $width  Width of avatar to fetch.
+	 * @param int $height Height of avatar to fetch.
+	 *
+	 * @return string
+	 */
+	function bp_get_group_member_avatar_mini( $width = 30, $height = 30 ) {
+		global $members_template;
+
+		$r = bp_parse_args( array(), array(
+			'item_id' => $members_template->member->user_id,
+			'type'    => 'thumb',
+			'email'   => $members_template->member->user_email,
+			'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), $members_template->member->display_name ),
+			'width'   => absint( $width ),
+			'height'  => absint( $height )
+		) );
+
+		/**
+		 * Filters the group member avatar mini while in the groups members loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value HTML markup for group member avatar mini.
+		 * @param array  $r     Parsed args used for the avatar query.
+		 */
+		return apply_filters( 'bp_get_group_member_avatar_mini', bp_core_fetch_avatar( $r ), $r );
+	}
+
+function bp_group_member_name() {
+	echo bp_get_group_member_name();
+}
+	function bp_get_group_member_name() {
+		global $members_template;
+
+		/**
+		 * Filters the group member display name of the current user in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $display_name Display name of the current user.
+		 */
+		return apply_filters( 'bp_get_group_member_name', $members_template->member->display_name );
+	}
+
+function bp_group_member_url() {
+	echo bp_get_group_member_url();
+}
+	function bp_get_group_member_url() {
+		global $members_template;
+
+		/**
+		 * Filters the group member url for the current user in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL for the current user.
+		 */
+		return apply_filters( 'bp_get_group_member_url', bp_core_get_user_domain( $members_template->member->user_id, $members_template->member->user_nicename, $members_template->member->user_login ) );
+	}
+
+function bp_group_member_link() {
+	echo bp_get_group_member_link();
+}
+	function bp_get_group_member_link() {
+		global $members_template;
+
+		/**
+		 * Filters the group member HTML link for the current user in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value HTML link for the current user.
+		 */
+		return apply_filters( 'bp_get_group_member_link', '<a href="' . bp_core_get_user_domain( $members_template->member->user_id, $members_template->member->user_nicename, $members_template->member->user_login ) . '">' . $members_template->member->display_name . '</a>' );
+	}
+
+function bp_group_member_domain() {
+	echo bp_get_group_member_domain();
+}
+	function bp_get_group_member_domain() {
+		global $members_template;
+
+		/**
+		 * Filters the group member domain for the current user in the loop.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Domain for the current user.
+		 */
+		return apply_filters( 'bp_get_group_member_domain', bp_core_get_user_domain( $members_template->member->user_id, $members_template->member->user_nicename, $members_template->member->user_login ) );
+	}
+
+function bp_group_member_is_friend() {
+	echo bp_get_group_member_is_friend();
+}
+	function bp_get_group_member_is_friend() {
+		global $members_template;
+
+		if ( !isset( $members_template->member->is_friend ) ) {
+			$friend_status = 'not_friends';
+		} else {
+			$friend_status = ( 0 == $members_template->member->is_friend )
+				? 'pending'
+				: 'is_friend';
+		}
+
+		/**
+		 * Filters the friendship status between current user and displayed user in group member loop.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $friend_status Current status of the friendship.
+		 */
+		return apply_filters( 'bp_get_group_member_is_friend', $friend_status );
+	}
+
+function bp_group_member_is_banned() {
+	echo bp_get_group_member_is_banned();
+}
+	function bp_get_group_member_is_banned() {
+		global $members_template;
+
+		/**
+		 * Filters whether the member is banned from the current group.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param bool $is_banned Whether or not the member is banned.
+		 */
+		return apply_filters( 'bp_get_group_member_is_banned', $members_template->member->is_banned );
+	}
+
+function bp_group_member_css_class() {
+	global $members_template;
+
+	if ( $members_template->member->is_banned ) {
+
+		/**
+		 * Filters the class to add to the HTML if member is banned.
+		 *
+		 * @since 1.2.6
+		 *
+		 * @param string $value HTML class to add.
+		 */
+		echo apply_filters( 'bp_group_member_css_class', 'banned-user' );
+	}
+}
+
+function bp_group_member_joined_since() {
+	echo bp_get_group_member_joined_since();
+}
+	function bp_get_group_member_joined_since() {
+		global $members_template;
+
+		/**
+		 * Filters the joined since time for the current member in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Joined since time.
+		 */
+		return apply_filters( 'bp_get_group_member_joined_since', bp_core_get_last_activity( $members_template->member->date_modified, __( 'joined %s', 'buddypress') ) );
+	}
+
+function bp_group_member_id() {
+	echo bp_get_group_member_id();
+}
+	function bp_get_group_member_id() {
+		global $members_template;
+
+		/**
+		 * Filters the member's user ID for group members loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $user_id User ID of the member.
+		 */
+		return apply_filters( 'bp_get_group_member_id', $members_template->member->user_id );
+	}
+
+function bp_group_member_needs_pagination() {
+	global $members_template;
+
+	if ( $members_template->total_member_count > $members_template->pag_num ) {
+		return true;
+	}
+
+	return false;
+}
+
+function bp_group_pag_id() {
+	echo bp_get_group_pag_id();
+}
+	function bp_get_group_pag_id() {
+
+		/**
+		 * Filters the string to be used as the group pag id.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Value to use for the pag id.
+		 */
+		return apply_filters( 'bp_get_group_pag_id', 'pag' );
+	}
+
+function bp_group_member_pagination() {
+	echo bp_get_group_member_pagination();
+	wp_nonce_field( 'bp_groups_member_list', '_member_pag_nonce' );
+}
+	function bp_get_group_member_pagination() {
+		global $members_template;
+
+		/**
+		 * Filters the HTML markup to be used for group member listing pagination.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $pag_links HTML markup for the pagination.
+		 */
+		return apply_filters( 'bp_get_group_member_pagination', $members_template->pag_links );
+	}
+
+function bp_group_member_pagination_count() {
+	echo bp_get_group_member_pagination_count();
+}
+	function bp_get_group_member_pagination_count() {
+		global $members_template;
+
+		$start_num = intval( ( $members_template->pag_page - 1 ) * $members_template->pag_num ) + 1;
+		$from_num  = bp_core_number_format( $start_num );
+		$to_num    = bp_core_number_format( ( $start_num + ( $members_template->pag_num - 1 ) > $members_template->total_member_count ) ? $members_template->total_member_count : $start_num + ( $members_template->pag_num - 1 ) );
+		$total     = bp_core_number_format( $members_template->total_member_count );
+
+		if ( 1 == $members_template->total_member_count ) {
+			$message = __( 'Viewing 1 member', 'buddypress' );
+		} else {
+			$message = sprintf( _n( 'Viewing %1$s - %2$s of %3$s member', 'Viewing %1$s - %2$s of %3$s members', $members_template->total_member_count, 'buddypress' ), $from_num, $to_num, $total );
+		}
+
+		/**
+		 * Filters the "Viewing x-y of z members" pagination message.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value    "Viewing x-y of z members" text.
+		 * @param string $from_num Total amount for the low value in the range.
+		 * @param string $to_num   Total amount for the high value in the range.
+		 * @param string $total    Total amount of members found.
+		 */
+		return apply_filters( 'bp_get_group_member_pagination_count', $message, $from_num, $to_num, $total );
+	}
+
+function bp_group_member_admin_pagination() {
+	echo bp_get_group_member_admin_pagination();
+	wp_nonce_field( 'bp_groups_member_admin_list', '_member_admin_pag_nonce' );
+}
+	function bp_get_group_member_admin_pagination() {
+		global $members_template;
+
+		return $members_template->pag_links;
+	}
+
+/**
+ * Output the contents of the current group's home page.
+ *
+ * You should only use this when on a single group page.
+ *
+ * @since 2.4.0
+ */
+function bp_groups_front_template_part() {
+	$located = bp_groups_get_front_template();
+
+	if ( false !== $located ) {
+		$slug = str_replace( '.php', '', $located );
+
+		/**
+		 * Let plugins adding an action to bp_get_template_part get it from here
+		 *
+		 * @param string $slug Template part slug requested.
+		 * @param string $name Template part name requested.
+		 */
+		do_action( 'get_template_part_' . $slug, $slug, false );
+
+		load_template( $located, true );
+
+	} else if ( bp_is_active( 'activity' ) ) {
+		bp_get_template_part( 'groups/single/activity' );
+
+	} else if ( bp_is_active( 'members'  ) ) {
+		bp_groups_members_template_part();
+	}
+
+	return $located;
+}
+
+/**
+ * Locate a custom group front template if it exists.
+ *
+ * @since 2.4.0
+ *
+ * @param  BP_Groups_Group|null $group Optional. Falls back to current group if not passed.
+ * @return string|bool                 Path to front template on success; boolean false on failure.
+ */
+function bp_groups_get_front_template( $group = null ) {
+	if ( ! is_a( $group, 'BP_Groups_Group' ) ) {
+		$group = groups_get_current_group();
+	}
+
+	if ( ! isset( $group->id ) ) {
+		return false;
+	}
+
+	if ( isset( $group->front_template ) ) {
+		return $group->front_template;
+	}
+
+	$template_names = apply_filters( 'bp_groups_get_front_template', array(
+		'groups/single/front-id-'     . sanitize_file_name( $group->id )     . '.php',
+		'groups/single/front-slug-'   . sanitize_file_name( $group->slug )   . '.php',
+		'groups/single/front-status-' . sanitize_file_name( $group->status ) . '.php',
+		'groups/single/front.php'
+	) );
+
+	return bp_locate_template( $template_names, false, true );
+}
+
+/**
+ * Output the Group members template
+ *
+ * @since 2.0.0
+ *
+ * @return string html output
+ */
+function bp_groups_members_template_part() {
+	?>
+	<div class="item-list-tabs" id="subnav" role="navigation">
+		<ul>
+			<li class="groups-members-search" role="search">
+				<?php bp_directory_members_search_form(); ?>
+			</li>
+
+			<?php bp_groups_members_filter(); ?>
+			<?php
+
+			/**
+			 * Fires at the end of the group members search unordered list.
+			 *
+			 * Part of bp_groups_members_template_part().
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'bp_members_directory_member_sub_types' ); ?>
+
+		</ul>
+	</div>
+
+	<div id="members-group-list" class="group_members dir-list">
+
+		<?php bp_get_template_part( 'groups/single/members' ); ?>
+
+	</div>
+	<?php
+}
+
+/**
+ * Output the Group members filters
+ *
+ * @since 2.0.0
+ *
+ * @return string html output
+ */
+function bp_groups_members_filter() {
+	?>
+	<li id="group_members-order-select" class="last filter">
+		<label for="group_members-order-by"><?php _e( 'Order By:', 'buddypress' ); ?></label>
+		<select id="group_members-order-by">
+			<option value="last_joined"><?php _e( 'Newest', 'buddypress' ); ?></option>
+			<option value="first_joined"><?php _e( 'Oldest', 'buddypress' ); ?></option>
+
+			<?php if ( bp_is_active( 'activity' ) ) : ?>
+				<option value="group_activity"><?php _e( 'Group Activity', 'buddypress' ); ?></option>
+			<?php endif; ?>
+
+			<option value="alphabetical"><?php _e( 'Alphabetical', 'buddypress' ); ?></option>
+
+			<?php
+
+			/**
+			 * Fires at the end of the Group members filters select input.
+			 *
+			 * Useful for plugins to add more filter options.
+			 *
+			 * @since 2.0.0
+			 */
+			do_action( 'bp_groups_members_order_options' ); ?>
+
+		</select>
+	</li>
+	<?php
+}
+
+/***************************************************************************
+ * Group Creation Process Template Tags
+ **/
+
+/**
+ * Determine if the current logged in user can create groups.
+ *
+ * @since 1.5.0
+ *
+ * @uses apply_filters() To call 'bp_user_can_create_groups'.
+ * @uses bp_get_option() To retrieve value of 'bp_restrict_group_creation'. Defaults to 0.
+ * @uses bp_current_user_can() To determine if current user if super admin.
+ *
+ * @return bool True if user can create groups. False otherwise.
+ */
+function bp_user_can_create_groups() {
+
+	// Super admin can always create groups
+	if ( bp_current_user_can( 'bp_moderate' ) ) {
+		return true;
+	}
+
+	// Get group creation option, default to 0 (allowed)
+	$restricted = (int) bp_get_option( 'bp_restrict_group_creation', 0 );
+
+	// Allow by default
+	$can_create = true;
+
+	// Are regular users restricted?
+	if ( $restricted ) {
+		$can_create = false;
+	}
+
+	/**
+	 * Filters if the current logged in user can create groups.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param bool $can_create Whether the person can create groups.
+	 * @param int  $restricted Whether or not group creation is restricted.
+	 */
+	return apply_filters( 'bp_user_can_create_groups', $can_create, $restricted );
+}
+
+function bp_group_creation_tabs() {
+	$bp = buddypress();
+
+	if ( !is_array( $bp->groups->group_creation_steps ) ) {
+		return false;
+	}
+
+	if ( !bp_get_groups_current_create_step() ) {
+		$keys = array_keys( $bp->groups->group_creation_steps );
+		$bp->groups->current_create_step = array_shift( $keys );
+	}
+
+	$counter = 1;
+
+	foreach ( (array) $bp->groups->group_creation_steps as $slug => $step ) {
+		$is_enabled = bp_are_previous_group_creation_steps_complete( $slug ); ?>
+
+		<li<?php if ( bp_get_groups_current_create_step() == $slug ) : ?> class="current"<?php endif; ?>><?php if ( $is_enabled ) : ?><a href="<?php bp_groups_directory_permalink(); ?>create/step/<?php echo $slug ?>/"><?php else: ?><span><?php endif; ?><?php echo $counter ?>. <?php echo $step['name'] ?><?php if ( $is_enabled ) : ?></a><?php else: ?></span><?php endif ?></li><?php
+		$counter++;
+	}
+
+	unset( $is_enabled );
+
+	/**
+	 * Fires at the end of the creation of the group tabs.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'groups_creation_tabs' );
+}
+
+function bp_group_creation_stage_title() {
+	$bp = buddypress();
+
+	/**
+	 * Filters the group creation stage title.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param string $value HTML markup for the group creation stage title.
+	 */
+	echo apply_filters( 'bp_group_creation_stage_title', '<span>&mdash; ' . $bp->groups->group_creation_steps[bp_get_groups_current_create_step()]['name'] . '</span>' );
+}
+
+function bp_group_creation_form_action() {
+	echo bp_get_group_creation_form_action();
+}
+	function bp_get_group_creation_form_action() {
+		$bp = buddypress();
+
+		if ( !bp_action_variable( 1 ) ) {
+			$keys = array_keys( $bp->groups->group_creation_steps );
+			$bp->action_variables[1] = array_shift( $keys );
+		}
+
+		/**
+		 * Filters the group creation form action.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Action to be used with group creation form.
+		 */
+		return apply_filters( 'bp_get_group_creation_form_action', trailingslashit( bp_get_groups_directory_permalink() . 'create/step/' . bp_action_variable( 1 ) ) );
+	}
+
+function bp_is_group_creation_step( $step_slug ) {
+
+	// Make sure we are in the groups component
+	if ( ! bp_is_groups_component() || ! bp_is_current_action( 'create' ) ) {
+		return false;
+	}
+
+	$bp = buddypress();
+
+	// If this the first step, we can just accept and return true
+	$keys = array_keys( $bp->groups->group_creation_steps );
+	if ( !bp_action_variable( 1 ) && array_shift( $keys ) == $step_slug ) {
+		return true;
+	}
+
+	// Before allowing a user to see a group creation step we must make sure
+	// previous steps are completed
+	if ( !bp_is_first_group_creation_step() ) {
+		if ( !bp_are_previous_group_creation_steps_complete( $step_slug ) ) {
+			return false;
+		}
+	}
+
+	// Check the current step against the step parameter
+	if ( bp_is_action_variable( $step_slug ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+function bp_is_group_creation_step_complete( $step_slugs ) {
+	$bp = buddypress();
+
+	if ( !isset( $bp->groups->completed_create_steps ) ) {
+		return false;
+	}
+
+	if ( is_array( $step_slugs ) ) {
+		$found = true;
+
+		foreach ( (array) $step_slugs as $step_slug ) {
+			if ( !in_array( $step_slug, $bp->groups->completed_create_steps ) ) {
+				$found = false;
+			}
+		}
+
+		return $found;
+	} else {
+		return in_array( $step_slugs, $bp->groups->completed_create_steps );
+	}
+
+	return true;
+}
+
+function bp_are_previous_group_creation_steps_complete( $step_slug ) {
+	$bp = buddypress();
+
+	// If this is the first group creation step, return true
+	$keys = array_keys( $bp->groups->group_creation_steps );
+	if ( array_shift( $keys ) == $step_slug ) {
+		return true;
+	}
+
+	reset( $bp->groups->group_creation_steps );
+
+	$previous_steps = array();
+
+	// Get previous steps
+	foreach ( (array) $bp->groups->group_creation_steps as $slug => $name ) {
+		if ( $slug === $step_slug ) {
+			break;
+		}
+
+		$previous_steps[] = $slug;
+	}
+
+	return bp_is_group_creation_step_complete( $previous_steps );
+}
+
+function bp_new_group_id() {
+	echo bp_get_new_group_id();
+}
+	function bp_get_new_group_id() {
+		$bp           = buddypress();
+		$new_group_id = isset( $bp->groups->new_group_id )
+			? $bp->groups->new_group_id
+			: 0;
+
+		/**
+		 * Filters the new group ID.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int $new_group_id ID of the new group.
+		 */
+		return apply_filters( 'bp_get_new_group_id', $new_group_id );
+	}
+
+function bp_new_group_name() {
+	echo bp_get_new_group_name();
+}
+	function bp_get_new_group_name() {
+		$bp   = buddypress();
+		$name = isset( $bp->groups->current_group->name )
+			? $bp->groups->current_group->name
+			: '';
+
+		/**
+		 * Filters the new group name.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $name Name of the new group.
+		 */
+		return apply_filters( 'bp_get_new_group_name', $name );
+	}
+
+function bp_new_group_description() {
+	echo bp_get_new_group_description();
+}
+	function bp_get_new_group_description() {
+		$bp          = buddypress();
+		$description = isset( $bp->groups->current_group->description )
+			? $bp->groups->current_group->description
+			: '';
+
+		/**
+		 * Filters the new group description.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $name Description of the new group.
+		 */
+		return apply_filters( 'bp_get_new_group_description', $description );
+	}
+
+function bp_new_group_enable_forum() {
+	echo bp_get_new_group_enable_forum();
+}
+	function bp_get_new_group_enable_forum() {
+		$bp    = buddypress();
+		$forum = isset( $bp->groups->current_group->enable_forum )
+			? $bp->groups->current_group->enable_forum
+			: false;
+
+		/**
+		 * Filters whether or not to enable forums for the new group.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int $forum Whether or not to enable forums.
+		 */
+		return (int) apply_filters( 'bp_get_new_group_enable_forum', $forum );
+	}
+
+function bp_new_group_status() {
+	echo bp_get_new_group_status();
+}
+	function bp_get_new_group_status() {
+		$bp     = buddypress();
+		$status = isset( $bp->groups->current_group->status )
+			? $bp->groups->current_group->status
+			: 'public';
+
+		/**
+		 * Filters the new group status.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $status Status for the new group.
+		 */
+		return apply_filters( 'bp_get_new_group_status', $status );
+	}
+
+/**
+ * Output the avatar for the group currently being created
+ *
+ * @since 1.1.0
+ *
+ * @see bp_core_fetch_avatar() For more information on accepted arguments
+ *
+ * @param  array  $args See bp_core_fetch_avatar()
+ */
+function bp_new_group_avatar( $args = '' ) {
+	echo bp_get_new_group_avatar( $args );
+}
+	/**
+	 * Return the avatar for the group currently being created
+	 *
+	 * @since 1.1.0
+	 *
+	 * @see bp_core_fetch_avatar() For a description of arguments and return values.
+	 *
+	 * @param array $args  {
+	 *     Arguments are listed here with an explanation of their defaults.
+	 *     For more information about the arguments, see {@link bp_core_fetch_avatar()}.
+	 *
+	 *     @type string   $alt     Default: 'Group photo'.
+	 *     @type string   $class   Default: 'avatar'.
+	 *     @type string   $type    Default: 'full'.
+	 *     @type int|bool $width   Default: false.
+	 *     @type int|bool $height  Default: false.
+	 *     @type string   $id      Passed to $css_id parameter. Default: 'avatar-crop-preview'.
+	 * }
+	 * @return string       The avatar for the group being created
+	 */
+	function bp_get_new_group_avatar( $args = '' ) {
+
+		// Parse arguments
+		$r = bp_parse_args( $args, array(
+			'type'    => 'full',
+			'width'   => false,
+			'height'  => false,
+			'class'   => 'avatar',
+			'id'      => 'avatar-crop-preview',
+			'alt'     => __( 'Group photo', 'buddypress' ),
+			'no_grav' => false
+		), 'get_new_group_avatar' );
+
+		// Merge parsed arguments with object specific data
+		$r = array_merge( $r, array(
+			'item_id'    => bp_get_current_group_id(),
+			'object'     => 'group',
+			'avatar_dir' => 'group-avatars',
+		) );
+
+		// Get the avatar
+		$avatar = bp_core_fetch_avatar( $r );
+
+		/**
+		 * Filters the new group avatar.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $avatar HTML markup for the new group avatar.
+		 * @param array  $r      Array of parsed arguments for the group avatar.
+		 * @param array  $args   Array of original arguments passed to the function.
+		 */
+		return apply_filters( 'bp_get_new_group_avatar', $avatar, $r, $args );
+	}
+
+/**
+ * Escape & output the URL to the previous group creation step
+ *
+ * @since 1.1.0
+ */
+function bp_group_creation_previous_link() {
+	echo esc_url( bp_get_group_creation_previous_link() );
+}
+	/**
+	 * Return the URL to the previous group creation step
+	 *
+	 * @since 1.1.0
+	 *
+	 * @return string
+	 */
+	function bp_get_group_creation_previous_link() {
+		$bp    = buddypress();
+		$steps = array_keys( $bp->groups->group_creation_steps );
+
+		// Loop through steps
+		foreach ( $steps as $slug ) {
+
+			// Break when the current step is found
+			if ( bp_is_action_variable( $slug ) ) {
+				break;
+			}
+
+			// Add slug to previous steps
+			$previous_steps[] = $slug;
+		}
+
+		// Generate the URL for the previous step
+		$group_directory = bp_get_groups_directory_permalink();
+		$create_step     = 'create/step/';
+		$previous_step   = array_pop( $previous_steps );
+		$url             = trailingslashit( $group_directory . $create_step . $previous_step );
+
+		/**
+		 * Filters the permalink for the previous step with the group creation process.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $url Permalink for the previous step.
+		 */
+		return apply_filters( 'bp_get_group_creation_previous_link', $url );
+	}
+
+/**
+ * Echoes the current group creation step.
+ *
+ * @since 1.6.0
+ */
+function bp_groups_current_create_step() {
+	echo bp_get_groups_current_create_step();
+}
+	/**
+	 * Returns the current group creation step. If none is found, returns an empty string.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses apply_filters() Filter bp_get_groups_current_create_step to modify.
+	 *
+	 * @return string $current_create_step
+	 */
+	function bp_get_groups_current_create_step() {
+		$bp = buddypress();
+
+		if ( !empty( $bp->groups->current_create_step ) ) {
+			$current_create_step = $bp->groups->current_create_step;
+		} else {
+			$current_create_step = '';
+		}
+
+		/**
+		 * Filters the current group creation step.
+		 *
+		 * If none is found, returns an empty string.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $current_create_step Current step in the group creation process.
+		 */
+		return apply_filters( 'bp_get_groups_current_create_step', $current_create_step );
+	}
+
+/**
+ * Is the user looking at the last step in the group creation process.
+ *
+ * @since 1.1.0
+ *
+ * @param  string $step Step to compare
+ * @return bool         True if yes, False if no
+ */
+function bp_is_last_group_creation_step( $step = '' ) {
+
+	// Use current step, if no step passed
+	if ( empty( $step ) ) {
+		$step = bp_get_groups_current_create_step();
+	}
+
+	// Get the last step
+	$bp     = buddypress();
+	$steps  = array_keys( $bp->groups->group_creation_steps );
+	$l_step = array_pop( $steps );
+
+	// Compare last step to step
+	$retval = ( $l_step === $step );
+
+	/**
+	 * Filters whether or not user is looking at last step in group creation process.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param bool   $retval Whether or not we are looking at last step.
+	 * @param array  $steps  Array of steps from the group creation process.
+	 * @param string $step   Step to compare.
+	 */
+	return (bool) apply_filters( 'bp_is_last_group_creation_step', $retval, $steps, $step );
+}
+
+/**
+ * Is the user looking at the first step in the group creation process
+ *
+ * @since 1.1.0
+ *
+ * @param  string $step Step to compare
+ * @return bool         True if yes, False if no
+ */
+function bp_is_first_group_creation_step( $step = '' ) {
+
+	// Use current step, if no step passed
+	if ( empty( $step ) ) {
+		$step = bp_get_groups_current_create_step();
+	}
+
+	// Get the first step
+	$bp     = buddypress();
+	$steps  = array_keys( $bp->groups->group_creation_steps );
+	$f_step = array_shift( $steps );
+
+	// Compare first step to step
+	$retval = ( $f_step === $step );
+
+	/**
+	 * Filters whether or not user is looking at first step in group creation process.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param bool   $retval Whether or not we are looking at first step.
+	 * @param array  $steps  Array of steps from the group creation process.
+	 * @param string $step   Step to compare.
+	 */
+	return (bool) apply_filters( 'bp_is_first_group_creation_step', $retval, $steps, $step );
+}
+
+/**
+ * Output a list of friends who can be invited to a group
+ *
+ * @since 1.0.0
+ */
+function bp_new_group_invite_friend_list( $args = array() ) {
+	echo bp_get_new_group_invite_friend_list( $args );
+}
+	/**
+	 * Return a list of friends who can be invited to a group
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param  array $args
+	 * @return mixed HTML list of checkboxes, or false
+	 */
+	function bp_get_new_group_invite_friend_list( $args = array() ) {
+
+		// Bail if no friends component
+		if ( ! bp_is_active( 'friends' ) ) {
+			return false;
+		}
+
+		// Parse arguments
+		$r = wp_parse_args( $args, array(
+			'user_id'   => bp_loggedin_user_id(),
+			'group_id'  => false,
+			'separator' => 'li'
+		) );
+
+		// No group passed, so look for new or current group ID's
+		if ( empty( $r['group_id'] ) ) {
+			$bp            = buddypress();
+			$r['group_id'] = ! empty( $bp->groups->new_group_id )
+				? $bp->groups->new_group_id
+				: $bp->groups->current_group->id;
+		}
+
+		// Setup empty items array
+		$items = array();
+
+		// Get user's friends who are not in this group already
+		$friends = friends_get_friends_invite_list( $r['user_id'], $r['group_id'] );
+
+		if ( ! empty( $friends ) ) {
+
+			// Get already invited users
+			$invites = groups_get_invites_for_group( $r['user_id'], $r['group_id'] );
+
+			for ( $i = 0, $count = count( $friends ); $i < $count; ++$i ) {
+				$checked = in_array( (int) $friends[ $i ]['id'], (array) $invites );
+				$items[] = '<' . $r['separator'] . '><label for="f-' . esc_attr( $friends[ $i ]['id'] ) . '"><input' . checked( $checked, true, false ) . ' type="checkbox" name="friends[]" id="f-' . esc_attr( $friends[ $i ]['id'] ) . '" value="' . esc_attr( $friends[ $i ]['id'] ) . '" /> ' . esc_html( $friends[ $i ]['full_name'] ) . '</label></' . $r['separator'] . '>';
+			}
+		}
+
+		/**
+		 * Filters the array of friends who can be invited to a group.
+		 *
+		 * @since 2.4.0
+		 *
+		 * @param array $items Array of friends.
+		 * @param array $r     Parsed arguments from bp_get_new_group_invite_friend_list()
+		 * @param array $args  Unparsed arguments from bp_get_new_group_invite_friend_list()
+		 */
+		$invitable_friends = apply_filters( 'bp_get_new_group_invite_friend_list', $items, $r, $args );
+
+		if ( ! empty( $invitable_friends ) && is_array( $invitable_friends ) ) {
+			$retval = implode( "\n", $invitable_friends );
+		} else {
+			$retval = false;
+		}
+
+		return $retval;
+	}
+
+function bp_directory_groups_search_form() {
+
+	$query_arg = bp_core_get_component_search_query_arg( 'groups' );
+
+	if ( ! empty( $_REQUEST[ $query_arg ] ) ) {
+		$search_value = stripslashes( $_REQUEST[ $query_arg ] );
+	} else {
+		$search_value = bp_get_search_default_text( 'groups' );
+	}
+
+	$search_form_html = '<form action="" method="get" id="search-groups-form">
+		<label for="groups_search"><input type="text" name="' . esc_attr( $query_arg ) . '" id="groups_search" placeholder="'. esc_attr( $search_value ) .'" /></label>
+		<input type="submit" id="groups_search_submit" name="groups_search_submit" value="'. __( 'Search', 'buddypress' ) .'" />
+	</form>';
+
+	/**
+	 * Filters the HTML markup for the groups search form.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $search_form_html HTML markup for the search form.
+	 */
+	echo apply_filters( 'bp_directory_groups_search_form', $search_form_html );
+
+}
+
+/**
+ * Displays group header tabs.
+ *
+ * @todo Deprecate?
+ */
+function bp_groups_header_tabs() {
+	$user_groups = bp_displayed_user_domain() . bp_get_groups_slug(); ?>
+
+	<li<?php if ( !bp_action_variable( 0 ) || bp_is_action_variable( 'recently-active', 0 ) ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( $user_groups . '/my-groups/recently-active' ); ?>"><?php _e( 'Recently Active', 'buddypress' ); ?></a></li>
+	<li<?php if ( bp_is_action_variable( 'recently-joined', 0 ) ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( $user_groups . '/my-groups/recently-joined' ); ?>"><?php _e( 'Recently Joined',  'buddypress' ); ?></a></li>
+	<li<?php if ( bp_is_action_variable( 'most-popular',    0 ) ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( $user_groups . '/my-groups/most-popular'    ); ?>"><?php _e( 'Most Popular',     'buddypress' ); ?></a></li>
+	<li<?php if ( bp_is_action_variable( 'admin-of',        0 ) ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( $user_groups . '/my-groups/admin-of'        ); ?>"><?php _e( 'Administrator Of', 'buddypress' ); ?></a></li>
+	<li<?php if ( bp_is_action_variable( 'mod-of',          0 ) ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( $user_groups . '/my-groups/mod-of'          ); ?>"><?php _e( 'Moderator Of',     'buddypress' ); ?></a></li>
+	<li<?php if ( bp_is_action_variable( 'alphabetically'     ) ) : ?> class="current"<?php endif; ?>><a href="<?php echo trailingslashit( $user_groups . '/my-groups/alphabetically'  ); ?>"><?php _e( 'Alphabetically',   'buddypress' ); ?></a></li>
+
+<?php
+	do_action( 'groups_header_tabs' );
+}
+
+/**
+ * Displays group filter titles.
+ *
+ * @todo Deprecate?
+ */
+function bp_groups_filter_title() {
+	$current_filter = bp_action_variable( 0 );
+
+	switch ( $current_filter ) {
+		case 'recently-active': default:
+			_e( 'Recently Active', 'buddypress' );
+			break;
+		case 'recently-joined':
+			_e( 'Recently Joined', 'buddypress' );
+			break;
+		case 'most-popular':
+			_e( 'Most Popular', 'buddypress' );
+			break;
+		case 'admin-of':
+			_e( 'Administrator Of', 'buddypress' );
+			break;
+		case 'mod-of':
+			_e( 'Moderator Of', 'buddypress' );
+			break;
+		case 'alphabetically':
+			_e( 'Alphabetically', 'buddypress' );
+		break;
+	}
+	do_action( 'bp_groups_filter_title' );
+}
+
+/**
+ * Is the current page a specific group admin screen?
+ *
+ * @since 1.1.0
+ *
+ * @param string $slug
+ *
+ * @return bool
+ */
+function bp_is_group_admin_screen( $slug = '' ) {
+	return (bool) ( bp_is_group_admin_page() && bp_is_action_variable( $slug ) );
+}
+
+/**
+ * Echoes the current group admin tab slug.
+ *
+ * @since 1.6.0
+ */
+function bp_group_current_admin_tab() {
+	echo bp_get_group_current_admin_tab();
+}
+	/**
+	 * Returns the current group admin tab slug.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses apply_filters() Filter bp_get_current_group_admin_tab to modify return value.
+	 *
+	 * @return string $tab The current tab's slug.
+	 */
+	function bp_get_group_current_admin_tab() {
+		if ( bp_is_groups_component() && bp_is_current_action( 'admin' ) ) {
+			$tab = bp_action_variable( 0 );
+		} else {
+			$tab = '';
+		}
+
+		/**
+		 * Filters the current group admin tab slug.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $tab Current group admin tab slug.
+		 */
+		return apply_filters( 'bp_get_current_group_admin_tab', $tab );
+	}
+
+/** Group Avatar Template Tags ************************************************/
+
+/**
+ * Outputs the current group avatar.
+ *
+ * @since 1.0.0
+ * @param string $type thumb or full ?
+ * @uses bp_get_group_current_avatar() to get the avatar of the current group.
+ */
+function bp_group_current_avatar( $type = 'thumb' ) {
+	echo bp_get_group_current_avatar( $type );
+}
+	/**
+	 * Returns the current group avatar.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $type thumb or full ?
+	 * @return string $tab The current tab's slug.
+	 */
+	function bp_get_group_current_avatar( $type = 'thumb' ) {
+
+		$group_avatar = bp_core_fetch_avatar( array(
+			'item_id'    => bp_get_current_group_id(),
+			'object'     => 'group',
+			'type'       => $type,
+			'avatar_dir' => 'group-avatars',
+			'alt'        => __( 'Group avatar', 'buddypress' ),
+			'class'      => 'avatar'
+		) );
+
+		/**
+		 * Filters the current group avatar.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $group_avatar HTML markup for current group avatar.
+		 */
+		return apply_filters( 'bp_get_group_current_avatar', $group_avatar );
+	}
+
+/**
+ * Return whether a group has an avatar.
+ *
+ * @since 1.1.0
+ *
+ * @param  int|bool $group_id
+ *
+ * @return boolean
+ */
+function bp_get_group_has_avatar( $group_id = false ) {
+
+	if ( false === $group_id ) {
+		$group_id = bp_get_current_group_id();
+	}
+
+	$group_avatar = bp_core_fetch_avatar( array(
+		'item_id' => $group_id,
+		'object'  => 'group',
+		'no_grav' => true,
+		'html'    => false,
+	) );
+
+	if ( bp_core_avatar_default( 'local' ) === $group_avatar ) {
+		return false;
+	}
+
+	return true;
+}
+
+function bp_group_avatar_delete_link() {
+	echo bp_get_group_avatar_delete_link();
+}
+	function bp_get_group_avatar_delete_link() {
+		$bp = buddypress();
+
+		/**
+		 * Filters the URL to delete the group avatar.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value URL to delete the group avatar.
+		 */
+		return apply_filters( 'bp_get_group_avatar_delete_link', wp_nonce_url( bp_get_group_permalink( $bp->groups->current_group ) . 'admin/group-avatar/delete', 'bp_group_avatar_delete' ) );
+	}
+
+function bp_custom_group_boxes() {
+	do_action( 'groups_custom_group_boxes' );
+}
+
+function bp_custom_group_admin_tabs() {
+	do_action( 'groups_custom_group_admin_tabs' );
+}
+
+function bp_custom_group_fields_editable() {
+	do_action( 'groups_custom_group_fields_editable' );
+}
+
+function bp_custom_group_fields() {
+	do_action( 'groups_custom_group_fields' );
+}
+
+/** Group Membership Requests *************************************************/
+
+class BP_Groups_Membership_Requests_Template {
+	public $current_request = -1;
+	public $request_count;
+	public $requests;
+	public $request;
+
+	public $in_the_loop;
+
+	public $pag_page;
+	public $pag_num;
+	public $pag_links;
+	public $total_request_count;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @param array $args {
+	 *     @type int $group_id ID of the group whose membership requests
+	 *                         are being queried. Default: current group id.
+	 *     @type int $per_page Number of records to return per page of
+	 *                         results. Default: 10.
+	 *     @type int $page     Page of results to show. Default: 1.
+	 *     @type int $max      Max items to return. Default: false (show all)
+	 * }
+	 */
+	public function __construct( $args = array() ) {
+
+		// Backward compatibility with old method of passing arguments
+		if ( ! is_array( $args ) || func_num_args() > 1 ) {
+			_deprecated_argument( __METHOD__, '2.0.0', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+			$old_args_keys = array(
+				0 => 'group_id',
+				1 => 'per_page',
+				2 => 'max',
+			);
+
+			$func_args = func_get_args();
+			$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
+		}
+
+		$r = wp_parse_args( $args, array(
+			'page'     => 1,
+			'per_page' => 10,
+			'page_arg' => 'mrpage',
+			'max'      => false,
+			'type'     => 'first_joined',
+			'group_id' => bp_get_current_group_id(),
+		) );
+
+		$this->pag_arg  = sanitize_key( $r['page_arg'] );
+		$this->pag_page = bp_sanitize_pagination_arg( $this->pag_arg, $r['page']     );
+		$this->pag_num  = bp_sanitize_pagination_arg( 'num',          $r['per_page'] );
+
+		$mquery = new BP_Group_Member_Query( array(
+			'group_id' => $r['group_id'],
+			'type'     => $r['type'],
+			'per_page' => $this->pag_num,
+			'page'     => $this->pag_page,
+
+			// These filters ensure we only get pending requests
+			'is_confirmed' => false,
+			'inviter_id'   => 0,
+		) );
+
+		$this->requests      = array_values( $mquery->results );
+		$this->request_count = count( $this->requests );
+
+		// Compatibility with legacy format of request data objects
+		foreach ( $this->requests as $rk => $rv ) {
+			// For legacy reasons, the 'id' property of each
+			// request must match the membership id, not the ID of
+			// the user (as it's returned by BP_Group_Member_Query)
+			$this->requests[ $rk ]->user_id = $rv->ID;
+			$this->requests[ $rk ]->id      = $rv->membership_id;
+
+			// Miscellaneous values
+			$this->requests[ $rk ]->group_id   = $r['group_id'];
+		}
+
+		if ( empty( $r['max'] ) || ( $r['max'] >= (int) $mquery->total_users ) ) {
+			$this->total_request_count = (int) $mquery->total_users;
+		} else {
+			$this->total_request_count = (int) $r['max'];
+		}
+
+		if ( empty( $r['max'] ) || ( $r['max'] >= count( $this->requests ) ) ) {
+			$this->request_count = count( $this->requests );
+		} else {
+			$this->request_count = (int) $r['max'];
+		}
+
+		$this->pag_links = paginate_links( array(
+			'base'      => add_query_arg( $this->pag_arg, '%#%' ),
+			'format'    => '',
+			'total'     => ceil( $this->total_request_count / $this->pag_num ),
+			'current'   => $this->pag_page,
+			'prev_text' => '&larr;',
+			'next_text' => '&rarr;',
+			'mid_size'  => 1,
+			'add_args'  => array(),
+		) );
+	}
+
+	public function has_requests() {
+		if ( ! empty( $this->request_count ) ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	public function next_request() {
+		$this->current_request++;
+		$this->request = $this->requests[ $this->current_request ];
+
+		return $this->request;
+	}
+
+	public function rewind_requests() {
+		$this->current_request = -1;
+
+		if ( $this->request_count > 0 ) {
+			$this->request = $this->requests[0];
+		}
+	}
+
+	public function requests() {
+		$tick = intval( $this->current_request + 1 );
+		if ( $tick < $this->request_count ) {
+			return true;
+		} elseif ( $tick == $this->request_count ) {
+
+			/**
+			 * Fires right before the rewinding of group membership requests list.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'group_request_loop_end' );
+			// Do some cleaning up after the loop
+			$this->rewind_requests();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	public function the_request() {
+		$this->in_the_loop = true;
+		$this->request     = $this->next_request();
+
+		// loop has just started
+		if ( 0 == $this->current_request ) {
+
+			/**
+			 * Fires if the current group membership request item is the first in the loop.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'group_request_loop_start' );
+		}
+	}
+}
+
+/**
+ * Initialize a group membership request template loop.
+ *
+ * @param array|string $args {
+ *     @type int $group_id ID of the group. Defaults to current group.
+ *     @type int $per_page Number of records to return per page. Default: 10.
+ *     @type int $page     Page of results to return. Default: 1.
+ *     @type int $max      Max number of items to return. Default: false.
+ * }
+ * @return bool True if there are requests, otherwise false.
+ */
+function bp_group_has_membership_requests( $args = '' ) {
+	global $requests_template;
+
+	$defaults = array(
+		'group_id' => bp_get_current_group_id(),
+		'per_page' => 10,
+		'page'     => 1,
+		'max'      => false
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+
+	$requests_template = new BP_Groups_Membership_Requests_Template( $r );
+
+	/**
+	 * Filters whether or not a group membership query has requests to display.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool                                   $value             Whether there are requests to display.
+	 * @param BP_Groups_Membership_Requests_Template $requests_template Object holding the requests query results.
+	 */
+	return apply_filters( 'bp_group_has_membership_requests', $requests_template->has_requests(), $requests_template );
+}
+
+function bp_group_membership_requests() {
+	global $requests_template;
+
+	return $requests_template->requests();
+}
+
+function bp_group_the_membership_request() {
+	global $requests_template;
+
+	return $requests_template->the_request();
+}
+
+function bp_group_request_user_avatar_thumb() {
+	global $requests_template;
+
+	/**
+	 * Filters the requesting user's avatar thumbnail.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value HTML markup for the user's avatar thumbnail.
+	 */
+	echo apply_filters( 'bp_group_request_user_avatar_thumb', bp_core_fetch_avatar( array( 'item_id' => $requests_template->request->user_id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_core_get_user_displayname( $requests_template->request->user_id ) ) ) ) );
+}
+
+function bp_group_request_reject_link() {
+	echo bp_get_group_request_reject_link();
+}
+	function bp_get_group_request_reject_link() {
+		global $requests_template;
+
+		/**
+		 * Filters the URL to use to reject a membership request.
+		 *
+		 * @since 1.2.6
+		 *
+		 * @param string $value URL to use to reject a membership request.
+		 */
+		return apply_filters( 'bp_get_group_request_reject_link', wp_nonce_url( bp_get_group_permalink( groups_get_current_group() ) . 'admin/membership-requests/reject/' . $requests_template->request->membership_id, 'groups_reject_membership_request' ) );
+	}
+
+function bp_group_request_accept_link() {
+	echo bp_get_group_request_accept_link();
+}
+	function bp_get_group_request_accept_link() {
+		global $requests_template;
+
+		/**
+		 * Filters the URL to use to accept a membership request.
+		 *
+		 * @since 1.2.6
+		 *
+		 * @param string $value URL to use to accept a membership request.
+		 */
+		return apply_filters( 'bp_get_group_request_accept_link', wp_nonce_url( bp_get_group_permalink( groups_get_current_group() ) . 'admin/membership-requests/accept/' . $requests_template->request->membership_id, 'groups_accept_membership_request' ) );
+	}
+
+function bp_group_request_user_link() {
+	echo bp_get_group_request_user_link();
+}
+	function bp_get_group_request_user_link() {
+		global $requests_template;
+
+		/**
+		 * Filters the URL for the user requesting membership.
+		 *
+		 * @since 1.2.6
+		 *
+		 * @param string $value URL for the user requestion membership.
+		 */
+		return apply_filters( 'bp_get_group_request_user_link', bp_core_get_userlink( $requests_template->request->user_id ) );
+	}
+
+function bp_group_request_time_since_requested() {
+	global $requests_template;
+
+	/**
+	 * Filters the formatted time since membership was requested.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Formatted time since membership was requested.
+	 */
+	echo apply_filters( 'bp_group_request_time_since_requested', sprintf( __( 'requested %s', 'buddypress' ), bp_core_time_since( strtotime( $requests_template->request->date_modified ) ) ) );
+}
+
+function bp_group_request_comment() {
+	global $requests_template;
+
+	/**
+	 * Filters the membership request comment left by user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Membership request comment left by user.
+	 */
+	echo apply_filters( 'bp_group_request_comment', strip_tags( stripslashes( $requests_template->request->comments ) ) );
+}
+
+/**
+ * Output pagination links for group membership requests.
+ *
+ * @since 2.0.0
+ */
+function bp_group_requests_pagination_links() {
+	echo bp_get_group_requests_pagination_links();
+}
+	/**
+	 * Get pagination links for group membership requests.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @return string
+	 */
+	function bp_get_group_requests_pagination_links() {
+		global $requests_template;
+
+		/**
+		 * Filters pagination links for group membership requests.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $value Pagination links for group membership requests.
+		 */
+		return apply_filters( 'bp_get_group_requests_pagination_links', $requests_template->pag_links );
+	}
+
+/**
+ * Output pagination count text for group membership requests.
+ *
+ * @since 2.0.0
+ */
+function bp_group_requests_pagination_count() {
+	echo bp_get_group_requests_pagination_count();
+}
+	/**
+	 * Get pagination count text for group membership requests.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @return string
+	 */
+	function bp_get_group_requests_pagination_count() {
+		global $requests_template;
+
+		$start_num = intval( ( $requests_template->pag_page - 1 ) * $requests_template->pag_num ) + 1;
+		$from_num  = bp_core_number_format( $start_num );
+		$to_num    = bp_core_number_format( ( $start_num + ( $requests_template->pag_num - 1 ) > $requests_template->total_request_count ) ? $requests_template->total_request_count : $start_num + ( $requests_template->pag_num - 1 ) );
+		$total     = bp_core_number_format( $requests_template->total_request_count );
+
+		if ( 1 == $requests_template->total_request_count ) {
+			$message = __( 'Viewing 1 request', 'buddypress' );
+		} else {
+			$message = sprintf( _n( 'Viewing %1$s - %2$s of %3$s request', 'Viewing %1$s - %2$s of %3$s requests', $requests_template->total_request_count, 'buddypress' ), $from_num, $to_num, $total );
+		}
+
+		/**
+		 * Filters pagination count text for group membership requests.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $message  Pagination count text for group membership requests.
+		 * @param string $from_num Total amount for the low value in the range.
+		 * @param string $to_num   Total amount for the high value in the range.
+		 * @param string $total    Total amount of members found.
+		 */
+		return apply_filters( 'bp_get_group_requests_pagination_count', $message, $from_num, $to_num, $total );
+	}
+
+/** Group Invitations *********************************************************/
+
+class BP_Groups_Invite_Template {
+	public $current_invite = -1;
+	public $invite_count;
+	public $invites;
+	public $invite;
+
+	public $in_the_loop;
+
+	public $pag_page;
+	public $pag_num;
+	public $pag_links;
+	public $total_invite_count;
+
+	public function __construct( $args = array() ) {
+
+		// Backward compatibility with old method of passing arguments
+		if ( ! is_array( $args ) || func_num_args() > 1 ) {
+			_deprecated_argument( __METHOD__, '2.0.0', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+			$old_args_keys = array(
+				0  => 'user_id',
+				1  => 'group_id',
+			);
+
+			$func_args = func_get_args();
+			$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
+		}
+
+		$r = wp_parse_args( $args, array(
+			'page'     => 1,
+			'per_page' => 10,
+			'page_arg' => 'invitepage',
+			'user_id'  => bp_loggedin_user_id(),
+			'group_id' => bp_get_current_group_id(),
+		) );
+
+		$this->pag_arg  = sanitize_key( $r['page_arg'] );
+		$this->pag_page = bp_sanitize_pagination_arg( $this->pag_arg, $r['page']     );
+		$this->pag_num  = bp_sanitize_pagination_arg( 'num',          $r['per_page'] );
+
+		$iquery = new BP_Group_Member_Query( array(
+			'group_id' => $r['group_id'],
+			'type'     => 'first_joined',
+			'per_page' => $this->pag_num,
+			'page'     => $this->pag_page,
+
+			// These filters ensure we get only pending invites
+			'is_confirmed' => false,
+			'inviter_id'   => $r['user_id'],
+		) );
+
+		$this->invite_data        = $iquery->results;
+		$this->total_invite_count = $iquery->total_users;
+		$this->invites            = array_values( wp_list_pluck( $this->invite_data, 'ID' ) );
+		$this->invite_count       = count( $this->invites );
+
+		// If per_page is set to 0 (show all results), don't generate
+		// pag_links
+		if ( ! empty( $this->pag_num ) ) {
+			$this->pag_links = paginate_links( array(
+				'base'      => add_query_arg( $this->pag_arg, '%#%' ),
+				'format'    => '',
+				'total'     => ceil( $this->total_invite_count / $this->pag_num ),
+				'current'   => $this->pag_page,
+				'prev_text' => '&larr;',
+				'next_text' => '&rarr;',
+				'mid_size'  => 1,
+				'add_args'  => array(),
+			) );
+		} else {
+			$this->pag_links = '';
+		}
+	}
+
+	public function has_invites() {
+		if ( ! empty( $this->invite_count ) ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	public function next_invite() {
+		$this->current_invite++;
+		$this->invite = $this->invites[ $this->current_invite ];
+
+		return $this->invite;
+	}
+
+	public function rewind_invites() {
+		$this->current_invite = -1;
+		if ( $this->invite_count > 0 ) {
+			$this->invite = $this->invites[0];
+		}
+	}
+
+	public function invites() {
+		$tick = intval( $this->current_invite + 1 );
+		if ( $tick < $this->invite_count ) {
+			return true;
+		} elseif ( $tick == $this->invite_count ) {
+
+			/**
+			 * Fires right before the rewinding of invites list.
+			 *
+			 * @since 1.1.0
+			 * @since 2.3.0 `$this` parameter added.
+			 *
+			 * @param BP_Groups_Invite_Template $this Instance of the current Invites template.
+			 */
+			do_action( 'loop_end', $this );
+
+			// Do some cleaning up after the loop
+			$this->rewind_invites();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	public function the_invite() {
+		global $group_id;
+
+		$this->in_the_loop  = true;
+		$user_id            = $this->next_invite();
+
+		$this->invite       = new stdClass;
+		$this->invite->user = $this->invite_data[ $user_id ];
+
+		// This method previously populated the user object with
+		// BP_Core_User. We manually configure BP_Core_User data for
+		// backward compatibility.
+		if ( bp_is_active( 'xprofile' ) ) {
+			$this->invite->user->profile_data = BP_XProfile_ProfileData::get_all_for_user( $user_id );
+		}
+
+		$this->invite->user->avatar       = bp_core_fetch_avatar( array( 'item_id' => $user_id, 'type' => 'full',  'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->invite->user->fullname ) ) );
+		$this->invite->user->avatar_thumb = bp_core_fetch_avatar( array( 'item_id' => $user_id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->invite->user->fullname ) ) );
+		$this->invite->user->avatar_mini  = bp_core_fetch_avatar( array( 'item_id' => $user_id, 'type' => 'thumb', 'alt' => sprintf( __( 'Profile photo of %s', 'buddypress' ), $this->invite->user->fullname ), 'width' => 30, 'height' => 30 ) );
+		$this->invite->user->email        = $this->invite->user->user_email;
+		$this->invite->user->user_url     = bp_core_get_user_domain( $user_id, $this->invite->user->user_nicename, $this->invite->user->user_login );
+		$this->invite->user->user_link    = "<a href='{$this->invite->user->user_url}' title='{$this->invite->user->fullname}'>{$this->invite->user->fullname}</a>";
+		$this->invite->user->last_active  = bp_core_get_last_activity( $this->invite->user->last_activity, __( 'active %s', 'buddypress' ) );
+
+		if ( bp_is_active( 'groups' ) ) {
+			$total_groups = BP_Groups_Member::total_group_count( $user_id );
+			$this->invite->user->total_groups = sprintf( _n( '%d group', '%d groups', $total_groups, 'buddypress' ), $total_groups );
+		}
+
+		if ( bp_is_active( 'friends' ) ) {
+			$this->invite->user->total_friends = BP_Friends_Friendship::total_friend_count( $user_id );
+		}
+
+		$this->invite->user->total_blogs = null;
+
+		// Global'ed in bp_group_has_invites()
+		$this->invite->group_id = $group_id;
+
+		// loop has just started
+		if ( 0 == $this->current_invite ) {
+
+			/**
+			 * Fires if the current invite item is the first in the loop.
+			 *
+			 * @since 1.1.0
+			 * @since 2.3.0 `$this` parameter added.
+			 *
+			 * @param BP_Groups_Invite_Template $this Instance of the current Invites template.
+			 */
+			do_action( 'loop_start', $this );
+		}
+	}
+}
+
+function bp_group_has_invites( $args = '' ) {
+	global $invites_template, $group_id;
+
+	$r = wp_parse_args( $args, array(
+		'group_id' => false,
+		'user_id'  => bp_loggedin_user_id(),
+		'per_page' => false,
+		'page'     => 1,
+	) );
+
+	if ( empty( $r['group_id'] ) ) {
+		if ( groups_get_current_group() ) {
+			$r['group_id'] = bp_get_current_group_id();
+		} elseif ( ! empty( buddypress()->groups->new_group_id ) ) {
+			$r['group_id'] = buddypress()->groups->new_group_id;
+		}
+	}
+
+	// Set the global (for use in BP_Groups_Invite_Template::the_invite())
+	if ( empty( $group_id ) ) {
+		$group_id = $r['group_id'];
+	}
+
+	if ( ! $group_id ) {
+		return false;
+	}
+
+	$invites_template = new BP_Groups_Invite_Template( $r );
+
+	/**
+	 * Filters whether or not a group invites query has invites to display.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool                      $value            Whether there are requests to display.
+	 * @param BP_Groups_Invite_Template $invites_template Object holding the invites query results.
+	 */
+	return apply_filters( 'bp_group_has_invites', $invites_template->has_invites(), $invites_template );
+}
+
+function bp_group_invites() {
+	global $invites_template;
+
+	return $invites_template->invites();
+}
+
+function bp_group_the_invite() {
+	global $invites_template;
+
+	return $invites_template->the_invite();
+}
+
+function bp_group_invite_item_id() {
+	echo bp_get_group_invite_item_id();
+}
+	function bp_get_group_invite_item_id() {
+		global $invites_template;
+
+		/**
+		 * Filters the group invite item ID.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Group invite item ID.
+		 */
+		return apply_filters( 'bp_get_group_invite_item_id', 'uid-' . $invites_template->invite->user->id );
+	}
+
+function bp_group_invite_user_avatar() {
+	echo bp_get_group_invite_user_avatar();
+}
+	function bp_get_group_invite_user_avatar() {
+		global $invites_template;
+
+		/**
+		 * Filters the group invite user avatar.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Group invite user avatar.
+		 */
+		return apply_filters( 'bp_get_group_invite_user_avatar', $invites_template->invite->user->avatar_thumb );
+	}
+
+function bp_group_invite_user_link() {
+	echo bp_get_group_invite_user_link();
+}
+	function bp_get_group_invite_user_link() {
+		global $invites_template;
+
+		/**
+		 * Filters the group invite user link.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Group invite user link.
+		 */
+		return apply_filters( 'bp_get_group_invite_user_link', bp_core_get_userlink( $invites_template->invite->user->id ) );
+	}
+
+function bp_group_invite_user_last_active() {
+	echo bp_get_group_invite_user_last_active();
+}
+	function bp_get_group_invite_user_last_active() {
+		global $invites_template;
+
+		/**
+		 * Filters the group invite user's last active time.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Group invite user's last active time.
+		 */
+		return apply_filters( 'bp_get_group_invite_user_last_active', $invites_template->invite->user->last_active );
+	}
+
+function bp_group_invite_user_remove_invite_url() {
+	echo bp_get_group_invite_user_remove_invite_url();
+}
+	function bp_get_group_invite_user_remove_invite_url() {
+		global $invites_template;
+
+		$user_id = intval( $invites_template->invite->user->id );
+
+		if ( bp_is_current_action( 'create' ) ) {
+			$uninvite_url = bp_get_groups_directory_permalink() . 'create/step/group-invites/?user_id=' . $user_id;
+		} else {
+			$uninvite_url = bp_get_group_permalink( groups_get_current_group() ) . 'send-invites/remove/' . $user_id;
+		}
+
+		return wp_nonce_url( $uninvite_url, 'groups_invite_uninvite_user' );
+	}
+
+/**
+ * Output pagination links for group invitations.
+ *
+ * @since 2.0.0
+ */
+function bp_group_invite_pagination_links() {
+	echo bp_get_group_invite_pagination_links();
+}
+	/**
+	 * Get pagination links for group invitations.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @return string
+	 */
+	function bp_get_group_invite_pagination_links() {
+		global $invites_template;
+
+		/**
+		 * Filters the pagination links for group invitations.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $value Pagination links for group invitations.
+		 */
+		return apply_filters( 'bp_get_group_invite_pagination_links', $invites_template->pag_links );
+	}
+
+/**
+ * Output pagination count text for group invitations.
+ *
+ * @since 2.0.0
+ */
+function bp_group_invite_pagination_count() {
+	echo bp_get_group_invite_pagination_count();
+}
+	/**
+	 * Get pagination count text for group invitations.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @return string
+	 */
+	function bp_get_group_invite_pagination_count() {
+		global $invites_template;
+
+		$start_num = intval( ( $invites_template->pag_page - 1 ) * $invites_template->pag_num ) + 1;
+		$from_num  = bp_core_number_format( $start_num );
+		$to_num    = bp_core_number_format( ( $start_num + ( $invites_template->pag_num - 1 ) > $invites_template->total_invite_count ) ? $invites_template->total_invite_count : $start_num + ( $invites_template->pag_num - 1 ) );
+		$total     = bp_core_number_format( $invites_template->total_invite_count );
+
+		if ( 1 == $invites_template->total_invite_count ) {
+			$message = __( 'Viewing 1 invitation', 'buddypress' );
+		} else {
+			$message = sprintf( _n( 'Viewing %1$s - %2$s of %3$s invitation', 'Viewing %1$s - %2$s of %3$s invitations', $invites_template->total_invite_count, 'buddypress' ), $from_num, $to_num, $total );
+		}
+
+		/** This filter is documented in bp-groups/bp-groups-template.php */
+		return apply_filters( 'bp_get_groups_pagination_count', $message, $from_num, $to_num, $total );
+	}
+
+/** Group RSS *****************************************************************/
+
+/**
+ * Hook group activity feed to <head>.
+ *
+ * @since 1.5.0
+ */
+function bp_groups_activity_feed() {
+
+	// Bail if not viewing a single group or activity is not active
+	if ( ! bp_is_active( 'groups' ) || ! bp_is_active( 'activity' ) || ! bp_is_group() ) {
+		return;
+	} ?>
+
+	<link rel="alternate" type="application/rss+xml" title="<?php bloginfo( 'name' ) ?> | <?php bp_current_group_name() ?> | <?php _e( 'Group Activity RSS Feed', 'buddypress' ) ?>" href="<?php bp_group_activity_feed_link() ?>" />
+
+<?php
+}
+add_action( 'bp_head', 'bp_groups_activity_feed' );
+
+/**
+ * Output the current group activity-stream RSS URL.
+ *
+ * @since 1.5.0
+ */
+function bp_group_activity_feed_link() {
+	echo bp_get_group_activity_feed_link();
+}
+	/**
+	 * Return the current group activity-stream RSS URL.
+	 *
+	 * @since 1.5.0
+	 * @return string
+	 */
+	function bp_get_group_activity_feed_link() {
+		$current_group = groups_get_current_group();
+		$group_link    = bp_get_group_permalink( $current_group ) . 'feed';
+		$feed_link     = trailingslashit( $group_link );
+
+		/**
+		 * Filters the current group activity-stream RSS URL.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $feed_link Current group activity-stream RSS URL.
+		 */
+		return apply_filters( 'bp_get_group_activity_feed_link', $feed_link );
+	}
+
+/** Current Group *************************************************************/
+
+/**
+ * Echoes the output of bp_get_current_group_id().
+ *
+ * @since 1.5.0
+ */
+function bp_current_group_id() {
+	echo bp_get_current_group_id();
+}
+	/**
+	 * Returns the ID of the current group.
+	 *
+	 * @since 1.5.0
+	 * @uses apply_filters() Filter bp_get_current_group_id to modify this output.
+	 *
+	 * @return int $current_group_id The id of the current group, if there is one.
+	 */
+	function bp_get_current_group_id() {
+		$current_group    = groups_get_current_group();
+		$current_group_id = isset( $current_group->id ) ? (int) $current_group->id : 0;
+
+		/**
+		 * Filters the ID of the current group.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int    $current_group_id ID of the current group.
+		 * @param object $current_group    Instance holding the current group.
+		 */
+		return apply_filters( 'bp_get_current_group_id', $current_group_id, $current_group );
+	}
+
+/**
+ * Echoes the output of bp_get_current_group_slug().
+ *
+ * @since 1.5.0
+ */
+function bp_current_group_slug() {
+	echo bp_get_current_group_slug();
+}
+	/**
+	 * Returns the slug of the current group.
+	 *
+	 * @since 1.5.0
+	 * @uses apply_filters() Filter bp_get_current_group_slug to modify this output.
+	 *
+	 * @return string $current_group_slug The slug of the current group, if there is one.
+	 */
+	function bp_get_current_group_slug() {
+		$current_group      = groups_get_current_group();
+		$current_group_slug = isset( $current_group->slug ) ? $current_group->slug : '';
+
+		/**
+		 * Filters the slug of the current group.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $current_group_slug Slug of the current group.
+		 * @param object $current_group      Instance holding the current group.
+		 */
+		return apply_filters( 'bp_get_current_group_slug', $current_group_slug, $current_group );
+	}
+
+/**
+ * Echoes the output of bp_get_current_group_name().
+ *
+ * @since 1.5.0
+ */
+function bp_current_group_name() {
+	echo bp_get_current_group_name();
+}
+	/**
+	 * Returns the name of the current group.
+	 *
+	 * @since 1.5.0
+	 * @uses apply_filters() Filter bp_get_current_group_name to modify this output.
+	 *
+	 * @return string The name of the current group, if there is one.
+	 */
+	function bp_get_current_group_name() {
+		$current_group      = groups_get_current_group();
+		$current_group_name = isset( $current_group->name ) ? $current_group->name : '';
+
+		/** This filter is documented in bp-groups/bp-groups-template.php */
+		$name               = apply_filters( 'bp_get_group_name', $current_group_name );
+
+		/**
+		 * Filters the name of the current group.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $name          Name of the current group.
+		 * @param object $current_group Instance holding the current group.
+		 */
+		return apply_filters( 'bp_get_current_group_name', $name, $current_group );
+	}
+
+/**
+ * Echoes the output of bp_get_current_group_description().
+ *
+ * @since 2.1.0
+ */
+function bp_current_group_description() {
+	echo bp_get_current_group_description();
+}
+	/**
+	 * Returns the description of the current group.
+	 *
+	 * @since 2.1.0
+	 * @uses apply_filters() Filter bp_get_current_group_description to modify
+	 *                       this output.
+	 *
+	 * @return string The description of the current group, if there is one.
+	 */
+	function bp_get_current_group_description() {
+		$current_group      = groups_get_current_group();
+		$current_group_desc = isset( $current_group->description ) ? $current_group->description : '';
+
+		/**
+		 * Filters the description of the current group.
+		 *
+		 * This filter is used to apply extra filters related to formatting.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $current_group_desc Description of the current group.
+		 */
+		$desc               = apply_filters( 'bp_get_group_description', $current_group_desc );
+
+		/**
+		 * Filters the description of the current group.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $desc Description of the current group.
+		 */
+		return apply_filters( 'bp_get_current_group_description', $desc );
+	}
+
+/**
+ * Output a URL for a group component action.
+ *
+ * @since 1.2.0
+ *
+ * @param string $action
+ * @param string $query_args
+ * @param bool $nonce
+ *
+ * @return string
+ */
+function bp_groups_action_link( $action = '', $query_args = '', $nonce = false ) {
+	echo bp_get_groups_action_link( $action, $query_args, $nonce );
+}
+	/**
+	 * Get a URL for a group component action.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $action
+	 * @param string $query_args
+	 * @param bool $nonce
+	 *
+	 * @return string
+	 */
+	function bp_get_groups_action_link( $action = '', $query_args = '', $nonce = false ) {
+
+		$current_group = groups_get_current_group();
+		$url           = '';
+
+		// Must be a group
+		if ( ! empty( $current_group->id ) ) {
+
+			// Append $action to $url if provided
+			if ( !empty( $action ) ) {
+				$url = bp_get_group_permalink( $current_group ) . $action;
+			} else {
+				$url = bp_get_group_permalink( $current_group );
+			}
+
+			// Add a slash at the end of our user url
+			$url = trailingslashit( $url );
+
+			// Add possible query args
+			if ( !empty( $query_args ) && is_array( $query_args ) ) {
+				$url = add_query_arg( $query_args, $url );
+			}
+
+			// To nonce, or not to nonce...
+			if ( true === $nonce ) {
+				$url = wp_nonce_url( $url );
+			} elseif ( is_string( $nonce ) ) {
+				$url = wp_nonce_url( $url, $nonce );
+			}
+		}
+
+		/**
+		 * Filters a URL for a group component action.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $url        URL for a group component action.
+		 * @param string $action     Action being taken for the group.
+		 * @param string $query_args Query arguments being passed.
+		 * @param bool   $nonce      Whether or not to add a nonce.
+		 */
+		return apply_filters( 'bp_get_groups_action_link', $url, $action, $query_args, $nonce );
+	}
+
+/** Stats **********************************************************************/
+
+/**
+ * Display the number of groups in user's profile.
+ *
+ * @since 2.0.0
+ *
+ * @param array|string $args before|after|user_id
+ *
+ * @uses bp_groups_get_profile_stats() to get the stats.
+ */
+function bp_groups_profile_stats( $args = '' ) {
+	echo bp_groups_get_profile_stats( $args );
+}
+add_action( 'bp_members_admin_user_stats', 'bp_groups_profile_stats', 8, 1 );
+
+/**
+ * Return the number of groups in user's profile.
+ *
+ * @since 2.0.0
+ *
+ * @param array|string $args before|after|user_id
+ * @return string HTML for stats output.
+ */
+function bp_groups_get_profile_stats( $args = '' ) {
+
+	// Parse the args
+	$r = bp_parse_args( $args, array(
+		'before'  => '<li class="bp-groups-profile-stats">',
+		'after'   => '</li>',
+		'user_id' => bp_displayed_user_id(),
+		'groups'  => 0,
+		'output'  => ''
+	), 'groups_get_profile_stats' );
+
+	// Allow completely overloaded output
+	if ( empty( $r['output'] ) ) {
+
+		// Only proceed if a user ID was passed
+		if ( ! empty( $r['user_id'] ) ) {
+
+			// Get the user groups
+			if ( empty( $r['groups'] ) ) {
+				$r['groups'] = absint( bp_get_total_group_count_for_user( $r['user_id'] ) );
+			}
+
+			// If groups exist, show some formatted output
+			$r['output'] = $r['before'] . sprintf( _n( '%s group', '%s groups', $r['groups'], 'buddypress' ), '<strong>' . $r['groups'] . '</strong>' ) . $r['after'];
+		}
+	}
+
+	/**
+	 * Filters the number of groups in user's profile.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $value HTML for stats output.
+	 * @param array  $r     Array of parsed arguments for query.
+	 */
+	return apply_filters( 'bp_groups_get_profile_stats', $r['output'], $r );
+}
Index: /tags/2.4.3/src/bp-groups/bp-groups-widgets.php
===================================================================
--- /tags/2.4.3/src/bp-groups/bp-groups-widgets.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/bp-groups-widgets.php	(revision 10410)
@@ -0,0 +1,255 @@
+<?php
+/**
+ * BuddyPress Groups Widgets
+ *
+ * @package BuddyPress
+ * @subpackage GroupsWidgets
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/* Register widgets for groups component */
+function groups_register_widgets() {
+	add_action('widgets_init', create_function('', 'return register_widget("BP_Groups_Widget");') );
+}
+add_action( 'bp_register_widgets', 'groups_register_widgets' );
+
+/*** GROUPS WIDGET *****************/
+
+class BP_Groups_Widget extends WP_Widget {
+	function __construct() {
+		$widget_ops = array(
+			'description' => __( 'A dynamic list of recently active, popular, and newest groups', 'buddypress' ),
+			'classname' => 'widget_bp_groups_widget buddypress widget',
+		);
+		parent::__construct( false, _x( '(BuddyPress) Groups', 'widget name', 'buddypress' ), $widget_ops );
+
+		if ( is_active_widget( false, false, $this->id_base ) && !is_admin() && !is_network_admin() ) {
+			$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+			wp_enqueue_script( 'groups_widget_groups_list-js', buddypress()->plugin_url . "bp-groups/js/widget-groups{$min}.js", array( 'jquery' ), bp_get_version() );
+		}
+	}
+
+	/**
+	 * PHP4 constructor
+	 *
+	 * For backward compatibility only
+	 */
+	function bp_groups_widget() {
+		$this->_construct();
+	}
+
+	function widget( $args, $instance ) {
+
+		/**
+		 * Filters the user ID to use with the widget instance.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Empty user ID.
+		 */
+		$user_id = apply_filters( 'bp_group_widget_user_id', '0' );
+
+		extract( $args );
+
+		if ( empty( $instance['group_default'] ) )
+			$instance['group_default'] = 'popular';
+
+		if ( empty( $instance['title'] ) )
+			$instance['title'] = __( 'Groups', 'buddypress' );
+
+		/**
+		 * Filters the title of the Groups widget.
+		 *
+		 * @since 1.8.0
+		 * @since 2.3.0 Added 'instance' and 'id_base' to arguments passed to filter.
+		 *
+		 * @param string $title    The widget title.
+		 * @param array  $instance The settings for the particular instance of the widget.
+		 * @param string $id_base  Root ID for all widgets of this type.
+		 */
+		$title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
+
+		/**
+		 * Filters the separator of the group widget links.
+		 *
+		 * @since 2.4.0
+		 *
+		 * @param string $separator Separator string. Default '|'.
+		 */
+		$separator = apply_filters( 'bp_groups_widget_separator', '|' );
+
+		echo $before_widget;
+
+		$title = !empty( $instance['link_title'] ) ? '<a href="' . bp_get_groups_directory_permalink() . '">' . $title . '</a>' : $title;
+
+		echo $before_title . $title . $after_title;
+
+		$group_args = array(
+			'user_id'         => $user_id,
+			'type'            => $instance['group_default'],
+			'per_page'        => $instance['max_groups'],
+			'max'             => $instance['max_groups'],
+		);
+
+		?>
+
+		<?php if ( bp_has_groups( $group_args ) ) : ?>
+			<div class="item-options" id="groups-list-options">
+				<a href="<?php bp_groups_directory_permalink(); ?>" id="newest-groups"<?php if ( $instance['group_default'] == 'newest' ) : ?> class="selected"<?php endif; ?>><?php _e("Newest", 'buddypress') ?></a>
+				<span class="bp-separator" role="separator"><?php echo esc_html( $separator ); ?></span>
+				<a href="<?php bp_groups_directory_permalink(); ?>" id="recently-active-groups"<?php if ( $instance['group_default'] == 'active' ) : ?> class="selected"<?php endif; ?>><?php _e("Active", 'buddypress') ?></a>
+				<span class="bp-separator" role="separator"><?php echo esc_html( $separator ); ?></span>
+				<a href="<?php bp_groups_directory_permalink(); ?>" id="popular-groups" <?php if ( $instance['group_default'] == 'popular' ) : ?> class="selected"<?php endif; ?>><?php _e("Popular", 'buddypress') ?></a>
+			</div>
+
+			<ul id="groups-list" class="item-list">
+				<?php while ( bp_groups() ) : bp_the_group(); ?>
+					<li <?php bp_group_class(); ?>>
+						<div class="item-avatar">
+							<a href="<?php bp_group_permalink() ?>" title="<?php bp_group_name() ?>"><?php bp_group_avatar_thumb() ?></a>
+						</div>
+
+						<div class="item">
+							<div class="item-title"><a href="<?php bp_group_permalink() ?>" title="<?php bp_group_name() ?>"><?php bp_group_name() ?></a></div>
+							<div class="item-meta">
+								<span class="activity">
+								<?php
+									if ( 'newest' == $instance['group_default'] )
+										printf( __( 'created %s', 'buddypress' ), bp_get_group_date_created() );
+									if ( 'active' == $instance['group_default'] )
+										printf( __( 'active %s', 'buddypress' ), bp_get_group_last_active() );
+									elseif ( 'popular' == $instance['group_default'] )
+										bp_group_member_count();
+								?>
+								</span>
+							</div>
+						</div>
+					</li>
+
+				<?php endwhile; ?>
+			</ul>
+			<?php wp_nonce_field( 'groups_widget_groups_list', '_wpnonce-groups' ); ?>
+			<input type="hidden" name="groups_widget_max" id="groups_widget_max" value="<?php echo esc_attr( $instance['max_groups'] ); ?>" />
+
+		<?php else: ?>
+
+			<div class="widget-error">
+				<?php _e('There are no groups to display.', 'buddypress') ?>
+			</div>
+
+		<?php endif; ?>
+
+		<?php echo $after_widget; ?>
+	<?php
+	}
+
+	function update( $new_instance, $old_instance ) {
+		$instance = $old_instance;
+
+		$instance['title']         = strip_tags( $new_instance['title'] );
+		$instance['max_groups']    = strip_tags( $new_instance['max_groups'] );
+		$instance['group_default'] = strip_tags( $new_instance['group_default'] );
+		$instance['link_title']    = (bool)$new_instance['link_title'];
+
+		return $instance;
+	}
+
+	function form( $instance ) {
+		$defaults = array(
+			'title'         => __( 'Groups', 'buddypress' ),
+			'max_groups'    => 5,
+			'group_default' => 'active',
+			'link_title'    => false
+		);
+		$instance = wp_parse_args( (array) $instance, $defaults );
+
+		$title 	       = strip_tags( $instance['title'] );
+		$max_groups    = strip_tags( $instance['max_groups'] );
+		$group_default = strip_tags( $instance['group_default'] );
+		$link_title    = (bool)$instance['link_title'];
+		?>
+
+		<p><label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e('Title:', 'buddypress'); ?> <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( $title ); ?>" style="width: 100%" /></label></p>
+
+		<p><label for="<?php echo $this->get_field_id('link_title') ?>"><input type="checkbox" name="<?php echo $this->get_field_name('link_title') ?>" id="<?php echo $this->get_field_id('link_title') ?>" value="1" <?php checked( $link_title ) ?> /> <?php _e( 'Link widget title to Groups directory', 'buddypress' ) ?></label></p>
+
+		<p><label for="<?php echo $this->get_field_id( 'max_groups' ); ?>"><?php _e('Max groups to show:', 'buddypress'); ?> <input class="widefat" id="<?php echo $this->get_field_id( 'max_groups' ); ?>" name="<?php echo $this->get_field_name( 'max_groups' ); ?>" type="text" value="<?php echo esc_attr( $max_groups ); ?>" style="width: 30%" /></label></p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'group_default' ); ?>"><?php _e('Default groups to show:', 'buddypress'); ?></label>
+			<select name="<?php echo $this->get_field_name( 'group_default' ); ?>" id="<?php echo $this->get_field_id( 'group_default' ); ?>">
+				<option value="newest" <?php if ( $group_default == 'newest' ) : ?>selected="selected"<?php endif; ?>><?php _e( 'Newest', 'buddypress' ) ?></option>
+				<option value="active" <?php if ( $group_default == 'active' ) : ?>selected="selected"<?php endif; ?>><?php _e( 'Active', 'buddypress' ) ?></option>
+				<option value="popular"  <?php if ( $group_default == 'popular' ) : ?>selected="selected"<?php endif; ?>><?php _e( 'Popular', 'buddypress' ) ?></option>
+			</select>
+		</p>
+	<?php
+	}
+}
+
+function groups_ajax_widget_groups_list() {
+
+	check_ajax_referer('groups_widget_groups_list');
+
+	switch ( $_POST['filter'] ) {
+		case 'newest-groups':
+			$type = 'newest';
+		break;
+		case 'recently-active-groups':
+			$type = 'active';
+		break;
+		case 'popular-groups':
+			$type = 'popular';
+		break;
+	}
+
+	$per_page = isset( $_POST['max_groups'] ) ? intval( $_POST['max_groups'] ) : 5;
+
+	$groups_args = array(
+		'user_id'  => 0,
+		'type'     => $type,
+		'per_page' => $per_page,
+		'max'      => $per_page,
+	);
+
+	if ( bp_has_groups( $groups_args ) ) : ?>
+		<?php echo "0[[SPLIT]]"; ?>
+		<?php while ( bp_groups() ) : bp_the_group(); ?>
+			<li <?php bp_group_class(); ?>>
+				<div class="item-avatar">
+					<a href="<?php bp_group_permalink() ?>"><?php bp_group_avatar_thumb() ?></a>
+				</div>
+
+				<div class="item">
+					<div class="item-title"><a href="<?php bp_group_permalink() ?>" title="<?php bp_group_name() ?>"><?php bp_group_name() ?></a></div>
+					<div class="item-meta">
+						<span class="activity">
+							<?php
+							if ( 'newest-groups' == $_POST['filter'] ) {
+								printf( __( 'created %s', 'buddypress' ), bp_get_group_date_created() );
+							} elseif ( 'recently-active-groups' == $_POST['filter'] ) {
+								printf( __( 'active %s', 'buddypress' ), bp_get_group_last_active() );
+							} elseif ( 'popular-groups' == $_POST['filter'] ) {
+								bp_group_member_count();
+							}
+							?>
+						</span>
+					</div>
+				</div>
+			</li>
+		<?php endwhile; ?>
+
+		<?php wp_nonce_field( 'groups_widget_groups_list', '_wpnonce-groups' ); ?>
+		<input type="hidden" name="groups_widget_max" id="groups_widget_max" value="<?php echo esc_attr( $_POST['max_groups'] ); ?>" />
+
+	<?php else: ?>
+
+		<?php echo "-1[[SPLIT]]<li>" . __("No groups matched the current filter.", 'buddypress'); ?>
+
+	<?php endif;
+
+}
+add_action( 'wp_ajax_widget_groups_list',        'groups_ajax_widget_groups_list' );
+add_action( 'wp_ajax_nopriv_widget_groups_list', 'groups_ajax_widget_groups_list' );
Index: /tags/2.4.3/src/bp-groups/classes/class-bp-group-extension.php
===================================================================
--- /tags/2.4.3/src/bp-groups/classes/class-bp-group-extension.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/classes/class-bp-group-extension.php	(revision 10410)
@@ -0,0 +1,1696 @@
+<?php
+/**
+ * BuddyPress Groups Classes.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * API for creating group extensions without having to hardcode the content into
+ * the theme.
+ *
+ * To implement, extend this class. In your constructor, pass an optional array
+ * of arguments to parent::init() to configure your widget. The config array
+ * supports the following values:
+ *   - 'slug' A unique identifier for your extension. This value will be used
+ *     to build URLs, so make it URL-safe.
+ *   - 'name' A translatable name for your extension. This value is used to
+ *     populate the navigation tab, as well as the default titles for admin/
+ *     edit/create tabs.
+ *   - 'visibility' Set to 'public' (default) for your extension (the main tab
+ *     as well as the widget) to be available to anyone who can access the
+ *     group, 'private' otherwise.
+ *   - 'nav_item_position' An integer explaining where the nav item should
+ *     appear in the tab list.
+ *   - 'enable_nav_item' Set to true for your extension's main tab to be
+ *     available to anyone who can access the group.
+ *   - 'nav_item_name' The translatable text you want to appear in the nav tab.
+ *     Defaults to the value of 'name'.
+ *   - 'display_hook' The WordPress action that the widget_display() method is
+ *     hooked to.
+ *   - 'template_file' The template file that will be used to load the content
+ *     of your main extension tab. Defaults to 'groups/single/plugins.php'.
+ *   - 'screens' A multi-dimensional array, described below.
+ * 	 - 'access' Which users can visit the plugin's tab.
+ * 	 - 'show_tab' Which users can see the plugin's navigation tab.
+ *
+ * BP_Group_Extension uses the concept of "settings screens". There are three
+ * contexts for settings screens:
+ *   - 'create', which inserts a new step into the group creation process
+ *   - 'edit', which adds a tab for your extension into the Admin section of
+ *     a group
+ *   - 'admin', which adds a metabox to the Groups administration panel in the
+ *     WordPress Dashboard
+ * Each of these settings screens is populated by a pair of methods: one that
+ * creates the markup for the screen, and one that processes form data
+ * submitted from the screen. If your plugin needs screens in all three
+ * contexts, and if the markup and form processing logic will be the same in
+ * each case, you can define two methods to handle all of the screens:
+ *   function settings_screen() {}
+ *   function settings_screen_save() {}
+ * If one or more of your settings screen needs separate logic, you may define
+ * context-specific methods, for example:
+ *   function edit_screen() {}
+ *   function edit_screen_save() {}
+ * BP_Group_Extension will use the more specific methods if they are available.
+ *
+ * You can further customize the settings screens (tab names, etc) by passing
+ * an optional 'screens' parameter to the init array. The format is as follows:
+ *   'screens' => array(
+ *       'create' => array(
+ *	     'slug' => 'foo',
+ *	     'name' => 'Foo',
+ *	     'position' => 55,
+ *	     'screen_callback' => 'my_create_screen_callback',
+ *	     'screen_save_callback' => 'my_create_screen_save_callback',
+ *	 ),
+ *	 'edit' => array( // ...
+ *   ),
+ * Only provide those arguments that you actually want to change from the
+ * default configuration. BP_Group_Extension will do the rest.
+ *
+ * Note that the 'edit' screen accepts an additional parameter: 'submit_text',
+ * which defines the text of the Submit button automatically added to the Edit
+ * screen of the extension (defaults to 'Save Changes'). Also, the 'admin'
+ * screen accepts two additional parameters: 'metabox_priority' and
+ * 'metabox_context'. See the docs for add_meta_box() for more details on these
+ * arguments.
+ *
+ * Prior to BuddyPress 1.7, group extension configurations were set slightly
+ * differently. The legacy method is still supported, though deprecated.
+ *
+ * @package BuddyPress
+ * @subpackage Groups
+ * @since 1.1.0
+ */
+class BP_Group_Extension {
+
+	/** Public ************************************************************/
+
+	/**
+	 * Information about this extension's screens.
+	 *
+	 * @since 1.8.0
+	 * @var array
+	 */
+	public $screens = array();
+
+	/**
+	 * The name of the extending class.
+	 *
+	 * @since 1.8.0
+	 * @var string
+	 */
+	public $class_name = '';
+
+	/**
+	 * A ReflectionClass object of the current extension.
+	 *
+	 * @since 1.8.0
+	 * @var ReflectionClass
+	 */
+	public $class_reflection = null;
+
+	/**
+	 * Parsed configuration parameters for the extension.
+	 *
+	 * @since 1.8.0
+	 * @var array
+	 */
+	public $params = array();
+
+	/**
+	 * Raw config params, as passed by the extending class.
+	 *
+	 * @since 2.1.0
+	 * @var array
+	 */
+	public $params_raw = array();
+
+	/**
+	 * The ID of the current group.
+	 *
+	 * @since 1.8.0
+	 * @var int
+	 */
+	public $group_id = 0;
+
+	/**
+	 * The slug of the current extension.
+	 *
+	 * @var string
+	 */
+	public $slug = '';
+
+	/**
+	 * The translatable name of the current extension.
+	 *
+	 * @var string
+	 */
+	public $name = '';
+
+	/**
+	 * The visibility of the extension tab. 'public' or 'private'.
+	 *
+	 * @var string
+	 */
+	public $visibility = 'public';
+
+	/**
+	 * The numeric position of the main nav item.
+	 *
+	 * @var int
+	 */
+	public $nav_item_position = 81;
+
+	/**
+	 * Whether to show the nav item.
+	 *
+	 * @var bool
+	 */
+	public $enable_nav_item = true;
+
+	/**
+	 * Whether the current user should see the navigation item.
+	 *
+	 * @since 2.1.0
+	 * @var bool
+	 */
+	public $user_can_see_nav_item;
+
+	/**
+	 * Whether the current user can visit the tab.
+	 *
+	 * @since 2.1.0
+	 * @var bool
+	 */
+	public $user_can_visit;
+
+	/**
+	 * The text of the nav item. Defaults to self::name.
+	 *
+	 * @var string
+	 */
+	public $nav_item_name = '';
+
+	/**
+	 * The WP action that self::widget_display() is attached to.
+	 *
+	 * Default: 'groups_custom_group_boxes'.
+	 *
+	 * @var string
+	 */
+	public $display_hook = 'groups_custom_group_boxes';
+
+	/**
+	 * The template file used to load the plugin content.
+	 *
+	 * Default: 'groups/single/plugins'.
+	 *
+	 * @var string
+	 */
+	public $template_file = 'groups/single/plugins';
+
+	/** Protected *********************************************************/
+
+	/**
+	 * Has the extension been initialized?
+	 *
+	 * @since 1.8.0
+	 * @var bool
+	 */
+	protected $initialized = false;
+
+	/**
+	 * Extension properties as set by legacy extensions.
+	 *
+	 * @since 1.8.0
+	 * @var array
+	 */
+	protected $legacy_properties = array();
+
+	/**
+	 * Converted legacy parameters.
+	 *
+	 * These are the extension properties as set by legacy extensions, but
+	 * then converted to match the new format for params.
+	 *
+	 * @since 1.8.0
+	 * @var array
+	 */
+	protected $legacy_properties_converted = array();
+
+	/**
+	 * Redirect location as defined by post-edit save callback.
+	 *
+	 * @since 2.1.0
+	 * @var string
+	 */
+	protected $post_save_redirect;
+
+	/**
+	 * Miscellaneous data as set by the __set() magic method.
+	 *
+	 * @since 1.8.0
+	 * @var array
+	 */
+	protected $data = array();
+
+	/** Screen Overrides **************************************************/
+
+	/*
+	 * Screen override methods are how your extension will display content
+	 * and handle form submits. Your extension should only override those
+	 * methods that it needs for its purposes.
+	 */
+
+	/**
+	 * The content of the group tab.
+	 *
+	 * @param int|null $group_id
+	 */
+	public function display( $group_id = null ) {}
+
+	/**
+	 * Content displayed in a widget sidebar, if applicable
+	 */
+	public function widget_display() {}
+
+	// *_screen() displays the settings form for the given context
+	// *_screen_save() processes data submitted via the settings form
+	// The settings_* methods are generic fallbacks, which can optionally
+	// be overridden by the more specific edit_*, create_*, and admin_*
+	// versions.
+	public function settings_screen( $group_id = null ) {}
+	public function settings_screen_save( $group_id = null ) {}
+	public function edit_screen( $group_id = null ) {}
+	public function edit_screen_save( $group_id = null ) {}
+	public function create_screen( $group_id = null ) {}
+	public function create_screen_save( $group_id = null ) {}
+	public function admin_screen( $group_id = null ) {}
+	public function admin_screen_save( $group_id = null ) {}
+
+	/** Setup *************************************************************/
+
+	/**
+	 * Initialize the extension, using your config settings.
+	 *
+	 * Your plugin should call this method at the very end of its
+	 * constructor, like so:
+	 *
+	 *   public function __construct() {
+	 *       $args = array(
+	 *           'slug' => 'my-group-extension',
+	 *           'name' => 'My Group Extension',
+	 *           // ...
+	 *       );
+	 *
+	 *       parent::init( $args );
+	 *   }
+	 *
+	 * @since 1.8.0
+	 * @since 2.1.0 Added 'access' and 'show_tab' arguments to `$args`.
+	 *
+	 * @param array $args {
+	 *     Array of initialization arguments.
+	 *     @type string       $slug              Unique, URL-safe identifier for your extension.
+	 *     @type string       $name              Translatable name for your extension. Used to populate
+	 *                                           navigation items.
+	 *     @type string       $visibility        Optional. Set to 'public' for your extension (the main tab as well
+	 *                                           as the widget) to be available to anyone who can access the group;
+	 *                                           set to 'private' otherwise. Default: 'public'.
+	 *     @type int          $nav_item_position Optional. Location of the nav item in the tab list.
+	 *                                           Default: 81.
+	 *     @type bool         $enable_nav_item   Optional. Whether the extension's tab should be accessible to
+	 *                                           anyone who can view the group. Default: true.
+	 *     @type string       $nav_item_name     Optional. The translatable text you want to appear in the nav tab.
+	 *                                           Default: the value of `$name`.
+	 *     @type string       $display_hook      Optional. The WordPress action that the widget_display() method is
+	 *                                           hooked to. Default: 'groups_custom_group_boxes'.
+	 *     @type string       $template_file     Optional. Theme-relative path to the template file BP should use
+	 *                                           to load the content of your main extension tab.
+	 *                                           Default: 'groups/single/plugins.php'.
+	 *     @type array        $screens           A multi-dimensional array of configuration information for the
+	 *                                           extension screens. See docblock of {@link BP_Group_Extension}
+	 *                                           for more details.
+	 *     @type string|array $access            Which users can visit the plugin's tab. Possible values: 'anyone',
+	 *                                           'loggedin', 'member', 'mod', 'admin' or 'noone'. ('member', 'mod',
+	 *                                           'admin' refer to user's role in group.) Note that 'mod' targets
+	 *                                           only group moderators. If you want to allow access to group moderators
+	 *                                           and admins, specify `array( 'mod', 'admin' )`. Defaults to 'anyone'
+	 *                                           for public groups and 'member' for private groups.
+	 *     @type string|array $show_tab          Which users can see the plugin's navigation tab. Possible values:
+	 *                                           'anyone', 'loggedin', 'member', 'mod', 'admin' or 'noone'.
+	 *                                           ('member', 'mod', 'admin' refer to user's role in group.) Note
+	 *                                           that 'mod' targets only group moderators. If you want to show the
+	 *                                           tab to group moderators and admins, specify
+	 *                                           `array( 'mod', 'admin' )`. Defaults to 'anyone' for public groups
+	 *                                           and 'member' for private groups.
+	 * }
+	 */
+	public function init( $args = array() ) {
+		// Store the raw arguments
+		$this->params_raw = $args;
+
+		// Before this init() method was introduced, plugins were
+		// encouraged to set their config directly. For backward
+		// compatibility with these plugins, we detect whether this is
+		// one of those legacy plugins, and parse any legacy arguments
+		// with those passed to init()
+		$this->parse_legacy_properties();
+		$args = $this->parse_args_r( $args, $this->legacy_properties_converted );
+
+		// Parse with defaults
+		$this->params = $this->parse_args_r( $args, array(
+			'slug'              => $this->slug,
+			'name'              => $this->name,
+			'visibility'        => $this->visibility,
+			'nav_item_position' => $this->nav_item_position,
+			'enable_nav_item'   => (bool) $this->enable_nav_item,
+			'nav_item_name'     => $this->nav_item_name,
+			'display_hook'      => $this->display_hook,
+			'template_file'     => $this->template_file,
+			'screens'           => $this->get_default_screens(),
+			'access'            => null,
+			'show_tab'          => null,
+		) );
+
+		$this->initialized = true;
+	}
+
+	/**
+	 * The main setup routine for the extension.
+	 *
+	 * This method contains the primary logic for setting up an extension's
+	 * configuration, setting up backward compatibility for legacy plugins,
+	 * and hooking the extension's screen functions into WP and BP.
+	 *
+	 * Marked 'public' because it must be accessible to add_action().
+	 * However, you should never need to invoke this method yourself - it
+	 * is called automatically at the right point in the load order by
+	 * bp_register_group_extension().
+	 *
+	 * @since 1.1.0
+	 */
+	public function _register() {
+
+		// Detect and parse properties set by legacy extensions
+		$this->parse_legacy_properties();
+
+		// Initialize, if necessary. This should only happen for
+		// legacy extensions that don't call parent::init() themselves
+		if ( true !== $this->initialized ) {
+			$this->init();
+		}
+
+		// Set some config values, based on the parsed params
+		$this->group_id          = $this->get_group_id();
+		$this->slug              = $this->params['slug'];
+		$this->name              = $this->params['name'];
+		$this->visibility        = $this->params['visibility'];
+		$this->nav_item_position = $this->params['nav_item_position'];
+		$this->nav_item_name     = $this->params['nav_item_name'];
+		$this->display_hook      = $this->params['display_hook'];
+		$this->template_file     = $this->params['template_file'];
+
+		// Configure 'screens': create, admin, and edit contexts
+		$this->setup_screens();
+
+		// Configure access-related settings
+		$this->setup_access_settings();
+
+		// Mirror configuration data so it's accessible to plugins
+		// that look for it in its old locations
+		$this->setup_legacy_properties();
+
+		// Hook the extension into BuddyPress
+		$this->setup_display_hooks();
+		$this->setup_create_hooks();
+		$this->setup_edit_hooks();
+		$this->setup_admin_hooks();
+	}
+
+	/**
+	 * Set up some basic info about the Extension.
+	 *
+	 * Here we collect the name of the extending class, as well as a
+	 * ReflectionClass that is used in get_screen_callback() to determine
+	 * whether your extension overrides certain callback methods.
+	 *
+	 * @since 1.8.0
+	 */
+	protected function setup_class_info() {
+		if ( empty( $this->class_name ) ) {
+			$this->class_name = get_class( $this );
+		}
+
+		if ( is_null( $this->class_reflection ) ) {
+			$this->class_reflection = new ReflectionClass( $this->class_name );
+		}
+	}
+
+	/**
+	 * Get the current group ID.
+	 *
+	 * Check for:
+	 *   - current group
+	 *   - new group
+	 *   - group admin
+	 *
+	 * @since 1.8.0
+	 *
+	 * @return int
+	 */
+	public static function get_group_id() {
+
+		// Usually this will work
+		$group_id = bp_get_current_group_id();
+
+		// On the admin, get the group id out of the $_GET params
+		if ( empty( $group_id ) && is_admin() && ( isset( $_GET['page'] ) && ( 'bp-groups' === $_GET['page'] ) ) && ! empty( $_GET['gid'] ) ) {
+			$group_id = (int) $_GET['gid'];
+		}
+
+		// This fallback will only be hit when the create step is very
+		// early
+		if ( empty( $group_id ) && bp_get_new_group_id() ) {
+			$group_id = bp_get_new_group_id();
+		}
+
+		// On some setups, the group id has to be fetched out of the
+		// $_POST array
+		// @todo Figure out why this is happening during group creation
+		if ( empty( $group_id ) && isset( $_POST['group_id'] ) ) {
+			$group_id = (int) $_POST['group_id'];
+		}
+
+		return $group_id;
+	}
+
+	/**
+	 * Gather configuration data about your screens.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @return array
+	 */
+	protected function get_default_screens() {
+		$this->setup_class_info();
+
+		$screens = array(
+			'create' => array(
+				'position' => 81,
+			),
+			'edit'   => array(
+				'submit_text' => __( 'Save Changes', 'buddypress' ),
+			),
+			'admin'  => array(
+				'metabox_context'  => 'normal',
+				'metabox_priority' => 'core',
+			),
+		);
+
+		foreach ( $screens as $context => &$screen ) {
+			$screen['enabled']     = true;
+			$screen['name']        = $this->name;
+			$screen['slug']        = $this->slug;
+
+			$screen['screen_callback']      = $this->get_screen_callback( $context, 'screen'      );
+			$screen['screen_save_callback'] = $this->get_screen_callback( $context, 'screen_save' );
+		}
+
+		return $screens;
+	}
+
+	/**
+	 * Set up screens array based on params.
+	 *
+	 * @since 1.8.0
+	 */
+	protected function setup_screens() {
+		foreach ( (array) $this->params['screens'] as $context => $screen ) {
+			if ( empty( $screen['slug'] ) ) {
+				$screen['slug'] = $this->slug;
+			}
+
+			if ( empty( $screen['name'] ) ) {
+				$screen['name'] = $this->name;
+			}
+
+			$this->screens[ $context ] = $screen;
+		}
+	}
+
+	/**
+	 * Set up access-related settings for this extension.
+	 *
+	 * @since 2.1.0
+	 */
+	protected function setup_access_settings() {
+		// Bail if no group ID is available
+		if ( empty( $this->group_id ) ) {
+			return;
+		}
+
+		// Backward compatibility
+		if ( isset( $this->params['enable_nav_item'] ) ) {
+			$this->enable_nav_item = (bool) $this->params['enable_nav_item'];
+		}
+
+		// Tab Access
+		$this->user_can_visit = false;
+
+		// Backward compatibility for components that do not provide
+		// explicit 'access' parameter
+		if ( empty( $this->params['access'] ) ) {
+			if ( false === $this->enable_nav_item ) {
+				$this->params['access'] = 'noone';
+			} else {
+				$group = groups_get_group( array(
+					'group_id' => $this->group_id,
+				) );
+
+				if ( ! empty( $group->status ) && 'public' === $group->status ) {
+					// Tabs in public groups are accessible to anyone by default
+					$this->params['access'] = 'anyone';
+				} else {
+					// All other groups have members-only as the default
+					$this->params['access'] = 'member';
+				}
+			}
+		}
+
+		// Parse multiple access conditions into an array
+		$access_conditions = $this->params['access'];
+		if ( ! is_array( $access_conditions ) ) {
+			$access_conditions = explode( ',', $access_conditions );
+		}
+
+		// If the current user meets at least one condition, the
+		// get access
+		foreach ( $access_conditions as $access_condition ) {
+			if ( $this->user_meets_access_condition( $access_condition ) ) {
+				$this->user_can_visit = true;
+				break;
+			}
+		}
+
+		// Tab Visibility
+		$this->user_can_see_nav_item = false;
+
+		// Backward compatibility for components that do not provide
+		// explicit 'show_tab' parameter
+		if ( empty( $this->params['show_tab'] ) ) {
+			if ( false === $this->params['enable_nav_item'] ) {
+				// enable_nav_item is only false if it's been
+				// defined explicitly as such in the
+				// constructor. So we always trust this value
+				$this->params['show_tab'] = 'noone';
+
+			} elseif ( isset( $this->params_raw['enable_nav_item'] ) || isset( $this->params_raw['visibility'] ) ) {
+				// If enable_nav_item or visibility is passed,
+				// we assume this  is a legacy extension.
+				// Legacy behavior is that enable_nav_item=true +
+				// visibility=private implies members-only
+				if ( 'public' !== $this->visibility ) {
+					$this->params['show_tab'] = 'member';
+				} else {
+					$this->params['show_tab'] = 'anyone';
+				}
+
+			} else {
+				// No show_tab or enable_nav_item value is
+				// available, so match the value of 'access'
+				$this->params['show_tab'] = $this->params['access'];
+			}
+		}
+
+		// Parse multiple access conditions into an array
+		$access_conditions = $this->params['show_tab'];
+		if ( ! is_array( $access_conditions ) ) {
+			$access_conditions = explode( ',', $access_conditions );
+		}
+
+		// If the current user meets at least one condition, the
+		// get access
+		foreach ( $access_conditions as $access_condition ) {
+			if ( $this->user_meets_access_condition( $access_condition ) ) {
+				$this->user_can_see_nav_item = true;
+				break;
+			}
+		}
+	}
+
+	/**
+	 * Check whether the current user meets an access condition.
+	 *
+	 * @param string $access_condition 'anyone', 'loggedin', 'member',
+	 *                                 'mod', 'admin' or 'noone'.
+	 * @return bool
+	 */
+	protected function user_meets_access_condition( $access_condition ) {
+		$group = groups_get_group( array(
+			'group_id' => $this->group_id,
+		) );
+
+		switch ( $access_condition ) {
+			case 'admin' :
+				$meets_condition = groups_is_user_admin( bp_loggedin_user_id(), $this->group_id );
+				break;
+
+			case 'mod' :
+				$meets_condition = groups_is_user_mod( bp_loggedin_user_id(), $this->group_id );
+				break;
+
+			case 'member' :
+				$meets_condition = groups_is_user_member( bp_loggedin_user_id(), $this->group_id );
+				break;
+
+			case 'loggedin' :
+				$meets_condition = is_user_logged_in();
+				break;
+
+			case 'noone' :
+				$meets_condition = false;
+				break;
+
+			case 'anyone' :
+			default :
+				$meets_condition = true;
+				break;
+		}
+
+		return $meets_condition;
+	}
+
+	/** Display ***********************************************************/
+
+	/**
+	 * Hook this extension's group tab into BuddyPress, if necessary.
+	 *
+	 * @since 1.8.0
+	 */
+	protected function setup_display_hooks() {
+
+		// Bail if not a group
+		if ( ! bp_is_group() ) {
+			return;
+		}
+
+		// Backward compatibility only
+		if ( ( 'public' !== $this->visibility ) && ! buddypress()->groups->current_group->user_has_access ) {
+			return;
+		}
+
+		// If the user can see the nav item, we create it.
+		$user_can_see_nav_item = $this->user_can_see_nav_item();
+
+		if ( $user_can_see_nav_item ) {
+			$group_permalink = bp_get_group_permalink( groups_get_current_group() );
+
+			bp_core_create_subnav_link( array(
+				'name'            => ! $this->nav_item_name ? $this->name : $this->nav_item_name,
+				'slug'            => $this->slug,
+				'parent_slug'     => bp_get_current_group_slug(),
+				'parent_url'      => $group_permalink,
+				'position'        => $this->nav_item_position,
+				'item_css_id'     => 'nav-' . $this->slug,
+				'screen_function' => array( &$this, '_display_hook' ),
+				'user_has_access' => $user_can_see_nav_item,
+				'no_access_url'   => $group_permalink,
+			) );
+		}
+
+		// If the user can visit the screen, we register it.
+		$user_can_visit = $this->user_can_visit();
+
+		if ( $user_can_visit ) {
+			$group_permalink = bp_get_group_permalink( groups_get_current_group() );
+
+			bp_core_register_subnav_screen_function( array(
+				'slug'            => $this->slug,
+				'parent_slug'     => bp_get_current_group_slug(),
+				'screen_function' => array( &$this, '_display_hook' ),
+				'user_has_access' => $user_can_visit,
+				'no_access_url'   => $group_permalink,
+			) );
+
+			// When we are viewing the extension display page, set the title and options title
+			if ( bp_is_current_action( $this->slug ) ) {
+				add_filter( 'bp_group_user_has_access',   array( $this, 'group_access_protection' ), 10, 2 );
+				add_action( 'bp_template_content_header', create_function( '', 'echo "' . esc_attr( $this->name ) . '";' ) );
+				add_action( 'bp_template_title',          create_function( '', 'echo "' . esc_attr( $this->name ) . '";' ) );
+			}
+		}
+
+		// Hook the group home widget
+		if ( ! bp_current_action() && bp_is_current_action( 'home' ) ) {
+			add_action( $this->display_hook, array( &$this, 'widget_display' ) );
+		}
+	}
+
+	/**
+	 * Hook the main display method, and loads the template file.
+	 */
+	public function _display_hook() {
+		add_action( 'bp_template_content', array( &$this, 'call_display' ) );
+
+		/**
+		 * Filters the template to load for the main display method.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $template_file Path to the template to load.
+		 */
+		bp_core_load_template( apply_filters( 'bp_core_template_plugin', $this->template_file ) );
+	}
+
+	/**
+	 * Call the display() method.
+	 *
+	 * We use this wrapper so that we can pass the group_id to the
+	 * display() callback.
+	 *
+	 * @since 2.1.1
+	 */
+	public function call_display() {
+		$this->display( $this->group_id );
+	}
+
+	/**
+	 * Determine whether the current user should see this nav tab.
+	 *
+	 * Note that this controls only the display of the navigation item.
+	 * Access to the tab is controlled by the user_can_visit() check.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $user_can_see_nav_item
+	 *
+	 * @return bool
+	 */
+	public function user_can_see_nav_item( $user_can_see_nav_item = false ) {
+		if ( 'noone' !== $this->params['show_tab'] && current_user_can( 'bp_moderate' ) ) {
+			return true;
+		}
+
+		return $this->user_can_see_nav_item;
+	}
+
+	/**
+	 * Determine whether the current user has access to visit this tab.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $user_can_visit
+	 *
+	 * @return bool
+	 */
+	public function user_can_visit( $user_can_visit = false ) {
+		if ( 'noone' !== $this->params['access'] && current_user_can( 'bp_moderate' ) ) {
+			return true;
+		}
+
+		return $this->user_can_visit;
+	}
+
+	/**
+	 * Filter the access check in bp_groups_group_access_protection() for this extension.
+	 *
+	 * Note that $no_access_args is passed by reference, as there are some
+	 * circumstances where the bp_core_no_access() arguments need to be
+	 * modified before the redirect takes place.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool  $user_can_visit
+	 * @param array $no_access_args
+	 *
+	 * @return bool
+	 */
+	public function group_access_protection( $user_can_visit, &$no_access_args ) {
+		$user_can_visit = $this->user_can_visit();
+
+		if ( ! $user_can_visit && is_user_logged_in() ) {
+			$current_group = groups_get_group( array(
+				'group_id' => $this->group_id,
+			) );
+
+			$no_access_args['message'] = __( 'You do not have access to this content.', 'buddypress' );
+			$no_access_args['root'] = bp_get_group_permalink( $current_group ) . 'home/';
+			$no_access_args['redirect'] = false;
+		}
+
+		return $user_can_visit;
+	}
+
+
+	/** Create ************************************************************/
+
+	/**
+	 * Hook this extension's Create step into BuddyPress, if necessary.
+	 *
+	 * @since 1.8.0
+	 */
+	protected function setup_create_hooks() {
+		if ( ! $this->is_screen_enabled( 'create' ) ) {
+			return;
+		}
+
+		$screen = $this->screens['create'];
+
+		// Insert the group creation step for the new group extension
+		buddypress()->groups->group_creation_steps[ $screen['slug'] ] = array(
+			'name'     => $screen['name'],
+			'slug'     => $screen['slug'],
+			'position' => $screen['position'],
+		);
+
+		// The maybe_ methods check to see whether the create_*
+		// callbacks should be invoked (ie, are we on the
+		// correct group creation step). Hooked in separate
+		// methods because current creation step info not yet
+		// available at this point
+		add_action( 'groups_custom_create_steps', array( $this, 'maybe_create_screen' ) );
+		add_action( 'groups_create_group_step_save_' . $screen['slug'], array( $this, 'maybe_create_screen_save' ) );
+	}
+
+	/**
+	 * Call the create_screen() method, if we're on the right page.
+	 *
+	 * @since 1.8.0
+	 */
+	public function maybe_create_screen() {
+		if ( ! bp_is_group_creation_step( $this->screens['create']['slug'] ) ) {
+			return;
+		}
+
+		call_user_func( $this->screens['create']['screen_callback'], $this->group_id );
+		$this->nonce_field( 'create' );
+
+		// The create screen requires an additional nonce field
+		// due to a quirk in the way the templates are built
+		wp_nonce_field( 'groups_create_save_' . bp_get_groups_current_create_step(), '_wpnonce', false );
+	}
+
+	/**
+	 * Call the create_screen_save() method, if we're on the right page.
+	 *
+	 * @since 1.8.0
+	 */
+	public function maybe_create_screen_save() {
+		if ( ! bp_is_group_creation_step( $this->screens['create']['slug'] ) ) {
+			return;
+		}
+
+		$this->check_nonce( 'create' );
+		call_user_func( $this->screens['create']['screen_save_callback'], $this->group_id );
+	}
+
+	/** Edit **************************************************************/
+
+	/**
+	 * Hook this extension's Edit panel into BuddyPress, if necessary.
+	 *
+	 * @since 1.8.0
+	 */
+	protected function setup_edit_hooks() {
+		// Bail if not in a group
+		if ( ! bp_is_group() ) {
+			return;
+		}
+
+		// Bail if not an edit screen
+		if ( ! $this->is_screen_enabled( 'edit' ) || ! bp_is_item_admin() ) {
+			return;
+		}
+
+		$screen = $this->screens['edit'];
+
+		$position = isset( $screen['position'] ) ? (int) $screen['position'] : 10;
+		$position += 40;
+
+		$current_group = groups_get_current_group();
+		$admin_link = trailingslashit( bp_get_group_permalink( $current_group ) . 'admin' );
+
+		$subnav_args = array(
+			'name'            => $screen['name'],
+			'slug'            => $screen['slug'],
+			'parent_slug'     => $current_group->slug . '_manage',
+			'parent_url'      => trailingslashit( bp_get_group_permalink( $current_group ) . 'admin' ),
+			'user_has_access' => bp_is_item_admin(),
+			'position'        => $position,
+			'screen_function' => 'groups_screen_group_admin',
+		);
+
+		// Should we add a menu to the Group's WP Admin Bar
+		if ( ! empty( $screen['show_in_admin_bar'] ) ) {
+			$subnav_args['show_in_admin_bar'] = true;
+		}
+
+		// Add the tab to the manage navigation
+		bp_core_new_subnav_item( $subnav_args );
+
+		// Catch the edit screen and forward it to the plugin template
+		if ( bp_is_groups_component() && bp_is_current_action( 'admin' ) && bp_is_action_variable( $screen['slug'], 0 ) ) {
+			$this->call_edit_screen_save( $this->group_id );
+
+			add_action( 'groups_custom_edit_steps', array( &$this, 'call_edit_screen' ) );
+
+			// Determine the proper template and save for later
+			// loading
+			if ( '' !== bp_locate_template( array( 'groups/single/home.php' ), false ) ) {
+				$this->edit_screen_template = '/groups/single/home';
+			} else {
+				add_action( 'bp_template_content_header', create_function( '', 'echo "<ul class=\"content-header-nav\">"; bp_group_admin_tabs(); echo "</ul>";' ) );
+				add_action( 'bp_template_content', array( &$this, 'call_edit_screen' ) );
+				$this->edit_screen_template = '/groups/single/plugins';
+			}
+
+			// We load the template at bp_screens, to give all
+			// extensions a chance to load
+			add_action( 'bp_screens', array( $this, 'call_edit_screen_template_loader' ) );
+		}
+	}
+
+	/**
+	 * Call the edit_screen() method.
+	 *
+	 * Previous versions of BP_Group_Extension required plugins to provide
+	 * their own Submit button and nonce fields when building markup. In
+	 * BP 1.8, this requirement was lifted - BP_Group_Extension now handles
+	 * all required submit buttons and nonces.
+	 *
+	 * We put the edit screen markup into an output buffer before echoing.
+	 * This is so that we can check for the presence of a hardcoded submit
+	 * button, as would be present in legacy plugins; if one is found, we
+	 * do not auto-add our own button.
+	 *
+	 * @since 1.8.0
+	 */
+	public function call_edit_screen() {
+		ob_start();
+		call_user_func( $this->screens['edit']['screen_callback'], $this->group_id );
+		$screen = ob_get_contents();
+		ob_end_clean();
+
+		echo $this->maybe_add_submit_button( $screen );
+
+		$this->nonce_field( 'edit' );
+	}
+
+	/**
+	 * Check the nonce, and call the edit_screen_save() method.
+	 *
+	 * @since 1.8.0
+	 */
+	public function call_edit_screen_save() {
+		if ( empty( $_POST ) ) {
+			return;
+		}
+
+		// When DOING_AJAX, the POST global will be populated, but we
+		// should assume it's a save
+		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
+			return;
+		}
+
+		$this->check_nonce( 'edit' );
+
+		// Detect whether the screen_save_callback is performing a
+		// redirect, so that we don't do one of our own
+		add_filter( 'wp_redirect', array( $this, 'detect_post_save_redirect' ) );
+
+		// Call the extension's save routine
+		call_user_func( $this->screens['edit']['screen_save_callback'], $this->group_id );
+
+		// Clean up detection filters
+		remove_filter( 'wp_redirect', array( $this, 'detect_post_save_redirect' ) );
+
+		// Perform a redirect only if one has not already taken place
+		if ( empty( $this->post_save_redirect ) ) {
+
+			/**
+			 * Filters the URL to redirect to after group edit screen save.
+			 *
+			 * Only runs if a redirect has not already occurred.
+			 *
+			 * @since 2.1.0
+			 *
+			 * @param string $value URL to redirect to.
+			 */
+			$redirect_to = apply_filters( 'bp_group_extension_edit_screen_save_redirect', bp_get_requested_url( ) );
+
+			bp_core_redirect( $redirect_to );
+			die();
+		}
+	}
+
+	/**
+	 * Load the template that houses the Edit screen.
+	 *
+	 * Separated out into a callback so that it can run after all other
+	 * Group Extensions have had a chance to register their navigation, to
+	 * avoid missing tabs.
+	 *
+	 * Hooked to 'bp_screens'.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @see BP_Group_Extension::setup_edit_hooks()
+	 */
+	public function call_edit_screen_template_loader() {
+		bp_core_load_template( $this->edit_screen_template );
+	}
+
+	/**
+	 * Add a submit button to the edit form, if it needs one.
+	 *
+	 * There's an inconsistency in the way that the group Edit and Create
+	 * screens are rendered: the Create screen has a submit button built
+	 * in, but the Edit screen does not. This function allows plugin
+	 * authors to write markup that does not contain the submit button for
+	 * use on both the Create and Edit screens - BP will provide the button
+	 * if one is not found.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $screen The screen markup, captured in the output
+	 *                       buffer.
+	 *
+	 * @return string $screen The same markup, with a submit button added.
+	 */
+	protected function maybe_add_submit_button( $screen = '' ) {
+		if ( $this->has_submit_button( $screen ) ) {
+			return $screen;
+		}
+
+		return $screen . sprintf(
+			'<div id="%s"><input type="submit" name="save" value="%s" id="%s"></div>',
+			'bp-group-edit-' . $this->slug . '-submit-wrapper',
+			$this->screens['edit']['submit_text'],
+			'bp-group-edit-' . $this->slug . '-submit'
+		);
+	}
+
+	/**
+	 * Does the given markup have a submit button?
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $screen The markup to check.
+	 *
+	 * @return bool True if a Submit button is found, otherwise false.
+	 */
+	public static function has_submit_button( $screen = '' ) {
+		$pattern = "/<input[^>]+type=[\'\"]submit[\'\"]/";
+		preg_match( $pattern, $screen, $matches );
+		return ! empty( $matches[0] );
+	}
+
+	/**
+	 * Detect redirects hardcoded into edit_screen_save() callbacks.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param string $redirect
+	 *
+	 * @return string
+	 */
+	public function detect_post_save_redirect( $redirect = '' ) {
+		if ( ! empty( $redirect ) ) {
+			$this->post_save_redirect = $redirect;
+		}
+
+		return $redirect;
+	}
+
+	/** Admin *************************************************************/
+
+	/**
+	 * Hook this extension's Admin metabox into BuddyPress, if necessary.
+	 *
+	 * @since 1.8.0
+	 */
+	protected function setup_admin_hooks() {
+		if ( ! $this->is_screen_enabled( 'admin' ) || ! is_admin() ) {
+			return;
+		}
+
+		// Hook the admin screen markup function to the content hook
+		add_action( 'bp_groups_admin_meta_box_content_' . $this->slug, array( $this, 'call_admin_screen' ) );
+
+		// Initialize the metabox
+		add_action( 'bp_groups_admin_meta_boxes', array( $this, '_meta_box_display_callback' ) );
+
+		// Catch the metabox save
+		add_action( 'bp_group_admin_edit_after', array( $this, 'call_admin_screen_save' ), 10 );
+	}
+
+	/**
+	 * Call the admin_screen() method, and add a nonce field.
+	 *
+	 * @since 1.8.0
+	 */
+	public function call_admin_screen() {
+		call_user_func( $this->screens['admin']['screen_callback'], $this->group_id );
+		$this->nonce_field( 'admin' );
+	}
+
+	/**
+	 * Check the nonce, and call the admin_screen_save() method.
+	 *
+	 * @since 1.8.0
+	 */
+	public function call_admin_screen_save() {
+		$this->check_nonce( 'admin' );
+		call_user_func( $this->screens['admin']['screen_save_callback'], $this->group_id );
+	}
+
+	/**
+	 * Create the Dashboard meta box for this extension.
+	 *
+	 * @since 1.7.0
+	 */
+	public function _meta_box_display_callback() {
+		$group_id = isset( $_GET['gid'] ) ? (int) $_GET['gid'] : 0;
+		$screen   = $this->screens['admin'];
+
+		add_meta_box(
+			$screen['slug'],
+			$screen['name'],
+			create_function( '', 'do_action( "bp_groups_admin_meta_box_content_' . $this->slug . '", ' . $group_id . ' );' ),
+			get_current_screen()->id,
+			$screen['metabox_context'],
+			$screen['metabox_priority']
+		);
+	}
+
+
+	/** Utilities *********************************************************/
+
+	/**
+	 * Generate the nonce fields for a settings form.
+	 *
+	 * The nonce field name (the second param passed to wp_nonce_field)
+	 * contains this extension's slug and is thus unique to this extension.
+	 * This is necessary because in some cases (namely, the Dashboard),
+	 * more than one extension may generate nonces on the same page, and we
+	 * must avoid name clashes.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $context Screen context. 'create', 'edit', or 'admin'.
+	 */
+	public function nonce_field( $context = '' ) {
+		wp_nonce_field( 'bp_group_extension_' . $this->slug . '_' . $context, '_bp_group_' . $context . '_nonce_' . $this->slug );
+	}
+
+	/**
+	 * Check the nonce on a submitted settings form.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $context Screen context. 'create', 'edit', or 'admin'.
+	 */
+	public function check_nonce( $context = '' ) {
+		check_admin_referer( 'bp_group_extension_' . $this->slug . '_' . $context, '_bp_group_' . $context . '_nonce_' . $this->slug );
+	}
+
+	/**
+	 * Is the specified screen enabled?
+	 *
+	 * To be enabled, a screen must both have the 'enabled' key set to true
+	 * (legacy: $this->enable_create_step, etc), and its screen_callback
+	 * must also exist and be callable.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $context Screen context. 'create', 'edit', or 'admin'.
+	 *
+	 * @return bool True if the screen is enabled, otherwise false.
+	 */
+	public function is_screen_enabled( $context = '' ) {
+		$enabled = false;
+
+		if ( isset( $this->screens[ $context ] ) ) {
+			$enabled = $this->screens[ $context ]['enabled'] && is_callable( $this->screens[ $context ]['screen_callback'] );
+		}
+
+		return (bool) $enabled;
+	}
+
+	/**
+	 * Get the appropriate screen callback for the specified context/type.
+	 *
+	 * BP Group Extensions have three special "screen contexts": create,
+	 * admin, and edit. Each of these contexts has a corresponding
+	 * _screen() and _screen_save() method, which allow group extension
+	 * plugins to define different markup and logic for each context.
+	 *
+	 * BP also supports fallback settings_screen() and
+	 * settings_screen_save() methods, which can be used to define markup
+	 * and logic that is shared between context. For each context, you may
+	 * either provide context-specific methods, or you can let BP fall back
+	 * on the shared settings_* callbacks.
+	 *
+	 * For example, consider a BP_Group_Extension implementation that looks
+	 * like this:
+	 *
+	 *   // ...
+	 *   function create_screen( $group_id ) { ... }
+	 *   function create_screen_save( $group_id ) { ... }
+	 *   function settings_screen( $group_id ) { ... }
+	 *   function settings_screen_save( $group_id ) { ... }
+	 *   // ...
+	 *
+	 * BP_Group_Extension will use your create_* methods for the Create
+	 * steps, and will use your generic settings_* methods for the Edit
+	 * and Admin contexts. This schema allows plugin authors maximum
+	 * flexibility without having to repeat themselves.
+	 *
+	 * The get_screen_callback() method uses a ReflectionClass object to
+	 * determine whether your extension has provided a given callback.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $context Screen context. 'create', 'edit', or 'admin'.
+	 * @param string $type    Screen type. 'screen' or 'screen_save'. Default:
+	 *                        'screen'.
+	 *
+	 * @return callable A callable function handle.
+	 */
+	public function get_screen_callback( $context = '', $type = 'screen' ) {
+		$callback = '';
+
+		// Try the context-specific callback first
+		$method  = $context . '_' . $type;
+		$rmethod = $this->class_reflection->getMethod( $method );
+		if ( isset( $rmethod->class ) && $this->class_name === $rmethod->class ) {
+			$callback = array( $this, $method );
+		}
+
+		if ( empty( $callback ) ) {
+			$fallback_method  = 'settings_' . $type;
+			$rfallback_method = $this->class_reflection->getMethod( $fallback_method );
+			if ( isset( $rfallback_method->class ) && $this->class_name === $rfallback_method->class ) {
+				$callback = array( $this, $fallback_method );
+			}
+		}
+
+		return $callback;
+	}
+
+	/**
+	 * Recursive argument parsing.
+	 *
+	 * This acts like a multi-dimensional version of wp_parse_args() (minus
+	 * the querystring parsing - you must pass arrays).
+	 *
+	 * Values from $a override those from $b; keys in $b that don't exist
+	 * in $a are passed through.
+	 *
+	 * This is different from array_merge_recursive(), both because of the
+	 * order of preference ($a overrides $b) and because of the fact that
+	 * array_merge_recursive() combines arrays deep in the tree, rather
+	 * than overwriting the b array with the a array.
+	 *
+	 * The implementation of this function is specific to the needs of
+	 * BP_Group_Extension, where we know that arrays will always be
+	 * associative, and that an argument under a given key in one array
+	 * will be matched by a value of identical depth in the other one. The
+	 * function is NOT designed for general use, and will probably result
+	 * in unexpected results when used with data in the wild. See, eg,
+	 * https://core.trac.wordpress.org/ticket/19888
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param array $a First set of arguments.
+	 * @param array $b Second set of arguments.
+	 *
+	 * @return array Parsed arguments.
+	 */
+	public static function parse_args_r( &$a, $b ) {
+		$a = (array) $a;
+		$b = (array) $b;
+		$r = $b;
+
+		foreach ( $a as $k => &$v ) {
+			if ( is_array( $v ) && isset( $r[ $k ] ) ) {
+				$r[ $k ] = self::parse_args_r( $v, $r[ $k ] );
+			} else {
+				$r[ $k ] = $v;
+			}
+		}
+
+		return $r;
+	}
+
+	/** Legacy Support ********************************************************/
+
+	/*
+	 * In BuddyPress 1.8, the recommended technique for configuring
+	 * extensions changed from directly setting various object properties
+	 * in the class constructor, to passing a configuration array to
+	 * parent::init(). The following methods ensure that extensions created
+	 * in the old way continue to work, by converting legacy configuration
+	 * data to the new format.
+	 */
+
+	/**
+	 * Provide access to otherwise unavailable object properties.
+	 *
+	 * This magic method is here for backward compatibility with plugins
+	 * that refer to config properties that have moved to a different
+	 * location (such as enable_create_step, which is now at
+	 * $this->screens['create']['enabled']
+	 *
+	 * The legacy_properties array is set up in
+	 * self::setup_legacy_properties().
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $key Property name.
+	 *
+	 * @return mixed The value if found, otherwise null.
+	 */
+	public function __get( $key ) {
+		if ( isset( $this->legacy_properties[ $key ] ) ) {
+			return $this->legacy_properties[ $key ];
+		} elseif ( isset( $this->data[ $key ] ) ) {
+			return $this->data[ $key ];
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 * Provide a fallback for isset( $this->foo ) when foo is unavailable.
+	 *
+	 * This magic method is here for backward compatibility with plugins
+	 * that have set their class config options directly in the class
+	 * constructor. The parse_legacy_properties() method of the current
+	 * class needs to check whether any legacy keys have been put into the
+	 * $this->data array.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $key Property name.
+	 *
+	 * @return bool True if the value is set, otherwise false.
+	 */
+	public function __isset( $key ) {
+		if ( isset( $this->legacy_properties[ $key ] ) ) {
+			return true;
+		} elseif ( isset( $this->data[ $key ] ) ) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * Allow plugins to set otherwise unavailable object properties.
+	 *
+	 * This magic method is here for backward compatibility with plugins
+	 * that may attempt to modify the group extension by manually assigning
+	 * a value to an object property that no longer exists, such as
+	 * $this->enable_create_step.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $key Property name.
+	 * @param mixed $value Property value.
+	 */
+	public function __set( $key, $value ) {
+
+		if ( empty( $this->initialized ) ) {
+			$this->data[ $key ] = $value;
+		}
+
+		switch ( $key ) {
+			case 'enable_create_step' :
+				$this->screens['create']['enabled'] = $value;
+				break;
+
+			case 'enable_edit_item' :
+				$this->screens['edit']['enabled'] = $value;
+				break;
+
+			case 'enable_admin_item' :
+				$this->screens['admin']['enabled'] = $value;
+				break;
+
+			case 'create_step_position' :
+				$this->screens['create']['position'] = $value;
+				break;
+
+			// Note: 'admin' becomes 'edit' to distinguish from Dashboard 'admin'
+			case 'admin_name' :
+				$this->screens['edit']['name'] = $value;
+				break;
+
+			case 'admin_slug' :
+				$this->screens['edit']['slug'] = $value;
+				break;
+
+			case 'create_name' :
+				$this->screens['create']['name'] = $value;
+				break;
+
+			case 'create_slug' :
+				$this->screens['create']['slug'] = $value;
+				break;
+
+			case 'admin_metabox_context' :
+				$this->screens['admin']['metabox_context'] = $value;
+				break;
+
+			case 'admin_metabox_priority' :
+				$this->screens['admin']['metabox_priority'] = $value;
+				break;
+
+			default :
+				$this->data[ $key ] = $value;
+				break;
+		}
+	}
+
+	/**
+	 * Return a list of legacy properties.
+	 *
+	 * The legacy implementation of BP_Group_Extension used all of these
+	 * object properties for configuration. Some have been moved.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @return array List of legacy property keys.
+	 */
+	protected function get_legacy_property_list() {
+		return array(
+			'name',
+			'slug',
+			'admin_name',
+			'admin_slug',
+			'create_name',
+			'create_slug',
+			'visibility',
+			'create_step_position',
+			'nav_item_position',
+			'admin_metabox_context',
+			'admin_metabox_priority',
+			'enable_create_step',
+			'enable_nav_item',
+			'enable_edit_item',
+			'enable_admin_item',
+			'nav_item_name',
+			'display_hook',
+			'template_file',
+		);
+	}
+
+	/**
+	 * Parse legacy properties.
+	 *
+	 * The old standard for BP_Group_Extension was for plugins to register
+	 * their settings as properties in their constructor. The new method is
+	 * to pass a config array to the init() method. In order to support
+	 * legacy plugins, we slurp up legacy properties, and later on we'll
+	 * parse them into the new init() array.
+	 *
+	 * @since 1.8.0
+	 */
+	protected function parse_legacy_properties() {
+
+		// Only run this one time
+		if ( ! empty( $this->legacy_properties_converted ) ) {
+			return;
+		}
+
+		$properties = $this->get_legacy_property_list();
+
+		// By-reference variable for convenience
+		$lpc =& $this->legacy_properties_converted;
+
+		foreach ( $properties as $property ) {
+
+			// No legacy config exists for this key
+			if ( ! isset( $this->{$property} ) ) {
+				continue;
+			}
+
+			// Grab the value and record it as appropriate
+			$value = $this->{$property};
+
+			switch ( $property ) {
+				case 'enable_create_step' :
+					$lpc['screens']['create']['enabled'] = (bool) $value;
+					break;
+
+				case 'enable_edit_item' :
+					$lpc['screens']['edit']['enabled'] = (bool) $value;
+					break;
+
+				case 'enable_admin_item' :
+					$lpc['screens']['admin']['enabled'] = (bool) $value;
+					break;
+
+				case 'create_step_position' :
+					$lpc['screens']['create']['position'] = $value;
+					break;
+
+				// Note: 'admin' becomes 'edit' to distinguish from Dashboard 'admin'
+				case 'admin_name' :
+					$lpc['screens']['edit']['name'] = $value;
+					break;
+
+				case 'admin_slug' :
+					$lpc['screens']['edit']['slug'] = $value;
+					break;
+
+				case 'create_name' :
+					$lpc['screens']['create']['name'] = $value;
+					break;
+
+				case 'create_slug' :
+					$lpc['screens']['create']['slug'] = $value;
+					break;
+
+				case 'admin_metabox_context' :
+					$lpc['screens']['admin']['metabox_context'] = $value;
+					break;
+
+				case 'admin_metabox_priority' :
+					$lpc['screens']['admin']['metabox_priority'] = $value;
+					break;
+
+				default :
+					$lpc[ $property ] = $value;
+					break;
+			}
+		}
+	}
+
+	/**
+	 * Set up legacy properties.
+	 *
+	 * This method is responsible for ensuring that all legacy config
+	 * properties are stored in an array $this->legacy_properties, so that
+	 * they remain available to plugins that reference the variables at
+	 * their old locations.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @see BP_Group_Extension::__get()
+	 */
+	protected function setup_legacy_properties() {
+
+		// Only run this one time
+		if ( ! empty( $this->legacy_properties ) ) {
+			return;
+		}
+
+		$properties = $this->get_legacy_property_list();
+		$params     = $this->params;
+		$lp         =& $this->legacy_properties;
+
+		foreach ( $properties as $property ) {
+			switch ( $property ) {
+				case 'enable_create_step' :
+					$lp['enable_create_step'] = $params['screens']['create']['enabled'];
+					break;
+
+				case 'enable_edit_item' :
+					$lp['enable_edit_item'] = $params['screens']['edit']['enabled'];
+					break;
+
+				case 'enable_admin_item' :
+					$lp['enable_admin_item'] = $params['screens']['admin']['enabled'];
+					break;
+
+				case 'create_step_position' :
+					$lp['create_step_position'] = $params['screens']['create']['position'];
+					break;
+
+				// Note: 'admin' becomes 'edit' to distinguish from Dashboard 'admin'
+				case 'admin_name' :
+					$lp['admin_name'] = $params['screens']['edit']['name'];
+					break;
+
+				case 'admin_slug' :
+					$lp['admin_slug'] = $params['screens']['edit']['slug'];
+					break;
+
+				case 'create_name' :
+					$lp['create_name'] = $params['screens']['create']['name'];
+					break;
+
+				case 'create_slug' :
+					$lp['create_slug'] = $params['screens']['create']['slug'];
+					break;
+
+				case 'admin_metabox_context' :
+					$lp['admin_metabox_context'] = $params['screens']['admin']['metabox_context'];
+					break;
+
+				case 'admin_metabox_priority' :
+					$lp['admin_metabox_priority'] = $params['screens']['admin']['metabox_priority'];
+					break;
+
+				default :
+					// All other items get moved over
+					$lp[ $property ] = $params[ $property ];
+
+					// Also reapply to the object, for backpat
+					$this->{$property} = $params[ $property ];
+
+					break;
+			}
+		}
+	}
+}
+
+/**
+ * Register a new Group Extension.
+ *
+ * @param string Name of the Extension class.
+ * @return false|null Returns false on failure, otherwise null.
+ */
+function bp_register_group_extension( $group_extension_class = '' ) {
+
+	if ( ! class_exists( $group_extension_class ) ) {
+		return false;
+	}
+
+	// Register the group extension on the bp_init action so we have access
+	// to all plugins.
+	add_action( 'bp_init', create_function( '', '
+		$extension = new ' . $group_extension_class . ';
+		add_action( "bp_actions", array( &$extension, "_register" ), 8 );
+		add_action( "admin_init", array( &$extension, "_register" ) );
+	' ), 11 );
+}
Index: /tags/2.4.3/src/bp-groups/classes/class-bp-group-member-query.php
===================================================================
--- /tags/2.4.3/src/bp-groups/classes/class-bp-group-member-query.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/classes/class-bp-group-member-query.php	(revision 10410)
@@ -0,0 +1,377 @@
+<?php
+/**
+ * BuddyPress Groups Classes.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Query for the members of a group.
+ *
+ * Special notes about the group members data schema:
+ * - *Members* are entries with is_confirmed = 1.
+ * - *Pending requests* are entries with is_confirmed = 0 and inviter_id = 0.
+ * - *Pending and sent invitations* are entries with is_confirmed = 0 and
+ *   inviter_id != 0 and invite_sent = 1.
+ * - *Pending and unsent invitations* are entries with is_confirmed = 0 and
+ *   inviter_id != 0 and invite_sent = 0.
+ * - *Membership requests* are entries with is_confirmed = 0 and
+ *   inviter_id = 0 (and invite_sent = 0).
+ *
+ * @since 1.8.0
+ *
+ * @param array $args  {
+ *     Array of arguments. Accepts all arguments from
+ *     {@link BP_User_Query}, with the following additions:
+ *
+ *     @type int    $group_id     ID of the group to limit results to.
+ *     @type array  $group_role   Array of group roles to match ('member',
+ *                                'mod', 'admin', 'banned').
+ *                                Default: array( 'member' ).
+ *     @type bool   $is_confirmed Whether to limit to confirmed members.
+ *                                Default: true.
+ *     @type string $type         Sort order. Accepts any value supported by
+ *                                {@link BP_User_Query}, in addition to 'last_joined'
+ *                                and 'first_joined'. Default: 'last_joined'.
+ * }
+ */
+class BP_Group_Member_Query extends BP_User_Query {
+
+	/**
+	 * Array of group member ids, cached to prevent redundant lookups.
+	 *
+	 * @since 1.8.1
+	 * @var null|array Null if not yet defined, otherwise an array of ints.
+	 */
+	protected $group_member_ids;
+
+	/**
+	 * Set up action hooks.
+	 *
+	 * @since 1.8.0
+	 */
+	public function setup_hooks() {
+		// Take this early opportunity to set the default 'type' param
+		// to 'last_joined', which will ensure that BP_User_Query
+		// trusts our order and does not try to apply its own
+		if ( empty( $this->query_vars_raw['type'] ) ) {
+			$this->query_vars_raw['type'] = 'last_joined';
+		}
+
+		// Set the sort order
+		add_action( 'bp_pre_user_query', array( $this, 'set_orderby' ) );
+
+		// Set up our populate_extras method
+		add_action( 'bp_user_query_populate_extras', array( $this, 'populate_group_member_extras' ), 10, 2 );
+	}
+
+	/**
+	 * Get a list of user_ids to include in the IN clause of the main query.
+	 *
+	 * Overrides BP_User_Query::get_include_ids(), adding our additional
+	 * group-member logic.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param array $include Existing group IDs in the $include parameter,
+	 *                       as calculated in BP_User_Query.
+	 * @return array
+	 */
+	public function get_include_ids( $include = array() ) {
+		// The following args are specific to group member queries, and
+		// are not present in the query_vars of a normal BP_User_Query.
+		// We loop through to make sure that defaults are set (though
+		// values passed to the constructor will, as usual, override
+		// these defaults).
+		$this->query_vars = wp_parse_args( $this->query_vars, array(
+			'group_id'     => 0,
+			'group_role'   => array( 'member' ),
+			'is_confirmed' => true,
+			'invite_sent'  => null,
+			'inviter_id'   => null,
+			'type'         => 'last_joined',
+		) );
+
+		$group_member_ids = $this->get_group_member_ids();
+
+		// If the group member query returned no users, bail with an
+		// array that will guarantee no matches for BP_User_Query
+		if ( empty( $group_member_ids ) ) {
+			return array( 0 );
+		}
+
+		if ( ! empty( $include ) ) {
+			$group_member_ids = array_intersect( $include, $group_member_ids );
+		}
+
+		return $group_member_ids;
+	}
+
+	/**
+	 * Get the members of the queried group.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @return array $ids User IDs of relevant group member ids.
+	 */
+	protected function get_group_member_ids() {
+		global $wpdb;
+
+		if ( is_array( $this->group_member_ids ) ) {
+			return $this->group_member_ids;
+		}
+
+		$bp  = buddypress();
+		$sql = array(
+			'select'  => "SELECT user_id FROM {$bp->groups->table_name_members}",
+			'where'   => array(),
+			'orderby' => '',
+			'order'   => '',
+		);
+
+		/** WHERE clauses *****************************************************/
+
+		// Group id
+		$sql['where'][] = $wpdb->prepare( "group_id = %d", $this->query_vars['group_id'] );
+
+		// is_confirmed
+		$is_confirmed = ! empty( $this->query_vars['is_confirmed'] ) ? 1 : 0;
+		$sql['where'][] = $wpdb->prepare( "is_confirmed = %d", $is_confirmed );
+
+		// invite_sent
+		if ( ! is_null( $this->query_vars['invite_sent'] ) ) {
+			$invite_sent = ! empty( $this->query_vars['invite_sent'] ) ? 1 : 0;
+			$sql['where'][] = $wpdb->prepare( "invite_sent = %d", $invite_sent );
+		}
+
+		// inviter_id
+		if ( ! is_null( $this->query_vars['inviter_id'] ) ) {
+			$inviter_id = $this->query_vars['inviter_id'];
+
+			// Empty: inviter_id = 0. (pass false, 0, or empty array)
+			if ( empty( $inviter_id ) ) {
+				$sql['where'][] = "inviter_id = 0";
+
+			// The string 'any' matches any non-zero value (inviter_id != 0)
+			} elseif ( 'any' === $inviter_id ) {
+				$sql['where'][] = "inviter_id != 0";
+
+			// Assume that a list of inviter IDs has been passed
+			} else {
+				// Parse and sanitize
+				$inviter_ids = wp_parse_id_list( $inviter_id );
+				if ( ! empty( $inviter_ids ) ) {
+					$inviter_ids_sql = implode( ',', $inviter_ids );
+					$sql['where'][] = "inviter_id IN ({$inviter_ids_sql})";
+				}
+			}
+		}
+
+		// Role information is stored as follows: admins have
+		// is_admin = 1, mods have is_mod = 1, banned have is_banned =
+		// 1, and members have all three set to 0.
+		$roles = !empty( $this->query_vars['group_role'] ) ? $this->query_vars['group_role'] : array();
+		if ( is_string( $roles ) ) {
+			$roles = explode( ',', $roles );
+		}
+
+		// Sanitize: Only 'admin', 'mod', 'member', and 'banned' are valid
+		$allowed_roles = array( 'admin', 'mod', 'member', 'banned' );
+		foreach ( $roles as $role_key => $role_value ) {
+			if ( ! in_array( $role_value, $allowed_roles ) ) {
+				unset( $roles[ $role_key ] );
+			}
+		}
+
+		$roles = array_unique( $roles );
+
+		// When querying for a set of roles containing 'member' (for
+		// which there is no dedicated is_ column), figure out a list
+		// of columns *not* to match
+		$roles_sql = '';
+		if ( in_array( 'member', $roles ) ) {
+			$role_columns = array();
+			foreach ( array_diff( $allowed_roles, $roles ) as $excluded_role ) {
+				$role_columns[] = 'is_' . $excluded_role . ' = 0';
+			}
+
+			if ( ! empty( $role_columns ) ) {
+				$roles_sql = '(' . implode( ' AND ', $role_columns ) . ')';
+			}
+
+		// When querying for a set of roles *not* containing 'member',
+		// simply construct a list of is_* = 1 clauses
+		} else {
+			$role_columns = array();
+			foreach ( $roles as $role ) {
+				$role_columns[] = 'is_' . $role . ' = 1';
+			}
+
+			if ( ! empty( $role_columns ) ) {
+				$roles_sql = '(' . implode( ' OR ', $role_columns ) . ')';
+			}
+		}
+
+		if ( ! empty( $roles_sql ) ) {
+			$sql['where'][] = $roles_sql;
+		}
+
+		$sql['where'] = ! empty( $sql['where'] ) ? 'WHERE ' . implode( ' AND ', $sql['where'] ) : '';
+
+		// We fetch group members in order of last_joined, regardless
+		// of 'type'. If the 'type' value is not 'last_joined' or
+		// 'first_joined', the order will be overridden in
+		// BP_Group_Member_Query::set_orderby()
+		$sql['orderby'] = "ORDER BY date_modified";
+		$sql['order']   = 'first_joined' === $this->query_vars['type'] ? 'ASC' : 'DESC';
+
+		$this->group_member_ids = $wpdb->get_col( "{$sql['select']} {$sql['where']} {$sql['orderby']} {$sql['order']}" );
+
+		/**
+		 * Filters the member IDs for the current group member query.
+		 *
+		 * Use this filter to build a custom query (such as when you've
+		 * defined a custom 'type').
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array                 $group_member_ids Array of associated member IDs.
+		 * @param BP_Group_Member_Query $this             Current BP_Group_Member_Query instance.
+		 */
+		$this->group_member_ids = apply_filters( 'bp_group_member_query_group_member_ids', $this->group_member_ids, $this );
+
+		return $this->group_member_ids;
+	}
+
+	/**
+	 * Tell BP_User_Query to order by the order of our query results.
+	 *
+	 * We only override BP_User_Query's native ordering in case of the
+	 * 'last_joined' and 'first_joined' $type parameters.
+	 *
+	 * @param BP_User_Query $query BP_User_Query object.
+	 */
+	public function set_orderby( $query ) {
+		$gm_ids = $this->get_group_member_ids();
+		if ( empty( $gm_ids ) ) {
+			$gm_ids = array( 0 );
+		}
+
+		// For 'last_joined', 'first_joined', and 'group_activity'
+		// types, we override the default orderby clause of
+		// BP_User_Query. In the case of 'group_activity', we perform
+		// a separate query to get the necessary order. In the case of
+		// 'last_joined' and 'first_joined', we can trust the order of
+		// results from  BP_Group_Member_Query::get_group_members().
+		// In all other cases, we fall through and let BP_User_Query
+		// do its own (non-group-specific) ordering.
+		if ( in_array( $query->query_vars['type'], array( 'last_joined', 'first_joined', 'group_activity' ) ) ) {
+
+			// Group Activity DESC
+			if ( 'group_activity' == $query->query_vars['type'] ) {
+				$gm_ids = $this->get_gm_ids_ordered_by_activity( $query, $gm_ids );
+			}
+
+			// The first param in the FIELD() clause is the sort column id
+			$gm_ids = array_merge( array( 'u.id' ), wp_parse_id_list( $gm_ids ) );
+			$gm_ids_sql = implode( ',', $gm_ids );
+
+			$query->uid_clauses['orderby'] = "ORDER BY FIELD(" . $gm_ids_sql . ")";
+		}
+
+		// Prevent this filter from running on future BP_User_Query
+		// instances on the same page
+		remove_action( 'bp_pre_user_query', array( $this, 'set_orderby' ) );
+	}
+
+	/**
+	 * Fetch additional data required in bp_group_has_members() loops.
+	 *
+	 * Additional data fetched:
+	 *
+	 *      - is_banned
+	 *      - date_modified
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param BP_User_Query $query        BP_User_Query object. Because we're
+	 *                                    filtering the current object, we use
+	 *                                    $this inside of the method instead.
+	 * @param string        $user_ids_sql Sanitized, comma-separated string of
+	 *                                    the user ids returned by the main query.
+	 */
+	public function populate_group_member_extras( $query, $user_ids_sql ) {
+		global $wpdb;
+
+		$bp     = buddypress();
+		$extras = $wpdb->get_results( $wpdb->prepare( "SELECT id, user_id, date_modified, is_admin, is_mod, comments, user_title, invite_sent, is_confirmed, inviter_id, is_banned FROM {$bp->groups->table_name_members} WHERE user_id IN ({$user_ids_sql}) AND group_id = %d", $this->query_vars['group_id'] ) );
+
+		foreach ( (array) $extras as $extra ) {
+			if ( isset( $this->results[ $extra->user_id ] ) ) {
+				// user_id is provided for backward compatibility
+				$this->results[ $extra->user_id ]->user_id       = (int) $extra->user_id;
+				$this->results[ $extra->user_id ]->is_admin      = (int) $extra->is_admin;
+				$this->results[ $extra->user_id ]->is_mod        = (int) $extra->is_mod;
+				$this->results[ $extra->user_id ]->is_banned     = (int) $extra->is_banned;
+				$this->results[ $extra->user_id ]->date_modified = $extra->date_modified;
+				$this->results[ $extra->user_id ]->user_title    = $extra->user_title;
+				$this->results[ $extra->user_id ]->comments      = $extra->comments;
+				$this->results[ $extra->user_id ]->invite_sent   = (int) $extra->invite_sent;
+				$this->results[ $extra->user_id ]->inviter_id    = (int) $extra->inviter_id;
+				$this->results[ $extra->user_id ]->is_confirmed  = (int) $extra->is_confirmed;
+				$this->results[ $extra->user_id ]->membership_id = (int) $extra->id;
+			}
+		}
+
+		// Don't filter other BP_User_Query objects on the same page
+		remove_action( 'bp_user_query_populate_extras', array( $this, 'populate_group_member_extras' ), 10, 2 );
+	}
+
+	/**
+	 * Sort user IDs by how recently they have generated activity within a given group.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param BP_User_Query $query  BP_User_Query object.
+	 * @param array         $gm_ids array of group member ids.
+	 *
+	 * @return array
+	 */
+	public function get_gm_ids_ordered_by_activity( $query, $gm_ids = array() ) {
+		global $wpdb;
+
+		if ( empty( $gm_ids ) ) {
+			return $gm_ids;
+		}
+
+		if ( ! bp_is_active( 'activity' ) ) {
+			return $gm_ids;
+		}
+
+		$activity_table = buddypress()->activity->table_name;
+
+		$sql = array(
+			'select'  => "SELECT user_id, max( date_recorded ) as date_recorded FROM {$activity_table}",
+			'where'   => array(),
+			'groupby' => 'GROUP BY user_id',
+			'orderby' => 'ORDER BY date_recorded',
+			'order'   => 'DESC',
+		);
+
+		$sql['where'] = array(
+			'user_id IN (' . implode( ',', wp_parse_id_list( $gm_ids ) ) . ')',
+			'item_id = ' . absint( $query->query_vars['group_id'] ),
+			$wpdb->prepare( "component = %s", buddypress()->groups->id ),
+		);
+
+		$sql['where'] = 'WHERE ' . implode( ' AND ', $sql['where'] );
+
+		$group_user_ids = $wpdb->get_results( "{$sql['select']} {$sql['where']} {$sql['groupby']} {$sql['orderby']} {$sql['order']}" );
+
+		return wp_list_pluck( $group_user_ids, 'user_id' );
+	}
+}
Index: /tags/2.4.3/src/bp-groups/classes/class-bp-groups-group.php
===================================================================
--- /tags/2.4.3/src/bp-groups/classes/class-bp-groups-group.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/classes/class-bp-groups-group.php	(revision 10410)
@@ -0,0 +1,1584 @@
+<?php
+/**
+ * BuddyPress Groups Classes.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BuddyPress Group object.
+ */
+class BP_Groups_Group {
+
+	/**
+	 * ID of the group.
+	 *
+	 * @var int
+	 */
+	public $id;
+
+	/**
+	 * User ID of the group's creator.
+	 *
+	 * @var int
+	 */
+	public $creator_id;
+
+	/**
+	 * Name of the group.
+	 *
+	 * @var string
+	 */
+	public $name;
+
+	/**
+	 * Group slug.
+	 *
+	 * @var string
+	 */
+	public $slug;
+
+	/**
+	 * Group description.
+	 *
+	 * @var string
+	 */
+	public $description;
+
+	/**
+	 * Group status.
+	 *
+	 * Core statuses are 'public', 'private', and 'hidden'.
+	 *
+	 * @var string
+	 */
+	public $status;
+
+	/**
+	 * Should (legacy) bbPress forums be enabled for this group?
+	 *
+	 * @var int
+	 */
+	public $enable_forum;
+
+	/**
+	 * Date the group was created.
+	 *
+	 * @var string
+	 */
+	public $date_created;
+
+	/**
+	 * Data about the group's admins.
+	 *
+	 * @var array
+	 */
+	public $admins;
+
+	/**
+	 * Data about the group's moderators.
+	 *
+	 * @var array
+	 */
+	public $mods;
+
+	/**
+	 * Total count of group members.
+	 *
+	 * @var int
+	 */
+	public $total_member_count;
+
+	/**
+	 * Is the current user a member of this group?
+	 *
+	 * @since 1.2.0
+	 * @var bool
+	 */
+	public $is_member;
+
+	/**
+	 * Does the current user have an outstanding invitation to this group?
+	 *
+	 * @since 1.9.0
+	 * @var bool
+	 */
+	public $is_invited;
+
+	/**
+	 * Does the current user have a pending membership request to this group?
+	 *
+	 * @since 1.9.0
+	 * @var bool
+	 */
+	public $is_pending;
+
+	/**
+	 * Timestamp of the last activity that happened in this group.
+	 *
+	 * @since 1.2.0
+	 * @var string
+	 */
+	public $last_activity;
+
+	/**
+	 * If this is a private or hidden group, does the current user have access?
+	 *
+	 * @since 1.6.0
+	 * @var bool
+	 */
+	public $user_has_access;
+
+	/**
+	 * Raw arguments passed to the constructor.
+	 *
+	 * @since 2.0.0
+	 * @var array
+	 */
+	public $args;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @param int|null $id Optional. If the ID of an existing group is provided,
+	 *                     the object will be pre-populated with info about that group.
+	 * @param array    $args {
+	 *     Array of optional arguments.
+	 *     @type bool $populate_extras Whether to fetch "extra" data about the group
+	 *                                 (group admins/mods, access for the current user).
+	 *                                 Default: false.
+	 * }
+	 */
+	public function __construct( $id = null, $args = array() ) {
+		$this->args = wp_parse_args( $args, array(
+			'populate_extras' => false,
+		) );
+
+		if ( !empty( $id ) ) {
+			$this->id = $id;
+			$this->populate();
+		}
+	}
+
+	/**
+	 * Set up data about the current group.
+	 */
+	public function populate() {
+		global $wpdb;
+
+		// Get BuddyPress
+		$bp    = buddypress();
+
+		// Check cache for group data
+		$group = wp_cache_get( $this->id, 'bp_groups' );
+
+		// Cache missed, so query the DB
+		if ( false === $group ) {
+			$group = $wpdb->get_row( $wpdb->prepare( "SELECT g.* FROM {$bp->groups->table_name} g WHERE g.id = %d", $this->id ) );
+
+			wp_cache_set( $this->id, $group, 'bp_groups' );
+		}
+
+		// No group found so set the ID and bail
+		if ( empty( $group ) || is_wp_error( $group ) ) {
+			$this->id = 0;
+			return;
+		}
+
+		// Group found so setup the object variables
+		$this->id           = $group->id;
+		$this->creator_id   = $group->creator_id;
+		$this->name         = stripslashes( $group->name );
+		$this->slug         = $group->slug;
+		$this->description  = stripslashes( $group->description );
+		$this->status       = $group->status;
+		$this->enable_forum = $group->enable_forum;
+		$this->date_created = $group->date_created;
+
+		// Are we getting extra group data?
+		if ( ! empty( $this->args['populate_extras'] ) ) {
+
+			/**
+			 * Filters the SQL prepared statement used to fetch group admins and mods.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param string $value SQL select statement used to fetch admins and mods.
+			 */
+			$admin_mods = $wpdb->get_results( apply_filters( 'bp_group_admin_mods_user_join_filter', $wpdb->prepare( "SELECT u.ID as user_id, u.user_login, u.user_email, u.user_nicename, m.is_admin, m.is_mod FROM {$wpdb->users} u, {$bp->groups->table_name_members} m WHERE u.ID = m.user_id AND m.group_id = %d AND ( m.is_admin = 1 OR m.is_mod = 1 )", $this->id ) ) );
+
+			// Add admins and moderators to their respective arrays
+			foreach ( (array) $admin_mods as $user ) {
+				if ( !empty( $user->is_admin ) ) {
+					$this->admins[] = $user;
+				} else {
+					$this->mods[] = $user;
+				}
+			}
+
+			// Set up some specific group vars from meta. Excluded
+			// from the bp_groups cache because it's cached independently
+			$this->last_activity      = groups_get_groupmeta( $this->id, 'last_activity' );
+			$this->total_member_count = groups_get_groupmeta( $this->id, 'total_member_count' );
+
+			// Set user-specific data
+			$user_id          = bp_loggedin_user_id();
+			$this->is_member  = BP_Groups_Member::check_is_member( $user_id, $this->id );
+			$this->is_invited = BP_Groups_Member::check_has_invite( $user_id, $this->id );
+			$this->is_pending = BP_Groups_Member::check_for_membership_request( $user_id, $this->id );
+
+			// If this is a private or hidden group, does the current user have access?
+			if ( ( 'private' === $this->status ) || ( 'hidden' === $this->status ) ) {
+
+				// Assume user does not have access to hidden/private groups
+				$this->user_has_access = false;
+
+				// Group members or community moderators have access
+				if ( ( $this->is_member && is_user_logged_in() ) || bp_current_user_can( 'bp_moderate' ) ) {
+					$this->user_has_access = true;
+				}
+			} else {
+				$this->user_has_access = true;
+			}
+		}
+	}
+
+	/**
+	 * Save the current group to the database.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function save() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$this->creator_id   = apply_filters( 'groups_group_creator_id_before_save',   $this->creator_id,   $this->id );
+		$this->name         = apply_filters( 'groups_group_name_before_save',         $this->name,         $this->id );
+ 		$this->slug         = apply_filters( 'groups_group_slug_before_save',         $this->slug,         $this->id );
+		$this->description  = apply_filters( 'groups_group_description_before_save',  $this->description,  $this->id );
+ 		$this->status       = apply_filters( 'groups_group_status_before_save',       $this->status,       $this->id );
+		$this->enable_forum = apply_filters( 'groups_group_enable_forum_before_save', $this->enable_forum, $this->id );
+		$this->date_created = apply_filters( 'groups_group_date_created_before_save', $this->date_created, $this->id );
+
+		/**
+		 * Fires before the current group item gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Groups_Group $this Current instance of the group item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'groups_group_before_save', array( &$this ) );
+
+		// Groups need at least a name
+		if ( empty( $this->name ) ) {
+			return false;
+		}
+
+		// Set slug with group title if not passed
+		if ( empty( $this->slug ) ) {
+			$this->slug = sanitize_title( $this->name );
+		}
+
+		// Sanity check
+		if ( empty( $this->slug ) ) {
+			return false;
+		}
+
+		// Check for slug conflicts if creating new group
+		if ( empty( $this->id ) ) {
+			$this->slug = groups_check_slug( $this->slug );
+		}
+
+		if ( !empty( $this->id ) ) {
+			$sql = $wpdb->prepare(
+				"UPDATE {$bp->groups->table_name} SET
+					creator_id = %d,
+					name = %s,
+					slug = %s,
+					description = %s,
+					status = %s,
+					enable_forum = %d,
+					date_created = %s
+				WHERE
+					id = %d
+				",
+					$this->creator_id,
+					$this->name,
+					$this->slug,
+					$this->description,
+					$this->status,
+					$this->enable_forum,
+					$this->date_created,
+					$this->id
+			);
+		} else {
+			$sql = $wpdb->prepare(
+				"INSERT INTO {$bp->groups->table_name} (
+					creator_id,
+					name,
+					slug,
+					description,
+					status,
+					enable_forum,
+					date_created
+				) VALUES (
+					%d, %s, %s, %s, %s, %d, %s
+				)",
+					$this->creator_id,
+					$this->name,
+					$this->slug,
+					$this->description,
+					$this->status,
+					$this->enable_forum,
+					$this->date_created
+			);
+		}
+
+		if ( false === $wpdb->query($sql) )
+			return false;
+
+		if ( empty( $this->id ) )
+			$this->id = $wpdb->insert_id;
+
+		/**
+		 * Fires after the current group item has been saved.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Groups_Group $this Current instance of the group item that was saved. Passed by reference.
+		 */
+		do_action_ref_array( 'groups_group_after_save', array( &$this ) );
+
+		wp_cache_delete( $this->id, 'bp_groups' );
+
+		return true;
+	}
+
+	/**
+	 * Delete the current group.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function delete() {
+		global $wpdb;
+
+		// Delete groupmeta for the group
+		groups_delete_groupmeta( $this->id );
+
+		// Fetch the user IDs of all the members of the group
+		$user_ids    = BP_Groups_Member::get_group_member_ids( $this->id );
+		$user_id_str = esc_sql( implode( ',', wp_parse_id_list( $user_ids ) ) );
+
+		// Modify group count usermeta for members
+		$wpdb->query( "UPDATE {$wpdb->usermeta} SET meta_value = meta_value - 1 WHERE meta_key = 'total_group_count' AND user_id IN ( {$user_id_str} )" );
+
+		// Now delete all group member entries
+		BP_Groups_Member::delete_all( $this->id );
+
+		/**
+		 * Fires before the deletion of a group.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param BP_Groups_Group $this     Current instance of the group item being deleted. Passed by reference.
+		 * @param array           $user_ids Array of user IDs that were members of the group.
+		 */
+		do_action_ref_array( 'bp_groups_delete_group', array( &$this, $user_ids ) );
+
+		wp_cache_delete( $this->id, 'bp_groups' );
+
+		$bp = buddypress();
+
+		// Finally remove the group entry from the DB
+		if ( !$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->groups->table_name} WHERE id = %d", $this->id ) ) )
+			return false;
+
+		return true;
+	}
+
+	/** Static Methods ****************************************************/
+
+	/**
+	 * Get whether a group exists for a given slug.
+	 *
+	 * @param string      $slug       Slug to check.
+	 * @param string|bool $table_name Optional. Name of the table to check
+	 *                                against. Default: $bp->groups->table_name.
+	 *
+	 * @return string|null ID of the group, if one is found, else null.
+	 */
+	public static function group_exists( $slug, $table_name = false ) {
+		global $wpdb;
+
+		if ( empty( $table_name ) )
+			$table_name = buddypress()->groups->table_name;
+
+		if ( empty( $slug ) )
+			return false;
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$table_name} WHERE slug = %s", strtolower( $slug ) ) );
+	}
+
+	/**
+	 * Get the ID of a group by the group's slug.
+	 *
+	 * Alias of {@link BP_Groups_Group::group_exists()}.
+	 *
+	 * @param string $slug See {@link BP_Groups_Group::group_exists()}.
+	 *
+	 * @return string|null See {@link BP_Groups_Group::group_exists()}.
+	 */
+	public static function get_id_from_slug( $slug ) {
+		return BP_Groups_Group::group_exists( $slug );
+	}
+
+	/**
+	 * Get IDs of users with outstanding invites to a given group from a specified user.
+	 *
+	 * @param int $user_id ID of the inviting user.
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return array IDs of users who have been invited to the group by the
+	 *               user but have not yet accepted.
+	 */
+	public static function get_invites( $user_id, $group_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_col( $wpdb->prepare( "SELECT user_id FROM {$bp->groups->table_name_members} WHERE group_id = %d and is_confirmed = 0 AND inviter_id = %d", $group_id, $user_id ) );
+	}
+
+	/**
+	 * Get a list of a user's groups, filtered by a search string.
+	 *
+	 * @param string   $filter  Search term. Matches against 'name' and
+	 *                          'description' fields.
+	 * @param int      $user_id ID of the user whose groups are being searched.
+	 *                          Default: the displayed user.
+	 * @param mixed    $order   Not used.
+	 * @param int|null $limit   Optional. The max number of results to return.
+	 *                          Default: null (no limit).
+	 * @param int|null $page    Optional. The page offset of results to return.
+	 *                          Default: null (no limit).
+	 * @return false|array {
+	 *     @type array $groups Array of matched and paginated group objects.
+	 *     @type int   $total  Total count of groups matching the query.
+	 * }
+	 */
+	public static function filter_user_groups( $filter, $user_id = 0, $order = false, $limit = null, $page = null ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			$user_id = bp_displayed_user_id();
+
+		$search_terms_like = bp_esc_like( $filter ) . '%';
+
+		$pag_sql = $order_sql = $hidden_sql = '';
+
+		if ( !empty( $limit ) && !empty( $page ) )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		// Get all the group ids for the current user's groups.
+		$gids = BP_Groups_Member::get_group_ids( $user_id );
+
+		if ( empty( $gids['groups'] ) )
+			return false;
+
+		$bp = buddypress();
+
+		$gids = esc_sql( implode( ',', wp_parse_id_list( $gids['groups'] ) ) );
+
+		$paged_groups = $wpdb->get_results( $wpdb->prepare( "SELECT id as group_id FROM {$bp->groups->table_name} WHERE ( name LIKE %s OR description LIKE %s ) AND id IN ({$gids}) {$pag_sql}", $search_terms_like, $search_terms_like ) );
+		$total_groups = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->groups->table_name} WHERE ( name LIKE %s OR description LIKE %s ) AND id IN ({$gids})", $search_terms_like, $search_terms_like ) );
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Get a list of groups, filtered by a search string.
+	 *
+	 * @param string      $filter  Search term. Matches against 'name' and
+	 *                             'description' fields.
+	 * @param int|null    $limit   Optional. The max number of results to return.
+	 *                             Default: null (no limit).
+	 * @param int|null    $page    Optional. The page offset of results to return.
+	 *                             Default: null (no limit).
+	 * @param string|bool $sort_by Column to sort by. Default: false (default
+	 *        sort).
+	 * @param string|bool $order   ASC or DESC. Default: false (default sort).
+	 *
+	 * @return array {
+	 *     @type array $groups Array of matched and paginated group objects.
+	 *     @type int   $total  Total count of groups matching the query.
+	 * }
+	 */
+	public static function search_groups( $filter, $limit = null, $page = null, $sort_by = false, $order = false ) {
+		global $wpdb;
+
+		$search_terms_like = '%' . bp_esc_like( $filter ) . '%';
+
+		$pag_sql = $order_sql = $hidden_sql = '';
+
+		if ( !empty( $limit ) && !empty( $page ) )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		if ( !empty( $sort_by ) && !empty( $order ) ) {
+			$sort_by   = esc_sql( $sort_by );
+			$order     = esc_sql( $order );
+			$order_sql = "ORDER BY {$sort_by} {$order}";
+		}
+
+		if ( !bp_current_user_can( 'bp_moderate' ) )
+			$hidden_sql = "AND status != 'hidden'";
+
+		$bp = buddypress();
+
+		$paged_groups = $wpdb->get_results( $wpdb->prepare( "SELECT id as group_id FROM {$bp->groups->table_name} WHERE ( name LIKE %s OR description LIKE %s ) {$hidden_sql} {$order_sql} {$pag_sql}", $search_terms_like, $search_terms_like ) );
+		$total_groups = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->groups->table_name} WHERE ( name LIKE %s OR description LIKE %s ) {$hidden_sql}", $search_terms_like, $search_terms_like ) );
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Check for the existence of a slug.
+	 *
+	 * @param string $slug Slug to check.
+	 *
+	 * @return string|null The slug, if found. Otherwise null.
+	 */
+	public static function check_slug( $slug ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM {$bp->groups->table_name} WHERE slug = %s", $slug ) );
+	}
+
+	/**
+	 * Get the slug for a given group ID.
+	 *
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return string|null The slug, if found. Otherwise null.
+	 */
+	public static function get_slug( $group_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM {$bp->groups->table_name} WHERE id = %d", $group_id ) );
+	}
+
+	/**
+	 * Check whether a given group has any members.
+	 *
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return bool True if the group has members, otherwise false.
+	 */
+	public static function has_members( $group_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$members = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->groups->table_name_members} WHERE group_id = %d", $group_id ) );
+
+		if ( empty( $members ) )
+			return false;
+
+		return true;
+	}
+
+	/**
+	 * Check whether a group has outstanding membership requests.
+	 *
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return int|null The number of outstanding requests, or null if
+	 *                  none are found.
+	 */
+	public static function has_membership_requests( $group_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->groups->table_name_members} WHERE group_id = %d AND is_confirmed = 0", $group_id ) );
+	}
+
+	/**
+	 * Get outstanding membership requests for a group.
+	 *
+	 * @param int      $group_id ID of the group.
+	 * @param int|null $limit    Optional. Max number of results to return.
+	 *                           Default: null (no limit).
+	 * @param int|null $page     Optional. Page offset of results returned. Default:
+	 *                           null (no limit).
+	 * @return array {
+	 *     @type array $requests The requested page of located requests.
+	 *     @type int   $total    Total number of requests outstanding for the
+	 *                           group.
+	 * }
+	 */
+	public static function get_membership_requests( $group_id, $limit = null, $page = null ) {
+		global $wpdb;
+
+		if ( !empty( $limit ) && !empty( $page ) ) {
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+		}
+
+		$bp = buddypress();
+
+		$paged_requests = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$bp->groups->table_name_members} WHERE group_id = %d AND is_confirmed = 0 AND inviter_id = 0{$pag_sql}", $group_id ) );
+		$total_requests = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->groups->table_name_members} WHERE group_id = %d AND is_confirmed = 0 AND inviter_id = 0", $group_id ) );
+
+		return array( 'requests' => $paged_requests, 'total' => $total_requests );
+	}
+
+	/**
+	 * Query for groups.
+	 *
+	 * @see WP_Meta_Query::queries for a description of the 'meta_query'
+	 *      parameter format.
+	 *
+	 * @param array {
+	 *     Array of parameters. All items are optional.
+	 *     @type string       $type              Optional. Shorthand for certain orderby/
+	 *                                           order combinations. 'newest', 'active', 'popular',
+	 *                                           'alphabetical', 'random'. When present, will override
+	 *                                           orderby and order params. Default: null.
+	 *     @type string       $orderby           Optional. Property to sort by.
+	 *                                           'date_created', 'last_activity', 'total_member_count',
+	 *                                           'name', 'random'. Default: 'date_created'.
+	 *     @type string       $order             Optional. Sort order. 'ASC' or 'DESC'.
+	 *                                           Default: 'DESC'.
+	 *     @type int          $per_page          Optional. Number of items to return per page
+	 *                                           of results. Default: null (no limit).
+	 *     @type int          $page              Optional. Page offset of results to return.
+	 *                                           Default: null (no limit).
+	 *     @type int          $user_id           Optional. If provided, results will be limited to groups
+	 *                                           of which the specified user is a member. Default: null.
+	 *     @type string       $search_terms      Optional. If provided, only groups whose names
+	 *                                           or descriptions match the search terms will be
+	 *                                           returned. Default: false.
+	 *     @type array        $meta_query        Optional. An array of meta_query conditions.
+	 *                                           See {@link WP_Meta_Query::queries} for description.
+	 *     @type array|string $value             Optional. Array or comma-separated list of group IDs.
+	 *                                           Results will be limited to groups within the
+	 *                                           list. Default: false.
+	 *     @type bool         $populate_extras   Whether to fetch additional information
+	 *                                           (such as member count) about groups. Default: true.
+	 *     @type array|string $exclude           Optional. Array or comma-separated list of group IDs.
+	 *                                           Results will exclude the listed groups. Default: false.
+	 *     @type bool         $update_meta_cache Whether to pre-fetch groupmeta for
+	 *                                           the returned groups. Default: true.
+	 *     @type bool         $show_hidden       Whether to include hidden groups in results. Default: false.
+	 * }
+	 * @return array {
+	 *     @type array $groups Array of group objects returned by the
+	 *                         paginated query.
+	 *     @type int   $total  Total count of all groups matching non-
+	 *                         paginated query params.
+	 * }
+	 */
+	public static function get( $args = array() ) {
+		global $wpdb;
+
+		// Backward compatibility with old method of passing arguments
+		if ( ! is_array( $args ) || func_num_args() > 1 ) {
+			_deprecated_argument( __METHOD__, '1.7', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+			$old_args_keys = array(
+				0 => 'type',
+				1 => 'per_page',
+				2 => 'page',
+				3 => 'user_id',
+				4 => 'search_terms',
+				5 => 'include',
+				6 => 'populate_extras',
+				7 => 'exclude',
+				8 => 'show_hidden',
+			);
+
+			$func_args = func_get_args();
+			$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
+		}
+
+		$defaults = array(
+			'type'              => null,
+			'orderby'           => 'date_created',
+			'order'             => 'DESC',
+			'per_page'          => null,
+			'page'              => null,
+			'user_id'           => 0,
+			'search_terms'      => false,
+			'meta_query'        => false,
+			'include'           => false,
+			'populate_extras'   => true,
+			'update_meta_cache' => true,
+			'exclude'           => false,
+			'show_hidden'       => false,
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+
+		$bp = buddypress();
+
+		$sql       = array();
+		$total_sql = array();
+
+		$sql['select'] = "SELECT DISTINCT g.id, g.*, gm1.meta_value AS total_member_count, gm2.meta_value AS last_activity";
+		$sql['from']   = " FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2,";
+
+		if ( ! empty( $r['user_id'] ) ) {
+			$sql['members_from'] = " {$bp->groups->table_name_members} m,";
+		}
+
+		$sql['group_from'] = " {$bp->groups->table_name} g WHERE";
+
+		if ( ! empty( $r['user_id'] ) ) {
+			$sql['user_where'] = " g.id = m.group_id AND";
+		}
+
+		$sql['where'] = " g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count'";
+
+		if ( empty( $r['show_hidden'] ) ) {
+			$sql['hidden'] = " AND g.status != 'hidden'";
+		}
+
+		if ( ! empty( $r['search_terms'] ) ) {
+			$search_terms_like = '%' . bp_esc_like( $r['search_terms'] ) . '%';
+			$sql['search'] = $wpdb->prepare( " AND ( g.name LIKE %s OR g.description LIKE %s )", $search_terms_like, $search_terms_like );
+		}
+
+		$meta_query_sql = self::get_meta_query_sql( $r['meta_query'] );
+
+		if ( ! empty( $meta_query_sql['join'] ) ) {
+			$sql['from'] .= $meta_query_sql['join'];
+		}
+
+		if ( ! empty( $meta_query_sql['where'] ) ) {
+			$sql['meta'] = $meta_query_sql['where'];
+		}
+
+		if ( ! empty( $r['user_id'] ) ) {
+			$sql['user'] = $wpdb->prepare( " AND m.user_id = %d AND m.is_confirmed = 1 AND m.is_banned = 0", $r['user_id'] );
+		}
+
+		if ( ! empty( $r['include'] ) ) {
+			$include        = implode( ',', wp_parse_id_list( $r['include'] ) );
+			$sql['include'] = " AND g.id IN ({$include})";
+		}
+
+		if ( ! empty( $r['exclude'] ) ) {
+			$exclude        = implode( ',', wp_parse_id_list( $r['exclude'] ) );
+			$sql['exclude'] = " AND g.id NOT IN ({$exclude})";
+		}
+
+		/** Order/orderby ********************************************/
+
+		$order   = $r['order'];
+		$orderby = $r['orderby'];
+
+		// If a 'type' parameter was passed, parse it and overwrite
+		// 'order' and 'orderby' params passed to the function
+		if (  ! empty( $r['type'] ) ) {
+
+			/**
+			 * Filters the 'type' parameter used to overwrite 'order' and 'orderby' values.
+			 *
+			 * @since 2.1.0
+			 *
+			 * @param array  $value Converted 'type' value for order and orderby.
+			 * @param string $value Parsed 'type' value for the get method.
+			 */
+			$order_orderby = apply_filters( 'bp_groups_get_orderby', self::convert_type_to_order_orderby( $r['type'] ), $r['type'] );
+
+			// If an invalid type is passed, $order_orderby will be
+			// an array with empty values. In this case, we stick
+			// with the default values of $order and $orderby
+			if ( ! empty( $order_orderby['order'] ) ) {
+				$order = $order_orderby['order'];
+			}
+
+			if ( ! empty( $order_orderby['orderby'] ) ) {
+				$orderby = $order_orderby['orderby'];
+			}
+		}
+
+		// Sanitize 'order'
+		$order = bp_esc_sql_order( $order );
+
+		/**
+		 * Filters the converted 'orderby' term.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $value   Converted 'orderby' term.
+		 * @param string $orderby Original orderby value.
+		 * @param string $value   Parsed 'type' value for the get method.
+		 */
+		$orderby = apply_filters( 'bp_groups_get_orderby_converted_by_term', self::convert_orderby_to_order_by_term( $orderby ), $orderby, $r['type'] );
+
+		// Random order is a special case
+		if ( 'rand()' === $orderby ) {
+			$sql[] = "ORDER BY rand()";
+		} else {
+			$sql[] = "ORDER BY {$orderby} {$order}";
+		}
+
+		if ( ! empty( $r['per_page'] ) && ! empty( $r['page'] ) && $r['per_page'] != -1 ) {
+			$sql['pagination'] = $wpdb->prepare( "LIMIT %d, %d", intval( ( $r['page'] - 1 ) * $r['per_page']), intval( $r['per_page'] ) );
+		}
+
+		/**
+		 * Filters the pagination SQL statement.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Concatenated SQL statement.
+		 * @param array  $sql   Array of SQL parts before concatenation.
+		 * @param array  $r     Array of parsed arguments for the get method.
+		 */
+		$paged_groups_sql = apply_filters( 'bp_groups_get_paged_groups_sql', join( ' ', (array) $sql ), $sql, $r );
+		$paged_groups     = $wpdb->get_results( $paged_groups_sql );
+
+		$total_sql['select'] = "SELECT COUNT(DISTINCT g.id) FROM {$bp->groups->table_name} g, {$bp->groups->table_name_groupmeta} gm";
+
+		if ( ! empty( $r['user_id'] ) ) {
+			$total_sql['select'] .= ", {$bp->groups->table_name_members} m";
+		}
+
+		if ( ! empty( $sql['hidden'] ) ) {
+			$total_sql['where'][] = "g.status != 'hidden'";
+		}
+
+		if ( ! empty( $sql['search'] ) ) {
+			$total_sql['where'][] = $wpdb->prepare( "( g.name LIKE %s OR g.description LIKE %s )", $search_terms_like, $search_terms_like );
+		}
+
+		if ( ! empty( $r['user_id'] ) ) {
+			$total_sql['where'][] = $wpdb->prepare( "m.group_id = g.id AND m.user_id = %d AND m.is_confirmed = 1 AND m.is_banned = 0", $r['user_id'] );
+		}
+
+		// Temporary implementation of meta_query for total count
+		// See #5099
+		if ( ! empty( $meta_query_sql['where'] ) ) {
+			// Join the groupmeta table
+			$total_sql['select'] .= ", ". substr( $meta_query_sql['join'], 0, -2 );
+
+			// Modify the meta_query clause from paged_sql for our syntax
+			$meta_query_clause = preg_replace( '/^\s*AND/', '', $meta_query_sql['where'] );
+			$total_sql['where'][] = $meta_query_clause;
+		}
+
+		// Already escaped in the paginated results block
+		if ( ! empty( $include ) ) {
+			$total_sql['where'][] = "g.id IN ({$include})";
+		}
+
+		// Already escaped in the paginated results block
+		if ( ! empty( $exclude ) ) {
+			$total_sql['where'][] = "g.id NOT IN ({$exclude})";
+		}
+
+		$total_sql['where'][] = "g.id = gm.group_id";
+		$total_sql['where'][] = "gm.meta_key = 'last_activity'";
+
+		$t_sql = $total_sql['select'];
+
+		if ( ! empty( $total_sql['where'] ) ) {
+			$t_sql .= " WHERE " . join( ' AND ', (array) $total_sql['where'] );
+		}
+
+		/**
+		 * Filters the SQL used to retrieve total group results.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $t_sql     Concatenated SQL statement used for retrieving total group results.
+		 * @param array  $total_sql Array of SQL parts for the query.
+		 * @param array  $r         Array of parsed arguments for the get method.
+		 */
+		$total_groups_sql = apply_filters( 'bp_groups_get_total_groups_sql', $t_sql, $total_sql, $r );
+		$total_groups     = $wpdb->get_var( $total_groups_sql );
+
+		$group_ids = array();
+		foreach ( (array) $paged_groups as $group ) {
+			$group_ids[] = $group->id;
+		}
+
+		// Populate some extra information instead of querying each time in the loop
+		if ( !empty( $r['populate_extras'] ) ) {
+			$paged_groups = BP_Groups_Group::get_group_extras( $paged_groups, $group_ids, $r['type'] );
+		}
+
+		// Grab all groupmeta
+		if ( ! empty( $r['update_meta_cache'] ) ) {
+			bp_groups_update_meta_cache( $group_ids );
+		}
+
+		unset( $sql, $total_sql );
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Get the SQL for the 'meta_query' param in BP_Activity_Activity::get()
+	 *
+	 * We use WP_Meta_Query to do the heavy lifting of parsing the
+	 * meta_query array and creating the necessary SQL clauses. However,
+	 * since BP_Activity_Activity::get() builds its SQL differently than
+	 * WP_Query, we have to alter the return value (stripping the leading
+	 * AND keyword from the 'where' clause).
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param array $meta_query An array of meta_query filters. See the
+	 *                          documentation for {@link WP_Meta_Query} for details.
+	 *
+	 * @return array $sql_array 'join' and 'where' clauses.
+	 */
+	protected static function get_meta_query_sql( $meta_query = array() ) {
+		global $wpdb;
+
+		$sql_array = array(
+			'join'  => '',
+			'where' => '',
+		);
+
+		if ( ! empty( $meta_query ) ) {
+			$groups_meta_query = new WP_Meta_Query( $meta_query );
+
+			// WP_Meta_Query expects the table name at
+			// $wpdb->group
+			$wpdb->groupmeta = buddypress()->groups->table_name_groupmeta;
+
+			$meta_sql = $groups_meta_query->get_sql( 'group', 'g', 'id' );
+
+			// BP_Groups_Group::get uses the comma syntax for table
+			// joins, which means that we have to do some regex to
+			// convert the INNER JOIN and move the ON clause to a
+			// WHERE condition
+			//
+			// @todo It may be better in the long run to refactor
+			// the more general query syntax to accord better with
+			// BP/WP convention
+			preg_match_all( '/JOIN (.+?) ON/', $meta_sql['join'], $matches_a );
+			preg_match_all( '/ON \((.+?)\)/', $meta_sql['join'], $matches_b );
+
+			if ( ! empty( $matches_a[1] ) && ! empty( $matches_b[1] ) ) {
+				$sql_array['join']  = implode( ',', $matches_a[1] ) . ', ';
+				$sql_array['where'] = $meta_sql['where'] . ' AND ' . implode( ' AND ', $matches_b[1] );
+			}
+		}
+
+		return $sql_array;
+	}
+
+	/**
+	 * Convert the 'type' parameter to 'order' and 'orderby'.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $type The 'type' shorthand param.
+	 *
+	 * @return array {
+	 *	@type string $order   SQL-friendly order string.
+	 *	@type string $orderby SQL-friendly orderby column name.
+	 * }
+	 */
+	protected static function convert_type_to_order_orderby( $type = '' ) {
+		$order = $orderby = '';
+
+		switch ( $type ) {
+			case 'newest' :
+				$order   = 'DESC';
+				$orderby = 'date_created';
+				break;
+
+			case 'active' :
+				$order   = 'DESC';
+				$orderby = 'last_activity';
+				break;
+
+			case 'popular' :
+				$order   = 'DESC';
+				$orderby = 'total_member_count';
+				break;
+
+			case 'alphabetical' :
+				$order   = 'ASC';
+				$orderby = 'name';
+				break;
+
+			case 'random' :
+				$order   = '';
+				$orderby = 'random';
+				break;
+		}
+
+		return array( 'order' => $order, 'orderby' => $orderby );
+	}
+
+	/**
+	 * Get a list of groups, sorted by those that have the most legacy forum topics.
+	 *
+	 * @param int|null          $limit           Optional. The max number of results to return.
+	 *                                           Default: null (no limit).
+	 * @param int|null          $page            Optional. The page offset of results to return.
+	 *                                           Default: null (no limit).
+	 * @param int               $user_id         Optional. If present, groups will be limited to
+	 *                                           those of which the specified user is a member.
+	 * @param string|bool       $search_terms    Optional. Limit groups to those whose name
+	 *                                           or description field contain the search string.
+	 * @param bool              $populate_extras Optional. Whether to fetch extra
+	 *                                           information about the groups. Default: true.
+	 * @param string|array|bool $exclude         Optional. Array or comma-separated list of group
+	 *                                           IDs to exclude from results.
+	 *
+	 * @return array {
+	 *     @type array $groups Array of group objects returned by the
+	 *                         paginated query.
+	 *     @type int   $total  Total count of all groups matching non-
+	 *                         paginated query params.
+	 * }
+	 */
+	public static function get_by_most_forum_topics( $limit = null, $page = null, $user_id = 0, $search_terms = false, $populate_extras = true, $exclude = false ) {
+		global $wpdb, $bbdb;
+
+		if ( empty( $bbdb ) ) {
+
+			/** This action is documented in bp-forums/bp-forums-screens */
+			do_action( 'bbpress_init' );
+		}
+
+		if ( !empty( $limit ) && !empty( $page ) ) {
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+		}
+
+		if ( !is_user_logged_in() || ( !bp_current_user_can( 'bp_moderate' ) && ( $user_id != bp_loggedin_user_id() ) ) )
+			$hidden_sql = " AND g.status != 'hidden'";
+
+		if ( !empty( $search_terms ) ) {
+			$search_terms_like = '%' . bp_esc_like( $search_terms ) . '%';
+			$search_sql        = $wpdb->prepare( ' AND ( g.name LIKE %s OR g.description LIKE %s ) ', $search_terms_like, $search_terms_like );
+		}
+
+		if ( !empty( $exclude ) ) {
+			$exclude     = implode( ',', wp_parse_id_list( $exclude ) );
+			$exclude_sql = " AND g.id NOT IN ({$exclude})";
+		}
+
+		$bp = buddypress();
+
+		if ( !empty( $user_id ) ) {
+			$user_id      = absint( esc_sql( $user_id ) );
+			$paged_groups = $wpdb->get_results( "SELECT DISTINCT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_groupmeta} gm3, {$bp->groups->table_name_members} m, {$bbdb->forums} f, {$bp->groups->table_name} g WHERE g.id = m.group_id AND g.id = gm1.group_id AND g.id = gm2.group_id AND g.id = gm3.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND (gm3.meta_key = 'forum_id' AND gm3.meta_value = f.forum_id) AND f.topics > 0 {$hidden_sql} {$search_sql} AND m.user_id = {$user_id} AND m.is_confirmed = 1 AND m.is_banned = 0 {$exclude_sql} ORDER BY f.topics DESC {$pag_sql}" );
+			$total_groups = $wpdb->get_var( "SELECT COUNT(DISTINCT g.id) FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_groupmeta} gm3, {$bbdb->forums} f, {$bp->groups->table_name} g WHERE g.id = gm1.group_id AND g.id = gm2.group_id AND g.id = gm3.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND (gm3.meta_key = 'forum_id' AND gm3.meta_value = f.forum_id) AND f.topics > 0 {$hidden_sql} {$search_sql} AND m.user_id = {$user_id} AND m.is_confirmed = 1 AND m.is_banned = 0 {$exclude_sql}" );
+		} else {
+			$paged_groups = $wpdb->get_results( "SELECT DISTINCT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_groupmeta} gm3, {$bbdb->forums} f, {$bp->groups->table_name} g WHERE g.id = gm1.group_id AND g.id = gm2.group_id AND g.id = gm3.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND (gm3.meta_key = 'forum_id' AND gm3.meta_value = f.forum_id) AND f.topics > 0 {$hidden_sql} {$search_sql} {$exclude_sql} ORDER BY f.topics DESC {$pag_sql}" );
+			$total_groups = $wpdb->get_var( "SELECT COUNT(DISTINCT g.id) FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_groupmeta} gm3, {$bbdb->forums} f, {$bp->groups->table_name} g WHERE g.id = gm1.group_id AND g.id = gm2.group_id AND g.id = gm3.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND (gm3.meta_key = 'forum_id' AND gm3.meta_value = f.forum_id) AND f.topics > 0 {$hidden_sql} {$search_sql} {$exclude_sql}" );
+		}
+
+		if ( !empty( $populate_extras ) ) {
+			foreach ( (array) $paged_groups as $group ) {
+				$group_ids[] = $group->id;
+			}
+			$paged_groups = BP_Groups_Group::get_group_extras( $paged_groups, $group_ids, 'newest' );
+		}
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Convert the 'orderby' param into a proper SQL term/column.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $orderby Orderby term as passed to get().
+	 *
+	 * @return string $order_by_term SQL-friendly orderby term.
+	 */
+	protected static function convert_orderby_to_order_by_term( $orderby ) {
+		$order_by_term = '';
+
+		switch ( $orderby ) {
+			case 'date_created' :
+			default :
+				$order_by_term = 'g.date_created';
+				break;
+
+			case 'last_activity' :
+				$order_by_term = 'last_activity';
+				break;
+
+			case 'total_member_count' :
+				$order_by_term = 'CONVERT(gm1.meta_value, SIGNED)';
+				break;
+
+			case 'name' :
+				$order_by_term = 'g.name';
+				break;
+
+			case 'random' :
+				$order_by_term = 'rand()';
+				break;
+		}
+
+		return $order_by_term;
+	}
+
+	/**
+	 * Get a list of groups, sorted by those that have the most legacy forum posts.
+	 *
+	 * @param int|null          $limit           Optional. The max number of results to return.
+	 *                                           Default: null (no limit).
+	 * @param int|null          $page            Optional. The page offset of results to return.
+	 *                                           Default: null (no limit).
+	 * @param string|bool       $search_terms    Optional. Limit groups to those whose name
+	 *                                           or description field contain the search string.
+	 * @param bool              $populate_extras Optional. Whether to fetch extra
+	 *                                           information about the groups. Default: true.
+	 * @param string|array|bool $exclude         Optional. Array or comma-separated list of group
+	 *                                           IDs to exclude from results.
+	 *
+	 * @return array {
+	 *     @type array $groups Array of group objects returned by the
+	 *                         paginated query.
+	 *     @type int   $total  Total count of all groups matching non-
+	 *                         paginated query params.
+	 * }
+	 */
+	public static function get_by_most_forum_posts( $limit = null, $page = null, $search_terms = false, $populate_extras = true, $exclude = false ) {
+		global $wpdb, $bbdb;
+
+		if ( empty( $bbdb ) ) {
+
+			/** This action is documented in bp-forums/bp-forums-screens */
+			do_action( 'bbpress_init' );
+		}
+
+		if ( !empty( $limit ) && !empty( $page ) ) {
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+		}
+
+		if ( !is_user_logged_in() || ( !bp_current_user_can( 'bp_moderate' ) && ( $user_id != bp_loggedin_user_id() ) ) )
+			$hidden_sql = " AND g.status != 'hidden'";
+
+		if ( !empty( $search_terms ) ) {
+			$search_terms_like = '%' . bp_esc_like( $search_terms ) . '%';
+			$search_sql        = $wpdb->prepare( ' AND ( g.name LIKE %s OR g.description LIKE %s ) ', $search_terms_like, $search_terms_like );
+		}
+
+		if ( !empty( $exclude ) ) {
+			$exclude     = implode( ',', wp_parse_id_list( $exclude ) );
+			$exclude_sql = " AND g.id NOT IN ({$exclude})";
+		}
+
+		$bp = buddypress();
+
+		if ( !empty( $user_id ) ) {
+			$user_id = esc_sql( $user_id );
+			$paged_groups = $wpdb->get_results( "SELECT DISTINCT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_groupmeta} gm3, {$bp->groups->table_name_members} m, {$bbdb->forums} f, {$bp->groups->table_name} g WHERE g.id = m.group_id AND g.id = gm1.group_id AND g.id = gm2.group_id AND g.id = gm3.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND (gm3.meta_key = 'forum_id' AND gm3.meta_value = f.forum_id) {$hidden_sql} {$search_sql} AND m.user_id = {$user_id} AND m.is_confirmed = 1 AND m.is_banned = 0 {$exclude_sql} ORDER BY f.posts ASC {$pag_sql}" );
+			$total_groups = $wpdb->get_results( "SELECT COUNT(DISTINCT g.id) FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_groupmeta} gm3, {$bp->groups->table_name_members} m, {$bbdb->forums} f, {$bp->groups->table_name} g WHERE g.id = m.group_id AND g.id = gm1.group_id AND g.id = gm2.group_id AND g.id = gm3.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND (gm3.meta_key = 'forum_id' AND gm3.meta_value = f.forum_id) AND f.posts > 0 {$hidden_sql} {$search_sql} AND m.user_id = {$user_id} AND m.is_confirmed = 1 AND m.is_banned = 0 {$exclude_sql} " );
+		} else {
+			$paged_groups = $wpdb->get_results( "SELECT DISTINCT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_groupmeta} gm3, {$bbdb->forums} f, {$bp->groups->table_name} g WHERE g.id = gm1.group_id AND g.id = gm2.group_id AND g.id = gm3.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND (gm3.meta_key = 'forum_id' AND gm3.meta_value = f.forum_id) AND f.posts > 0 {$hidden_sql} {$search_sql} {$exclude_sql} ORDER BY f.posts ASC {$pag_sql}" );
+			$total_groups = $wpdb->get_var( "SELECT COUNT(DISTINCT g.id) FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_groupmeta} gm3, {$bbdb->forums} f, {$bp->groups->table_name} g WHERE g.id = gm1.group_id AND g.id = gm2.group_id AND g.id = gm3.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND (gm3.meta_key = 'forum_id' AND gm3.meta_value = f.forum_id) {$hidden_sql} {$search_sql} {$exclude_sql}" );
+		}
+
+		if ( !empty( $populate_extras ) ) {
+			foreach ( (array) $paged_groups as $group ) {
+				$group_ids[] = $group->id;
+			}
+			$paged_groups = BP_Groups_Group::get_group_extras( $paged_groups, $group_ids, 'newest' );
+		}
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Get a list of groups whose names start with a given letter.
+	 *
+	 * @param string            $letter          The letter.
+	 * @param int|null          $limit           Optional. The max number of results to return.
+	 *                                           Default: null (no limit).
+	 * @param int|null          $page            Optional. The page offset of results to return.
+	 *                                           Default: null (no limit).
+	 * @param bool              $populate_extras Optional. Whether to fetch extra
+	 *                                           information about the groups. Default: true.
+	 * @param string|array|bool $exclude         Optional. Array or comma-separated list of group
+	 *                                           IDs to exclude from results.
+	 * @return false|array {
+	 *     @type array $groups Array of group objects returned by the
+	 *                         paginated query.
+	 *     @type int   $total  Total count of all groups matching non-
+	 *                         paginated query params.
+	 * }
+	 */
+	public static function get_by_letter( $letter, $limit = null, $page = null, $populate_extras = true, $exclude = false ) {
+		global $wpdb;
+
+		$pag_sql = $hidden_sql = $exclude_sql = '';
+
+		// Multibyte compliance
+		if ( function_exists( 'mb_strlen' ) ) {
+			if ( mb_strlen( $letter, 'UTF-8' ) > 1 || is_numeric( $letter ) || !$letter ) {
+				return false;
+			}
+		} else {
+			if ( strlen( $letter ) > 1 || is_numeric( $letter ) || !$letter ) {
+				return false;
+			}
+		}
+
+		$bp = buddypress();
+
+		if ( !empty( $exclude ) ) {
+			$exclude     = implode( ',', wp_parse_id_list( $exclude ) );
+			$exclude_sql = " AND g.id NOT IN ({$exclude})";
+		}
+
+		if ( !bp_current_user_can( 'bp_moderate' ) )
+			$hidden_sql = " AND status != 'hidden'";
+
+		$letter_like = bp_esc_like( $letter ) . '%';
+
+		if ( !empty( $limit ) && !empty( $page ) ) {
+			$pag_sql      = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+		}
+
+		$total_groups = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT g.id) FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name} g WHERE g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND g.name LIKE %s {$hidden_sql} {$exclude_sql}", $letter_like ) );
+
+		$paged_groups = $wpdb->get_results( $wpdb->prepare( "SELECT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name} g WHERE g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND g.name LIKE %s {$hidden_sql} {$exclude_sql} ORDER BY g.name ASC {$pag_sql}", $letter_like ) );
+
+		if ( !empty( $populate_extras ) ) {
+			foreach ( (array) $paged_groups as $group ) {
+				$group_ids[] = $group->id;
+			}
+			$paged_groups = BP_Groups_Group::get_group_extras( $paged_groups, $group_ids, 'newest' );
+		}
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Get a list of random groups.
+	 *
+	 * Use BP_Groups_Group::get() with 'type' = 'random' instead.
+	 *
+	 * @param int|null          $limit           Optional. The max number of results to return.
+	 *                                           Default: null (no limit).
+	 * @param int|null          $page            Optional. The page offset of results to return.
+	 *                                           Default: null (no limit).
+	 * @param int               $user_id         Optional. If present, groups will be limited to
+	 *                                           those of which the specified user is a member.
+	 * @param string|bool       $search_terms    Optional. Limit groups to those whose name
+	 *                                           or description field contain the search string.
+	 * @param bool              $populate_extras Optional. Whether to fetch extra
+	 *                                           information about the groups. Default: true.
+	 * @param string|array|bool $exclude         Optional. Array or comma-separated list of group
+	 *                                           IDs to exclude from results.
+	 *
+	 * @return array {
+	 *     @type array $groups Array of group objects returned by the
+	 *                         paginated query.
+	 *     @type int   $total  Total count of all groups matching non-
+	 *                         paginated query params.
+	 * }
+	 */
+	public static function get_random( $limit = null, $page = null, $user_id = 0, $search_terms = false, $populate_extras = true, $exclude = false ) {
+		global $wpdb;
+
+		$pag_sql = $hidden_sql = $search_sql = $exclude_sql = '';
+
+		if ( !empty( $limit ) && !empty( $page ) )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		if ( !is_user_logged_in() || ( !bp_current_user_can( 'bp_moderate' ) && ( $user_id != bp_loggedin_user_id() ) ) )
+			$hidden_sql = "AND g.status != 'hidden'";
+
+		if ( !empty( $search_terms ) ) {
+			$search_terms_like = '%' . bp_esc_like( $search_terms ) . '%';
+			$search_sql = $wpdb->prepare( " AND ( g.name LIKE %s OR g.description LIKE %s )", $search_terms_like, $search_terms_like );
+		}
+
+		if ( !empty( $exclude ) ) {
+			$exclude     = wp_parse_id_list( $exclude );
+			$exclude     = esc_sql( implode( ',', $exclude ) );
+			$exclude_sql = " AND g.id NOT IN ({$exclude})";
+		}
+
+		$bp = buddypress();
+
+		if ( !empty( $user_id ) ) {
+			$user_id = esc_sql( $user_id );
+			$paged_groups = $wpdb->get_results( "SELECT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE g.id = m.group_id AND g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' {$hidden_sql} {$search_sql} AND m.user_id = {$user_id} AND m.is_confirmed = 1 AND m.is_banned = 0 {$exclude_sql} ORDER BY rand() {$pag_sql}" );
+			$total_groups = $wpdb->get_var( "SELECT COUNT(DISTINCT m.group_id) FROM {$bp->groups->table_name_members} m LEFT JOIN {$bp->groups->table_name_groupmeta} gm ON m.group_id = gm.group_id INNER JOIN {$bp->groups->table_name} g ON m.group_id = g.id WHERE gm.meta_key = 'last_activity'{$hidden_sql} {$search_sql} AND m.user_id = {$user_id} AND m.is_confirmed = 1 AND m.is_banned = 0 {$exclude_sql}" );
+		} else {
+			$paged_groups = $wpdb->get_results( "SELECT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name} g WHERE g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' {$hidden_sql} {$search_sql} {$exclude_sql} ORDER BY rand() {$pag_sql}" );
+			$total_groups = $wpdb->get_var( "SELECT COUNT(DISTINCT g.id) FROM {$bp->groups->table_name_groupmeta} gm INNER JOIN {$bp->groups->table_name} g ON gm.group_id = g.id WHERE gm.meta_key = 'last_activity'{$hidden_sql} {$search_sql} {$exclude_sql}" );
+		}
+
+		if ( !empty( $populate_extras ) ) {
+			foreach ( (array) $paged_groups as $group ) {
+				$group_ids[] = $group->id;
+			}
+			$paged_groups = BP_Groups_Group::get_group_extras( $paged_groups, $group_ids, 'newest' );
+		}
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Fetch extra data for a list of groups.
+	 *
+	 * This method is used throughout the class, by methods that take a
+	 * $populate_extras parameter.
+	 *
+	 * Data fetched:
+	 *
+	 *     - Logged-in user's status within each group (is_member,
+	 *       is_confirmed, is_pending, is_banned)
+	 *
+	 * @param array        $paged_groups Array of groups.
+	 * @param string|array $group_ids    Array or comma-separated list of IDs matching
+	 *                                   $paged_groups.
+	 * @param string|bool  $type         Not used.
+	 *
+	 * @return array $paged_groups
+	 */
+	public static function get_group_extras( &$paged_groups, &$group_ids, $type = false ) {
+		global $wpdb;
+
+		if ( empty( $group_ids ) )
+			return $paged_groups;
+
+		$bp = buddypress();
+
+		// Sanitize group IDs
+		$group_ids = implode( ',', wp_parse_id_list( $group_ids ) );
+
+		// Fetch the logged-in user's status within each group
+		if ( is_user_logged_in() ) {
+			$user_status_results = $wpdb->get_results( $wpdb->prepare( "SELECT group_id, is_confirmed, invite_sent FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id IN ( {$group_ids} ) AND is_banned = 0", bp_loggedin_user_id() ) );
+		} else {
+			$user_status_results = array();
+		}
+
+		// Reindex
+		$user_status = array();
+		foreach ( $user_status_results as $user_status_result ) {
+			$user_status[ $user_status_result->group_id ] = $user_status_result;
+		}
+
+		for ( $i = 0, $count = count( $paged_groups ); $i < $count; ++$i ) {
+			$is_member = $is_invited = $is_pending = '0';
+			$gid = $paged_groups[ $i ]->id;
+
+			if ( isset( $user_status[ $gid ] ) ) {
+
+				// is_confirmed means the user is a member
+				if ( $user_status[ $gid ]->is_confirmed ) {
+					$is_member = '1';
+
+				// invite_sent means the user has been invited
+				} elseif ( $user_status[ $gid ]->invite_sent ) {
+					$is_invited = '1';
+
+				// User has sent request, but has not been confirmed
+				} else {
+					$is_pending = '1';
+				}
+			}
+
+			$paged_groups[ $i ]->is_member = $is_member;
+			$paged_groups[ $i ]->is_invited = $is_invited;
+			$paged_groups[ $i ]->is_pending = $is_pending;
+		}
+
+		if ( is_user_logged_in() ) {
+			$user_banned = $wpdb->get_col( $wpdb->prepare( "SELECT group_id FROM {$bp->groups->table_name_members} WHERE is_banned = 1 AND user_id = %d AND group_id IN ( {$group_ids} )", bp_loggedin_user_id() ) );
+		} else {
+			$user_banned = array();
+		}
+
+		for ( $i = 0, $count = count( $paged_groups ); $i < $count; ++$i ) {
+			$paged_groups[$i]->is_banned = false;
+
+			foreach ( (array) $user_banned as $group_id ) {
+				if ( $group_id == $paged_groups[$i]->id ) {
+					$paged_groups[$i]->is_banned = true;
+				}
+			}
+		}
+
+		return $paged_groups;
+	}
+
+	/**
+	 * Delete all invitations to a given group.
+	 *
+	 * @param int $group_id ID of the group whose invitations are being deleted.
+	 *
+	 * @return int|null Number of rows records deleted on success, null on
+	 *                  failure.
+	 */
+	public static function delete_all_invites( $group_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->groups->table_name_members} WHERE group_id = %d AND invite_sent = 1", $group_id ) );
+	}
+
+	/**
+	 * Get a total group count for the site.
+	 *
+	 * Will include hidden groups in the count only if
+	 * current_user_can( 'bp_moderate' ).
+	 *
+	 * @return int Group count.
+	 */
+	public static function get_total_group_count() {
+		global $wpdb;
+
+		$hidden_sql = '';
+		if ( !bp_current_user_can( 'bp_moderate' ) )
+			$hidden_sql = "WHERE status != 'hidden'";
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( "SELECT COUNT(id) FROM {$bp->groups->table_name} {$hidden_sql}" );
+	}
+
+	/**
+	 * Get global count of forum topics in public groups (legacy forums).
+	 *
+	 * @param string $type Optional. If 'unreplied', count will be limited to
+	 *                     those topics that have received no replies.
+	 *
+	 * @return int Forum topic count.
+	 */
+	public static function get_global_forum_topic_count( $type ) {
+		global $bbdb, $wpdb;
+
+		$bp = buddypress();
+
+		if ( 'unreplied' == $type )
+			$bp->groups->filter_sql = ' AND t.topic_posts = 1';
+
+		/**
+		 * Filters the portion of the SQL related to global count of forum topics in public groups.
+		 *
+		 * https://buddypress.trac.wordpress.org/ticket/4306.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $filter_sql SQL portion for the query.
+		 * @param string $type       Type of forum topics to query for.
+		 */
+		$extra_sql = apply_filters( 'get_global_forum_topic_count_extra_sql', $bp->groups->filter_sql, $type );
+
+		// Make sure the $extra_sql begins with an AND
+		if ( 'AND' != substr( trim( strtoupper( $extra_sql ) ), 0, 3 ) )
+			$extra_sql = ' AND ' . $extra_sql;
+
+		return $wpdb->get_var( "SELECT COUNT(t.topic_id) FROM {$bbdb->topics} AS t, {$bp->groups->table_name} AS g LEFT JOIN {$bp->groups->table_name_groupmeta} AS gm ON g.id = gm.group_id WHERE (gm.meta_key = 'forum_id' AND gm.meta_value = t.forum_id) AND g.status = 'public' AND t.topic_status = '0' AND t.topic_sticky != '2' {$extra_sql} " );
+	}
+
+	/**
+	 * Get the member count for a group.
+	 *
+	 * @param int $group_id Group ID.
+	 *
+	 * @return int Count of confirmed members for the group.
+	 */
+	public static function get_total_member_count( $group_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->groups->table_name_members} WHERE group_id = %d AND is_confirmed = 1 AND is_banned = 0", $group_id ) );
+	}
+
+	/**
+	 * Get a total count of all topics of a given status, across groups/forums.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string      $status       Which group type to count. 'public', 'private',
+	 *                                  'hidden', or 'all'. Default: 'public'.
+	 * @param string|bool $search_terms Provided search terms.
+	 *
+	 * @return int The topic count
+	 */
+	public static function get_global_topic_count( $status = 'public', $search_terms = false ) {
+		global $bbdb, $wpdb;
+
+		switch ( $status ) {
+			case 'all' :
+				$status_sql = '';
+				break;
+
+			case 'hidden' :
+				$status_sql = "AND g.status = 'hidden'";
+				break;
+
+			case 'private' :
+				$status_sql = "AND g.status = 'private'";
+				break;
+
+			case 'public' :
+			default :
+				$status_sql = "AND g.status = 'public'";
+				break;
+		}
+
+		$bp = buddypress();
+
+		$sql = array();
+
+		$sql['select'] = "SELECT COUNT(t.topic_id)";
+		$sql['from']   = "FROM {$bbdb->topics} AS t INNER JOIN {$bp->groups->table_name_groupmeta} AS gm ON t.forum_id = gm.meta_value INNER JOIN {$bp->groups->table_name} AS g ON gm.group_id = g.id";
+		$sql['where']  = "WHERE gm.meta_key = 'forum_id' {$status_sql} AND t.topic_status = '0' AND t.topic_sticky != '2'";
+
+		if ( !empty( $search_terms ) ) {
+			$search_terms_like = '%' . bp_esc_like( $search_terms ) . '%';
+			$sql['where'] .= $wpdb->prepare( " AND ( t.topic_title LIKE %s )", $search_terms_like );
+		}
+
+		return $wpdb->get_var( implode( ' ', $sql ) );
+	}
+
+	/**
+	 * Get an array containing ids for each group type.
+	 *
+	 * A bit of a kludge workaround for some issues
+	 * with bp_has_groups().
+	 *
+	 * @since 1.7.0
+	 *
+	 * @return array
+	 */
+	public static function get_group_type_ids() {
+		global $wpdb;
+
+		$bp  = buddypress();
+		$ids = array();
+
+		$ids['all']     = $wpdb->get_col( "SELECT id FROM {$bp->groups->table_name}" );
+		$ids['public']  = $wpdb->get_col( "SELECT id FROM {$bp->groups->table_name} WHERE status = 'public'" );
+		$ids['private'] = $wpdb->get_col( "SELECT id FROM {$bp->groups->table_name} WHERE status = 'private'" );
+		$ids['hidden']  = $wpdb->get_col( "SELECT id FROM {$bp->groups->table_name} WHERE status = 'hidden'" );
+
+		return $ids;
+	}
+}
Index: /tags/2.4.3/src/bp-groups/classes/class-bp-groups-member-suggestions.php
===================================================================
--- /tags/2.4.3/src/bp-groups/classes/class-bp-groups-member-suggestions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/classes/class-bp-groups-member-suggestions.php	(revision 10410)
@@ -0,0 +1,176 @@
+<?php
+/**
+ * BuddyPress Groups Classes.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Adds support for user at-mentions (for users in a specific Group) to the Suggestions API.
+ *
+ * @since 2.1.0
+ */
+class BP_Groups_Member_Suggestions extends BP_Members_Suggestions {
+
+	/**
+	 * Default arguments for this suggestions service.
+	 *
+	 * @since 2.1.0
+	 * @var array $args {
+	 *     @type int    $group_id     Positive integers will restrict the search to members in that group.
+	 *                                Negative integers will restrict the search to members in every other group.
+	 *     @type int    $limit        Maximum number of results to display. Default: 16.
+	 *     @type bool   $only_friends If true, only match the current user's friends. Default: false.
+	 *     @type string $term         The suggestion service will try to find results that contain this string.
+	 *                                Mandatory.
+	 * }
+	 */
+	protected $default_args = array(
+		'group_id'     => 0,
+		'limit'        => 16,
+		'only_friends' => false,
+		'term'         => '',
+		'type'         => '',
+	);
+
+
+	/**
+	 * Validate and sanitise the parameters for the suggestion service query.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return true|WP_Error If validation fails, return a WP_Error object. On success, return true (bool).
+	 */
+	public function validate() {
+		$this->args['group_id'] = (int) $this->args['group_id'];
+
+		/**
+		 * Filters the arguments used to validate and sanitize suggestion service query.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param array                        $args  Array of arguments for the suggestion service query.
+		 * @param BP_Groups_Member_Suggestions $this  Instance of the current suggestion class.
+		 */
+		$this->args             = apply_filters( 'bp_groups_member_suggestions_args', $this->args, $this );
+
+		// Check for invalid or missing mandatory parameters.
+		if ( ! $this->args['group_id'] || ! bp_is_active( 'groups' ) ) {
+			return new WP_Error( 'missing_requirement' );
+		}
+
+		// Check that the specified group_id exists, and that the current user can access it.
+		$the_group = groups_get_group( array(
+			'group_id'        => absint( $this->args['group_id'] ),
+			'populate_extras' => true,
+		) );
+
+		if ( $the_group->id === 0 || ! $the_group->user_has_access ) {
+			return new WP_Error( 'access_denied' );
+		}
+
+		/**
+		 * Filters the validation results for the suggestion service query.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param bool|WP_Error                $value True if valid, WP_Error if not.
+		 * @param BP_Groups_Member_Suggestions $this  Instance of the current suggestion class.
+		 */
+		return apply_filters( 'bp_groups_member_suggestions_validate_args', parent::validate(), $this );
+	}
+
+	/**
+	 * Find and return a list of username suggestions that match the query.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return array|WP_Error Array of results. If there were problems, returns a WP_Error object.
+	 */
+	public function get_suggestions() {
+		$user_query = array(
+			'count_total'     => '',  // Prevents total count
+			'populate_extras' => false,
+			'type'            => 'alphabetical',
+
+			'group_role'      => array( 'admin', 'member', 'mod' ),
+			'page'            => 1,
+			'per_page'        => $this->args['limit'],
+			'search_terms'    => $this->args['term'],
+			'search_wildcard' => 'right',
+		);
+
+		// Only return matches of friends of this user.
+		if ( $this->args['only_friends'] && is_user_logged_in() ) {
+			$user_query['user_id'] = get_current_user_id();
+		}
+
+		// Positive Group IDs will restrict the search to members in that group.
+		if ( $this->args['group_id'] > 0 ) {
+			$user_query['group_id'] = $this->args['group_id'];
+
+		// Negative Group IDs will restrict the search to members in every other group.
+		} else {
+			$group_query = array(
+				'count_total'     => '',  // Prevents total count
+				'populate_extras' => false,
+				'type'            => 'alphabetical',
+
+				'group_id'        => absint( $this->args['group_id'] ),
+				'group_role'      => array( 'admin', 'member', 'mod' ),
+				'page'            => 1,
+			);
+			$group_users = new BP_Group_Member_Query( $group_query );
+
+			if ( $group_users->results ) {
+				$user_query['exclude'] = wp_list_pluck( $group_users->results, 'ID' );
+			} else {
+				$user_query['include'] = array( 0 );
+			}
+		}
+
+		/**
+		 * Filters the arguments for the user query for the Suggestion API.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param array                        $user_query Array of arguments for the query.
+		 * @param BP_Groups_Member_Suggestions $this       Instance of the current suggestion class.
+		 */
+		$user_query = apply_filters( 'bp_groups_member_suggestions_query_args', $user_query, $this );
+		if ( is_wp_error( $user_query ) ) {
+			return $user_query;
+		}
+
+
+		if ( isset( $user_query['group_id'] ) ) {
+			$user_query = new BP_Group_Member_Query( $user_query );
+		} else {
+			$user_query = new BP_User_Query( $user_query );
+		}
+
+		$results = array();
+		foreach ( $user_query->results as $user ) {
+			$result        = new stdClass();
+			$result->ID    = $user->user_nicename;
+			$result->image = bp_core_fetch_avatar( array( 'html' => false, 'item_id' => $user->ID ) );
+			$result->name  = bp_core_get_user_displayname( $user->ID );
+
+			$results[] = $result;
+		}
+
+		/**
+		 * Filters the results of the member suggestions user query.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param array                        $results Array of member suggestions.
+		 * @param BP_Groups_Member_Suggestions $this    Instance of the current suggestion class.
+		 */
+		return apply_filters( 'bp_groups_member_suggestions_get_suggestions', $results, $this );
+	}
+}
Index: /tags/2.4.3/src/bp-groups/classes/class-bp-groups-member.php
===================================================================
--- /tags/2.4.3/src/bp-groups/classes/class-bp-groups-member.php	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/classes/class-bp-groups-member.php	(revision 10410)
@@ -0,0 +1,1154 @@
+<?php
+/**
+ * BuddyPress Groups Classes.
+ *
+ * @package BuddyPress
+ * @subpackage GroupsClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BuddyPress Group Membership object.
+ */
+class BP_Groups_Member {
+
+	/**
+	 * ID of the membership.
+	 *
+	 * @var int
+	 */
+	var $id;
+
+	/**
+	 * ID of the group associated with the membership.
+	 *
+	 * @var int
+	 */
+	var $group_id;
+
+	/**
+	 * ID of the user associated with the membership.
+	 *
+	 * @var int
+	 */
+	var $user_id;
+
+	/**
+	 * ID of the user whose invitation initiated the membership.
+	 *
+	 * @var int
+	 */
+	var $inviter_id;
+
+	/**
+	 * Whether the member is an admin of the group.
+	 *
+	 * @var int
+	 */
+	var $is_admin;
+
+	/**
+	 * Whether the member is a mod of the group.
+	 *
+	 * @var int
+	 */
+	var $is_mod;
+
+	/**
+	 * Whether the member is banned from the group.
+	 *
+	 * @var int
+	 */
+	var $is_banned;
+
+	/**
+	 * Title used to describe the group member's role in the group.
+	 *
+	 * Eg, 'Group Admin'.
+	 *
+	 * @var int
+	 */
+	var $user_title;
+
+	/**
+	 * Last modified date of the membership.
+	 *
+	 * This value is updated when, eg, invitations are accepted.
+	 *
+	 * @var string
+	 */
+	var $date_modified;
+
+	/**
+	 * Whether the membership has been confirmed.
+	 *
+	 * @var int
+	 */
+	var $is_confirmed;
+
+	/**
+	 * Comments associated with the membership.
+	 *
+	 * In BP core, these are limited to the optional message users can
+	 * include when requesting membership to a private group.
+	 *
+	 * @var string
+	 */
+	var $comments;
+
+	/**
+	 * Whether an invitation has been sent for this membership.
+	 *
+	 * The purpose of this flag is to mark when an invitation has been
+	 * "drafted" (the user has been added via the interface at Send
+	 * Invites), but the Send button has not been pressed, so the
+	 * invitee has not yet been notified.
+	 *
+	 * @var int
+	 */
+	var $invite_sent;
+
+	/**
+	 * WP_User object representing the membership's user.
+	 *
+	 * @var WP_User
+	 */
+	var $user;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @param int      $user_id  Optional. Along with $group_id, can be used to
+	 *                           look up a membership.
+	 * @param int      $group_id Optional. Along with $user_id, can be used to
+	 *                           look up a membership.
+	 * @param int|bool $id       Optional. The unique ID of the membership object.
+	 * @param bool     $populate Whether to populate the properties of the
+	 *                           located membership. Default: true.
+	 */
+	public function __construct( $user_id = 0, $group_id = 0, $id = false, $populate = true ) {
+
+		// User and group are not empty, and ID is
+		if ( !empty( $user_id ) && !empty( $group_id ) && empty( $id ) ) {
+			$this->user_id  = $user_id;
+			$this->group_id = $group_id;
+
+			if ( !empty( $populate ) ) {
+				$this->populate();
+			}
+		}
+
+		// ID is not empty
+		if ( !empty( $id ) ) {
+			$this->id = $id;
+
+			if ( !empty( $populate ) ) {
+				$this->populate();
+			}
+		}
+	}
+
+	/**
+	 * Populate the object's properties.
+	 */
+	public function populate() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( $this->user_id && $this->group_id && !$this->id )
+			$sql = $wpdb->prepare( "SELECT * FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d", $this->user_id, $this->group_id );
+
+		if ( !empty( $this->id ) )
+			$sql = $wpdb->prepare( "SELECT * FROM {$bp->groups->table_name_members} WHERE id = %d", $this->id );
+
+		$member = $wpdb->get_row($sql);
+
+		if ( !empty( $member ) ) {
+			$this->id            = $member->id;
+			$this->group_id      = $member->group_id;
+			$this->user_id       = $member->user_id;
+			$this->inviter_id    = $member->inviter_id;
+			$this->is_admin      = $member->is_admin;
+			$this->is_mod        = $member->is_mod;
+			$this->is_banned     = $member->is_banned;
+			$this->user_title    = $member->user_title;
+			$this->date_modified = $member->date_modified;
+			$this->is_confirmed  = $member->is_confirmed;
+			$this->comments      = $member->comments;
+			$this->invite_sent   = $member->invite_sent;
+
+			$this->user = new BP_Core_User( $this->user_id );
+		}
+	}
+
+	/**
+	 * Save the membership data to the database.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function save() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$this->user_id       = apply_filters( 'groups_member_user_id_before_save',       $this->user_id,       $this->id );
+		$this->group_id      = apply_filters( 'groups_member_group_id_before_save',      $this->group_id,      $this->id );
+		$this->inviter_id    = apply_filters( 'groups_member_inviter_id_before_save',    $this->inviter_id,    $this->id );
+		$this->is_admin      = apply_filters( 'groups_member_is_admin_before_save',      $this->is_admin,      $this->id );
+		$this->is_mod        = apply_filters( 'groups_member_is_mod_before_save',        $this->is_mod,        $this->id );
+		$this->is_banned     = apply_filters( 'groups_member_is_banned_before_save',     $this->is_banned,     $this->id );
+		$this->user_title    = apply_filters( 'groups_member_user_title_before_save',    $this->user_title,    $this->id );
+		$this->date_modified = apply_filters( 'groups_member_date_modified_before_save', $this->date_modified, $this->id );
+		$this->is_confirmed  = apply_filters( 'groups_member_is_confirmed_before_save',  $this->is_confirmed,  $this->id );
+		$this->comments      = apply_filters( 'groups_member_comments_before_save',      $this->comments,      $this->id );
+		$this->invite_sent   = apply_filters( 'groups_member_invite_sent_before_save',   $this->invite_sent,   $this->id );
+
+		/**
+		 * Fires before the current group membership item gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Groups_Member $this Current instance of the group membership item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'groups_member_before_save', array( &$this ) );
+
+		// The following properties are required; bail if not met.
+		if ( empty( $this->user_id ) || empty( $this->group_id ) ) {
+			return false;
+		}
+
+		if ( !empty( $this->id ) ) {
+			$sql = $wpdb->prepare( "UPDATE {$bp->groups->table_name_members} SET inviter_id = %d, is_admin = %d, is_mod = %d, is_banned = %d, user_title = %s, date_modified = %s, is_confirmed = %d, comments = %s, invite_sent = %d WHERE id = %d", $this->inviter_id, $this->is_admin, $this->is_mod, $this->is_banned, $this->user_title, $this->date_modified, $this->is_confirmed, $this->comments, $this->invite_sent, $this->id );
+		} else {
+			// Ensure that user is not already a member of the group before inserting
+			if ( $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d AND is_confirmed = 1 LIMIT 1", $this->user_id, $this->group_id ) ) ) {
+				return false;
+			}
+
+			$sql = $wpdb->prepare( "INSERT INTO {$bp->groups->table_name_members} ( user_id, group_id, inviter_id, is_admin, is_mod, is_banned, user_title, date_modified, is_confirmed, comments, invite_sent ) VALUES ( %d, %d, %d, %d, %d, %d, %s, %s, %d, %s, %d )", $this->user_id, $this->group_id, $this->inviter_id, $this->is_admin, $this->is_mod, $this->is_banned, $this->user_title, $this->date_modified, $this->is_confirmed, $this->comments, $this->invite_sent );
+		}
+
+		if ( !$wpdb->query( $sql ) )
+			return false;
+
+		$this->id = $wpdb->insert_id;
+
+		// Update the user's group count
+		self::refresh_total_group_count_for_user( $this->user_id );
+
+		// Update the group's member count
+		self::refresh_total_member_count_for_group( $this->group_id );
+
+		/**
+		 * Fires after the current group membership item has been saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Groups_Member $this Current instance of the group membership item has been saved. Passed by reference.
+		 */
+		do_action_ref_array( 'groups_member_after_save', array( &$this ) );
+
+		return true;
+	}
+
+	/**
+	 * Promote a member to a new status.
+	 *
+	 * @param string $status The new status. 'mod' or 'admin'.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function promote( $status = 'mod' ) {
+		if ( 'mod' == $status ) {
+			$this->is_admin   = 0;
+			$this->is_mod     = 1;
+			$this->user_title = __( 'Group Mod', 'buddypress' );
+		}
+
+		if ( 'admin' == $status ) {
+			$this->is_admin   = 1;
+			$this->is_mod     = 0;
+			$this->user_title = __( 'Group Admin', 'buddypress' );
+		}
+
+		return $this->save();
+	}
+
+	/**
+	 * Demote membership to Member status (non-admin, non-mod).
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function demote() {
+		$this->is_mod     = 0;
+		$this->is_admin   = 0;
+		$this->user_title = false;
+
+		return $this->save();
+	}
+
+	/**
+	 * Ban the user from the group.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function ban() {
+		if ( !empty( $this->is_admin ) )
+			return false;
+
+		$this->is_mod = 0;
+		$this->is_banned = 1;
+
+		return $this->save();
+	}
+
+	/**
+	 * Unban the user from the group.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function unban() {
+		if ( !empty( $this->is_admin ) )
+			return false;
+
+		$this->is_banned = 0;
+
+		return $this->save();
+	}
+
+	/**
+	 * Mark a pending invitation as accepted.
+	 */
+	public function accept_invite() {
+		$this->inviter_id    = 0;
+		$this->is_confirmed  = 1;
+		$this->date_modified = bp_core_current_time();
+	}
+
+	/**
+	 * Confirm a membership request.
+	 */
+	public function accept_request() {
+		$this->is_confirmed = 1;
+		$this->date_modified = bp_core_current_time();
+	}
+
+	/**
+	 * Remove the current membership.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function remove() {
+		global $wpdb;
+
+		/**
+		 * Fires before a member is removed from a group.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param BP_Groups_Member $this Current group membership object.
+		 */
+		do_action_ref_array( 'groups_member_before_remove', array( $this ) );
+
+		$bp  = buddypress();
+		$sql = $wpdb->prepare( "DELETE FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d", $this->user_id, $this->group_id );
+
+		if ( !$result = $wpdb->query( $sql ) )
+			return false;
+
+		// Update the user's group count
+		self::refresh_total_group_count_for_user( $this->user_id );
+
+		// Update the group's member count
+		self::refresh_total_member_count_for_group( $this->group_id );
+
+		/**
+		 * Fires after a member is removed from a group.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param BP_Groups_Member $this Current group membership object.
+		 */
+		do_action_ref_array( 'groups_member_after_remove', array( $this ) );
+
+		return $result;
+	}
+
+	/** Static Methods ****************************************************/
+
+	/**
+	 * Refresh the total_group_count for a user.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param int $user_id ID of the user.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public static function refresh_total_group_count_for_user( $user_id ) {
+		return bp_update_user_meta( $user_id, 'total_group_count', (int) self::total_group_count( $user_id ) );
+	}
+
+	/**
+	 * Refresh the total_member_count for a group.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public static function refresh_total_member_count_for_group( $group_id ) {
+		return groups_update_groupmeta( $group_id, 'total_member_count', (int) BP_Groups_Group::get_total_member_count( $group_id ) );
+	}
+
+	/**
+	 * Delete a membership, based on user + group IDs.
+	 *
+	 * @param int $user_id  ID of the user.
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return True on success, false on failure.
+	 */
+	public static function delete( $user_id, $group_id ) {
+		global $wpdb;
+
+		/**
+		 * Fires before a group membership is deleted.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param int $user_id  ID of the user.
+		 * @param int $group_id ID of the group.
+		 */
+		do_action( 'bp_groups_member_before_delete', $user_id, $group_id );
+
+		$bp = buddypress();
+		$remove = $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d", $user_id, $group_id ) );
+
+		// Update the user's group count
+		self::refresh_total_group_count_for_user( $user_id );
+
+		// Update the group's member count
+		self::refresh_total_member_count_for_group( $group_id );
+
+		/**
+		 * Fires after a member is removed from a group.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param int $user_id  ID of the user.
+		 * @param int $group_id ID of the group.
+		 */
+		do_action( 'bp_groups_member_after_delete', $user_id, $group_id );
+
+		return $remove;
+	}
+
+	/**
+	 * Get the IDs of the groups of which a specified user is a member.
+	 *
+	 * @param int      $user_id ID of the user.
+	 * @param int|bool $limit   Optional. Max number of results to return.
+	 *                          Default: false (no limit).
+	 * @param int|bool $page    Optional. Page offset of results to return.
+	 *                          Default: false (no limit).
+	 * @return array {
+	 *     @type array $groups Array of groups returned by paginated query.
+	 *     @type int   $total  Count of groups matching query.
+	 * }
+	 */
+	public static function get_group_ids( $user_id, $limit = false, $page = false ) {
+		global $wpdb;
+
+		$pag_sql = '';
+		if ( !empty( $limit ) && !empty( $page ) )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		$bp = buddypress();
+
+		// If the user is logged in and viewing their own groups, we can show hidden and private groups
+		if ( $user_id != bp_loggedin_user_id() ) {
+			$group_sql = $wpdb->prepare( "SELECT DISTINCT m.group_id FROM {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE g.status != 'hidden' AND m.user_id = %d AND m.is_confirmed = 1 AND m.is_banned = 0{$pag_sql}", $user_id );
+			$total_groups = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT m.group_id) FROM {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE g.status != 'hidden' AND m.user_id = %d AND m.is_confirmed = 1 AND m.is_banned = 0", $user_id ) );
+		} else {
+			$group_sql = $wpdb->prepare( "SELECT DISTINCT group_id FROM {$bp->groups->table_name_members} WHERE user_id = %d AND is_confirmed = 1 AND is_banned = 0{$pag_sql}", $user_id );
+			$total_groups = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT group_id) FROM {$bp->groups->table_name_members} WHERE user_id = %d AND is_confirmed = 1 AND is_banned = 0", $user_id ) );
+		}
+
+		$groups = $wpdb->get_col( $group_sql );
+
+		return array( 'groups' => $groups, 'total' => (int) $total_groups );
+	}
+
+	/**
+	 * Get the IDs of the groups of which a specified user is a member, sorted by the date joined.
+	 *
+	 * @param int         $user_id ID of the user.
+	 * @param int|bool    $limit   Optional. Max number of results to return.
+	 *                             Default: false (no limit).
+	 * @param int|bool    $page    Optional. Page offset of results to return.
+	 *                             Default: false (no limit).
+	 * @param string|bool $filter  Optional. Limit results to groups whose name or
+	 *                             description field matches search terms.
+	 * @return array {
+	 *     @type array $groups Array of groups returned by paginated query.
+	 *     @type int   $total  Count of groups matching query.
+	 * }
+	 */
+	public static function get_recently_joined( $user_id, $limit = false, $page = false, $filter = false ) {
+		global $wpdb;
+
+		$user_id_sql = $pag_sql = $hidden_sql = $filter_sql = '';
+
+		$user_id_sql = $wpdb->prepare( 'm.user_id = %d', $user_id );
+
+		if ( !empty( $limit ) && !empty( $page ) )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		if ( !empty( $filter ) ) {
+			$search_terms_like = '%' . bp_esc_like( $filter ) . '%';
+			$filter_sql = $wpdb->prepare( " AND ( g.name LIKE %s OR g.description LIKE %s )", $search_terms_like, $search_terms_like );
+		}
+
+		if ( $user_id != bp_loggedin_user_id() )
+			$hidden_sql = " AND g.status != 'hidden'";
+
+		$bp = buddypress();
+
+		$paged_groups = $wpdb->get_results( "SELECT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE g.id = m.group_id AND g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count'{$hidden_sql}{$filter_sql} AND {$user_id_sql} AND m.is_confirmed = 1 AND m.is_banned = 0 ORDER BY m.date_modified DESC {$pag_sql}" );
+		$total_groups = $wpdb->get_var( "SELECT COUNT(DISTINCT m.group_id) FROM {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE m.group_id = g.id{$hidden_sql}{$filter_sql} AND {$user_id_sql} AND m.is_banned = 0 AND m.is_confirmed = 1 ORDER BY m.date_modified DESC" );
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Get the IDs of the groups of which a specified user is an admin.
+	 *
+	 * @param int         $user_id ID of the user.
+	 * @param int|bool    $limit   Optional. Max number of results to return.
+	 *                             Default: false (no limit).
+	 * @param int|bool    $page    Optional. Page offset of results to return.
+	 *                             Default: false (no limit).
+	 * @param string|bool $filter  Optional. Limit results to groups whose name or
+	 *                             description field matches search terms.
+	 *
+	 * @return array {
+	 *     @type array $groups Array of groups returned by paginated query.
+	 *     @type int   $total  Count of groups matching query.
+	 * }
+	 */
+	public static function get_is_admin_of( $user_id, $limit = false, $page = false, $filter = false ) {
+		global $wpdb;
+
+		$user_id_sql = $pag_sql = $hidden_sql = $filter_sql = '';
+
+		$user_id_sql = $wpdb->prepare( 'm.user_id = %d', $user_id );
+
+		if ( !empty( $limit ) && !empty( $page ) )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		if ( !empty( $filter ) ) {
+			$search_terms_like = '%' . bp_esc_like( $filter ) . '%';
+			$filter_sql = $wpdb->prepare( " AND ( g.name LIKE %s OR g.description LIKE %s )", $search_terms_like, $search_terms_like );
+		}
+
+		if ( $user_id != bp_loggedin_user_id() )
+			$hidden_sql = " AND g.status != 'hidden'";
+
+		$bp = buddypress();
+
+		$paged_groups = $wpdb->get_results( "SELECT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE g.id = m.group_id AND g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count'{$hidden_sql}{$filter_sql} AND {$user_id_sql} AND m.is_confirmed = 1 AND m.is_banned = 0 AND m.is_admin = 1 ORDER BY m.date_modified ASC {$pag_sql}" );
+		$total_groups = $wpdb->get_var( "SELECT COUNT(DISTINCT m.group_id) FROM {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE m.group_id = g.id{$hidden_sql}{$filter_sql} AND {$user_id_sql} AND m.is_confirmed = 1 AND m.is_banned = 0 AND m.is_admin = 1 ORDER BY date_modified ASC" );
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Get the IDs of the groups of which a specified user is a moderator.
+	 *
+	 * @param int         $user_id ID of the user.
+	 * @param int|bool    $limit   Optional. Max number of results to return.
+	 *                             Default: false (no limit).
+	 * @param int|bool    $page    Optional. Page offset of results to return.
+	 *                             Default: false (no limit).
+	 * @param string|bool $filter  Optional. Limit results to groups whose name or
+	 *                             description field matches search terms.
+	 *
+	 * @return array {
+	 *     @type array $groups Array of groups returned by paginated query.
+	 *     @type int   $total  Count of groups matching query.
+	 * }
+	 */
+	public static function get_is_mod_of( $user_id, $limit = false, $page = false, $filter = false ) {
+		global $wpdb;
+
+		$user_id_sql = $pag_sql = $hidden_sql = $filter_sql = '';
+
+		$user_id_sql = $wpdb->prepare( 'm.user_id = %d', $user_id );
+
+		if ( !empty( $limit ) && !empty( $page ) )
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		if ( !empty( $filter ) ) {
+			$search_terms_like = '%' . bp_esc_like( $filter ) . '%';
+			$filter_sql = $wpdb->prepare( " AND ( g.name LIKE %s OR g.description LIKE %s )", $search_terms_like, $search_terms_like );
+		}
+
+		if ( $user_id != bp_loggedin_user_id() )
+			$hidden_sql = " AND g.status != 'hidden'";
+
+		$bp = buddypress();
+
+		$paged_groups = $wpdb->get_results( "SELECT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE g.id = m.group_id AND g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count'{$hidden_sql}{$filter_sql} AND {$user_id_sql} AND m.is_confirmed = 1 AND m.is_banned = 0 AND m.is_mod = 1 ORDER BY m.date_modified ASC {$pag_sql}" );
+		$total_groups = $wpdb->get_var( "SELECT COUNT(DISTINCT m.group_id) FROM {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE m.group_id = g.id{$hidden_sql}{$filter_sql} AND {$user_id_sql} AND m.is_confirmed = 1 AND m.is_banned = 0 AND m.is_mod = 1 ORDER BY date_modified ASC" );
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Get the groups of which a specified user is banned from.
+	 *
+	 * @since 2.4
+	 *
+	 * @param int         $user_id ID of the user.
+	 * @param int|bool    $limit   Optional. Max number of results to return.
+	 *                             Default: false (no limit).
+	 * @param int|bool    $page    Optional. Page offset of results to return.
+	 *                             Default: false (no limit).
+	 * @param string|bool $filter  Optional. Limit results to groups whose name or
+	 *                             description field matches search terms.
+	 * @return array {
+	 *     @type array $groups Array of groups returned by paginated query.
+	 *     @type int   $total  Count of groups matching query.
+	 * }
+	 */
+	public static function get_is_banned_of( $user_id, $limit = false, $page = false, $filter = false ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$user_id_sql = $pag_sql = $hidden_sql = $filter_sql = '';
+		$user_id_sql = $wpdb->prepare( 'm.user_id = %d', $user_id );
+
+		if ( $limit && $page ) {
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit ), intval( $limit ) );
+	  }
+
+		if ( $filter ) {
+			$search_terms_like = '%' . bp_esc_like( $filter ) . '%';
+			$filter_sql        = $wpdb->prepare( " AND ( g.name LIKE %s OR g.description LIKE %s )", $search_terms_like, $search_terms_like );
+		}
+
+		if ( $user_id !== bp_loggedin_user_id() && ! bp_current_user_can( 'bp_moderate' ) ) {
+			$hidden_sql = " AND g.status != 'hidden'";
+		}
+
+		$paged_groups = $wpdb->get_results( "SELECT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE g.id = m.group_id AND g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count'{$hidden_sql}{$filter_sql} AND {$user_id_sql} AND m.is_banned = 1  ORDER BY m.date_modified ASC {$pag_sql}" );
+		$total_groups = $wpdb->get_var( "SELECT COUNT(DISTINCT m.group_id) FROM {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE m.group_id = g.id{$hidden_sql}{$filter_sql} AND {$user_id_sql} AND m.is_banned = 1 ORDER BY date_modified ASC" );
+
+		return array( 'groups' => $paged_groups, 'total' => $total_groups );
+	}
+
+	/**
+	 * Get the count of groups of which the specified user is a member.
+	 *
+	 * @param int $user_id Optional. Default: ID of the displayed user.
+	 *
+	 * @return int Group count.
+	 */
+	public static function total_group_count( $user_id = 0 ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			$user_id = bp_displayed_user_id();
+
+		$bp = buddypress();
+
+		if ( $user_id != bp_loggedin_user_id() && !bp_current_user_can( 'bp_moderate' ) ) {
+			return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT m.group_id) FROM {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE m.group_id = g.id AND g.status != 'hidden' AND m.user_id = %d AND m.is_confirmed = 1 AND m.is_banned = 0", $user_id ) );
+		} else {
+			return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT m.group_id) FROM {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE m.group_id = g.id AND m.user_id = %d AND m.is_confirmed = 1 AND m.is_banned = 0", $user_id ) );
+		}
+	}
+
+	/**
+	 * Get a user's outstanding group invitations.
+	 *
+	 * @param int               $user_id ID of the invitee.
+	 * @param int|bool          $limit   Optional. Max number of results to return.
+	 *                                   Default: false (no limit).
+	 * @param int|bool          $page    Optional. Page offset of results to return.
+	 *                                   Default: false (no limit).
+	 * @param string|array|bool $exclude Optional. Array or comma-separated list
+	 *                                   of group IDs to exclude from results.
+	 *
+	 * @return array {
+	 *     @type array $groups Array of groups returned by paginated query.
+	 *     @type int   $total  Count of groups matching query.
+	 * }
+	 */
+	public static function get_invites( $user_id, $limit = false, $page = false, $exclude = false ) {
+		global $wpdb;
+
+		$pag_sql = ( !empty( $limit ) && !empty( $page ) ) ? $wpdb->prepare( " LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) ) : '';
+
+		if ( !empty( $exclude ) ) {
+			$exclude     = implode( ',', wp_parse_id_list( $exclude ) );
+			$exclude_sql = " AND g.id NOT IN ({$exclude})";
+		} else {
+			$exclude_sql = '';
+		}
+
+		$bp = buddypress();
+
+		$paged_groups = $wpdb->get_results( $wpdb->prepare( "SELECT g.*, gm1.meta_value as total_member_count, gm2.meta_value as last_activity FROM {$bp->groups->table_name_groupmeta} gm1, {$bp->groups->table_name_groupmeta} gm2, {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE g.id = m.group_id AND g.id = gm1.group_id AND g.id = gm2.group_id AND gm2.meta_key = 'last_activity' AND gm1.meta_key = 'total_member_count' AND m.is_confirmed = 0 AND m.inviter_id != 0 AND m.invite_sent = 1 AND m.user_id = %d {$exclude_sql} ORDER BY m.date_modified ASC {$pag_sql}", $user_id ) );
+
+		return array( 'groups' => $paged_groups, 'total' => self::get_invite_count_for_user( $user_id ) );
+	}
+
+	/**
+	 * Gets the total group invite count for a user.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param int $user_id The user ID.
+	 *
+	 * @return int
+	 */
+	public static function get_invite_count_for_user( $user_id = 0 ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$count = wp_cache_get( $user_id, 'bp_group_invite_count' );
+
+		if ( false === $count ) {
+			$count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT m.group_id) FROM {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE m.group_id = g.id AND m.is_confirmed = 0 AND m.inviter_id != 0 AND m.invite_sent = 1 AND m.user_id = %d", $user_id ) );
+			wp_cache_set( $user_id, $count, 'bp_group_invite_count' );
+		}
+
+		return $count;
+	}
+
+	/**
+	 * Check whether a user has an outstanding invitation to a given group.
+	 *
+	 * @param int    $user_id  ID of the potential invitee.
+	 * @param int    $group_id ID of the group.
+	 * @param string $type     If 'sent', results are limited to those invitations
+	 *                         that have actually been sent (non-draft). Default: 'sent'.
+	 *
+	 * @return int|null The ID of the invitation if found, otherwise null.
+	 */
+	public static function check_has_invite( $user_id, $group_id, $type = 'sent' ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			return false;
+
+		$bp  = buddypress();
+		$sql = "SELECT id FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d AND is_confirmed = 0 AND inviter_id != 0";
+
+		if ( 'sent' == $type )
+			$sql .= " AND invite_sent = 1";
+
+		return $wpdb->get_var( $wpdb->prepare( $sql, $user_id, $group_id ) );
+	}
+
+	/**
+	 * Delete an invitation, by specifying user ID and group ID.
+	 *
+	 * @global WPDB $wpdb
+	 *
+	 * @param  int $user_id  ID of the user.
+	 * @param  int $group_id ID of the group.
+	 *
+	 * @return int Number of records deleted.
+	 */
+	public static function delete_invite( $user_id, $group_id ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		$table_name = buddypress()->groups->table_name_members;
+
+		$sql = "DELETE FROM {$table_name}
+				WHERE user_id = %d
+					AND group_id = %d
+					AND is_confirmed = 0
+					AND inviter_id != 0";
+
+		$prepared = $wpdb->prepare( $sql, $user_id, $group_id );
+
+		return $wpdb->query( $prepared );
+	}
+
+	/**
+	 * Delete an unconfirmed membership request, by user ID and group ID.
+	 *
+	 * @param int $user_id  ID of the user.
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return int Number of records deleted.
+	 */
+	public static function delete_request( $user_id, $group_id ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			return false;
+
+		$bp = buddypress();
+
+ 		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d AND is_confirmed = 0 AND inviter_id = 0 AND invite_sent = 0", $user_id, $group_id ) );
+	}
+
+	/**
+	 * Check whether a user is an admin of a given group.
+	 *
+	 * @param int $user_id  ID of the user.
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return mixed
+	 */
+	public static function check_is_admin( $user_id, $group_id ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			return false;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "SELECT id FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d AND is_admin = 1 AND is_banned = 0", $user_id, $group_id ) );
+	}
+
+	/**
+	 * Check whether a user is a mod of a given group.
+	 *
+	 * @param int $user_id  ID of the user.
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return mixed
+	 */
+	public static function check_is_mod( $user_id, $group_id ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			return false;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "SELECT id FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d AND is_mod = 1 AND is_banned = 0", $user_id, $group_id ) );
+	}
+
+	/**
+	 * Check whether a user is a member of a given group.
+	 *
+	 * @param int $user_id  ID of the user.
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return mixed
+	 */
+	public static function check_is_member( $user_id, $group_id ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			return false;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "SELECT id FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d AND is_confirmed = 1 AND is_banned = 0", $user_id, $group_id ) );
+	}
+
+	/**
+	 * Check whether a user is banned from a given group.
+	 *
+	 * @param int $user_id  ID of the user.
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return mixed
+	 */
+	public static function check_is_banned( $user_id, $group_id ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			return false;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT is_banned FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d", $user_id, $group_id ) );
+	}
+
+	/**
+	 * Is the specified user the creator of the group?
+	 *
+	 * @since 1.2.6
+	 *
+	 * @param int $user_id  ID of the user.
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return int|null ID of the group if the user is the creator,
+	 *                  otherwise false.
+	 */
+	public static function check_is_creator( $user_id, $group_id ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			return false;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->groups->table_name} WHERE creator_id = %d AND id = %d", $user_id, $group_id ) );
+	}
+
+	/**
+	 * Check whether a user has an outstanding membership request for a given group.
+	 *
+	 * @param int $user_id  ID of the user.
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return int|null ID of the membership if found, otherwise false.
+	 */
+	public static function check_for_membership_request( $user_id, $group_id ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) )
+			return false;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "SELECT id FROM {$bp->groups->table_name_members} WHERE user_id = %d AND group_id = %d AND is_confirmed = 0 AND is_banned = 0 AND inviter_id = 0", $user_id, $group_id ) );
+	}
+
+	/**
+	 * Get a list of randomly selected IDs of groups that the member belongs to.
+	 *
+	 * @param int $user_id      ID of the user.
+	 * @param int $total_groups Max number of group IDs to return. Default: 5.
+	 *
+	 * @return array Group IDs.
+	 */
+	public static function get_random_groups( $user_id = 0, $total_groups = 5 ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		// If the user is logged in and viewing their random groups, we can show hidden and private groups
+		if ( bp_is_my_profile() ) {
+			return $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT group_id FROM {$bp->groups->table_name_members} WHERE user_id = %d AND is_confirmed = 1 AND is_banned = 0 ORDER BY rand() LIMIT %d", $user_id, $total_groups ) );
+		} else {
+			return $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT m.group_id FROM {$bp->groups->table_name_members} m, {$bp->groups->table_name} g WHERE m.group_id = g.id AND g.status != 'hidden' AND m.user_id = %d AND m.is_confirmed = 1 AND m.is_banned = 0 ORDER BY rand() LIMIT %d", $user_id, $total_groups ) );
+		}
+	}
+
+	/**
+	 * Get the IDs of all a given group's members.
+	 *
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return array IDs of all group members.
+	 */
+	public static function get_group_member_ids( $group_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_col( $wpdb->prepare( "SELECT user_id FROM {$bp->groups->table_name_members} WHERE group_id = %d AND is_confirmed = 1 AND is_banned = 0", $group_id ) );
+	}
+
+	/**
+	 * Get a list of all a given group's admins.
+	 *
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return array Info about group admins (user_id + date_modified).
+	 */
+	public static function get_group_administrator_ids( $group_id ) {
+		global $wpdb;
+
+		$group_admins = wp_cache_get( $group_id, 'bp_group_admins' );
+
+		if ( false === $group_admins ) {
+			$bp = buddypress();
+			$group_admins = $wpdb->get_results( $wpdb->prepare( "SELECT user_id, date_modified FROM {$bp->groups->table_name_members} WHERE group_id = %d AND is_admin = 1 AND is_banned = 0", $group_id ) );
+
+			wp_cache_set( $group_id, $group_admins, 'bp_group_admins' );
+		}
+
+		return $group_admins;
+	}
+
+	/**
+	 * Get a list of all a given group's moderators.
+	 *
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return array Info about group mods (user_id + date_modified).
+	 */
+	public static function get_group_moderator_ids( $group_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_results( $wpdb->prepare( "SELECT user_id, date_modified FROM {$bp->groups->table_name_members} WHERE group_id = %d AND is_mod = 1 AND is_banned = 0", $group_id ) );
+	}
+
+	/**
+	 * Get the IDs users with outstanding membership requests to the group.
+	 *
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return array IDs of users with outstanding membership requests.
+	 */
+	public static function get_all_membership_request_user_ids( $group_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_col( $wpdb->prepare( "SELECT user_id FROM {$bp->groups->table_name_members} WHERE group_id = %d AND is_confirmed = 0 AND inviter_id = 0", $group_id ) );
+	}
+
+	/**
+	 * Get members of a group.
+	 *
+	 * @deprecated 1.8.0
+	 *
+	 * @param $group_id
+	 * @param $limit
+	 * @param $page
+	 * @param $exclude_admins_mods
+	 * @param $exclude_banned
+	 * @param $exclude
+	 *
+	 * @return mixed
+	 */
+	public static function get_all_for_group( $group_id, $limit = false, $page = false, $exclude_admins_mods = true, $exclude_banned = true, $exclude = false ) {
+		global $wpdb;
+
+		_deprecated_function( __METHOD__, '1.8', 'BP_Group_Member_Query' );
+
+		$pag_sql = '';
+		if ( !empty( $limit ) && !empty( $page ) )
+			$pag_sql = $wpdb->prepare( "LIMIT %d, %d", intval( ( $page - 1 ) * $limit), intval( $limit ) );
+
+		$exclude_admins_sql = '';
+		if ( !empty( $exclude_admins_mods ) )
+			$exclude_admins_sql = "AND is_admin = 0 AND is_mod = 0";
+
+		$banned_sql = '';
+		if ( !empty( $exclude_banned ) )
+			$banned_sql = " AND is_banned = 0";
+
+		$exclude_sql = '';
+		if ( !empty( $exclude ) ) {
+			$exclude     = implode( ',', wp_parse_id_list( $exclude ) );
+			$exclude_sql = " AND m.user_id NOT IN ({$exclude})";
+		}
+
+		$bp = buddypress();
+
+		if ( bp_is_active( 'xprofile' ) ) {
+
+			/**
+			 * Filters the SQL prepared statement used to fetch group members.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param string $value SQL prepared statement for fetching group members.
+			 */
+			$members = $wpdb->get_results( apply_filters( 'bp_group_members_user_join_filter', $wpdb->prepare( "SELECT m.user_id, m.date_modified, m.is_banned, u.user_login, u.user_nicename, u.user_email, pd.value as display_name FROM {$bp->groups->table_name_members} m, {$wpdb->users} u, {$bp->profile->table_name_data} pd WHERE u.ID = m.user_id AND u.ID = pd.user_id AND pd.field_id = 1 AND group_id = %d AND is_confirmed = 1 {$banned_sql} {$exclude_admins_sql} {$exclude_sql} ORDER BY m.date_modified DESC {$pag_sql}", $group_id ) ) );
+		} else {
+
+			/** This filter is documented in bp-groups/bp-groups-classes */
+			$members = $wpdb->get_results( apply_filters( 'bp_group_members_user_join_filter', $wpdb->prepare( "SELECT m.user_id, m.date_modified, m.is_banned, u.user_login, u.user_nicename, u.user_email, u.display_name FROM {$bp->groups->table_name_members} m, {$wpdb->users} u WHERE u.ID = m.user_id AND group_id = %d AND is_confirmed = 1 {$banned_sql} {$exclude_admins_sql} {$exclude_sql} ORDER BY m.date_modified DESC {$pag_sql}", $group_id ) ) );
+		}
+
+		if ( empty( $members ) ) {
+			return false;
+		}
+
+		if ( empty( $pag_sql ) ) {
+			$total_member_count = count( $members );
+		} else {
+
+			/**
+			 * Filters the SQL prepared statement used to fetch group members total count.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param string $value SQL prepared statement for fetching group member count.
+			 */
+			$total_member_count = $wpdb->get_var( apply_filters( 'bp_group_members_count_user_join_filter', $wpdb->prepare( "SELECT COUNT(user_id) FROM {$bp->groups->table_name_members} m WHERE group_id = %d AND is_confirmed = 1 {$banned_sql} {$exclude_admins_sql} {$exclude_sql}", $group_id ) ) );
+		}
+
+		// Fetch whether or not the user is a friend
+		foreach ( (array) $members as $user )
+			$user_ids[] = $user->user_id;
+
+		$user_ids = implode( ',', wp_parse_id_list( $user_ids ) );
+
+		if ( bp_is_active( 'friends' ) ) {
+			$friend_status = $wpdb->get_results( $wpdb->prepare( "SELECT initiator_user_id, friend_user_id, is_confirmed FROM {$bp->friends->table_name} WHERE (initiator_user_id = %d AND friend_user_id IN ( {$user_ids} ) ) OR (initiator_user_id IN ( {$user_ids} ) AND friend_user_id = %d )", bp_loggedin_user_id(), bp_loggedin_user_id() ) );
+			for ( $i = 0, $count = count( $members ); $i < $count; ++$i ) {
+				foreach ( (array) $friend_status as $status ) {
+					if ( $status->initiator_user_id == $members[$i]->user_id || $status->friend_user_id == $members[$i]->user_id ) {
+						$members[$i]->is_friend = $status->is_confirmed;
+					}
+				}
+			}
+		}
+
+		return array( 'members' => $members, 'count' => $total_member_count );
+	}
+
+	/**
+	 * Delete all memberships for a given group.
+	 *
+	 * @param int $group_id ID of the group.
+	 *
+	 * @return int Number of records deleted.
+	 */
+	public static function delete_all( $group_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->groups->table_name_members} WHERE group_id = %d", $group_id ) );
+	}
+
+	/**
+	 * Delete all group membership information for the specified user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $user_id ID of the user.
+	 *
+	 * @return mixed
+	 */
+	public static function delete_all_for_user( $user_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		// Get all the group ids for the current user's groups and update counts
+		$group_ids = BP_Groups_Member::get_group_ids( $user_id );
+		foreach ( $group_ids['groups'] as $group_id ) {
+			groups_update_groupmeta( $group_id, 'total_member_count', groups_get_total_member_count( $group_id ) - 1 );
+
+			// If current user is the creator of a group and is the sole admin, delete that group to avoid counts going out-of-sync
+			if ( groups_is_user_admin( $user_id, $group_id ) && count( groups_get_group_admins( $group_id ) ) < 2 && groups_is_user_creator( $user_id, $group_id ) )
+				groups_delete_group( $group_id );
+		}
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->groups->table_name_members} WHERE user_id = %d", $user_id ) );
+	}
+}
Index: /tags/2.4.3/src/bp-groups/js/widget-groups.js
===================================================================
--- /tags/2.4.3/src/bp-groups/js/widget-groups.js	(revision 10410)
+++ /tags/2.4.3/src/bp-groups/js/widget-groups.js	(revision 10410)
@@ -0,0 +1,49 @@
+jQuery(document).ready( function() {
+	jQuery('.widget div#groups-list-options a').on('click',
+		function() {
+			var link = this;
+			jQuery(link).addClass('loading');
+
+			jQuery('.widget div#groups-list-options a').removeClass('selected');
+			jQuery(this).addClass('selected');
+
+			jQuery.post( ajaxurl, {
+				action: 'widget_groups_list',
+				'cookie': encodeURIComponent(document.cookie),
+				'_wpnonce': jQuery('input#_wpnonce-groups').val(),
+				'max_groups': jQuery('input#groups_widget_max').val(),
+				'filter': jQuery(this).attr('id')
+			},
+			function(response)
+			{
+				jQuery(link).removeClass('loading');
+				groups_widget_response(response);
+			});
+
+			return false;
+		}
+	);
+});
+
+function groups_widget_response(response) {
+	response = response.substr(0, response.length-1);
+	response = response.split('[[SPLIT]]');
+
+	if ( response[0] !== '-1' ) {
+		jQuery('.widget ul#groups-list').fadeOut(200,
+			function() {
+				jQuery('.widget ul#groups-list').html(response[1]);
+				jQuery('.widget ul#groups-list').fadeIn(200);
+			}
+		);
+
+	} else {
+		jQuery('.widget ul#groups-list').fadeOut(200,
+			function() {
+				var message = '<p>' + response[1] + '</p>';
+				jQuery('.widget ul#groups-list').html(message);
+				jQuery('.widget ul#groups-list').fadeIn(200);
+			}
+		);
+	}
+}
Index: /tags/2.4.3/src/bp-loader.php
===================================================================
--- /tags/2.4.3/src/bp-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-loader.php	(revision 10410)
@@ -0,0 +1,684 @@
+<?php
+/**
+ * The BuddyPress Plugin.
+ *
+ * BuddyPress is social networking software with a twist from the creators of WordPress.
+ *
+ * @package BuddyPress
+ * @subpackage Main
+ */
+
+/**
+ * Plugin Name: BuddyPress
+ * Plugin URI:  https://buddypress.org/
+ * Description: BuddyPress helps you run any kind of social network on your WordPress, with member profiles, activity streams, user groups, messaging, and more.
+ * Author:      The BuddyPress Community
+ * Author URI:  https://buddypress.org/
+ * Version:     2.4.3
+ * Text Domain: buddypress
+ * Domain Path: /bp-languages/
+ * License:     GPLv2 or later (license.txt)
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) || exit;
+
+/** Constants *****************************************************************/
+
+if ( !class_exists( 'BuddyPress' ) ) :
+/**
+ * Main BuddyPress Class.
+ *
+ * Tap tap tap... Is this thing on?
+ *
+ * @since 1.6.0
+ */
+class BuddyPress {
+
+	/** Magic *****************************************************************/
+
+	/**
+	 * BuddyPress uses many variables, most of which can be filtered to
+	 * customize the way that it works. To prevent unauthorized access,
+	 * these variables are stored in a private array that is magically
+	 * updated using PHP 5.2+ methods. This is to prevent third party
+	 * plugins from tampering with essential information indirectly, which
+	 * would cause issues later.
+	 *
+	 * @see BuddyPress::setup_globals()
+	 * @var array
+	 */
+	private $data;
+
+	/** Not Magic *************************************************************/
+
+	/**
+	 * @var array Primary BuddyPress navigation.
+	 */
+	public $bp_nav = array();
+
+	/**
+	 * @var array Secondary BuddyPress navigation to $bp_nav.
+	 */
+	public $bp_options_nav = array();
+
+	/**
+	 * @var array The unfiltered URI broken down into chunks.
+	 * @see bp_core_set_uri_globals()
+	 */
+	public $unfiltered_uri = array();
+
+	/**
+	 * @var array The canonical URI stack.
+	 * @see bp_redirect_canonical()
+	 * @see bp_core_new_nav_item()
+	 */
+	public $canonical_stack = array();
+
+	/**
+	 * @var array Additional navigation elements (supplemental).
+	 */
+	public $action_variables = array();
+
+	/**
+	 * @var string Current member directory type.
+	 */
+	public $current_member_type = '';
+
+	/**
+	 * @var array Required components (core, members).
+	 */
+	public $required_components = array();
+
+	/**
+	 * @var array Additional active components.
+	 */
+	public $loaded_components = array();
+
+	/**
+	 * @var array Active components.
+	 */
+	public $active_components = array();
+
+	/** Option Overload *******************************************************/
+
+	/**
+	 * @var array Optional Overloads default options retrieved from get_option().
+	 */
+	public $options = array();
+
+	/** Singleton *************************************************************/
+
+	/**
+	 * Main BuddyPress Instance.
+	 *
+	 * BuddyPress is great.
+	 * Please load it only one time.
+	 * For this, we thank you.
+	 *
+	 * Insures that only one instance of BuddyPress exists in memory at any
+	 * one time. Also prevents needing to define globals all over the place.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @static object $instance
+	 * @uses BuddyPress::constants() Setup the constants (mostly deprecated).
+	 * @uses BuddyPress::setup_globals() Setup the globals needed.
+	 * @uses BuddyPress::legacy_constants() Setup the legacy constants (deprecated).
+	 * @uses BuddyPress::includes() Include the required files.
+	 * @uses BuddyPress::setup_actions() Setup the hooks and actions.
+	 * @see buddypress()
+	 *
+	 * @return BuddyPress The one true BuddyPress.
+	 */
+	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 run previously
+		if ( null === $instance ) {
+			$instance = new BuddyPress;
+			$instance->constants();
+			$instance->setup_globals();
+			$instance->legacy_constants();
+			$instance->includes();
+			$instance->setup_actions();
+		}
+
+		// Always return the instance
+		return $instance;
+
+		// The last metroid is in captivity. The galaxy is at peace.
+	}
+
+	/** Magic Methods *********************************************************/
+
+	/**
+	 * A dummy constructor to prevent BuddyPress from being loaded more than once.
+	 *
+	 * @since 1.7.0
+	 * @see BuddyPress::instance()
+	 * @see buddypress()
+	 */
+	private function __construct() { /* Do nothing here */ }
+
+	/**
+	 * A dummy magic method to prevent BuddyPress from being cloned.
+	 *
+	 * @since 1.7.0
+	 */
+	public function __clone() { _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'buddypress' ), '1.7' ); }
+
+	/**
+	 * A dummy magic method to prevent BuddyPress from being unserialized.
+	 *
+	 * @since 1.7.0
+	 */
+	public function __wakeup() { _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'buddypress' ), '1.7' ); }
+
+	/**
+	 * Magic method for checking the existence of a certain custom field.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $key Key to check the set status for.
+	 *
+	 * @return bool
+	 */
+	public function __isset( $key ) { return isset( $this->data[$key] ); }
+
+	/**
+	 * Magic method for getting BuddyPress variables.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $key Key to return the value for.
+	 *
+	 * @return mixed
+	 */
+	public function __get( $key ) { return isset( $this->data[$key] ) ? $this->data[$key] : null; }
+
+	/**
+	 * Magic method for setting BuddyPress variables.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $key   Key to set a value for.
+	 * @param mixed  $value Value to set.
+	 */
+	public function __set( $key, $value ) { $this->data[$key] = $value; }
+
+	/**
+	 * Magic method for unsetting BuddyPress variables.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $key Key to unset a value for.
+	 */
+	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 1.7.0
+	 *
+	 * @param string $name
+	 * @param array  $args
+	 *
+	 * @return null
+	 */
+	public function __call( $name = '', $args = array() ) { unset( $name, $args ); return null; }
+
+	/** Private Methods *******************************************************/
+
+	/**
+	 * Bootstrap constants.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses is_multisite()
+	 * @uses get_current_site()
+	 * @uses get_current_blog_id()
+	 * @uses plugin_dir_path()
+	 * @uses plugin_dir_url()
+	 */
+	private function constants() {
+
+		// Place your custom code (actions/filters) in a file called
+		// '/plugins/bp-custom.php' and it will be loaded before anything else.
+		if ( file_exists( WP_PLUGIN_DIR . '/bp-custom.php' ) ) {
+			require( WP_PLUGIN_DIR . '/bp-custom.php' );
+		}
+
+		// Path and URL
+		if ( ! defined( 'BP_PLUGIN_DIR' ) ) {
+			define( 'BP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
+		}
+
+		if ( ! defined( 'BP_PLUGIN_URL' ) ) {
+			define( 'BP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
+		}
+
+		// Only applicable to those running trunk
+		if ( ! defined( 'BP_SOURCE_SUBDIRECTORY' ) ) {
+			define( 'BP_SOURCE_SUBDIRECTORY', '' );
+		}
+
+		// Define on which blog ID BuddyPress should run
+		if ( ! defined( 'BP_ROOT_BLOG' ) ) {
+
+			// Default to use current blog ID
+			// Fulfills non-network installs and BP_ENABLE_MULTIBLOG installs
+			$root_blog_id = get_current_blog_id();
+
+			// Multisite check
+			if ( is_multisite() ) {
+
+				// Multiblog isn't enabled
+				if ( ! defined( 'BP_ENABLE_MULTIBLOG' ) || ( defined( 'BP_ENABLE_MULTIBLOG' ) && (int) constant( 'BP_ENABLE_MULTIBLOG' ) === 0 ) ) {
+					// Check to see if BP is network-activated
+					// We're not using is_plugin_active_for_network() b/c you need to include the
+					// /wp-admin/includes/plugin.php file in order to use that function.
+
+					// get network-activated plugins
+					$plugins = get_site_option( 'active_sitewide_plugins');
+
+					// basename
+					$basename = basename( constant( 'BP_PLUGIN_DIR' ) ) . '/bp-loader.php';
+
+					// plugin is network-activated; use main site ID instead
+					if ( isset( $plugins[ $basename ] ) ) {
+						$current_site = get_current_site();
+						$root_blog_id = $current_site->blog_id;
+					}
+				}
+
+			}
+
+			define( 'BP_ROOT_BLOG', $root_blog_id );
+		}
+
+		// Whether to refrain from loading deprecated functions
+		if ( ! defined( 'BP_IGNORE_DEPRECATED' ) ) {
+			define( 'BP_IGNORE_DEPRECATED', false );
+		}
+
+		// The search slug has to be defined nice and early because of the way
+		// search requests are loaded
+		//
+		// @todo Make this better
+		if ( ! defined( 'BP_SEARCH_SLUG' ) ) {
+			define( 'BP_SEARCH_SLUG', 'search' );
+		}
+	}
+
+	/**
+	 * Component global variables.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses plugin_dir_path() To generate BuddyPress plugin path.
+	 * @uses plugin_dir_url() To generate BuddyPress plugin url.
+	 * @uses apply_filters() Calls various filters.
+	 */
+	private function setup_globals() {
+
+		/** Versions **********************************************************/
+
+		$this->version    = '2.4.3';
+		$this->db_version = 10071;
+
+		/** Loading ***********************************************************/
+
+		/**
+		 * Filters the load_deprecated property value.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @const constant BP_IGNORE_DEPRECATED Whether or not to ignore deprecated functionality.
+		 */
+		$this->load_deprecated = ! apply_filters( 'bp_ignore_deprecated', BP_IGNORE_DEPRECATED );
+
+		/** Toolbar ***********************************************************/
+
+		/**
+		 * @var string The primary toolbar ID.
+		 */
+		$this->my_account_menu_id = '';
+
+		/** URIs **************************************************************/
+
+		/**
+		 * @var int The current offset of the URI.
+		 * @see bp_core_set_uri_globals()
+		 */
+		$this->unfiltered_uri_offset = 0;
+
+		/**
+		 * @var bool Are status headers already sent?
+		 */
+		$this->no_status_set = false;
+
+		/** Components ********************************************************/
+
+		/**
+		 * @var string Name of the current BuddyPress component (primary).
+		 */
+		$this->current_component = '';
+
+		/**
+		 * @var string Name of the current BuddyPress item (secondary).
+		 */
+		$this->current_item = '';
+
+		/**
+		 * @var string Name of the current BuddyPress action (tertiary).
+		 */
+		$this->current_action = '';
+
+		/**
+		 * @var bool Displaying custom 2nd level navigation menu (I.E a group).
+		 */
+		$this->is_single_item = false;
+
+		/** Root **************************************************************/
+
+		/**
+		 * Filters the BuddyPress Root blog ID.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @const constant BP_ROOT_BLOG BuddyPress Root blog ID.
+		 */
+		$this->root_blog_id = (int) apply_filters( 'bp_get_root_blog_id', BP_ROOT_BLOG );
+
+		/** Paths**************************************************************/
+
+		// BuddyPress root directory
+		$this->file           = constant( 'BP_PLUGIN_DIR' ) . 'bp-loader.php';
+		$this->basename       = basename( constant( 'BP_PLUGIN_DIR' ) ) . '/bp-loader.php';
+		$this->plugin_dir     = trailingslashit( constant( 'BP_PLUGIN_DIR' ) . constant( 'BP_SOURCE_SUBDIRECTORY' ) );
+		$this->plugin_url     = trailingslashit( constant( 'BP_PLUGIN_URL' ) . constant( 'BP_SOURCE_SUBDIRECTORY' ) );
+
+		// Languages
+		$this->lang_dir       = $this->plugin_dir . 'bp-languages';
+
+		// Templates (theme compatibility)
+		$this->themes_dir     = $this->plugin_dir . 'bp-templates';
+		$this->themes_url     = $this->plugin_url . 'bp-templates';
+
+		// Themes (for bp-default)
+		$this->old_themes_dir = $this->plugin_dir . 'bp-themes';
+		$this->old_themes_url = $this->plugin_url . 'bp-themes';
+
+		/** 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 stdClass();
+		$this->displayed_user = new stdClass();
+	}
+
+	/**
+	 * Legacy BuddyPress constants.
+	 *
+	 * Try to avoid using these. Their values have been moved into variables
+	 * in the instance, and have matching functions to get/set their values.
+	 *
+	 * @since 1.7.0
+	 */
+	private function legacy_constants() {
+
+		// Define the BuddyPress version
+		if ( ! defined( 'BP_VERSION' ) ) {
+			define( 'BP_VERSION', $this->version );
+		}
+
+		// Define the database version
+		if ( ! defined( 'BP_DB_VERSION' ) ) {
+			define( 'BP_DB_VERSION', $this->db_version );
+		}
+	}
+
+	/**
+	 * Include required files.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses is_admin() If in WordPress admin, load additional file.
+	 */
+	private function includes() {
+
+		// Load the WP abstraction file so BuddyPress can run on all WordPress setups.
+		require( $this->plugin_dir . 'bp-core/bp-core-wpabstraction.php' );
+
+		// Setup the versions (after we include multisite abstraction above)
+		$this->versions();
+
+		/** Update/Install ****************************************************/
+
+		// Theme compatibility
+		require( $this->plugin_dir . 'bp-core/bp-core-template-loader.php'     );
+		require( $this->plugin_dir . 'bp-core/bp-core-theme-compatibility.php' );
+
+		// Require all of the BuddyPress core libraries
+		require( $this->plugin_dir . 'bp-core/bp-core-dependency.php'  );
+		require( $this->plugin_dir . 'bp-core/bp-core-actions.php'     );
+		require( $this->plugin_dir . 'bp-core/bp-core-caps.php'        );
+		require( $this->plugin_dir . 'bp-core/bp-core-cache.php'       );
+		require( $this->plugin_dir . 'bp-core/bp-core-cssjs.php'       );
+		require( $this->plugin_dir . 'bp-core/bp-core-update.php'      );
+		require( $this->plugin_dir . 'bp-core/bp-core-options.php'     );
+		require( $this->plugin_dir . 'bp-core/bp-core-classes.php'     );
+		require( $this->plugin_dir . 'bp-core/bp-core-taxonomy.php'    );
+		require( $this->plugin_dir . 'bp-core/bp-core-filters.php'     );
+		require( $this->plugin_dir . 'bp-core/bp-core-attachments.php' );
+		require( $this->plugin_dir . 'bp-core/bp-core-avatars.php'     );
+		require( $this->plugin_dir . 'bp-core/bp-core-widgets.php'     );
+		require( $this->plugin_dir . 'bp-core/bp-core-template.php'    );
+		require( $this->plugin_dir . 'bp-core/bp-core-adminbar.php'    );
+		require( $this->plugin_dir . 'bp-core/bp-core-buddybar.php'    );
+		require( $this->plugin_dir . 'bp-core/bp-core-catchuri.php'    );
+		require( $this->plugin_dir . 'bp-core/bp-core-component.php'   );
+		require( $this->plugin_dir . 'bp-core/bp-core-functions.php'   );
+		require( $this->plugin_dir . 'bp-core/bp-core-moderation.php'  );
+		require( $this->plugin_dir . 'bp-core/bp-core-loader.php'      );
+
+		// Skip or load deprecated content
+		if ( false !== $this->load_deprecated ) {
+			require( $this->plugin_dir . 'bp-core/deprecated/1.2.php' );
+			require( $this->plugin_dir . 'bp-core/deprecated/1.5.php' );
+			require( $this->plugin_dir . 'bp-core/deprecated/1.6.php' );
+			require( $this->plugin_dir . 'bp-core/deprecated/1.7.php' );
+			require( $this->plugin_dir . 'bp-core/deprecated/1.9.php' );
+			require( $this->plugin_dir . 'bp-core/deprecated/2.0.php' );
+			require( $this->plugin_dir . 'bp-core/deprecated/2.1.php' );
+			require( $this->plugin_dir . 'bp-core/deprecated/2.2.php' );
+			require( $this->plugin_dir . 'bp-core/deprecated/2.3.php' );
+			require( $this->plugin_dir . 'bp-core/deprecated/2.4.php' );
+		}
+	}
+
+	/**
+	 * Set up the default hooks and actions.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @uses register_activation_hook() To register the activation hook.
+	 * @uses register_deactivation_hook() To register the deactivation hook.
+	 * @uses add_action() To add various actions.
+	 */
+	private function setup_actions() {
+
+		// Add actions to plugin activation and deactivation hooks
+		add_action( 'activate_'   . $this->basename, 'bp_activation'   );
+		add_action( 'deactivate_' . $this->basename, 'bp_deactivation' );
+
+		// If BuddyPress is being deactivated, do not add any actions
+		if ( bp_is_deactivation( $this->basename ) ) {
+			return;
+		}
+
+		// Array of BuddyPress core actions
+		$actions = array(
+			'setup_theme',              // Setup the default theme compat
+			'setup_current_user',       // Setup currently logged in user
+			'register_post_types',      // Register post types
+			'register_post_statuses',   // Register post statuses
+			'register_taxonomies',      // Register taxonomies
+			'register_views',           // Register the views
+			'register_theme_directory', // Register the theme directory
+			'register_theme_packages',  // Register bundled theme packages (bp-themes)
+			'load_textdomain',          // Load textdomain
+			'add_rewrite_tags',         // Add rewrite tags
+			'generate_rewrite_rules'    // Generate rewrite rules
+		);
+
+		// Add the actions
+		foreach( $actions as $class_action ) {
+			if ( method_exists( $this, $class_action ) ) {
+				add_action( 'bp_' . $class_action, array( $this, $class_action ), 5 );
+			}
+		}
+
+		/**
+		 * Fires after the setup of all BuddyPress actions.
+		 *
+		 * Includes bbp-core-hooks.php.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param BuddyPress $this. Current BuddyPress instance. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_after_setup_actions', array( &$this ) );
+	}
+
+	/**
+	 * Private method to align the active and database versions.
+	 *
+	 * @since 1.7.0
+	 */
+	private function versions() {
+
+		// Get the possible DB versions (boy is this gross)
+		$versions               = array();
+		$versions['1.6-single'] = get_blog_option( $this->root_blog_id, '_bp_db_version' );
+
+		// 1.6-single exists, so trust it
+		if ( !empty( $versions['1.6-single'] ) ) {
+			$this->db_version_raw = (int) $versions['1.6-single'];
+
+		// If no 1.6-single exists, use the max of the others
+		} else {
+			$versions['1.2']        = get_site_option(                      'bp-core-db-version' );
+			$versions['1.5-multi']  = get_site_option(                           'bp-db-version' );
+			$versions['1.6-multi']  = get_site_option(                          '_bp_db_version' );
+			$versions['1.5-single'] = get_blog_option( $this->root_blog_id,      'bp-db-version' );
+
+			// Remove empty array items
+			$versions             = array_filter( $versions );
+			$this->db_version_raw = (int) ( !empty( $versions ) ) ? (int) max( $versions ) : 0;
+		}
+	}
+
+	/** Public Methods ********************************************************/
+
+	/**
+	 * Set up BuddyPress's legacy theme directory.
+	 *
+	 * Starting with version 1.2, and ending with version 1.8, BuddyPress
+	 * registered a custom theme directory - bp-themes - which contained
+	 * the bp-default theme. Since BuddyPress 1.9, bp-themes is no longer
+	 * registered (and bp-default no longer offered) on new installations.
+	 * Sites using bp-default (or a child theme of bp-default) will
+	 * continue to have bp-themes registered as before.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @todo Move bp-default to wordpress.org/extend/themes and remove this.
+	 */
+	public function register_theme_directory() {
+		if ( ! bp_do_register_theme_directory() ) {
+			return;
+		}
+
+		register_theme_directory( $this->old_themes_dir );
+	}
+
+	/**
+	 * Register bundled theme packages.
+	 *
+	 * Note that since we currently have complete control over bp-themes and
+	 * the bp-legacy folders, it's fine to hardcode these here. If at a
+	 * later date we need to automate this, an API will need to be built.
+	 *
+	 * @since 1.7.0
+	 */
+	public function register_theme_packages() {
+
+		// Register the default theme compatibility package
+		bp_register_theme_package( array(
+			'id'      => 'legacy',
+			'name'    => __( 'BuddyPress Default', 'buddypress' ),
+			'version' => bp_get_version(),
+			'dir'     => trailingslashit( $this->themes_dir . '/bp-legacy' ),
+			'url'     => trailingslashit( $this->themes_url . '/bp-legacy' )
+		) );
+
+		// Register the basic theme stack. This is really dope.
+		bp_register_template_stack( 'get_stylesheet_directory', 10 );
+		bp_register_template_stack( 'get_template_directory',   12 );
+		bp_register_template_stack( 'bp_get_theme_compat_dir',  14 );
+	}
+
+	/**
+	 * Set up the default BuddyPress theme compatibility location.
+	 *
+	 * @since 1.7.0
+	 */
+	public function setup_theme() {
+
+		// Bail if something already has this under control
+		if ( ! empty( $this->theme_compat->theme ) ) {
+			return;
+		}
+
+		// Setup the theme package to use for compatibility
+		bp_setup_theme_compat( bp_get_theme_package_id() );
+	}
+}
+
+/**
+ * The main function responsible for returning the one true BuddyPress Instance to functions everywhere.
+ *
+ * Use this function like you would a global variable, except without needing
+ * to declare the global.
+ *
+ * Example: <?php $bp = buddypress(); ?>
+ *
+ * @return BuddyPress The one true BuddyPress Instance.
+ */
+function buddypress() {
+	return BuddyPress::instance();
+}
+
+/**
+ * Hook BuddyPress early onto the 'plugins_loaded' action.
+ *
+ * This gives all other plugins the chance to load before BuddyPress, to get
+ * their actions, filters, and overrides setup without BuddyPress being in the
+ * way.
+ */
+if ( defined( 'BUDDYPRESS_LATE_LOAD' ) ) {
+	add_action( 'plugins_loaded', 'buddypress', (int) BUDDYPRESS_LATE_LOAD );
+
+// "And now here's something we hope you'll really like!"
+} else {
+	$GLOBALS['bp'] = buddypress();
+}
+
+endif;
Index: /tags/2.4.3/src/bp-members/admin/bp-members-admin-classes.php
===================================================================
--- /tags/2.4.3/src/bp-members/admin/bp-members-admin-classes.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/admin/bp-members-admin-classes.php	(revision 10410)
@@ -0,0 +1,778 @@
+<?php
+/**
+ * BuddyPress Members List Classes.
+ *
+ * @package BuddyPress
+ * @subpackage MembersAdminClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+if ( class_exists( 'WP_Users_List_Table') ) :
+
+/**
+ * List table class for signups admin page.
+ *
+ * @since 2.0.0
+ */
+class BP_Members_List_Table extends WP_Users_List_Table {
+
+	/**
+	 * Signup counts.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @var int
+	 */
+	public $signup_counts = 0;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		// Define singular and plural labels, as well as whether we support AJAX.
+		parent::__construct( array(
+			'ajax'     => false,
+			'plural'   => 'signups',
+			'singular' => 'signup',
+		) );
+	}
+
+	/**
+	 * Set up items for display in the list table.
+	 *
+	 * Handles filtering of data, sorting, pagination, and any other data
+	 * manipulation required prior to rendering.
+	 *
+	 * @since 2.0.0
+	 */
+	public function prepare_items() {
+		global $usersearch;
+
+		$usersearch       = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : '';
+		$signups_per_page = $this->get_items_per_page( str_replace( '-', '_', "{$this->screen->id}_per_page" ) );
+		$paged            = $this->get_pagenum();
+
+		$args = array(
+			'offset'     => ( $paged - 1 ) * $signups_per_page,
+			'number'     => $signups_per_page,
+			'usersearch' => $usersearch,
+			'orderby'    => 'signup_id',
+			'order'      => 'DESC'
+		);
+
+		if ( isset( $_REQUEST['orderby'] ) ) {
+			$args['orderby'] = $_REQUEST['orderby'];
+		}
+
+		if ( isset( $_REQUEST['order'] ) ) {
+			$args['order'] = $_REQUEST['order'];
+		}
+
+		$signups = BP_Signup::get( $args );
+
+		$this->items = $signups['signups'];
+		$this->signup_counts = $signups['total'];
+
+		$this->set_pagination_args( array(
+			'total_items' => $this->signup_counts,
+			'per_page'    => $signups_per_page,
+		) );
+	}
+
+	/**
+	 * Get the views (the links above the WP List Table).
+	 *
+	 * @since 2.0.0
+	 *
+	 * @uses WP_Users_List_Table::get_views() to get the users views.
+	 */
+	public function get_views() {
+		$views = parent::get_views();
+
+		// Remove the 'current' class from the 'All' link.
+		$views['all']        = str_replace( 'class="current"', '', $views['all'] );
+		$views['registered'] = sprintf( '<a href="%1$s" class="current">%2$s</a>', esc_url( add_query_arg( 'page', 'bp-signups', bp_get_admin_url( 'users.php' ) ) ), sprintf( _x( 'Pending %s', 'signup users', 'buddypress' ), '<span class="count">(' . number_format_i18n( $this->signup_counts ) . ')</span>' ) );
+
+		return $views;
+	}
+
+	/**
+	 * Get rid of the extra nav.
+	 *
+	 * WP_Users_List_Table will add an extra nav to change user's role.
+	 * As we're dealing with signups, we don't need this.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $which Current table nav item.
+	 */
+	public function extra_tablenav( $which ) {
+		return;
+	}
+
+	/**
+	 * Specific signups columns.
+	 *
+	 * @since 2.0.0
+	 */
+	public function get_columns() {
+
+		/**
+		 * Filters the single site Members signup columns.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $value Array of columns to display.
+		 */
+		return apply_filters( 'bp_members_signup_columns', array(
+			'cb'         => '<input type="checkbox" />',
+			'username'   => __( 'Username',    'buddypress' ),
+			'name'       => __( 'Name',        'buddypress' ),
+			'email'      => __( 'Email',       'buddypress' ),
+			'registered' => __( 'Registered',  'buddypress' ),
+			'date_sent'  => __( 'Last Sent',   'buddypress' ),
+			'count_sent' => __( 'Emails Sent', 'buddypress' )
+		) );
+	}
+
+	/**
+	 * Specific bulk actions for signups.
+	 *
+	 * @since 2.0.0
+	 */
+	public function get_bulk_actions() {
+		$actions = array(
+			'activate' => _x( 'Activate', 'Pending signup action', 'buddypress' ),
+			'resend'   => _x( 'Email',    'Pending signup action', 'buddypress' ),
+		);
+
+		if ( current_user_can( 'delete_users' ) ) {
+			$actions['delete'] = __( 'Delete', 'buddypress' );
+		}
+
+		return $actions;
+	}
+
+	/**
+	 * The text shown when no items are found.
+	 *
+	 * Nice job, clean sheet!
+	 *
+	 * @since 2.0.0
+	 */
+	public function no_items() {
+
+		if ( bp_get_signup_allowed() ) {
+			esc_html_e( 'No pending accounts found.', 'buddypress' );
+		} else {
+			$link = false;
+
+			// Specific case when BuddyPress is not network activated.
+			if ( is_multisite() && current_user_can( 'manage_network_users') ) {
+				$link = sprintf( '<a href="%1$s">%2$s</a>', esc_url( network_admin_url( 'settings.php'       ) ), esc_html__( 'Edit settings', 'buddypress' ) );
+			} elseif ( current_user_can( 'manage_options' ) ) {
+				$link = sprintf( '<a href="%1$s">%2$s</a>', esc_url( bp_get_admin_url( 'options-general.php' ) ), esc_html__( 'Edit settings', 'buddypress' ) );
+			}
+
+			printf( __( 'Registration is disabled. %s', 'buddypress' ), $link );
+		}
+
+	}
+
+	/**
+	 * The columns signups can be reordered with.
+	 *
+	 * @since 2.0.0
+	 */
+	public function get_sortable_columns() {
+		return array(
+			'username'   => 'login',
+			'email'      => 'email',
+			'registered' => 'signup_id',
+		);
+	}
+
+	/**
+	 * Display signups rows.
+	 *
+	 * @since 2.0.0
+	 */
+	public function display_rows() {
+		$style = '';
+		foreach ( $this->items as $userid => $signup_object ) {
+
+			// Avoid a notice error appearing since 4.3.0.
+			if ( isset( $signup_object->id ) ) {
+				$signup_object->ID = $signup_object->id;
+			}
+
+			$style = ( ' class="alternate"' == $style ) ? '' : ' class="alternate"';
+			echo "\n\t" . $this->single_row( $signup_object, $style );
+		}
+	}
+
+	/**
+	 * Display a signup row.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @see WP_List_Table::single_row() for explanation of params.
+	 *
+	 * @param object|null $signup_object Signup user object.
+	 * @param string      $style         Styles for the row.
+	 * @param string      $role          Role to be assigned to user.
+	 * @param int         $numposts      Numper of posts.
+	 * @return void
+	 */
+	public function single_row( $signup_object = null, $style = '', $role = '', $numposts = 0 ) {
+		echo '<tr' . $style . ' id="signup-' . esc_attr( $signup_object->id ) . '">';
+		echo $this->single_row_columns( $signup_object );
+		echo '</tr>';
+	}
+
+	/**
+	 * Markup for the checkbox used to select items for bulk actions.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object The signup data object.
+	 */
+	public function column_cb( $signup_object = null ) {
+	?>
+		<label class="screen-reader-text" for="signup_<?php echo intval( $signup_object->id ); ?>"><?php printf( esc_html__( 'Select user: %s', 'buddypress' ), $signup_object->user_login ); ?></label>
+		<input type="checkbox" id="signup_<?php echo intval( $signup_object->id ) ?>" name="allsignups[]" value="<?php echo esc_attr( $signup_object->id ) ?>" />
+		<?php
+	}
+
+	/**
+	 * The row actions (delete/activate/email).
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object The signup data object.
+	 */
+	public function column_username( $signup_object = null ) {
+		$avatar	= get_avatar( $signup_object->user_email, 32 );
+
+		// Activation email link.
+		$email_link = add_query_arg(
+			array(
+				'page'	    => 'bp-signups',
+				'signup_id' => $signup_object->id,
+				'action'    => 'resend',
+			),
+			bp_get_admin_url( 'users.php' )
+		);
+
+		// Activate link.
+		$activate_link = add_query_arg(
+			array(
+				'page'      => 'bp-signups',
+				'signup_id' => $signup_object->id,
+				'action'    => 'activate',
+			),
+			bp_get_admin_url( 'users.php' )
+		);
+
+		// Delete link.
+		$delete_link = add_query_arg(
+			array(
+				'page'      => 'bp-signups',
+				'signup_id' => $signup_object->id,
+				'action'    => 'delete',
+			),
+			bp_get_admin_url( 'users.php' )
+		);
+
+		echo $avatar . sprintf( '<strong><a href="%1$s" class="edit" title="%2$s">%3$s</a></strong><br/>', esc_url( $activate_link ), esc_attr__( 'Activate', 'buddypress' ), $signup_object->user_login );
+
+		$actions = array();
+
+		$actions['activate'] = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $activate_link ), __( 'Activate', 'buddypress' ) );
+		$actions['resend']   = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $email_link ), __( 'Email', 'buddypress' ) );
+
+		if ( current_user_can( 'delete_users' ) ) {
+			$actions['delete'] = sprintf( '<a href="%1$s" class="delete">%2$s</a>', esc_url( $delete_link ), __( 'Delete', 'buddypress' ) );
+		}
+
+		/**
+		 * Filters the multisite row actions for each user in list.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array  $actions       Array of actions and corresponding links.
+		 * @param object $signup_object The signup data object.
+		 */
+		$actions = apply_filters( 'bp_members_ms_signup_row_actions', $actions, $signup_object );
+
+		echo $this->row_actions( $actions );
+	}
+
+	/**
+	 * Display user name, if any.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object The signup data object.
+	 */
+	public function column_name( $signup_object = null ) {
+		echo esc_html( $signup_object->user_name );
+	}
+
+	/**
+	 * Display user email.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object The signup data object.
+	 */
+	public function column_email( $signup_object = null ) {
+		printf( '<a href="mailto:%1$s">%2$s</a>', esc_attr( $signup_object->user_email ), esc_html( $signup_object->user_email ) );
+	}
+
+	/**
+	 * Display registration date.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object The signup data object.
+	 */
+	public function column_registered( $signup_object = null ) {
+		echo mysql2date( 'Y/m/d', $signup_object->registered );
+	}
+
+	/**
+	 * Display the last time an activation email has been sent.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object The signup data object.
+	 */
+	public function column_date_sent( $signup_object = null ) {
+		echo mysql2date( 'Y/m/d', $signup_object->date_sent );
+	}
+
+	/**
+	 * Display number of time an activation email has been sent.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object Signup object instance.
+	 */
+	public function column_count_sent( $signup_object = null ) {
+		echo absint( $signup_object->count_sent );
+	}
+
+	/**
+	 * Allow plugins to add their custom column.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param object|null $signup_object The signup data object.
+	 * @param string      $column_name   The column name.
+	 * @return string
+	 */
+	function column_default( $signup_object = null, $column_name = '' ) {
+
+		/**
+		 * Filters the single site custom columns for plugins.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $column_name   The column name.
+		 * @param object $signup_object The signup data object.
+		 */
+		return apply_filters( 'bp_members_signup_custom_column', '', $column_name, $signup_object );
+	}
+}
+
+endif;
+
+if ( class_exists( 'WP_MS_Users_List_Table' ) ) :
+
+/**
+ * List table class for signups network admin page.
+ *
+ * @since 2.0.0
+ */
+class BP_Members_MS_List_Table extends WP_MS_Users_List_Table {
+
+	/**
+	 * Signup counts.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @var int
+	 */
+	public $signup_counts = 0;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		// Define singular and plural labels, as well as whether we support AJAX.
+		parent::__construct( array(
+			'ajax'     => false,
+			'plural'   => 'signups',
+			'singular' => 'signup',
+		) );
+	}
+
+	/**
+	 * Set up items for display in the list table.
+	 *
+	 * Handles filtering of data, sorting, pagination, and any other data
+	 * manipulation required prior to rendering.
+	 *
+	 * @since 2.0.0
+	 */
+	public function prepare_items() {
+		global $usersearch, $mode;
+
+		$usersearch       = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : '';
+		$signups_per_page = $this->get_items_per_page( str_replace( '-', '_', "{$this->screen->id}_per_page" ) );
+		$paged            = $this->get_pagenum();
+
+		$args = array(
+			'offset'     => ( $paged - 1 ) * $signups_per_page,
+			'number'     => $signups_per_page,
+			'usersearch' => $usersearch,
+			'orderby'    => 'signup_id',
+			'order'      => 'DESC'
+		);
+
+		if ( isset( $_REQUEST['orderby'] ) ) {
+			$args['orderby'] = $_REQUEST['orderby'];
+		}
+
+		if ( isset( $_REQUEST['order'] ) ) {
+			$args['order'] = $_REQUEST['order'];
+		}
+
+		$mode    = empty( $_REQUEST['mode'] ) ? 'list' : $_REQUEST['mode'];
+		$signups = BP_Signup::get( $args );
+
+		$this->items         = $signups['signups'];
+		$this->signup_counts = $signups['total'];
+
+		$this->set_pagination_args( array(
+			'total_items' => $this->signup_counts,
+			'per_page'    => $signups_per_page,
+		) );
+	}
+
+	/**
+	 * Get the views : the links above the WP List Table.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @uses WP_MS_Users_List_Table::get_views() to get the users views.
+	 */
+	public function get_views() {
+		$views = parent::get_views();
+
+		// Remove the 'current' class from the 'All' link.
+		$views['all']        = str_replace( 'class="current"', '', $views['all'] );
+		$views['registered'] = sprintf( '<a href="%1$s" class="current">%2$s</a>', esc_url( add_query_arg( 'page', 'bp-signups', bp_get_admin_url( 'users.php' ) ) ), sprintf( _x( 'Pending %s', 'signup users', 'buddypress' ), '<span class="count">(' . number_format_i18n( $this->signup_counts ) . ')</span>' ) );
+
+		return $views;
+	}
+
+	/**
+	 * Specific signups columns.
+	 *
+	 * @since 2.0.0
+	 */
+	public function get_columns() {
+
+		/**
+		 * Filters the multisite Members signup columns.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $value Array of columns to display.
+		 */
+		return apply_filters( 'bp_members_ms_signup_columns', array(
+			'cb'         => '<input type="checkbox" />',
+			'username'   => __( 'Username',    'buddypress' ),
+			'name'       => __( 'Name',        'buddypress' ),
+			'email'      => __( 'Email',       'buddypress' ),
+			'registered' => __( 'Registered',  'buddypress' ),
+			'date_sent'  => __( 'Last Sent',   'buddypress' ),
+			'count_sent' => __( 'Emails Sent', 'buddypress' )
+		) );
+	}
+
+	/**
+	 * Specific bulk actions for signups.
+	 *
+	 * @since 2.0.0
+	 */
+	public function get_bulk_actions() {
+		$actions = array(
+			'activate' => _x( 'Activate', 'Pending signup action', 'buddypress' ),
+			'resend'   => _x( 'Email',    'Pending signup action', 'buddypress' ),
+		);
+
+		if ( current_user_can( 'delete_users' ) ) {
+			$actions['delete'] = __( 'Delete', 'buddypress' );
+		}
+
+		return $actions;
+	}
+
+	/**
+	 * The text shown when no items are found.
+	 *
+	 * Nice job, clean sheet!
+	 *
+	 * @since 2.0.0
+	 */
+	public function no_items() {
+		if ( bp_get_signup_allowed() ) {
+			esc_html_e( 'No pending accounts found.', 'buddypress' );
+		} else {
+			$link = false;
+
+			if ( current_user_can( 'manage_network_users' ) ) {
+				$link = sprintf( '<a href="%1$s">%2$s</a>', esc_url( network_admin_url( 'settings.php' ) ), esc_html__( 'Edit settings', 'buddypress' ) );
+			}
+
+			printf( __( 'Registration is disabled. %s', 'buddypress' ), $link );
+		}
+	}
+
+	/**
+	 * The columns signups can be reordered with.
+	 *
+	 * @since 2.0.0
+	 */
+	public function get_sortable_columns() {
+		return array(
+			'username'   => 'login',
+			'email'      => 'email',
+			'registered' => 'signup_id',
+		);
+	}
+
+	/**
+	 * Display signups rows.
+	 *
+	 * @since 2.0.0
+	 */
+	public function display_rows() {
+		$style = '';
+		foreach ( $this->items as $userid => $signup_object ) {
+
+			// Avoid a notice error appearing since 4.3.0.
+			if ( isset( $signup_object->id ) ) {
+				$signup_object->ID = $signup_object->id;
+			}
+
+			$style = ( ' class="alternate"' == $style ) ? '' : ' class="alternate"';
+			echo "\n\t" . $this->single_row( $signup_object, $style );
+		}
+	}
+
+	/**
+	 * Display a signup row.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @see WP_List_Table::single_row() for explanation of params.
+	 *
+	 * @param object|null $signup_object Signup user object.
+	 * @param string      $style         Styles for the row.
+	 */
+	public function single_row( $signup_object = null, $style = '' ) {
+		echo '<tr' . $style . ' id="signup-' . esc_attr( $signup_object->id ) . '">';
+		echo $this->single_row_columns( $signup_object );
+		echo '</tr>';
+	}
+
+	/**
+	 * Prevents regular users row actions to be output.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param object $signup_object Signup being acted upon.
+	 * @param string $column_name   Current column name.
+	 * @param string $primary       Primary column name.
+	 * @return string
+	 */
+	protected function handle_row_actions( $signup_object = null, $column_name = '', $primary = '' ) {
+		return '';
+	}
+
+	/**
+	 * Markup for the checkbox used to select items for bulk actions.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object|null $signup_object The signup data object.
+	 */
+	public function column_cb( $signup_object = null ) {
+	?>
+		<label class="screen-reader-text" for="signup_<?php echo intval( $signup_object->id ); ?>"><?php printf( esc_html__( 'Select user: %s', 'buddypress' ), $signup_object->user_login ); ?></label>
+		<input type="checkbox" id="signup_<?php echo intval( $signup_object->id ) ?>" name="allsignups[]" value="<?php echo esc_attr( $signup_object->id ) ?>" />
+		<?php
+	}
+
+	/**
+	 * The row actions (delete/activate/email).
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object The signup data object.
+	 */
+	public function column_username( $signup_object = null ) {
+		$avatar	= get_avatar( $signup_object->user_email, 32 );
+
+		// Activation email link.
+		$email_link = add_query_arg(
+			array(
+				'page'	    => 'bp-signups',
+				'signup_id' => $signup_object->id,
+				'action'    => 'resend',
+			),
+			bp_get_admin_url( 'users.php' )
+		);
+
+		// Activate link.
+		$activate_link = add_query_arg(
+			array(
+				'page'      => 'bp-signups',
+				'signup_id' => $signup_object->id,
+				'action'    => 'activate',
+			),
+			bp_get_admin_url( 'users.php' )
+		);
+
+		// Delete link.
+		$delete_link = add_query_arg(
+			array(
+				'page'      => 'bp-signups',
+				'signup_id' => $signup_object->id,
+				'action'    => 'delete',
+			),
+			bp_get_admin_url( 'users.php' )
+		);
+
+		echo $avatar . sprintf( '<strong><a href="%1$s" class="edit" title="%2$s">%3$s</a></strong><br/>', esc_url( $activate_link ), esc_attr__( 'Activate', 'buddypress' ), $signup_object->user_login );
+
+		$actions = array();
+
+		$actions['activate'] = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $activate_link ), __( 'Activate', 'buddypress' ) );
+		$actions['resend']   = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $email_link    ), __( 'Email',    'buddypress' ) );
+
+		if ( current_user_can( 'delete_users' ) ) {
+			$actions['delete'] = sprintf( '<a href="%1$s" class="delete">%2$s</a>', esc_url( $delete_link ), __( 'Delete', 'buddypress' ) );
+		}
+
+		/** This filter is documented in bp-members/admin/bp-members-classes.php */
+		$actions = apply_filters( 'bp_members_ms_signup_row_actions', $actions, $signup_object );
+
+		echo $this->row_actions( $actions );
+	}
+
+	/**
+	 * Display user name, if any.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object The signup data object.
+	 */
+	public function column_name( $signup_object = null ) {
+		echo esc_html( $signup_object->user_name );
+	}
+
+	/**
+	 * Display user email.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object The signup data object.
+	 */
+	public function column_email( $signup_object = null ) {
+		printf( '<a href="mailto:%1$s">%2$s</a>', esc_attr( $signup_object->user_email ), esc_html( $signup_object->user_email ) );
+	}
+
+	/**
+	 * Display registration date.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object $signup_object The signup data object.
+	 */
+	public function column_registered( $signup_object = null ) {
+		global $mode;
+
+		if ( 'list' === $mode ) {
+			$date = 'Y/m/d';
+		} else {
+			$date = 'Y/m/d \<\b\r \/\> g:i:s a';
+		}
+
+		echo mysql2date( $date, $signup_object->registered ) . "</td>";
+	}
+
+	/**
+	 * Display the last time an activation email has been sent.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object|null $signup_object Signup object instance.
+	 */
+	public function column_date_sent( $signup_object = null ) {
+		global $mode;
+
+		if ( 'list' === $mode ) {
+			$date = 'Y/m/d';
+		} else {
+			$date = 'Y/m/d \<\b\r \/\> g:i:s a';
+		}
+
+		echo mysql2date( $date, $signup_object->date_sent );
+	}
+
+	/**
+	 * Display number of time an activation email has been sent.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object|null $signup_object Signup object instance.
+	 */
+	public function column_count_sent( $signup_object = null ) {
+		echo absint( $signup_object->count_sent );
+	}
+
+	/**
+	 * Allow plugins to add their custom column.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param object|null $signup_object The signup data object.
+	 * @param string      $column_name   The column name.
+	 * @return string
+	 */
+	function column_default( $signup_object = null, $column_name = '' ) {
+
+		/**
+		 * Filters the multisite custom columns for plugins.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $column_name   The column name.
+		 * @param object $signup_object The signup data object.
+		 */
+		return apply_filters( 'bp_members_ms_signup_custom_column', '', $column_name, $signup_object );
+	}
+}
+
+endif;
Index: /tags/2.4.3/src/bp-members/admin/css/admin-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-members/admin/css/admin-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-members/admin/css/admin-rtl.css	(revision 10410)
@@ -0,0 +1,166 @@
+/**** BP Members Profile Administration Screens ****/
+
+div#profile-page.wrap form#your-profile {
+	position: relative;
+	padding-top: 50px;
+}
+
+div#profile-page.wrap form#your-profile h3:first-of-type {
+	margin-top:5em;
+}
+
+div#profile-page.wrap form#your-profile #profile-nav {
+	position: absolute;
+	top: 0;
+	width: 97%;
+}
+
+div#community-profile-page #profile-nav {
+	margin-bottom: 1em;
+}
+
+#bp_members_admin_user_stats ul {
+	margin-bottom: 0;
+}
+div#community-profile-page li.bp-members-profile-stats:before,
+div#community-profile-page li.bp-friends-profile-stats:before,
+div#community-profile-page li.bp-groups-profile-stats:before,
+div#community-profile-page li.bp-blogs-profile-stats:before,
+div#community-profile-page a.bp-xprofile-avatar-user-admin:before,
+div#community-profile-page a.bp-xprofile-avatar-user-edit:before {
+	font: normal 20px/1 'dashicons';
+	speak: none;
+	display: inline-block;
+	padding: 0 0 0 2px;
+	top: 0;
+	right: -1px;
+	position: relative;
+	vertical-align: top;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+	text-decoration: none !important;
+	color: #888;
+}
+
+div#community-profile-page li.bp-members-profile-stats:before {
+	content: "\f130";
+}
+
+div#community-profile-page li.bp-friends-profile-stats:before {
+	content: "\f454";
+}
+
+div#community-profile-page li.bp-groups-profile-stats:before {
+	content: "\f456";
+}
+
+div#community-profile-page li.bp-blogs-profile-stats:before {
+	content: "\f120";
+}
+
+div#community-profile-page a.bp-xprofile-avatar-user-admin:before {
+	content:"\f182";
+}
+
+div#community-profile-page a.bp-xprofile-avatar-user-edit:before {
+	content: "\f107";
+}
+
+div#community-profile-page div#bp_xprofile_user_admin_avatar div.avatar {
+	width:150px;
+	margin:0 auto;
+}
+
+div#community-profile-page div#bp_xprofile_user_admin_avatar div.avatar img {
+	max-width: 100%;
+	height: auto;
+}
+
+div#community-profile-page div#bp_xprofile_user_admin_avatar a {
+	display:block;
+	margin:1em 0;
+	text-decoration:none;
+	color:#888;
+}
+
+div#community-profile-page p.not-activated {
+	margin:1em 1em 0;
+	color:red;
+}
+
+#community-profile-page #submitdiv #publishing-action {
+	float: none;
+	width: 100%;
+}
+
+.bp-view-profile {
+	float: right;
+}
+
+.alt {
+	background: none;
+}
+.bp-profile-field {
+	border-bottom: dotted 1px #ccc;
+	font-size: 14px;
+	margin: 15px 0;
+	padding: 10px;
+}
+.bp-profile-field:last-child {
+	border-bottom: 0;
+}
+.bp-profile-field p {
+	font-size: 14px;
+}
+.bp-profile-field .datebox > label,
+.bp-profile-field .radio > label,
+.bp-profile-field > label {  /* label takes on left side 200px */
+	display: inline-block;
+	font-weight: 600;
+	text-align: right;
+	vertical-align: middle;
+	width: 200px;
+}
+
+.field_type_textarea > label,
+.field_type_multiselectbox > label,
+.field_type_radio .radio > label,
+.field_type_checkbox .checkbox > label {  /* these fields are usually pretty tall, so align the label for better consistency */
+	vertical-align: top;
+}
+.bp-profile-field .description {  /* description also sits in the right side column */
+	margin: 10px 200px 10px 0;
+	text-align: right;
+}
+.clear-value {  /* 'clear value' option also sits in the right side column */
+	display: block;
+	font-size: 12px;
+	margin-right: 200px;
+}
+.field_type_checkbox .checkbox > label + label {  /* force checkboxes to new lines, in the right side column */
+	display: block;
+	margin-right: 200px;
+	width: auto;
+}
+.field_type_radio .radio div:not(.field-visibility-settings) label {  /* force radio buttons to new lines */
+	display: block;
+}
+.field_type_radio .radio div:not(.field-visibility-settings) {  /* make the radio buttons sit in the right side column */
+	display: inline-block;
+}
+.field-visibility-settings-notoggle,
+.field-visibility-settings-toggle {  /* visibility settings go in the left column */
+	margin: 10px 200px 10px 0;
+	text-align: right;
+}
+.field-visibility-settings {  /* visibility settings go in the left column */
+	display: none;
+	margin-right: 200px;
+}
+.field-visibility-settings .button {  /* visibility setting close button */
+	margin-bottom: 15px;
+}
+#normal-sortables .field-visibility-settings legend {  /* id required for css selector weight */
+	font-size: 16px;
+	margin-bottom: 10px;
+}
Index: /tags/2.4.3/src/bp-members/admin/css/admin.css
===================================================================
--- /tags/2.4.3/src/bp-members/admin/css/admin.css	(revision 10410)
+++ /tags/2.4.3/src/bp-members/admin/css/admin.css	(revision 10410)
@@ -0,0 +1,166 @@
+/**** BP Members Profile Administration Screens ****/
+
+div#profile-page.wrap form#your-profile {
+	position: relative;
+	padding-top: 50px;
+}
+
+div#profile-page.wrap form#your-profile h3:first-of-type {
+	margin-top:5em;
+}
+
+div#profile-page.wrap form#your-profile #profile-nav {
+	position: absolute;
+	top: 0;
+	width: 97%;
+}
+
+div#community-profile-page #profile-nav {
+	margin-bottom: 1em;
+}
+
+#bp_members_admin_user_stats ul {
+	margin-bottom: 0;
+}
+div#community-profile-page li.bp-members-profile-stats:before,
+div#community-profile-page li.bp-friends-profile-stats:before,
+div#community-profile-page li.bp-groups-profile-stats:before,
+div#community-profile-page li.bp-blogs-profile-stats:before,
+div#community-profile-page a.bp-xprofile-avatar-user-admin:before,
+div#community-profile-page a.bp-xprofile-avatar-user-edit:before {
+	font: normal 20px/1 'dashicons';
+	speak: none;
+	display: inline-block;
+	padding: 0 2px 0 0;
+	top: 0;
+	left: -1px;
+	position: relative;
+	vertical-align: top;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+	text-decoration: none !important;
+	color: #888;
+}
+
+div#community-profile-page li.bp-members-profile-stats:before {
+	content: "\f130";
+}
+
+div#community-profile-page li.bp-friends-profile-stats:before {
+	content: "\f454";
+}
+
+div#community-profile-page li.bp-groups-profile-stats:before {
+	content: "\f456";
+}
+
+div#community-profile-page li.bp-blogs-profile-stats:before {
+	content: "\f120";
+}
+
+div#community-profile-page a.bp-xprofile-avatar-user-admin:before {
+	content:"\f182";
+}
+
+div#community-profile-page a.bp-xprofile-avatar-user-edit:before {
+	content: "\f107";
+}
+
+div#community-profile-page div#bp_xprofile_user_admin_avatar div.avatar {
+	width:150px;
+	margin:0 auto;
+}
+
+div#community-profile-page div#bp_xprofile_user_admin_avatar div.avatar img {
+	max-width: 100%;
+	height: auto;
+}
+
+div#community-profile-page div#bp_xprofile_user_admin_avatar a {
+	display:block;
+	margin:1em 0;
+	text-decoration:none;
+	color:#888;
+}
+
+div#community-profile-page p.not-activated {
+	margin:1em 1em 0;
+	color:red;
+}
+
+#community-profile-page #submitdiv #publishing-action {
+	float: none;
+	width: 100%;
+}
+
+.bp-view-profile {
+	float: left;
+}
+
+.alt {
+	background: none;
+}
+.bp-profile-field {
+	border-bottom: dotted 1px #ccc;
+	font-size: 14px;
+	margin: 15px 0;
+	padding: 10px;
+}
+.bp-profile-field:last-child {
+	border-bottom: 0;
+}
+.bp-profile-field p {
+	font-size: 14px;
+}
+.bp-profile-field .datebox > label,
+.bp-profile-field .radio > label,
+.bp-profile-field > label {  /* label takes on left side 200px */
+	display: inline-block;
+	font-weight: 600;
+	text-align: left;
+	vertical-align: middle;
+	width: 200px;
+}
+
+.field_type_textarea > label,
+.field_type_multiselectbox > label,
+.field_type_radio .radio > label,
+.field_type_checkbox .checkbox > label {  /* these fields are usually pretty tall, so align the label for better consistency */
+	vertical-align: top;
+}
+.bp-profile-field .description {  /* description also sits in the right side column */
+	margin: 10px 0 10px 200px;
+	text-align: left;
+}
+.clear-value {  /* 'clear value' option also sits in the right side column */
+	display: block;
+	font-size: 12px;
+	margin-left: 200px;
+}
+.field_type_checkbox .checkbox > label + label {  /* force checkboxes to new lines, in the right side column */
+	display: block;
+	margin-left: 200px;
+	width: auto;
+}
+.field_type_radio .radio div:not(.field-visibility-settings) label {  /* force radio buttons to new lines */
+	display: block;
+}
+.field_type_radio .radio div:not(.field-visibility-settings) {  /* make the radio buttons sit in the right side column */
+	display: inline-block;
+}
+.field-visibility-settings-notoggle,
+.field-visibility-settings-toggle {  /* visibility settings go in the left column */
+	margin: 10px 0 10px 200px;
+	text-align: left;
+}
+.field-visibility-settings {  /* visibility settings go in the left column */
+	display: none;
+	margin-left: 200px;
+}
+.field-visibility-settings .button {  /* visibility setting close button */
+	margin-bottom: 15px;
+}
+#normal-sortables .field-visibility-settings legend {  /* id required for css selector weight */
+	font-size: 16px;
+	margin-bottom: 10px;
+}
Index: /tags/2.4.3/src/bp-members/admin/js/admin.js
===================================================================
--- /tags/2.4.3/src/bp-members/admin/js/admin.js	(revision 10410)
+++ /tags/2.4.3/src/bp-members/admin/js/admin.js	(revision 10410)
@@ -0,0 +1,55 @@
+/* exported clear */
+
+( function( $ ) {
+	// Profile Visibility Settings
+
+	$( '.visibility-toggle-link' ).on( 'click', function( event ) {
+		event.preventDefault();
+
+		$( this ).parent().hide()
+			.siblings( '.field-visibility-settings' ).show();
+	} );
+
+	$( '.field-visibility-settings-close' ).on( 'click', function( event ) {
+		event.preventDefault();
+
+		var settings_div = $(this).parent(),
+		vis_setting_text = settings_div.find( 'input:checked' ).parent().text();
+
+		settings_div.hide()
+			.siblings( '.field-visibility-settings-toggle' )
+				.children( '.current-visibility-level' ).text( vis_setting_text ).end()
+			.show();
+	} );
+
+} )( jQuery );
+
+
+/**
+ * Deselects any select options or input options for the specified field element.
+ *
+ * @param {String} container HTML ID of the field
+ * @since 1.0.0
+ */
+function clear( container ) {
+	container = document.getElementById( container );
+	if ( ! container ) {
+		return;
+	}
+
+	var radioButtons = container.getElementsByTagName( 'INPUT' ),
+		options = container.getElementsByTagName( 'OPTION' ),
+		i       = 0;
+
+	if ( radioButtons ) {
+		for ( i = 0; i < radioButtons.length; i++ ) {
+			radioButtons[i].checked = '';
+		}
+	}
+
+	if ( options ) {
+		for ( i = 0; i < options.length; i++ ) {
+			options[i].selected = false;
+		}
+	}
+}
Index: /tags/2.4.3/src/bp-members/bp-members-actions.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-actions.php	(revision 10410)
@@ -0,0 +1,122 @@
+<?php
+/**
+ * BuddyPress Members Actions.
+ *
+ * Action functions are exactly the same as screen functions, however they do not
+ * have a template screen associated with them. Usually they will send the user
+ * back to the default screen after execution.
+ *
+ * @package BuddyPress
+ * @subpackage MembersActions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Catch a "Mark as Spammer/Not Spammer" click from the toolbar.
+ *
+ * When a site admin selects "Mark as Spammer/Not Spammer" from the admin menu
+ * this action will fire and mark or unmark the user and their blogs as spam.
+ * Must be a site admin for this function to run.
+ *
+ * Note: no longer used in the current state. See the Settings component.
+ *
+ * @param int $user_id Optional. User ID to mark as spam. Defaults to displayed user.
+ */
+function bp_core_action_set_spammer_status( $user_id = 0 ) {
+
+	// Only super admins can currently spam users (but they can't spam
+	// themselves).
+	if ( ! is_super_admin() || bp_is_my_profile() ) {
+		return;
+	}
+
+	// Use displayed user if it's not yourself.
+	if ( empty( $user_id ) )
+		$user_id = bp_displayed_user_id();
+
+	if ( bp_is_current_component( 'admin' ) && ( in_array( bp_current_action(), array( 'mark-spammer', 'unmark-spammer' ) ) ) ) {
+
+		// Check the nonce.
+		check_admin_referer( 'mark-unmark-spammer' );
+
+		// To spam or not to spam.
+		$status = bp_is_current_action( 'mark-spammer' ) ? 'spam' : 'ham';
+
+		// The heavy lifting.
+		bp_core_process_spammer_status( $user_id, $status );
+
+		// Add feedback message. @todo - Error reporting.
+		if ( 'spam' == $status ) {
+			bp_core_add_message( __( 'User marked as spammer. Spam users are visible only to site admins.', 'buddypress' ) );
+		} else {
+			bp_core_add_message( __( 'User removed as spammer.', 'buddypress' ) );
+		}
+
+		// Deprecated. Use bp_core_process_spammer_status.
+		$is_spam = 'spam' == $status;
+		do_action( 'bp_core_action_set_spammer_status', bp_displayed_user_id(), $is_spam );
+
+		// Redirect back to where we came from.
+		bp_core_redirect( wp_get_referer() );
+	}
+}
+
+/*
+ * Unhooked in 1.6.0 - moved to settings.
+ * add_action( 'bp_actions', 'bp_core_action_set_spammer_status' );
+ */
+
+/**
+ * Process user deletion requests.
+ *
+ * Note: No longer called here. See the Settings component.
+ */
+function bp_core_action_delete_user() {
+
+	if ( !bp_current_user_can( 'bp_moderate' ) || bp_is_my_profile() || !bp_displayed_user_id() )
+		return false;
+
+	if ( bp_is_current_component( 'admin' ) && bp_is_current_action( 'delete-user' ) ) {
+
+		// Check the nonce.
+		check_admin_referer( 'delete-user' );
+
+		$errors = false;
+		do_action( 'bp_core_before_action_delete_user', $errors );
+
+		if ( bp_core_delete_account( bp_displayed_user_id() ) ) {
+			bp_core_add_message( sprintf( __( '%s has been deleted from the system.', 'buddypress' ), bp_get_displayed_user_fullname() ) );
+		} else {
+			bp_core_add_message( sprintf( __( 'There was an error deleting %s from the system. Please try again.', 'buddypress' ), bp_get_displayed_user_fullname() ), 'error' );
+			$errors = true;
+		}
+
+		do_action( 'bp_core_action_delete_user', $errors );
+
+		if ( $errors )
+			bp_core_redirect( bp_displayed_user_domain() );
+		else
+			bp_core_redirect( bp_loggedin_user_domain() );
+	}
+}
+
+/*
+ * Unhooked in 1.6.0 - moved to settings
+ * add_action( 'bp_actions', 'bp_core_action_delete_user' );
+ */
+
+/**
+ * Redirect to a random member page when visiting a ?random-member URL.
+ *
+ * @since 1.0.0
+ */
+function bp_core_get_random_member() {
+	if ( ! isset( $_GET['random-member'] ) )
+		return;
+
+	$user = bp_core_get_users( array( 'type' => 'random', 'per_page' => 1 ) );
+	bp_core_redirect( bp_core_get_user_domain( $user['users'][0]->id ) );
+}
+add_action( 'bp_actions', 'bp_core_get_random_member' );
Index: /tags/2.4.3/src/bp-members/bp-members-activity.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-activity.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-activity.php	(revision 10410)
@@ -0,0 +1,96 @@
+<?php
+/**
+ * BuddyPress Member Activity
+ *
+ * @package BuddyPress
+ * @subpackage MembersActivity
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register the 'new member' activity type.
+ *
+ * @since 2.2.0
+ *
+ * @uses bp_activity_set_action()
+ * @uses buddypress()
+ */
+function bp_members_register_activity_actions() {
+
+	bp_activity_set_action(
+		buddypress()->members->id,
+		'new_member',
+		__( 'New member registered', 'buddypress' ),
+		'bp_members_format_activity_action_new_member',
+		__( 'New Members', 'buddypress' ),
+		array( 'activity' )
+	);
+
+	/**
+	 * Fires after the default 'new member' activity types are registered.
+	 *
+	 * @since 2.2.0
+	 */
+	do_action( 'bp_members_register_activity_actions' );
+}
+add_action( 'bp_register_activity_actions', 'bp_members_register_activity_actions' );
+
+/**
+ * Format 'new_member' activity actions.
+ *
+ * @since 2.2.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity object.
+ * @return string $action
+ */
+function bp_members_format_activity_action_new_member( $action, $activity ) {
+	$userlink = bp_core_get_userlink( $activity->user_id );
+	$action   = sprintf( __( '%s became a registered member', 'buddypress' ), $userlink );
+
+	// Legacy filter - pass $user_id instead of $activity.
+	if ( has_filter( 'bp_core_activity_registered_member_action' ) ) {
+		$action = apply_filters( 'bp_core_activity_registered_member_action', $action, $activity->user_id );
+	}
+
+	/**
+	 * Filters the formatted 'new member' activity actions.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string $action   Static activity action.
+	 * @param object $activity Activity object.
+	 */
+	return apply_filters( 'bp_members_format_activity_action_new_member', $action, $activity );
+}
+
+/**
+ * Create a "became a registered user" activity item when a user activates his account.
+ *
+ * @param array $user Array of userdata passed to bp_core_activated_user hook.
+ * @return bool
+ */
+function bp_core_new_user_activity( $user ) {
+	if ( empty( $user ) ) {
+		return false;
+	}
+
+	if ( is_array( $user ) ) {
+		$user_id = $user['user_id'];
+	} else {
+		$user_id = $user;
+	}
+
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	bp_activity_add( array(
+		'user_id'   => $user_id,
+		'component' => buddypress()->members->id,
+		'type'      => 'new_member'
+	) );
+}
+add_action( 'bp_core_activated_user', 'bp_core_new_user_activity' );
Index: /tags/2.4.3/src/bp-members/bp-members-admin.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-admin.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-admin.php	(revision 10410)
@@ -0,0 +1,1983 @@
+<?php
+/**
+ * BuddyPress Members Admin
+ *
+ * @package BuddyPress
+ * @subpackage MembersAdmin
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+if ( !class_exists( 'BP_Members_Admin' ) ) :
+
+/**
+ * Load Members admin area.
+ *
+ * @since 2.0.0
+ */
+class BP_Members_Admin {
+
+	/** Directory *************************************************************/
+
+	/**
+	 * Path to the BP Members Admin directory.
+	 *
+	 * @var string $admin_dir
+	 */
+	public $admin_dir = '';
+
+	/** URLs ******************************************************************/
+
+	/**
+	 * URL to the BP Members Admin directory.
+	 *
+	 * @var string $admin_url
+	 */
+	public $admin_url = '';
+
+	/**
+	 * URL to the BP Members Admin CSS directory.
+	 *
+	 * @var string $css_url
+	 */
+	public $css_url = '';
+
+	/**
+	 * URL to the BP Members Admin JS directory.
+	 *
+	 * @var string
+	 */
+	public $js_url = '';
+
+	/** Other *****************************************************************/
+
+	/**
+	 * Screen id for edit user's profile page.
+	 *
+	 * @var string
+	 */
+	public $user_page = '';
+
+	/**
+	 * Setup BP Members Admin.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @uses buddypress() to get BuddyPress main instance.
+	 */
+	public static function register_members_admin() {
+		if ( ! is_admin() ) {
+			return;
+		}
+
+		$bp = buddypress();
+
+		if ( empty( $bp->members->admin ) ) {
+			$bp->members->admin = new self;
+		}
+
+		return $bp->members->admin;
+	}
+
+	/**
+	 * Constructor method.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		$this->setup_globals();
+		$this->setup_actions();
+	}
+
+	/**
+	 * Set admin-related globals.
+	 *
+	 * @since 2.0.0
+	 */
+	private function setup_globals() {
+		$bp = buddypress();
+
+		// Paths and URLs
+		$this->admin_dir = trailingslashit( $bp->plugin_dir  . 'bp-members/admin' ); // Admin path.
+		$this->admin_url = trailingslashit( $bp->plugin_url  . 'bp-members/admin' ); // Admin URL.
+		$this->css_url   = trailingslashit( $this->admin_url . 'css' ); // Admin CSS URL.
+		$this->js_url    = trailingslashit( $this->admin_url . 'js'  ); // Admin CSS URL.
+
+		// Capability depends on config.
+		$this->capability = bp_core_do_network_admin() ? 'manage_network_users' : 'edit_users';
+
+		// The Edit Profile Screen id.
+		$this->user_page = '';
+
+		// The Show Profile Screen id.
+		$this->user_profile = is_network_admin() ? 'users' : 'profile';
+
+		// The current user id.
+		$this->current_user_id = get_current_user_id();
+
+		// The user id being edited.
+		$this->user_id = 0;
+
+		// Is a member editing their own profile.
+		$this->is_self_profile = false;
+
+		// The screen ids to load specific css for.
+		$this->screen_id = array();
+
+		// The stats metabox default position.
+		$this->stats_metabox = new StdClass();
+
+		// BuddyPress edit user's profile args.
+		$this->edit_profile_args = array( 'page' => 'bp-profile-edit' );
+		$this->edit_profile_url  = '';
+		$this->edit_url          = '';
+
+		// Data specific to signups.
+		$this->users_page   = '';
+		$this->signups_page = '';
+		$this->users_url    = bp_get_admin_url( 'users.php' );
+		$this->users_screen = bp_core_do_network_admin() ? 'users-network' : 'users';
+
+		// Specific config: BuddyPress is not network activated.
+		$this->subsite_activated = (bool) is_multisite() && ! bp_is_network_activated();
+
+		// When BuddyPress is not network activated, only Super Admin can moderate signups.
+		if ( ! empty( $this->subsite_activated ) ) {
+			$this->capability = 'manage_network_users';
+		}
+	}
+
+	/**
+	 * Set admin-related actions and filters.
+	 *
+	 * @since 2.0.0
+	 */
+	private function setup_actions() {
+
+		/** Extended Profile *************************************************
+		 */
+
+		// Enqueue all admin JS and CSS.
+		add_action( 'bp_admin_enqueue_scripts', array( $this, 'enqueue_scripts'   )        );
+
+		// Add some page specific output to the <head>.
+		add_action( 'bp_admin_head',            array( $this, 'admin_head'        ), 999   );
+
+		// Add menu item to all users menu.
+		add_action( 'admin_menu',               array( $this, 'admin_menus'       ), 5     );
+		add_action( 'network_admin_menu',       array( $this, 'admin_menus'       ), 5     );
+		add_action( 'user_admin_menu',          array( $this, 'user_profile_menu' ), 5     );
+
+		// Create the Profile Navigation (Profile/Extended Profile).
+		add_action( 'edit_user_profile',        array( $this, 'profile_nav'       ), 99, 1 );
+		add_action( 'show_user_profile',        array( $this, 'profile_nav'       ), 99, 1 );
+
+		// Editing users of a specific site.
+		add_action( "admin_head-site-users.php", array( $this, 'profile_admin_head' ) );
+
+		// Add a row action to users listing.
+		if ( bp_core_do_network_admin() ) {
+			add_filter( 'ms_user_row_actions',        array( $this, 'row_actions'                    ), 10, 2 );
+			add_action( 'admin_init',                 array( $this, 'add_edit_profile_url_filter'    )        );
+			add_action( 'wp_after_admin_bar_render',  array( $this, 'remove_edit_profile_url_filter' )        );
+		}
+
+		// Add user row actions for single site.
+		add_filter( 'user_row_actions', array( $this, 'row_actions' ), 10, 2 );
+
+		// Process changes to member type.
+		add_action( 'bp_members_admin_load', array( $this, 'process_member_type_update' ) );
+
+		/** Signups **********************************************************
+		 */
+
+		if ( is_admin() ) {
+
+			// Filter non multisite user query to remove sign-up users.
+			if ( ! is_multisite() ) {
+				add_action( 'pre_user_query', array( $this, 'remove_signups_from_user_query' ), 10, 1 );
+			}
+
+			// Reorganise the views navigation in users.php and signups page.
+			if ( current_user_can( $this->capability ) ) {
+				add_filter( "views_{$this->users_screen}", array( $this, 'signup_filter_view'    ), 10, 1 );
+				add_filter( 'set-screen-option',           array( $this, 'signup_screen_options' ), 10, 3 );
+			}
+		}
+	}
+
+	/**
+	 * Get the user ID.
+	 *
+	 * Look for $_GET['user_id']. If anything else, force the user ID to the
+	 * current user's ID so they aren't left without a user to edit.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return int
+	 */
+	private function get_user_id() {
+		if ( ! empty( $this->user_id ) ) {
+			return $this->user_id;
+		}
+
+		$this->user_id = (int) get_current_user_id();
+
+		// We'll need a user ID when not on self profile.
+		if ( ! empty( $_GET['user_id'] ) ) {
+			$this->user_id = (int) $_GET['user_id'];
+		}
+
+		return $this->user_id;
+	}
+
+	/**
+	 * Can the current user edit the one displayed.
+	 *
+	 * Self profile editing / or bp_moderate check.
+	 * This might be replaced by more granular capabilities
+	 * in the future.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param int $user_id ID of the user being checked for edit ability.
+	 *
+	 * @return bool
+	 */
+	private function member_can_edit( $user_id = 0 ) {
+		$retval = false;
+
+		// Bail if no user ID was passed.
+		if ( empty( $user_id ) ) {
+			return $retval;
+		}
+
+		// Member can edit if they are viewing their own profile.
+		if ( $this->current_user_id === $user_id ) {
+			$retval = true;
+
+		// Trust the 'bp_moderate' capability.
+		} else {
+			$retval = bp_current_user_can( 'bp_moderate' );
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * Get admin notice when saving a user or member profile.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return array
+	 */
+	private function get_user_notice() {
+
+		// Setup empty notice for return value.
+		$notice = array();
+
+		// Updates.
+		if ( ! empty( $_REQUEST['updated'] ) ) {
+			switch ( $_REQUEST['updated'] ) {
+			case 'avatar':
+				$notice = array(
+					'class'   => 'updated',
+					'message' => __( 'Profile photo was deleted.', 'buddypress' )
+				);
+				break;
+			case 'ham' :
+				$notice = array(
+					'class'   => 'updated',
+					'message' => __( 'User removed as spammer.', 'buddypress' )
+				);
+				break;
+			case 'spam' :
+				$notice = array(
+					'class'   => 'updated',
+					'message' => __( 'User marked as spammer. Spam users are visible only to site admins.', 'buddypress' )
+				);
+				break;
+			case 1 :
+				$notice = array(
+					'class'   => 'updated',
+					'message' => __( 'Profile updated.', 'buddypress' )
+				);
+				break;
+			}
+		}
+
+		// Errors.
+		if ( ! empty( $_REQUEST['error'] ) ) {
+			switch ( $_REQUEST['error'] ) {
+			case 'avatar':
+				$notice = array(
+					'class'   => 'error',
+					'message' => __( 'There was a problem deleting that profile photo. Please try again.', 'buddypress' )
+				);
+				break;
+			case 'ham' :
+				$notice = array(
+					'class'   => 'error',
+					'message' => __( 'User could not be removed as spammer.', 'buddypress' )
+				);
+				break;
+			case 'spam' :
+				$notice = array(
+					'class'   => 'error',
+					'message' => __( 'User could not be marked as spammer.', 'buddypress' )
+				);
+				break;
+			case 1 :
+				$notice = array(
+					'class'   => 'error',
+					'message' => __( 'An error occurred while trying to update the profile.', 'buddypress' )
+				);
+				break;
+			case 2:
+				$notice = array(
+					'class'   => 'error',
+					'message' => __( 'Please make sure you fill in all required fields in this profile field group before saving.', 'buddypress' )
+				);
+				break;
+			case 3:
+				$notice = array(
+					'class'   => 'error',
+					'message' => __( 'There was a problem updating some of your profile information. Please try again.', 'buddypress' )
+				);
+				break;
+			}
+		}
+
+		return $notice;
+	}
+
+	/**
+	 * Create the /user/ admin Profile submenus for all members.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @uses add_submenu_page() To add the Edit Profile page in Profile section.
+	 */
+	public function user_profile_menu() {
+
+		// Setup the hooks array.
+		$hooks = array();
+
+		// Add the faux "Edit Profile" submenu page.
+		$hooks['user'] = $this->user_page = add_submenu_page(
+			'profile.php',
+			__( 'Edit Profile',  'buddypress' ),
+			__( 'Edit Profile',  'buddypress' ),
+			'exist',
+			'bp-profile-edit',
+			array( $this, 'user_admin' )
+		);
+
+		// Setup the screen ID's.
+		$this->screen_id = array(
+			$this->user_page    . '-user',
+			$this->user_profile . '-user'
+		);
+
+		// Loop through new hooks and add method actions.
+		foreach ( $hooks as $key => $hook ) {
+			add_action( "load-{$hook}", array( $this, $key . '_admin_load' ) );
+		}
+
+		// Add the profile_admin_head method to proper admin_head actions.
+		add_action( "admin_head-{$this->user_page}", array( $this, 'profile_admin_head' ) );
+		add_action( "admin_head-profile.php",        array( $this, 'profile_admin_head' ) );
+	}
+
+	/**
+	 * Create the All Users / Profile > Edit Profile and All Users Signups submenus.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @uses add_submenu_page() To add the Edit Profile page in Users/Profile section.
+	 */
+	public function admin_menus() {
+
+		// Setup the hooks array.
+		$hooks = array();
+
+		// Manage user's profile.
+		$hooks['user'] = $this->user_page = add_submenu_page(
+			$this->user_profile . '.php',
+			__( 'Edit Profile',  'buddypress' ),
+			__( 'Edit Profile',  'buddypress' ),
+			'read',
+			'bp-profile-edit',
+			array( $this, 'user_admin' )
+		);
+
+		// Only show sign-ups where they belong.
+		if ( ! is_multisite() || is_network_admin() ) {
+
+			// Manage signups.
+			$hooks['signups'] = $this->signups_page = add_users_page(
+				__( 'Manage Signups',  'buddypress' ),
+				__( 'Manage Signups',  'buddypress' ),
+				$this->capability,
+				'bp-signups',
+				array( $this, 'signups_admin' )
+			);
+		}
+
+		$edit_page         = 'user-edit';
+		$profile_page      = 'profile';
+		$this->users_page  = 'users';
+
+		// Self profile check is needed for this pages.
+		$page_head = array(
+			$edit_page        . '.php',
+			$profile_page     . '.php',
+			$this->user_page,
+			$this->users_page . '.php',
+		);
+
+		// Append '-network' to each array item if in network admin.
+		if ( is_network_admin() ) {
+			$edit_page          .= '-network';
+			$profile_page       .= '-network';
+			$this->user_page    .= '-network';
+			$this->users_page   .= '-network';
+			$this->signups_page .= '-network';
+		}
+
+		// Setup the screen ID's.
+		$this->screen_id = array(
+			$edit_page,
+			$this->user_page,
+			$profile_page
+		);
+
+		// Loop through new hooks and add method actions.
+		foreach ( $hooks as $key => $hook ) {
+			add_action( "load-{$hook}", array( $this, $key . '_admin_load' ) );
+		}
+
+		// Add the profile_admin_head method to proper admin_head actions.
+		foreach ( $page_head as $head ) {
+			add_action( "admin_head-{$head}", array( $this, 'profile_admin_head' ) );
+		}
+	}
+
+	/**
+	 * Highlight the Users menu if on Edit Profile and check if on the user's admin profile.
+	 *
+	 * @since 2.1.0
+	 */
+	public function profile_admin_head() {
+		global $submenu_file, $parent_file;
+
+		// Is the user editing their own profile?
+		if ( is_user_admin() || ( defined( 'IS_PROFILE_PAGE' ) && IS_PROFILE_PAGE ) ) {
+			$this->is_self_profile = true;
+
+		// Is the user attempting to edit their own profile.
+		} elseif ( isset( $_GET['user_id' ] ) || ( isset( $_GET['page'] ) && ( 'bp-profile-edit' === $_GET['page'] ) ) ) {
+			$this->is_self_profile = (bool) ( $this->get_user_id() === $this->current_user_id );
+		}
+
+		// Force the parent file to users.php to open the correct top level menu
+		// but only if not editing a site via the network site editing page.
+		if ( 'sites.php' !== $parent_file ) {
+			$parent_file  = 'users.php';
+			$submenu_file = 'users.php';
+		}
+
+		// Editing your own profile, so recheck some vars.
+		if ( true === $this->is_self_profile ) {
+
+			// Use profile.php as the edit page.
+			$edit_page = 'profile.php';
+
+			// Set profile.php as the parent & sub files to correct the menu nav.
+			if ( is_blog_admin() || is_user_admin() ) {
+				$parent_file  = 'profile.php';
+				$submenu_file = 'profile.php';
+			}
+
+		// Not editing yourself, so use user-edit.php.
+		} else {
+			$edit_page = 'user-edit.php';
+		}
+
+		if ( is_user_admin() ) {
+			$this->edit_profile_url = add_query_arg( $this->edit_profile_args, user_admin_url( 'profile.php' ) );
+			$this->edit_url         = user_admin_url( 'profile.php' );
+
+		} elseif ( is_blog_admin() ) {
+			$this->edit_profile_url = add_query_arg( $this->edit_profile_args, admin_url( 'users.php' ) );
+			$this->edit_url         = admin_url( $edit_page );
+
+		} elseif ( is_network_admin() ) {
+			$this->edit_profile_url = add_query_arg( $this->edit_profile_args, network_admin_url( 'users.php' ) );
+			$this->edit_url         = network_admin_url( $edit_page );
+		}
+	}
+
+	/**
+	 * Remove the Edit Profile page.
+	 *
+	 * We add these pages in order to integrate with WP's Users panel, but
+	 * we want them to show up as a row action of the WP panel, not as separate
+	 * subnav items under the Users menu.
+	 *
+	 * @since 2.0.0
+	 */
+	public function admin_head() {
+		remove_submenu_page( 'users.php',   'bp-profile-edit' );
+		remove_submenu_page( 'profile.php', 'bp-profile-edit' );
+	}
+
+	/** Community Profile *****************************************************/
+
+	/**
+	 * Add some specific styling to the Edit User and Edit User's Profile page.
+	 *
+	 * @since 2.0.0
+	 */
+	public function enqueue_scripts() {
+		if ( ! in_array( get_current_screen()->id, $this->screen_id ) ) {
+			return;
+		}
+
+		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+		$css = $this->css_url . "admin{$min}.css";
+
+		/**
+		 * Filters the CSS URL to enqueue in the Members admin area.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $css URL to the CSS admin file to load.
+		 */
+		$css = apply_filters( 'bp_members_admin_css', $css );
+
+		wp_enqueue_style( 'bp-members-css', $css, array(), bp_get_version() );
+
+		wp_style_add_data( 'bp-members-css', 'rtl', true );
+		if ( $min ) {
+			wp_style_add_data( 'bp-members-css', 'suffix', $min );
+		}
+
+		// Only load JavaScript for BuddyPress profile.
+		if ( get_current_screen()->id == $this->user_page ) {
+			$js = $this->js_url . "admin{$min}.js";
+
+			/**
+			 * Filters the JS URL to enqueue in the Members admin area.
+			 *
+			 * @since 2.0.0
+			 *
+			 * @param string $js URL to the JavaScript admin file to load.
+			 */
+			$js = apply_filters( 'bp_members_admin_js', $js );
+			wp_enqueue_script( 'bp-members-js', $js, array( 'jquery' ), bp_get_version(), true );
+		}
+
+		/**
+		 * Fires after all of the members JavaScript and CSS are enqueued.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $id        ID of the current screen.
+		 * @param array  $screen_id Array of allowed screens to add scripts and styles to.
+		 */
+		do_action( 'bp_members_admin_enqueue_scripts', get_current_screen()->id, $this->screen_id );
+	}
+
+	/**
+	 * Create the Profile navigation in Edit User & Edit Profile pages.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param object|null $user   User to create profile navigation for.
+	 * @param string      $active Which profile to highlight.
+	 * @return string
+	 */
+	public function profile_nav( $user = null, $active = 'WordPress' ) {
+
+		// Bail if no user ID exists here.
+		if ( empty( $user->ID ) ) {
+			return;
+		}
+
+		// Add the user ID to query arguments when not editing yourself.
+		if ( false === $this->is_self_profile ) {
+			$query_args = array( 'user_id' => $user->ID );
+		} else {
+			$query_args = array();
+		}
+
+		// Conditionally add a referer if it exists in the existing request.
+		if ( ! empty( $_REQUEST['wp_http_referer'] ) ) {
+			$query_args['wp_http_referer'] = urlencode( stripslashes_deep( $_REQUEST['wp_http_referer'] ) );
+		}
+
+		// Setup the two distinct "edit" URL's.
+		$community_url = add_query_arg( $query_args, $this->edit_profile_url );
+		$wordpress_url = add_query_arg( $query_args, $this->edit_url         );
+
+		$bp_active = false;
+		$wp_active = ' nav-tab-active';
+		if ( 'BuddyPress' === $active ) {
+			$bp_active = ' nav-tab-active';
+			$wp_active = false;
+		} ?>
+
+		<h2 id="profile-nav" class="nav-tab-wrapper">
+			<?php
+			/**
+			 * In configs where BuddyPress is not network activated, as regular
+			 * admins do not have the capacity to edit other users, we must add
+			 * this check.
+			 */
+			if ( current_user_can( 'edit_user', $user->ID ) ) : ?>
+
+				<a class="nav-tab<?php echo esc_attr( $wp_active ); ?>" href="<?php echo esc_url( $wordpress_url );?>"><?php _e( 'Profile', 'buddypress' ); ?></a>
+
+			<?php endif; ?>
+
+			<a class="nav-tab<?php echo esc_attr( $bp_active ); ?>" href="<?php echo esc_url( $community_url );?>"><?php _e( 'Extended Profile', 'buddypress' ); ?></a>
+		</h2>
+
+		<?php
+	}
+
+	/**
+	 * Set up the user's profile admin page.
+	 *
+	 * Loaded before the page is rendered, this function does all initial
+	 * setup, including: processing form requests, registering contextual
+	 * help, and setting up screen options.
+	 *
+	 * @since 2.0.0
+	 */
+	public function user_admin_load() {
+
+		// Get the user ID.
+		$user_id = $this->get_user_id();
+
+		// Can current user edit this profile?
+		if ( ! $this->member_can_edit( $user_id ) ) {
+			wp_die( __( 'You cannot edit the requested user.', 'buddypress' ) );
+		}
+
+		// Build redirection URL.
+		$redirect_to = remove_query_arg( array( 'action', 'error', 'updated', 'spam', 'ham', 'delete_avatar' ), $_SERVER['REQUEST_URI'] );
+		$doaction    = ! empty( $_REQUEST['action'] ) ? $_REQUEST['action'] : false;
+
+		if ( ! empty( $_REQUEST['user_status'] ) ) {
+			$spam = (bool) ( 'spam' === $_REQUEST['user_status'] );
+
+			if ( $spam !== bp_is_user_spammer( $user_id ) ) {
+				$doaction = $_REQUEST['user_status'];
+			}
+		}
+
+		/**
+		 * Fires at the start of the signups admin load.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $doaction Current bulk action being processed.
+		 * @param array  $_REQUEST Current $_REQUEST global.
+		 */
+		do_action_ref_array( 'bp_members_admin_load', array( $doaction, $_REQUEST ) );
+
+		/**
+		 * Filters the allowed actions for use in the user admin page.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $value Array of allowed actions to use.
+		 */
+		$allowed_actions = apply_filters( 'bp_members_admin_allowed_actions', array( 'update', 'delete_avatar', 'spam', 'ham' ) );
+
+		// Prepare the display of the Community Profile screen.
+		if ( ! in_array( $doaction, $allowed_actions ) ) {
+			add_screen_option( 'layout_columns', array( 'default' => 2, 'max' => 2, ) );
+
+			get_current_screen()->add_help_tab( array(
+				'id'      => 'bp-profile-edit-overview',
+				'title'   => __( 'Overview', 'buddypress' ),
+				'content' =>
+				'<p>' . __( 'This is the admin view of a user&#39;s profile.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'In the main column, you can edit the fields of the user&#39;s extended profile.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'In the right-hand column, you can update the user&#39;s status, delete the user&#39;s avatar, and view recent statistics.', 'buddypress' ) . '</p>'
+			) );
+
+			// Help panel - sidebar links.
+			get_current_screen()->set_help_sidebar(
+				'<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
+				'<p>' . __( '<a href="https://codex.buddypress.org/administrator-guide/extended-profiles/">Managing Profiles</a>', 'buddypress' ) . '</p>' .
+				'<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
+			);
+
+			// Register metaboxes for the edit screen.
+			add_meta_box(
+				'submitdiv',
+				_x( 'Status', 'members user-admin edit screen', 'buddypress' ),
+				array( $this, 'user_admin_status_metabox' ),
+				get_current_screen()->id,
+				'side',
+				'core'
+			);
+
+			// In case xprofile is not active.
+			$this->stats_metabox->context  = 'normal';
+			$this->stats_metabox->priority = 'core';
+
+			/**
+			 * Fires before loading the profile fields if component is active.
+			 *
+			 * Plugins should not use this hook, please use 'bp_members_admin_user_metaboxes' instead.
+			 *
+			 * @since 2.0.0
+			 *
+			 * @param int    $user_id       Current user ID for the screen.
+			 * @param string $id            Current screen ID.
+			 * @param object $stats_metabox Object holding position data for use with the stats metabox.
+			 */
+			do_action_ref_array( 'bp_members_admin_xprofile_metabox', array( $user_id, get_current_screen()->id, $this->stats_metabox ) );
+
+			// If xProfile is inactive, difficult to know what's profile we're on.
+			if ( 'normal' === $this->stats_metabox->context ) {
+				$display_name = bp_core_get_user_displayname( $user_id );
+			} else {
+				$display_name = __( 'Member', 'buddypress' );
+			}
+
+			// User Stat metabox.
+			add_meta_box(
+				'bp_members_admin_user_stats',
+				sprintf( _x( "%s's Stats", 'members user-admin edit screen', 'buddypress' ), $display_name ),
+				array( $this, 'user_admin_stats_metabox' ),
+				get_current_screen()->id,
+				sanitize_key( $this->stats_metabox->context ),
+				sanitize_key( $this->stats_metabox->priority )
+			);
+
+			// Member Type metabox. Only added if member types have been registered.
+			$member_types = bp_get_member_types();
+			if ( ! empty( $member_types ) ) {
+				add_meta_box(
+					'bp_members_admin_member_type',
+					_x( 'Member Type', 'members user-admin edit screen', 'buddypress' ),
+					array( $this, 'user_admin_member_type_metabox' ),
+					get_current_screen()->id,
+					'side',
+					'core'
+				);
+			}
+
+			/**
+			 * Fires at the end of the Community Profile screen.
+			 *
+			 * Plugins can restrict metabox to "bp_moderate" admins by checking if
+			 * the first argument ($this->is_self_profile) is false in their callback.
+			 * They can also restrict their metabox to self profile editing
+			 * by setting it to true.
+			 *
+			 * @since 2.0.0
+			 *
+			 * @param bool $is_self_profile Whether or not it is the current user's profile.
+			 * @param int  $user_id         Current user ID.
+			 */
+			do_action( 'bp_members_admin_user_metaboxes', $this->is_self_profile, $user_id );
+
+			// Enqueue JavaScript files.
+			wp_enqueue_script( 'postbox'   );
+			wp_enqueue_script( 'dashboard' );
+
+		// Spam or Ham user.
+		} elseif ( in_array( $doaction, array( 'spam', 'ham' ) ) && empty( $this->is_self_profile ) ) {
+
+			check_admin_referer( 'edit-bp-profile_' . $user_id );
+
+			if ( bp_core_process_spammer_status( $user_id, $doaction ) ) {
+				$redirect_to = add_query_arg( 'updated', $doaction, $redirect_to );
+			} else {
+				$redirect_to = add_query_arg( 'error', $doaction, $redirect_to );
+			}
+
+			bp_core_redirect( $redirect_to );
+
+		// Update other stuff once above ones are done.
+		} else {
+			$this->redirect = $redirect_to;
+
+			/**
+			 * Fires at end of user profile admin load if doaction does not match any available actions.
+			 *
+			 * @since 2.0.0
+			 *
+			 * @param string $doaction Current bulk action being processed.
+			 * @param int    $user_id  Current user ID.
+			 * @param array  $_REQUEST Current $_REQUEST global.
+			 * @param string $redirect Determined redirect url to send user to.
+			 */
+			do_action_ref_array( 'bp_members_admin_update_user', array( $doaction, $user_id, $_REQUEST, $this->redirect ) );
+
+			bp_core_redirect( $this->redirect );
+		}
+	}
+
+	/**
+	 * Display the user's profile.
+	 *
+	 * @since 2.0.0
+	 */
+	public function user_admin() {
+
+		if ( ! bp_current_user_can( 'bp_moderate' ) && empty( $this->is_self_profile ) ) {
+			die( '-1' );
+		}
+
+		// Get the user ID.
+		$user_id = $this->get_user_id();
+		$user    = get_user_to_edit( $user_id );
+
+		// Construct title.
+		if ( true === $this->is_self_profile ) {
+			$title = __( 'Profile',   'buddypress' );
+		} else {
+			$title = __( 'Edit User', 'buddypress' );
+		}
+
+		// Construct URL for form.
+		$request_url     = remove_query_arg( array( 'action', 'error', 'updated', 'spam', 'ham' ), $_SERVER['REQUEST_URI'] );
+		$form_action_url = add_query_arg( 'action', 'update', $request_url );
+		$wp_http_referer = false;
+		if ( ! empty( $_REQUEST['wp_http_referer'] ) ) {
+			$wp_http_referer = remove_query_arg( array( 'action', 'updated' ), $_REQUEST['wp_http_referer'] );
+		}
+
+		// Prepare notice for admin.
+		$notice = $this->get_user_notice();
+
+		if ( ! empty( $notice ) ) : ?>
+
+			<div <?php if ( 'updated' === $notice['class'] ) : ?>id="message" <?php endif; ?>class="<?php echo esc_attr( $notice['class'] ); ?>">
+
+				<p><?php echo esc_html( $notice['message'] ); ?></p>
+
+				<?php if ( !empty( $wp_http_referer ) && ( 'updated' === $notice['class'] ) ) : ?>
+
+					<p><a href="<?php echo esc_url( $wp_http_referer ); ?>"><?php esc_html_e( '&larr; Back to Users', 'buddypress' ); ?></a></p>
+
+				<?php endif; ?>
+
+			</div>
+
+		<?php endif; ?>
+
+		<div class="wrap" id="community-profile-page">
+			<h2><?php echo esc_html( $title ); ?>
+
+				<?php if ( empty( $this->is_self_profile ) ) : ?>
+
+					<?php if ( current_user_can( 'create_users' ) ) : ?>
+
+						<a href="user-new.php" class="add-new-h2"><?php echo esc_html_x( 'Add New', 'user', 'buddypress' ); ?></a>
+
+					<?php elseif ( is_multisite() && current_user_can( 'promote_users' ) ) : ?>
+
+						<a href="user-new.php" class="add-new-h2"><?php echo esc_html_x( 'Add Existing', 'user', 'buddypress' ); ?></a>
+
+					<?php endif; ?>
+
+				<?php endif; ?>
+			</h2>
+
+			<?php if ( ! empty( $user ) ) :
+
+				$this->profile_nav( $user, 'BuddyPress' ); ?>
+
+				<form action="<?php echo esc_url( $form_action_url ); ?>" id="your-profile" method="post">
+					<div id="poststuff">
+
+						<div id="post-body" class="metabox-holder columns-<?php echo 1 == get_current_screen()->get_columns() ? '1' : '2'; ?>">
+
+							<div id="postbox-container-1" class="postbox-container">
+								<?php do_meta_boxes( get_current_screen()->id, 'side', $user ); ?>
+							</div>
+
+							<div id="postbox-container-2" class="postbox-container">
+								<?php do_meta_boxes( get_current_screen()->id, 'normal',   $user ); ?>
+								<?php do_meta_boxes( get_current_screen()->id, 'advanced', $user ); ?>
+							</div>
+						</div><!-- #post-body -->
+
+					</div><!-- #poststuff -->
+
+					<?php wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); ?>
+					<?php wp_nonce_field( 'meta-box-order',  'meta-box-order-nonce', false ); ?>
+					<?php wp_nonce_field( 'edit-bp-profile_' . $user->ID ); ?>
+
+				</form>
+
+			<?php else : ?>
+
+				<p><?php printf( __( 'No user found with this ID. <a href="%s">Go back and try again</a>.', 'buddypress' ), esc_url( bp_get_admin_url( 'users.php' ) ) ); ?></p>
+
+			<?php endif; ?>
+
+		</div><!-- .wrap -->
+		<?php
+	}
+
+	/**
+	 * Render the Status metabox for user's profile screen.
+	 *
+	 * Actions are:
+	 * - Update profile fields if xProfile component is active
+	 * - Spam/Unspam user
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param WP_User $user The WP_User object to be edited.
+	 */
+	public function user_admin_status_metabox( $user = null ) {
+
+		// Bail if no user id or if the user has not activated their account yet.
+		if ( empty( $user->ID ) ) {
+			return;
+		}
+
+		// Bail if user has not been activated yet (how did you get here?).
+		if ( isset( $user->user_status ) && ( 2 == $user->user_status ) ) : ?>
+
+			<p class="not-activated"><?php esc_html_e( 'User account has not yet been activated', 'buddypress' ); ?></p><br/>
+
+			<?php return;
+
+		endif; ?>
+
+		<div class="submitbox" id="submitcomment">
+			<div id="minor-publishing">
+				<div id="misc-publishing-actions">
+					<?php
+
+					// Get the spam status once here to compare against below.
+					$is_spammer = bp_is_user_spammer( $user->ID );
+
+					/**
+					 * In configs where BuddyPress is not network activated,
+					 * regular admins cannot mark a user as a spammer on front
+					 * end. This prevent them to do it in backend.
+					 *
+					 * Also prevent admins from marking themselves or other
+					 * admins as spammers.
+					 */
+					if ( ( empty( $this->is_self_profile ) && ( ! in_array( $user->user_login, get_super_admins() ) ) && empty( $this->subsite_activated ) ) || ( ! empty( $this->subsite_activated ) && current_user_can( 'manage_network_users' ) ) ) : ?>
+
+						<div class="misc-pub-section" id="comment-status-radio">
+							<label class="approved"><input type="radio" name="user_status" value="ham" <?php checked( $is_spammer, false ); ?>><?php esc_html_e( 'Active', 'buddypress' ); ?></label><br />
+							<label class="spam"><input type="radio" name="user_status" value="spam" <?php checked( $is_spammer, true ); ?>><?php esc_html_e( 'Spammer', 'buddypress' ); ?></label>
+						</div>
+
+					<?php endif ;?>
+
+					<div class="misc-pub-section curtime misc-pub-section-last">
+						<?php
+
+						// Translators: Publish box date format, see http://php.net/date.
+						$datef = __( 'M j, Y @ G:i', 'buddypress' );
+						$date  = date_i18n( $datef, strtotime( $user->user_registered ) );
+						?>
+						<span id="timestamp"><?php printf( __( 'Registered on: %s', 'buddypress' ), '<strong>' . $date . '</strong>' ); ?></span>
+					</div>
+				</div> <!-- #misc-publishing-actions -->
+
+				<div class="clear"></div>
+			</div><!-- #minor-publishing -->
+
+			<div id="major-publishing-actions">
+
+				<div id="publishing-action">
+					<a class="button bp-view-profile" href="<?php echo esc_url( bp_core_get_user_domain( $user->ID ) ); ?>" target="_blank"><?php esc_html_e( 'View Profile', 'buddypress' ); ?></a>
+					<?php submit_button( esc_html__( 'Update Profile', 'buddypress' ), 'primary', 'save', false ); ?>
+				</div>
+				<div class="clear"></div>
+			</div><!-- #major-publishing-actions -->
+
+		</div><!-- #submitcomment -->
+
+		<?php
+	}
+
+	/**
+	 * Render the fallback metabox in case a user has been marked as a spammer.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param WP_User $user The WP_User object to be edited.
+	 */
+	public function user_admin_spammer_metabox( $user = null ) {
+	?>
+		<p><?php printf( __( '%s has been marked as a spammer. All BuddyPress data associated with the user has been removed', 'buddypress' ), esc_html( bp_core_get_user_displayname( $user->ID ) ) ) ;?></p>
+	<?php
+	}
+
+	/**
+	 * Render the Stats metabox to moderate inappropriate images.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param WP_User $user The WP_User object to be edited.
+	 */
+	public function user_admin_stats_metabox( $user = null ) {
+
+		// Bail if no user ID.
+		if ( empty( $user->ID ) ) {
+			return;
+		}
+
+		// If account is not activated last activity is the time user registered.
+		if ( isset( $user->user_status ) && 2 == $user->user_status ) {
+			$last_active = $user->user_registered;
+
+		// Account is activated, getting user's last activity.
+		} else {
+			$last_active = bp_get_user_last_activity( $user->ID );
+		}
+
+		$datef = __( 'M j, Y @ G:i', 'buddypress' );
+		$date  = date_i18n( $datef, strtotime( $last_active ) ); ?>
+
+		<ul>
+			<li class="bp-members-profile-stats"><?php printf( __( 'Last active: %1$s', 'buddypress' ), '<strong>' . $date . '</strong>' ); ?></li>
+
+			<?php
+			// Loading other stats only if user has activated their account.
+			if ( empty( $user->user_status ) ) {
+
+				/**
+				 * Fires in the user stats metabox if the user has activated their account.
+				 *
+				 * @since 2.0.0
+				 *
+				 * @param array  $value Array holding the user ID.
+				 * @param object $user  Current displayed user object.
+				 */
+				do_action( 'bp_members_admin_user_stats', array( 'user_id' => $user->ID ), $user );
+			}
+			?>
+		</ul>
+
+		<?php
+	}
+
+	/**
+	 * Render the Member Type metabox.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param WP_User $user The WP_User object to be edited.
+	 */
+	public function user_admin_member_type_metabox( $user = null ) {
+
+		// Bail if no user ID.
+		if ( empty( $user->ID ) ) {
+			return;
+		}
+
+		$types = bp_get_member_types( array(), 'objects' );
+		$current_type = bp_get_member_type( $user->ID );
+		?>
+
+		<label for="bp-members-profile-member-type" class="screen-reader-text"><?php esc_html_e( 'Select member type', 'buddypress' ); ?></label>
+		<select name="bp-members-profile-member-type" id="bp-members-profile-member-type">
+			<option value="" <?php selected( '', $current_type ); ?>><?php /* translators: no option picked in select box */ esc_attr_e( '----', 'buddypress' ) ?></option>
+			<?php foreach ( $types as $type ) : ?>
+				<option value="<?php echo esc_attr( $type->name ) ?>" <?php selected( $type->name, $current_type ) ?>><?php echo esc_html( $type->labels['singular_name'] ) ?></option>
+			<?php endforeach; ?>
+		</select>
+
+		<?php
+
+		wp_nonce_field( 'bp-member-type-change-' . $user->ID, 'bp-member-type-nonce' );
+	}
+
+	/**
+	 * Process changes from the Member Type metabox.
+	 *
+	 * @since 2.2.0
+	 */
+	public function process_member_type_update() {
+		if ( ! isset( $_POST['bp-member-type-nonce'] ) || ! isset( $_POST['bp-members-profile-member-type'] ) ) {
+			return;
+		}
+
+		$user_id = $this->get_user_id();
+
+		check_admin_referer( 'bp-member-type-change-' . $user_id, 'bp-member-type-nonce' );
+
+		// Permission check.
+		if ( ! current_user_can( 'bp_moderate' ) && $user_id != bp_loggedin_user_id() ) {
+			return;
+		}
+
+		// Member type string must either reference a valid member type, or be empty.
+		$member_type = stripslashes( $_POST['bp-members-profile-member-type'] );
+		if ( ! empty( $member_type ) && ! bp_get_member_type_object( $member_type ) ) {
+			return;
+		}
+
+		/*
+		 * If an invalid member type is passed, someone's doing something
+		 * fishy with the POST request, so we can fail silently.
+		 */
+		if ( bp_set_member_type( $user_id, $member_type ) ) {
+			// @todo Success messages can't be posted because other stuff happens on the page load.
+		}
+	}
+
+	/**
+	 * Add a link to Profile in Users listing row actions.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array|string $actions WordPress row actions (edit, delete).
+	 * @param object       $user    The object for the user row.
+	 * @return array Merged actions.
+	 */
+	public function row_actions( $actions = '', $user = null ) {
+
+		// Bail if no user ID.
+		if ( empty( $user->ID ) ) {
+			return;
+		}
+
+		// Setup args array.
+		$args = array();
+
+		// Add the user ID if it's not for the current user.
+		if ( $user->ID !== $this->current_user_id ) {
+			$args['user_id'] = $user->ID;
+		}
+
+		// Add the referer.
+		$args['wp_http_referer'] = urlencode( wp_unslash( $_SERVER['REQUEST_URI'] ) );
+
+		// Add the "Extended" link if the current user can edit this user.
+		if ( current_user_can( 'edit_user', $user->ID ) || bp_current_user_can( 'bp_moderate' ) ) {
+
+			// Add query args and setup the Extended link.
+			$edit_profile      = add_query_arg( $args, $this->edit_profile_url );
+			$edit_profile_link = sprintf( '<a href="%1$s">%2$s</a>',  esc_url( $edit_profile ), esc_html__( 'Extended', 'buddypress' ) );
+
+			/**
+			 * Check the edit action is available
+			 * and preserve the order edit | profile | remove/delete.
+			 */
+			if ( ! empty( $actions['edit'] ) ) {
+				$edit_action = $actions['edit'];
+				unset( $actions['edit'] );
+
+				$new_edit_actions = array(
+					'edit'         => $edit_action,
+					'edit-profile' => $edit_profile_link,
+				);
+
+			// If not available simply add the edit profile action.
+			} else {
+				$new_edit_actions = array( 'edit-profile' => $edit_profile_link );
+			}
+
+			$actions = array_merge( $new_edit_actions, $actions );
+		}
+
+		return $actions;
+	}
+
+	/**
+	 * Add a filter to edit profile url in WP Admin Bar.
+	 *
+	 * @since 2.1.0
+	 */
+	public function add_edit_profile_url_filter() {
+		add_filter( 'bp_members_edit_profile_url', array( $this, 'filter_adminbar_profile_link' ), 10, 3 );
+	}
+
+	/**
+	 * Filter the profile url.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @uses  user_admin_url()
+	 *
+	 * @param string $profile_link Profile Link for admin bar.
+	 * @param string $url          Profile URL.
+	 * @param int    $user_id      User ID.
+	 * @return string
+	 */
+	public function filter_adminbar_profile_link( $profile_link = '', $url = '', $user_id = 0 ) {
+		if ( ! is_super_admin( $user_id ) && is_admin() ) {
+			$profile_link = user_admin_url( 'profile.php' );
+		}
+		return $profile_link;
+	}
+
+	/**
+	 * Remove the filter to edit profile url in WP Admin Bar.
+	 *
+	 * @since 2.1.0
+	 */
+	public function remove_edit_profile_url_filter() {
+		remove_filter( 'bp_members_edit_profile_url', array( $this, 'filter_adminbar_profile_link' ), 10, 3 );
+	}
+
+	/** Signups Management ****************************************************/
+
+	/**
+	 * Display the admin preferences about signups pagination.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param int    $value     Value for signup option.
+	 * @param string $option    Value for the option key.
+	 * @param int    $new_value Value for the saved option.
+	 * @return int The pagination preferences.
+	 */
+	public function signup_screen_options( $value = 0, $option = '', $new_value = 0 ) {
+		if ( 'users_page_bp_signups_network_per_page' != $option && 'users_page_bp_signups_per_page' != $option ) {
+			return $value;
+		}
+
+		// Per page.
+		$new_value = (int) $new_value;
+		if ( $new_value < 1 || $new_value > 999 ) {
+			return $value;
+		}
+
+		return $new_value;
+	}
+
+	/**
+	 * Make sure no signups will show in users list.
+	 *
+	 * This is needed to handle signups that may have not been activated
+	 * before the 2.0.0 upgrade.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param WP_User_Query $query The users query.
+	 * @return WP_User_Query The users query without the signups.
+	 */
+	public function remove_signups_from_user_query( $query = null ) {
+		global $wpdb;
+
+		// Bail if this is an ajax request.
+		if ( defined( 'DOING_AJAX' ) ) {
+			return;
+		}
+
+		// Bail if updating BuddyPress.
+		if ( bp_is_update() ) {
+			return;
+		}
+
+		// Bail if there is no current admin screen.
+		if ( ! function_exists( 'get_current_screen' ) || ! get_current_screen() ) {
+			return;
+		}
+
+		// Get current screen.
+		$current_screen = get_current_screen();
+
+		// Bail if not on a users page.
+		if ( ! isset( $current_screen->id ) || $this->users_page !== $current_screen->id ) {
+			return;
+		}
+
+		// Bail if already querying by an existing role.
+		if ( ! empty( $query->query_vars['role'] ) ) {
+			return;
+		}
+
+		$query->query_where .= " AND {$wpdb->users}.user_status != 2";
+	}
+
+	/**
+	 * Filter the WP Users List Table views to include 'bp-signups'.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $views WP List Table views.
+	 * @return array The views with the signup view added.
+	 */
+	public function signup_filter_view( $views = array() ) {
+
+		// Remove the 'current' class from All if we're on the signups view.
+		if ( $this->signups_page == get_current_screen()->id ) {
+			$views['all'] = str_replace( 'class="current"', '', $views['all'] );
+			$class        = 'current';
+		} else {
+			$class        = '';
+		}
+
+		$signups = BP_Signup::count_signups();
+		$url     = add_query_arg( 'page', 'bp-signups', bp_get_admin_url( 'users.php' ) );
+		$text    = sprintf( _x( 'Pending %s', 'signup users', 'buddypress' ), '<span class="count">(' . number_format_i18n( $signups ) . ')</span>' );
+
+		$views['registered'] = sprintf( '<a href="%1$s" class="%2$s">%3$s</a>', esc_url( $url ), $class, $text );
+
+		return $views;
+	}
+
+	/**
+	 * Load the Signup WP Users List table.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $class    The name of the class to use.
+	 * @param string $required The parent class.
+	 * @return WP_List_Table The List table.
+	 */
+	public static function get_list_table_class( $class = '', $required = '' ) {
+		if ( empty( $class ) ) {
+			return;
+		}
+
+		if ( ! empty( $required ) ) {
+			require_once( ABSPATH . 'wp-admin/includes/class-wp-' . $required . '-list-table.php' );
+			require_once( buddypress()->members->admin->admin_dir . 'bp-members-admin-classes.php' );
+		}
+
+		return new $class();
+	}
+
+	/**
+	 * Set up the signups admin page.
+	 *
+	 * Loaded before the page is rendered, this function does all initial
+	 * setup, including: processing form requests, registering contextual
+	 * help, and setting up screen options.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @global $bp_members_signup_list_table
+	 */
+	public function signups_admin_load() {
+		global $bp_members_signup_list_table;
+
+		// Build redirection URL.
+		$redirect_to = remove_query_arg( array( 'action', 'error', 'updated', 'activated', 'notactivated', 'deleted', 'notdeleted', 'resent', 'notresent', 'do_delete', 'do_resend', 'do_activate', '_wpnonce', 'signup_ids' ), $_SERVER['REQUEST_URI'] );
+		$doaction    = bp_admin_list_table_current_bulk_action();
+
+		/**
+		 * Fires at the start of the signups admin load.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $doaction Current bulk action being processed.
+		 * @param array  $_REQUEST Current $_REQUEST global.
+		 */
+		do_action( 'bp_signups_admin_load', $doaction, $_REQUEST );
+
+		/**
+		 * Filters the allowed actions for use in the user signups admin page.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $value Array of allowed actions to use.
+		 */
+		$allowed_actions = apply_filters( 'bp_signups_admin_allowed_actions', array( 'do_delete', 'do_activate', 'do_resend' ) );
+
+		// Prepare the display of the Community Profile screen.
+		if ( ! in_array( $doaction, $allowed_actions ) || ( -1 == $doaction ) ) {
+
+			if ( bp_core_do_network_admin() ) {
+				$bp_members_signup_list_table = self::get_list_table_class( 'BP_Members_MS_List_Table', 'ms-users' );
+			} else {
+				$bp_members_signup_list_table = self::get_list_table_class( 'BP_Members_List_Table', 'users' );
+			}
+
+			// The per_page screen option.
+			add_screen_option( 'per_page', array( 'label' => _x( 'Pending Accounts', 'Pending Accounts per page (screen options)', 'buddypress' ) ) );
+
+			get_current_screen()->add_help_tab( array(
+				'id'      => 'bp-signups-overview',
+				'title'   => __( 'Overview', 'buddypress' ),
+				'content' =>
+				'<p>' . __( 'This is the administration screen for pending accounts on your site.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'From the screen options, you can customize the displayed columns and the pagination of this screen.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'You can reorder the list of your pending accounts by clicking on the Username, Email or Registered column headers.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'Using the search form, you can find pending accounts more easily. The Username and Email fields will be included in the search.', 'buddypress' ) . '</p>'
+			) );
+
+			get_current_screen()->add_help_tab( array(
+				'id'      => 'bp-signups-actions',
+				'title'   => __( 'Actions', 'buddypress' ),
+				'content' =>
+				'<p>' . __( 'Hovering over a row in the pending accounts list will display action links that allow you to manage pending accounts. You can perform the following actions:', 'buddypress' ) . '</p>' .
+				'<ul><li>' . __( '"Email" takes you to the confirmation screen before being able to send the activation link to the desired pending account. You can only send the activation email once per day.', 'buddypress' ) . '</li>' .
+				'<li>' . __( '"Delete" allows you to delete a pending account from your site. You will be asked to confirm this deletion.', 'buddypress' ) . '</li></ul>' .
+				'<p>' . __( 'By clicking on a Username you will be able to activate a pending account from the confirmation screen.', 'buddypress' ) . '</p>' .
+				'<p>' . __( 'Bulk actions allow you to perform these 3 actions for the selected rows.', 'buddypress' ) . '</p>'
+			) );
+
+			// Help panel - sidebar links.
+			get_current_screen()->set_help_sidebar(
+				'<p><strong>' . __( 'For more information:', 'buddypress' ) . '</strong></p>' .
+				'<p>' . __( '<a href="https://buddypress.org/support/">Support Forums</a>', 'buddypress' ) . '</p>'
+			);
+		} else {
+			if ( ! empty( $_REQUEST['signup_ids' ] ) ) {
+				$signups = wp_parse_id_list( $_REQUEST['signup_ids' ] );
+			}
+
+			// Handle resent activation links.
+			if ( 'do_resend' == $doaction ) {
+
+				// Nonce check.
+				check_admin_referer( 'signups_resend' );
+
+				$resent = BP_Signup::resend( $signups );
+
+				if ( empty( $resent ) ) {
+					$redirect_to = add_query_arg( 'error', $doaction, $redirect_to );
+				} else {
+					$query_arg = array( 'updated' => 'resent' );
+
+					if ( ! empty( $resent['resent'] ) ) {
+						$query_arg['resent'] = count( $resent['resent'] );
+					}
+
+					if ( ! empty( $resent['errors'] ) ) {
+						$query_arg['notsent'] = count( $resent['errors'] );
+						set_transient( '_bp_admin_signups_errors', $resent['errors'], 30 );
+					}
+
+					$redirect_to = add_query_arg( $query_arg, $redirect_to );
+				}
+
+				bp_core_redirect( $redirect_to );
+
+			// Handle activated accounts.
+			} elseif ( 'do_activate' == $doaction ) {
+
+				// Nonce check.
+				check_admin_referer( 'signups_activate' );
+
+				$activated = BP_Signup::activate( $signups );
+
+				if ( empty( $activated ) ) {
+					$redirect_to = add_query_arg( 'error', $doaction, $redirect_to );
+				} else {
+					$query_arg = array( 'updated' => 'activated' );
+
+					if ( ! empty( $activated['activated'] ) ) {
+						$query_arg['activated'] = count( $activated['activated'] );
+					}
+
+					if ( ! empty( $activated['errors'] ) ) {
+						$query_arg['notactivated'] = count( $activated['errors'] );
+						set_transient( '_bp_admin_signups_errors', $activated['errors'], 30 );
+					}
+
+					$redirect_to = add_query_arg( $query_arg, $redirect_to );
+				}
+
+				bp_core_redirect( $redirect_to );
+
+			// Handle sign-ups delete.
+			} elseif ( 'do_delete' == $doaction ) {
+
+				// Nonce check.
+				check_admin_referer( 'signups_delete' );
+
+				$deleted = BP_Signup::delete( $signups );
+
+				if ( empty( $deleted ) ) {
+					$redirect_to = add_query_arg( 'error', $doaction, $redirect_to );
+				} else {
+					$query_arg = array( 'updated' => 'deleted' );
+
+					if ( ! empty( $deleted['deleted'] ) ) {
+						$query_arg['deleted'] = count( $deleted['deleted'] );
+					}
+
+					if ( ! empty( $deleted['errors'] ) ) {
+						$query_arg['notdeleted'] = count( $deleted['errors'] );
+						set_transient( '_bp_admin_signups_errors', $deleted['errors'], 30 );
+					}
+
+					$redirect_to = add_query_arg( $query_arg, $redirect_to );
+				}
+
+				bp_core_redirect( $redirect_to );
+
+			// Plugins can update other stuff from here.
+			} else {
+				$this->redirect = $redirect_to;
+
+				/**
+				 * Fires at end of signups admin load if doaction does not match any actions.
+				 *
+				 * @since 2.0.0
+				 *
+				 * @param string $doaction Current bulk action being processed.
+				 * @param array  $_REQUEST Current $_REQUEST global.
+				 * @param string $redirect Determined redirect url to send user to.
+				 */
+				do_action( 'bp_members_admin_update_signups', $doaction, $_REQUEST, $this->redirect );
+
+				bp_core_redirect( $this->redirect );
+			}
+		}
+	}
+
+	/**
+	 * Display any activation errors.
+	 *
+	 * @since 2.0.0
+	 */
+	public function signups_display_errors() {
+
+		// Look for sign-up errors.
+		$errors = get_transient( '_bp_admin_signups_errors' );
+
+		// Bail if no activation errors.
+		if ( empty( $errors ) ) {
+			return;
+		}
+
+		// Loop through errors and display them.
+		foreach ( $errors as $error ) : ?>
+
+			<li><?php echo esc_html( $error[0] );?>: <?php echo esc_html( $error[1] );?></li>
+
+		<?php endforeach;
+
+		// Delete the redirect transient.
+		delete_transient( '_bp_admin_signups_errors' );
+	}
+
+	/**
+	 * Get admin notice when viewing the sign-up page.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return array
+	 */
+	private function get_signup_notice() {
+
+		// Setup empty notice for return value.
+		$notice = array();
+
+		// Updates.
+		if ( ! empty( $_REQUEST['updated'] ) ) {
+			switch ( $_REQUEST['updated'] ) {
+				case 'resent':
+					$notice = array(
+						'class'   => 'updated',
+						'message' => ''
+					);
+
+					if ( ! empty( $_REQUEST['resent'] ) ) {
+						$notice['message'] .= sprintf(
+							_nx( '%s activation email successfully sent! ', '%s activation emails successfully sent! ',
+							 absint( $_REQUEST['resent'] ),
+							 'signup resent',
+							 'buddypress'
+							),
+							number_format_i18n( absint( $_REQUEST['resent'] ) )
+						);
+					}
+
+					if ( ! empty( $_REQUEST['notsent'] ) ) {
+						$notice['message'] .= sprintf(
+							_nx( '%s activation email was not sent.', '%s activation emails were not sent.',
+							 absint( $_REQUEST['notsent'] ),
+							 'signup notsent',
+							 'buddypress'
+							),
+							number_format_i18n( absint( $_REQUEST['notsent'] ) )
+						);
+
+						if ( empty( $_REQUEST['resent'] ) ) {
+							$notice['class'] = 'error';
+						}
+					}
+
+					break;
+
+				case 'activated':
+					$notice = array(
+						'class'   => 'updated',
+						'message' => ''
+					);
+
+					if ( ! empty( $_REQUEST['activated'] ) ) {
+						$notice['message'] .= sprintf(
+							_nx( '%s account successfully activated! ', '%s accounts successfully activated! ',
+							 absint( $_REQUEST['activated'] ),
+							 'signup resent',
+							 'buddypress'
+							),
+							number_format_i18n( absint( $_REQUEST['activated'] ) )
+						);
+					}
+
+					if ( ! empty( $_REQUEST['notactivated'] ) ) {
+						$notice['message'] .= sprintf(
+							_nx( '%s account was not activated.', '%s accounts were not activated.',
+							 absint( $_REQUEST['notactivated'] ),
+							 'signup notsent',
+							 'buddypress'
+							),
+							number_format_i18n( absint( $_REQUEST['notactivated'] ) )
+						);
+
+						if ( empty( $_REQUEST['activated'] ) ) {
+							$notice['class'] = 'error';
+						}
+					}
+
+					break;
+
+				case 'deleted':
+					$notice = array(
+						'class'   => 'updated',
+						'message' => ''
+					);
+
+					if ( ! empty( $_REQUEST['deleted'] ) ) {
+						$notice['message'] .= sprintf(
+							_nx( '%s sign-up successfully deleted!', '%s sign-ups successfully deleted!',
+							 absint( $_REQUEST['deleted'] ),
+							 'signup deleted',
+							 'buddypress'
+							),
+							number_format_i18n( absint( $_REQUEST['deleted'] ) )
+						);
+					}
+
+					if ( ! empty( $_REQUEST['notdeleted'] ) ) {
+						$notice['message'] .= sprintf(
+							_nx( '%s sign-up was not deleted.', '%s sign-ups were not deleted.',
+							 absint( $_REQUEST['notdeleted'] ),
+							 'signup notdeleted',
+							 'buddypress'
+							),
+							number_format_i18n( absint( $_REQUEST['notdeleted'] ) )
+						);
+
+						if ( empty( $_REQUEST['deleted'] ) ) {
+							$notice['class'] = 'error';
+						}
+					}
+
+					break;
+			}
+		}
+
+		// Errors.
+		if ( ! empty( $_REQUEST['error'] ) ) {
+			switch ( $_REQUEST['error'] ) {
+				case 'do_resend':
+					$notice = array(
+						'class'   => 'error',
+						'message' => esc_html__( 'There was a problem sending the activation emails. Please try again.', 'buddypress' ),
+					);
+					break;
+
+				case 'do_activate':
+					$notice = array(
+						'class'   => 'error',
+						'message' => esc_html__( 'There was a problem activating accounts. Please try again.', 'buddypress' ),
+					);
+					break;
+
+				case 'do_delete':
+					$notice = array(
+						'class'   => 'error',
+						'message' => esc_html__( 'There was a problem deleting sign-ups. Please try again.', 'buddypress' ),
+					);
+					break;
+			}
+		}
+
+		return $notice;
+	}
+
+	/**
+	 * Signups admin page router.
+	 *
+	 * Depending on the context, display
+	 * - the list of signups,
+	 * - or the delete confirmation screen,
+	 * - or the activate confirmation screen,
+	 * - or the "resend" email confirmation screen.
+	 *
+	 * Also prepare the admin notices.
+	 *
+	 * @since 2.0.0
+	 */
+	public function signups_admin() {
+		$doaction = bp_admin_list_table_current_bulk_action();
+
+		// Prepare notices for admin.
+		$notice = $this->get_signup_notice();
+
+		// Display notices.
+		if ( ! empty( $notice ) ) :
+			if ( 'updated' === $notice['class'] ) : ?>
+
+				<div id="message" class="<?php echo esc_attr( $notice['class'] ); ?>">
+
+			<?php else: ?>
+
+				<div class="<?php echo esc_attr( $notice['class'] ); ?>">
+
+			<?php endif; ?>
+
+				<p><?php echo $notice['message']; ?></p>
+
+				<?php if ( ! empty( $_REQUEST['notactivated'] ) || ! empty( $_REQUEST['notdeleted'] ) || ! empty( $_REQUEST['notsent'] ) ) :?>
+
+					<ul><?php $this->signups_display_errors();?></ul>
+
+				<?php endif ;?>
+
+			</div>
+
+		<?php endif;
+
+		// Show the proper screen.
+		switch ( $doaction ) {
+			case 'activate' :
+			case 'delete' :
+			case 'resend' :
+				$this->signups_admin_manage( $doaction );
+				break;
+
+			default:
+				$this->signups_admin_index();
+				break;
+
+		}
+	}
+
+	/**
+	 * This is the list of the Pending accounts (signups).
+	 *
+	 * @since 2.0.0
+	 *
+	 * @global $plugin_page
+	 * @global $bp_members_signup_list_table
+	 */
+	public function signups_admin_index() {
+		global $plugin_page, $bp_members_signup_list_table;
+
+		$usersearch = ! empty( $_REQUEST['s'] ) ? stripslashes( $_REQUEST['s'] ) : '';
+
+		// Prepare the group items for display.
+		$bp_members_signup_list_table->prepare_items();
+
+		$form_url = add_query_arg(
+			array(
+				'page' => 'bp-signups',
+			),
+			bp_get_admin_url( 'users.php' )
+		);
+
+		$search_form_url = remove_query_arg(
+			array(
+				'action',
+				'deleted',
+				'notdeleted',
+				'error',
+				'updated',
+				'delete',
+				'activate',
+				'activated',
+				'notactivated',
+				'resend',
+				'resent',
+				'notresent',
+				'do_delete',
+				'do_activate',
+				'do_resend',
+				'action2',
+				'_wpnonce',
+				'signup_ids'
+			), $_SERVER['REQUEST_URI']
+		);
+
+		?>
+
+		<div class="wrap">
+			<h2><?php _e( 'Users', 'buddypress' ); ?>
+
+				<?php if ( current_user_can( 'create_users' ) ) : ?>
+
+					<a href="user-new.php" class="add-new-h2"><?php echo esc_html_x( 'Add New', 'user', 'buddypress' ); ?></a>
+
+				<?php elseif ( is_multisite() && current_user_can( 'promote_users' ) ) : ?>
+
+					<a href="user-new.php" class="add-new-h2"><?php echo esc_html_x( 'Add Existing', 'user', 'buddypress' ); ?></a>
+
+				<?php endif;
+
+				if ( $usersearch ) {
+					printf( '<span class="subtitle">' . __( 'Search results for &#8220;%s&#8221;', 'buddypress' ) . '</span>', esc_html( $usersearch ) );
+				}
+
+				?>
+			</h2>
+
+			<?php // Display each signups on its own row. ?>
+			<?php $bp_members_signup_list_table->views(); ?>
+
+			<form id="bp-signups-search-form" action="<?php echo esc_url( $search_form_url ) ;?>">
+				<input type="hidden" name="page" value="<?php echo esc_attr( $plugin_page ); ?>" />
+				<?php $bp_members_signup_list_table->search_box( __( 'Search Pending Users', 'buddypress' ), 'bp-signups' ); ?>
+			</form>
+
+			<form id="bp-signups-form" action="<?php echo esc_url( $form_url );?>" method="post">
+				<?php $bp_members_signup_list_table->display(); ?>
+			</form>
+		</div>
+	<?php
+	}
+
+	/**
+	 * This is the confirmation screen for actions.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $action Delete, activate, or resend activation link.
+	 * @return string
+	 */
+	public function signups_admin_manage( $action = '' ) {
+		if ( ! current_user_can( $this->capability ) || empty( $action ) ) {
+			die( '-1' );
+		}
+
+		// Get the user IDs from the URL.
+		$ids = false;
+		if ( ! empty( $_POST['allsignups'] ) ) {
+			$ids = wp_parse_id_list( $_POST['allsignups'] );
+		} elseif ( ! empty( $_GET['signup_id'] ) ) {
+			$ids = absint( $_GET['signup_id'] );
+		}
+
+		if ( empty( $ids ) ) {
+			return false;
+		}
+
+		// Query for signups, and filter out those IDs that don't
+		// correspond to an actual signup.
+		$signups_query = BP_Signup::get( array(
+			'include' => $ids,
+		) );
+
+		$signups    = $signups_query['signups'];
+		$signup_ids = wp_list_pluck( $signups, 'signup_id' );
+
+		// Set up strings.
+		switch ( $action ) {
+			case 'delete' :
+				$header_text = __( 'Delete Pending Accounts', 'buddypress' );
+				if ( 1 == count( $signup_ids ) ) {
+					$helper_text = __( 'You are about to delete the following account:', 'buddypress' );
+				} else {
+					$helper_text = __( 'You are about to delete the following accounts:', 'buddypress' );
+				}
+				break;
+
+			case 'activate' :
+				$header_text = __( 'Activate Pending Accounts', 'buddypress' );
+				if ( 1 == count( $signup_ids ) ) {
+					$helper_text = __( 'You are about to activate the following account:', 'buddypress' );
+				} else {
+					$helper_text = __( 'You are about to activate the following accounts:', 'buddypress' );
+				}
+				break;
+
+			case 'resend' :
+				$header_text = __( 'Resend Activation Emails', 'buddypress' );
+				if ( 1 == count( $signup_ids ) ) {
+					$helper_text = __( 'You are about to resend an activation email to the following account:', 'buddypress' );
+				} else {
+					$helper_text = __( 'You are about to resend an activation email to the following accounts:', 'buddypress' );
+				}
+				break;
+		}
+
+		// These arguments are added to all URLs.
+		$url_args = array( 'page' => 'bp-signups' );
+
+		// These arguments are only added when performing an action.
+		$action_args = array(
+			'action'     => 'do_' . $action,
+			'signup_ids' => implode( ',', $signup_ids )
+		);
+
+		$cancel_url = add_query_arg( $url_args, bp_get_admin_url( 'users.php' ) );
+		$action_url = wp_nonce_url(
+			add_query_arg(
+				array_merge( $url_args, $action_args ),
+				bp_get_admin_url( 'users.php' )
+			),
+			'signups_' . $action
+		);
+
+		?>
+
+		<div class="wrap">
+			<h2><?php echo esc_html( $header_text ); ?></h2>
+			<p><?php echo esc_html( $helper_text ); ?></p>
+
+			<ol class="bp-signups-list">
+			<?php foreach ( $signups as $signup ) :
+
+				$last_notified = mysql2date( 'Y/m/d g:i:s a', $signup->date_sent ); ?>
+
+				<li>
+					<?php echo esc_html( $signup->user_name ) ?> - <?php echo sanitize_email( $signup->user_email );?>
+
+					<?php if ( 'resend' == $action ) : ?>
+
+						<p class="description">
+							<?php printf( esc_html__( 'Last notified: %s', 'buddypress'), $last_notified ) ;?>
+
+							<?php if ( ! empty( $signup->recently_sent ) ) : ?>
+
+								<span class="attention wp-ui-text-notification"> <?php esc_html_e( '(less than 24 hours ago)', 'buddypress' ); ?></span>
+
+							<?php endif; ?>
+						</p>
+
+					<?php endif; ?>
+
+				</li>
+
+			<?php endforeach; ?>
+			</ol>
+
+			<?php if ( 'delete' === $action ) : ?>
+
+				<p><strong><?php esc_html_e( 'This action cannot be undone.', 'buddypress' ) ?></strong></p>
+
+			<?php endif ; ?>
+
+			<a class="button-primary" href="<?php echo esc_url( $action_url ); ?>"><?php esc_html_e( 'Confirm', 'buddypress' ); ?></a>
+			<a class="button" href="<?php echo esc_url( $cancel_url ); ?>"><?php esc_html_e( 'Cancel', 'buddypress' ) ?></a>
+		</div>
+
+		<?php
+	}
+}
+endif; // End class_exists check.
+
+// Load the BP Members admin.
+add_action( 'bp_init', array( 'BP_Members_Admin', 'register_members_admin' ) );
Index: /tags/2.4.3/src/bp-members/bp-members-adminbar.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-adminbar.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-adminbar.php	(revision 10410)
@@ -0,0 +1,179 @@
+<?php
+/**
+ * BuddyPress Members Toolbar.
+ *
+ * Handles the member functions related to the WordPress Toolbar.
+ *
+ * @package BuddyPress
+ * @subpackage MembersAdminBar
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Add the "My Account" menu and all submenus.
+ *
+ * @since 1.6.0
+ *
+ * @todo Deprecate WP 3.2 Toolbar compatibility when we drop 3.2 support.
+ */
+function bp_members_admin_bar_my_account_menu() {
+	global $wp_admin_bar;
+
+	// Bail if this is an ajax request.
+	if ( defined( 'DOING_AJAX' ) )
+		return;
+
+	// Logged in user.
+	if ( is_user_logged_in() ) {
+
+		$bp = buddypress();
+
+		// Stored in the global so we can add menus easily later on.
+		$bp->my_account_menu_id = 'my-account-buddypress';
+
+		// Create the main 'My Account' menu.
+		$wp_admin_bar->add_menu( array(
+			'id'     => $bp->my_account_menu_id,
+			'group'  => true,
+			'title'  => __( 'Edit My Profile', 'buddypress' ),
+			'href'   => bp_loggedin_user_domain(),
+			'meta'   => array(
+			'class'  => 'ab-sub-secondary'
+		) ) );
+
+	// Show login and sign-up links.
+	} elseif ( !empty( $wp_admin_bar ) ) {
+
+		add_filter ( 'show_admin_bar', '__return_true' );
+
+		// Create the main 'My Account' menu.
+		$wp_admin_bar->add_menu( array(
+			'id'    => 'bp-login',
+			'title' => __( 'Log in', 'buddypress' ),
+			'href'  => wp_login_url( bp_get_requested_url() )
+		) );
+
+		// Sign up.
+		if ( bp_get_signup_allowed() ) {
+			$wp_admin_bar->add_menu( array(
+				'id'    => 'bp-register',
+				'title' => __( 'Register', 'buddypress' ),
+				'href'  => bp_get_signup_page()
+			) );
+		}
+	}
+}
+add_action( 'bp_setup_admin_bar', 'bp_members_admin_bar_my_account_menu', 4 );
+
+/**
+ * Add the User Admin top-level menu to user pages.
+ *
+ * @since 1.5.0
+ */
+function bp_members_admin_bar_user_admin_menu() {
+	global $wp_admin_bar;
+
+	// Only show if viewing a user.
+	if ( !bp_is_user() )
+		return false;
+
+	// Don't show this menu to non site admins or if you're viewing your own profile.
+	if ( !current_user_can( 'edit_users' ) || bp_is_my_profile() )
+		return false;
+
+	$bp = buddypress();
+
+	// Unique ID for the 'My Account' menu.
+	$bp->user_admin_menu_id = 'user-admin';
+
+	// Add the top-level User Admin button.
+	$wp_admin_bar->add_menu( array(
+		'id'    => $bp->user_admin_menu_id,
+		'title' => __( 'Edit Member', 'buddypress' ),
+		'href'  => bp_displayed_user_domain()
+	) );
+
+	if ( bp_is_active( 'xprofile' ) ) {
+		// User Admin > Edit this user's profile.
+		$wp_admin_bar->add_menu( array(
+			'parent' => $bp->user_admin_menu_id,
+			'id'     => $bp->user_admin_menu_id . '-edit-profile',
+			'title'  => __( "Edit Profile", 'buddypress' ),
+			'href'   => bp_get_members_component_link( 'profile', 'edit' )
+		) );
+
+		// User Admin > Edit this user's avatar.
+		if ( buddypress()->avatar->show_avatars ) {
+			$wp_admin_bar->add_menu( array(
+				'parent' => $bp->user_admin_menu_id,
+				'id'     => $bp->user_admin_menu_id . '-change-avatar',
+				'title'  => __( "Edit Profile Photo", 'buddypress' ),
+				'href'   => bp_get_members_component_link( 'profile', 'change-avatar' )
+			) );
+		}
+
+		// User Admin > Edit this user's cover image.
+		if ( bp_displayed_user_use_cover_image_header() ) {
+			$wp_admin_bar->add_menu( array(
+				'parent' => $bp->user_admin_menu_id,
+				'id'     => $bp->user_admin_menu_id . '-change-cover-image',
+				'title'  => __( 'Edit Cover Image', 'buddypress' ),
+				'href'   => bp_get_members_component_link( 'profile', 'change-cover-image' )
+			) );
+		}
+
+	}
+
+	if ( bp_is_active( 'settings' ) ) {
+		// User Admin > Spam/unspam.
+		$wp_admin_bar->add_menu( array(
+			'parent' => $bp->user_admin_menu_id,
+			'id'     => $bp->user_admin_menu_id . '-user-capabilities',
+			'title'  => __( 'User Capabilities', 'buddypress' ),
+			'href'   => bp_displayed_user_domain() . 'settings/capabilities/'
+		) );
+
+		// User Admin > Delete Account.
+		$wp_admin_bar->add_menu( array(
+			'parent' => $bp->user_admin_menu_id,
+			'id'     => $bp->user_admin_menu_id . '-delete-user',
+			'title'  => __( 'Delete Account', 'buddypress' ),
+			'href'   => bp_displayed_user_domain() . 'settings/delete-account/'
+		) );
+
+	}
+
+}
+add_action( 'admin_bar_menu', 'bp_members_admin_bar_user_admin_menu', 99 );
+
+/**
+ * Build the "Notifications" dropdown.
+ *
+ * @since 1.5.0
+ *
+ * @return bool
+ */
+function bp_members_admin_bar_notifications_menu() {
+
+	// Bail if notifications is not active.
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return false;
+	}
+
+	bp_notifications_toolbar_menu();
+}
+add_action( 'admin_bar_menu', 'bp_members_admin_bar_notifications_menu', 90 );
+
+/**
+ * Remove rogue WP core Edit menu when viewing a single user.
+ *
+ * @since 1.6.0
+ */
+function bp_members_remove_edit_page_menu() {
+	if ( bp_is_user() ) {
+		remove_action( 'admin_bar_menu', 'wp_admin_bar_edit_menu', 80 );
+	}
+}
+add_action( 'add_admin_bar_menus', 'bp_members_remove_edit_page_menu' );
Index: /tags/2.4.3/src/bp-members/bp-members-cache.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-cache.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-cache.php	(revision 10410)
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Caching functions specific to BuddyPress Members.
+ *
+ * @since 2.2.0
+ *
+ * @package BuddyPress
+ * @subpackage MembersCache
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Pre-fetch member type data when initializing a Members loop.
+ *
+ * @since 2.2.0
+ *
+ * @param BP_User_Query $bp_user_query BP_User_Query object.
+ */
+function bp_members_prefetch_member_type( BP_User_Query $bp_user_query ) {
+	$uncached_member_ids = bp_get_non_cached_ids( $bp_user_query->user_ids, 'bp_member_member_type' );
+
+	$member_types = bp_get_object_terms( $uncached_member_ids, 'bp_member_type', array(
+		'fields' => 'all_with_object_id',
+	) );
+
+	// Rekey by user ID.
+	$keyed_member_types = array();
+	foreach ( $member_types as $member_type ) {
+		if ( ! isset( $keyed_member_types[ $member_type->object_id ] ) ) {
+			$keyed_member_types[ $member_type->object_id ] = array();
+		}
+
+		$keyed_member_types[ $member_type->object_id ][] = $member_type->name;
+	}
+
+	$cached_member_ids = array();
+	foreach ( $keyed_member_types as $user_id => $user_member_types ) {
+		wp_cache_set( $user_id, $user_member_types, 'bp_member_member_type' );
+		$cached_member_ids[] = $user_id;
+	}
+
+	// Cache an empty value for users with no type.
+	foreach ( array_diff( $uncached_member_ids, $cached_member_ids ) as $no_type_id ) {
+		wp_cache_set( $no_type_id, '', 'bp_member_member_type' );
+	}
+}
+add_action( 'bp_user_query_populate_extras', 'bp_members_prefetch_member_type' );
+
+/**
+ * Clear the member_type cache for a user.
+ *
+ * Called when the user is deleted or marked as spam.
+ *
+ * @since BuddyPres (2.2.0)
+ *
+ * @param int $user_id ID of the deleted user.
+ */
+function bp_members_clear_member_type_cache( $user_id ) {
+	wp_cache_delete( $user_id, 'bp_member_member_type' );
+}
+add_action( 'wpmu_delete_user', 'bp_members_clear_member_type_cache' );
+add_action( 'delete_user', 'bp_members_clear_member_type_cache' );
Index: /tags/2.4.3/src/bp-members/bp-members-classes.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-classes.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-classes.php	(revision 10410)
@@ -0,0 +1,12 @@
+<?php
+/**
+ * BuddyPress Members Classes.
+ *
+ * @package BuddyPress
+ * @subpackage MembersClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+require dirname( __FILE__ ) . '/classes/class-bp-signup.php';
Index: /tags/2.4.3/src/bp-members/bp-members-filters.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-filters.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-filters.php	(revision 10410)
@@ -0,0 +1,128 @@
+<?php
+/**
+ * BuddyPress Members Filters.
+ *
+ * Filters specific to the Members component.
+ *
+ * @package BuddyPress
+ * @subpackage MembersFilters
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Escape commonly used fullname output functions.
+ */
+add_filter( 'bp_displayed_user_fullname',    'esc_html' );
+add_filter( 'bp_get_loggedin_user_fullname', 'esc_html' );
+
+// Filter the user registration URL to point to BuddyPress's registration page.
+add_filter( 'register_url', 'bp_get_signup_page' );
+
+/**
+ * Load additional sign-up sanitization filters on bp_loaded.
+ *
+ * These are used to prevent XSS in the BuddyPress sign-up process. You can
+ * unhook these to allow for customization of your registration fields;
+ * however, it is highly recommended that you leave these in place for the
+ * safety of your network.
+ *
+ * @since 1.5.0
+ */
+function bp_members_signup_sanitization() {
+
+	// Filters on sign-up fields.
+	$fields = array (
+		'bp_get_signup_username_value',
+		'bp_get_signup_email_value',
+		'bp_get_signup_with_blog_value',
+		'bp_get_signup_blog_url_value',
+		'bp_get_signup_blog_title_value',
+		'bp_get_signup_blog_privacy_value',
+		'bp_get_signup_avatar_dir_value',
+	);
+
+	// Add the filters to each field.
+	foreach( $fields as $filter ) {
+		add_filter( $filter, 'esc_html',       1 );
+		add_filter( $filter, 'wp_filter_kses', 2 );
+		add_filter( $filter, 'stripslashes',   3 );
+	}
+
+	// Sanitize email.
+	add_filter( 'bp_get_signup_email_value', 'sanitize_email' );
+}
+add_action( 'bp_loaded', 'bp_members_signup_sanitization' );
+
+/**
+ * Make sure the username is not the blog slug in case of root profile & subdirectory blog.
+ *
+ * If BP_ENABLE_ROOT_PROFILES is defined & multisite config is set to subdirectories,
+ * then there is a chance site.url/username == site.url/blogslug. If so, user's profile
+ * is not reachable, instead the blog is displayed. This filter makes sure the signup username
+ * is not the same than the blog slug for this particular config.
+ *
+ * @since 2.1.0
+ *
+ * @param array $illegal_names Array of illiegal names.
+ * @return array $illegal_names
+ */
+function bp_members_signup_with_subdirectory_blog( $illegal_names = array() ) {
+	if ( ! bp_core_enable_root_profiles() ) {
+		return $illegal_names;
+	}
+
+	if ( is_network_admin() && isset( $_POST['blog'] ) ) {
+		$blog = $_POST['blog'];
+		$domain = '';
+
+		if ( preg_match( '|^([a-zA-Z0-9-])$|', $blog['domain'] ) ) {
+			$domain = strtolower( $blog['domain'] );
+		}
+
+		if ( username_exists( $domain ) ) {
+			$illegal_names[] = $domain;
+		}
+
+	} else {
+		$illegal_names[] = buddypress()->signup->username;
+	}
+
+	return $illegal_names;
+}
+add_filter( 'subdirectory_reserved_names', 'bp_members_signup_with_subdirectory_blog', 10, 1 );
+
+/**
+ * Filter the user profile URL to point to BuddyPress profile edit.
+ *
+ * @since 1.6.0
+ *
+ * @param string $url     WP profile edit URL.
+ * @param int    $user_id ID of the user.
+ * @param string $scheme  Scheme to use.
+ * @return string
+ */
+function bp_members_edit_profile_url( $url, $user_id, $scheme = 'admin' ) {
+
+	// If xprofile is active, use profile domain link.
+	if ( ! is_admin() && bp_is_active( 'xprofile' ) ) {
+		$profile_link = trailingslashit( bp_core_get_user_domain( $user_id ) . bp_get_profile_slug() . '/edit' );
+
+	// Default to $url.
+	} else {
+		$profile_link = $url;
+	}
+
+	/**
+	 * Filters the user profile URL to point to BuddyPress profile edit.
+	 *
+	 * @since 1.5.2
+	 *
+	 * @param string $url WP profile edit URL.
+	 * @param int    $user_id ID of the user.
+	 * @param string $scheme Scheme to use.
+	 */
+	return apply_filters( 'bp_members_edit_profile_url', $profile_link, $url, $user_id, $scheme );
+}
+add_filter( 'edit_profile_url', 'bp_members_edit_profile_url', 10, 3 );
Index: /tags/2.4.3/src/bp-members/bp-members-functions.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-functions.php	(revision 10410)
@@ -0,0 +1,2774 @@
+<?php
+/**
+ * BuddyPress Member Functions.
+ *
+ * Functions specific to the members component.
+ *
+ * @package BuddyPress
+ * @subpackage MembersFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Check for the existence of a Members directory page.
+ *
+ * @since 1.5.0
+ *
+ * @return bool True if found, otherwise false.
+ */
+function bp_members_has_directory() {
+	$bp = buddypress();
+
+	return (bool) !empty( $bp->pages->members->id );
+}
+
+/**
+ * Define the slug constants for the Members component.
+ *
+ * Handles the three slug constants used in the Members component -
+ * BP_MEMBERS_SLUG, BP_REGISTER_SLUG, and BP_ACTIVATION_SLUG. If these
+ * constants are not overridden in wp-config.php or bp-custom.php, they are
+ * defined here to match the slug of the corresponding WP pages.
+ *
+ * In general, fallback values are only used during initial BP page creation,
+ * when no slugs have been explicitly defined.
+ */
+function bp_core_define_slugs() {
+	$bp = buddypress();
+
+	// No custom members slug.
+	if ( !defined( 'BP_MEMBERS_SLUG' ) ) {
+		if ( !empty( $bp->pages->members ) ) {
+			define( 'BP_MEMBERS_SLUG', $bp->pages->members->slug );
+		} else {
+			define( 'BP_MEMBERS_SLUG', 'members' );
+		}
+	}
+
+	// No custom registration slug.
+	if ( !defined( 'BP_REGISTER_SLUG' ) ) {
+		if ( !empty( $bp->pages->register ) ) {
+			define( 'BP_REGISTER_SLUG', $bp->pages->register->slug );
+		} else {
+			define( 'BP_REGISTER_SLUG', 'register' );
+		}
+	}
+
+	// No custom activation slug.
+	if ( !defined( 'BP_ACTIVATION_SLUG' ) ) {
+		if ( !empty( $bp->pages->activate ) ) {
+			define( 'BP_ACTIVATION_SLUG', $bp->pages->activate->slug );
+		} else {
+			define( 'BP_ACTIVATION_SLUG', 'activate' );
+		}
+	}
+}
+add_action( 'bp_setup_globals', 'bp_core_define_slugs', 11 );
+
+/**
+ * Fetch an array of users based on the parameters passed.
+ *
+ * Since BuddyPress 1.7, bp_core_get_users() uses BP_User_Query. If you
+ * need backward compatibility with BP_Core_User::get_users(), filter the
+ * bp_use_legacy_user_query value, returning true.
+ *
+ * @param array|string $args {
+ *     Array of arguments. All are optional. See {@link BP_User_Query} for
+ *     a more complete description of arguments.
+ *     @type string       $type                Sort order. Default: 'active'.
+ *     @type int          $user_id             Limit results to friends of a user. Default: false.
+ *     @type mixed        $exclude             IDs to exclude from results. Default: false.
+ *     @type string       $search_terms        Limit to users matching search terms. Default: false.
+ *     @type string       $meta_key            Limit to users with a meta_key. Default: false.
+ *     @type string       $meta_value          Limit to users with a meta_value (with meta_key). Default: false.
+ *     @type array|string $member_type         Array or comma-separated string of member types.
+ *     @type array|string $member_type__in     Array or comma-separated string of member types.
+ *                                             `$member_type` takes precedence over this parameter.
+ *     @type array|string $member_type__not_in Array or comma-separated string of member types to be excluded.
+ *     @type mixed        $include             Limit results by user IDs. Default: false.
+ *     @type int          $per_page            Results per page. Default: 20.
+ *     @type int          $page                Page of results. Default: 1.
+ *     @type bool         $populate_extras     Fetch optional extras. Default: true.
+ *     @type string|bool  $count_total         How to do total user count. Default: 'count_query'.
+ * }
+ * @return array
+ */
+function bp_core_get_users( $args = '' ) {
+
+	// Parse the user query arguments.
+	$r = bp_parse_args( $args, array(
+		'type'                => 'active',     // Active, newest, alphabetical, random or popular.
+		'user_id'             => false,        // Pass a user_id to limit to only friend connections for this user.
+		'exclude'             => false,        // Users to exclude from results.
+		'search_terms'        => false,        // Limit to users that match these search terms.
+		'meta_key'            => false,        // Limit to users who have this piece of usermeta.
+		'meta_value'          => false,        // With meta_key, limit to users where usermeta matches this value.
+		'member_type'         => '',
+		'member_type__in'     => '',
+		'member_type__not_in' => '',
+		'include'             => false,        // Pass comma separated list of user_ids to limit to only these users.
+		'per_page'            => 20,           // The number of results to return per page.
+		'page'                => 1,            // The page to return if limiting per page.
+		'populate_extras'     => true,         // Fetch the last active, where the user is a friend, total friend count, latest update.
+		'count_total'         => 'count_query' // What kind of total user count to do, if any. 'count_query', 'sql_calc_found_rows', or false.
+	), 'core_get_users' );
+
+	// For legacy users. Use of BP_Core_User::get_users() is deprecated.
+	if ( apply_filters( 'bp_use_legacy_user_query', false, __FUNCTION__, $r ) ) {
+		$retval = BP_Core_User::get_users(
+			$r['type'],
+			$r['per_page'],
+			$r['page'],
+			$r['user_id'],
+			$r['include'],
+			$r['search_terms'],
+			$r['populate_extras'],
+			$r['exclude'],
+			$r['meta_key'],
+			$r['meta_value']
+		);
+
+	// Default behavior as of BuddyPress 1.7.0.
+	} else {
+
+		// Get users like we were asked to do...
+		$users = new BP_User_Query( $r );
+
+		// ...but reformat the results to match bp_core_get_users() behavior.
+		$retval = array(
+			'users' => array_values( $users->results ),
+			'total' => $users->total_users
+		);
+	}
+
+	/**
+	 * Filters the results of the user query.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $retval Array of users for the current query.
+	 * @param array $r      Array of parsed query arguments.
+	 */
+	return apply_filters( 'bp_core_get_users', $retval, $r );
+}
+
+/**
+ * Return the domain for the passed user: e.g. http://example.com/members/andy/.
+ *
+ * @param int         $user_id       The ID of the user.
+ * @param string|bool $user_nicename Optional. user_nicename of the user.
+ * @param string|bool $user_login    Optional. user_login of the user.
+ * @return string
+ */
+function bp_core_get_user_domain( $user_id = 0, $user_nicename = false, $user_login = false ) {
+
+	if ( empty( $user_id ) ) {
+		return;
+	}
+
+	$username = bp_core_get_username( $user_id, $user_nicename, $user_login );
+
+	if ( bp_is_username_compatibility_mode() ) {
+		$username = rawurlencode( $username );
+	}
+
+	$after_domain = bp_core_enable_root_profiles() ? $username : bp_get_members_root_slug() . '/' . $username;
+	$domain       = trailingslashit( bp_get_root_domain() . '/' . $after_domain );
+
+	// Don't use this filter.  Subject to removal in a future release.
+	// Use the 'bp_core_get_user_domain' filter instead.
+	$domain       = apply_filters( 'bp_core_get_user_domain_pre_cache', $domain, $user_id, $user_nicename, $user_login );
+
+	/**
+	 * Filters the domain for the passed user.
+	 *
+	 * @since 1.0.1
+	 *
+	 * @param string $domain        Domain for the passed user.
+	 * @param int    $user_id       ID of the passed user.
+	 * @param string $user_nicename User nicename of the passed user.
+	 * @param string $user_login    User login of the passed user.
+	 */
+	return apply_filters( 'bp_core_get_user_domain', $domain, $user_id, $user_nicename, $user_login );
+}
+
+/**
+ * Fetch everything in the wp_users table for a user, without any usermeta.
+ *
+ * @param int $user_id The ID of the user.
+ * @return array
+ */
+function bp_core_get_core_userdata( $user_id = 0 ) {
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	if ( !$userdata = wp_cache_get( 'bp_core_userdata_' . $user_id, 'bp' ) ) {
+		$userdata = BP_Core_User::get_core_userdata( $user_id );
+		wp_cache_set( 'bp_core_userdata_' . $user_id, $userdata, 'bp' );
+	}
+
+	/**
+	 * Filters the userdata for a passed user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param array $userdata Array of user data for a passed user.
+	 */
+	return apply_filters( 'bp_core_get_core_userdata', $userdata );
+}
+
+/**
+ * Return the ID of a user, based on user_login.
+ *
+ * No longer used.
+ *
+ * @todo Deprecate.
+ *
+ * @param string $user_login user_login of the user being queried.
+ * @return int
+ */
+function bp_core_get_displayed_userid( $user_login ) {
+	return apply_filters( 'bp_core_get_displayed_userid', bp_core_get_userid( $user_login ) );
+}
+
+/**
+ * Return the user ID based on a user's user_login.
+ *
+ * @since 1.0.0
+ *
+ * @param string $username user_login to check.
+ * @return int|null The ID of the matched user on success, null on failure.
+ */
+function bp_core_get_userid( $username = '' ) {
+	if ( empty( $username ) ) {
+		return false;
+	}
+
+	$user = get_user_by( 'login', $username );
+
+	/**
+	 * Filters the ID of a user, based on user_login.
+	 *
+	 * @since 1.0.1
+	 *
+	 * @param int|null $value    ID of the user or null.
+	 * @param string   $username User login to check.
+	 */
+	return apply_filters( 'bp_core_get_userid', ! empty( $user->ID ) ? $user->ID : NULL, $username );
+}
+
+/**
+ * Return the user ID based on a user's user_nicename.
+ *
+ * @since 1.2.3
+ *
+ * @param string $user_nicename user_nicename to check.
+ * @return int|null The ID of the matched user on success, null on failure.
+ */
+function bp_core_get_userid_from_nicename( $user_nicename = '' ) {
+	if ( empty( $user_nicename ) ) {
+		return false;
+	}
+
+	$user = get_user_by( 'slug', $user_nicename );
+
+	/**
+	 * Filters the user ID based on user_nicename.
+	 *
+	 * @since 1.2.3
+	 *
+	 * @param int|null $value         ID of the user or null.
+	 * @param string   $user_nicename User nicename to check.
+	 */
+	return apply_filters( 'bp_core_get_userid_from_nicename', ! empty( $user->ID ) ? $user->ID : NULL, $user_nicename );
+}
+
+/**
+ * Return the username for a user based on their user id.
+ *
+ * This function is sensitive to the BP_ENABLE_USERNAME_COMPATIBILITY_MODE,
+ * so it will return the user_login or user_nicename as appropriate.
+ *
+ * @param int         $user_id       User ID to check.
+ * @param string|bool $user_nicename Optional. user_nicename of user being checked.
+ * @param string|bool $user_login    Optional. user_login of user being checked.
+ * @return string|bool The username of the matched user, or false.
+ */
+function bp_core_get_username( $user_id = 0, $user_nicename = false, $user_login = false ) {
+	$bp = buddypress();
+
+	// Check cache for user nicename.
+	$username = wp_cache_get( 'bp_user_username_' . $user_id, 'bp' );
+	if ( false === $username ) {
+
+		// Cache not found so prepare to update it.
+		$update_cache = true;
+
+		// Nicename and login were not passed.
+		if ( empty( $user_nicename ) && empty( $user_login ) ) {
+
+			// User ID matches logged in user.
+			if ( bp_loggedin_user_id() == $user_id ) {
+				$userdata = &$bp->loggedin_user->userdata;
+
+			// User ID matches displayed in user.
+			} elseif ( bp_displayed_user_id() == $user_id ) {
+				$userdata = &$bp->displayed_user->userdata;
+
+			// No user ID match.
+			} else {
+				$userdata = false;
+			}
+
+			// No match so go dig.
+			if ( empty( $userdata ) ) {
+
+				// User not found so return false.
+				if ( !$userdata = bp_core_get_core_userdata( $user_id ) ) {
+					return false;
+				}
+			}
+
+			// Update the $user_id for later.
+			$user_id       = $userdata->ID;
+
+			// Two possible options.
+			$user_nicename = $userdata->user_nicename;
+			$user_login    = $userdata->user_login;
+		}
+
+		// Pull an audible and maybe use the login over the nicename.
+		$username = bp_is_username_compatibility_mode() ? $user_login : $user_nicename;
+
+	// Username found in cache so don't update it again.
+	} else {
+		$update_cache = false;
+	}
+
+	// Add this to cache.
+	if ( ( true === $update_cache ) && !empty( $username ) ) {
+		wp_cache_set( 'bp_user_username_' . $user_id, $username, 'bp' );
+
+	// @todo bust this cache if no $username found?
+	// } else {
+	// wp_cache_delete( 'bp_user_username_' . $user_id );
+	}
+
+	/**
+	 * Filters the username based on originally provided user ID.
+	 *
+	 * @since 1.0.1
+	 *
+	 * @param string $username Username determined by user ID.
+	 */
+	return apply_filters( 'bp_core_get_username', $username );
+}
+
+/**
+ * Return the user_nicename for a user based on their user_id.
+ *
+ * This should be used for linking to user profiles and anywhere else a
+ * sanitized and unique slug to a user is needed.
+ *
+ * @since 1.5.0
+ *
+ * @todo Refactor to use a WP core function, if possible.
+ *
+ * @param int $user_id User ID to check.
+ * @return string|bool The username of the matched user, or false.
+ */
+function bp_members_get_user_nicename( $user_id ) {
+	$bp = buddypress();
+
+	if ( !$user_nicename = wp_cache_get( 'bp_members_user_nicename_' . $user_id, 'bp' ) ) {
+		$update_cache = true;
+
+		// User ID matches logged in user.
+		if ( bp_loggedin_user_id() == $user_id ) {
+			$userdata = &$bp->loggedin_user->userdata;
+
+		// User ID matches displayed in user.
+		} elseif ( bp_displayed_user_id() == $user_id ) {
+			$userdata = &$bp->displayed_user->userdata;
+
+		// No user ID match.
+		} else {
+			$userdata = false;
+		}
+
+		// No match so go dig.
+		if ( empty( $userdata ) ) {
+
+			// User not found so return false.
+			if ( !$userdata = bp_core_get_core_userdata( $user_id ) ) {
+				return false;
+			}
+		}
+
+		// User nicename found.
+		$user_nicename = $userdata->user_nicename;
+
+	// Nicename found in cache so don't update it again.
+	} else {
+		$update_cache = false;
+	}
+
+	// Add this to cache.
+	if ( true == $update_cache && !empty( $user_nicename ) ) {
+		wp_cache_set( 'bp_members_user_nicename_' . $user_id, $user_nicename, 'bp' );
+	}
+
+	/**
+	 * Filters the user_nicename based on originally provided user ID.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $username User nice name determined by user ID.
+	 */
+	return apply_filters( 'bp_members_get_user_nicename', $user_nicename );
+}
+
+/**
+ * Return the email address for the user based on user ID.
+ *
+ * @param int $uid User ID to check.
+ * @return string The email for the matched user. Empty string if no user
+ *                matched the $uid.
+ */
+function bp_core_get_user_email( $uid ) {
+
+	if ( !$email = wp_cache_get( 'bp_user_email_' . $uid, 'bp' ) ) {
+
+		// User exists.
+		$ud = bp_core_get_core_userdata( $uid );
+		if ( ! empty( $ud ) ) {
+			$email = $ud->user_email;
+
+		// User was deleted.
+		} else {
+			$email = '';
+		}
+
+		wp_cache_set( 'bp_user_email_' . $uid, $email, 'bp' );
+	}
+
+	/**
+	 * Filters the user email for user based on user ID.
+	 *
+	 * @since 1.0.1
+	 *
+	 * @param string $email Email determined for the user.
+	 */
+	return apply_filters( 'bp_core_get_user_email', $email );
+}
+
+/**
+ * Return a HTML formatted link for a user with the user's full name as the link text.
+ *
+ * Eg: <a href="http://andy.example.com/">Andy Peatling</a>
+ *
+ * Optional parameters will return just the name or just the URL.
+ *
+ * @param int  $user_id   User ID to check.
+ * @param bool $no_anchor Disable URL and HTML and just return full name.
+ *                        Default: false.
+ * @param bool $just_link Disable full name and HTML and just return the URL
+ *                        text. Default false.
+ * @return string|bool The link text based on passed parameters, or false on
+ *                     no match.
+ */
+function bp_core_get_userlink( $user_id, $no_anchor = false, $just_link = false ) {
+	$display_name = bp_core_get_user_displayname( $user_id );
+
+	if ( empty( $display_name ) ) {
+		return false;
+	}
+
+	if ( ! empty( $no_anchor ) ) {
+		return $display_name;
+	}
+
+	if ( !$url = bp_core_get_user_domain( $user_id ) ) {
+		return false;
+	}
+
+	if ( ! empty( $just_link ) ) {
+		return $url;
+	}
+
+	/**
+	 * Filters the link text for the passed in user.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value   Link text based on passed parameters.
+	 * @param int    $user_id ID of the user to check.
+	 */
+	return apply_filters( 'bp_core_get_userlink', '<a href="' . $url . '" title="' . $display_name . '">' . $display_name . '</a>', $user_id );
+}
+
+/**
+ * Fetch the display name for a group of users.
+ *
+ * Uses the 'Name' field in xprofile if available. Falls back on WP
+ * display_name, and then user_nicename.
+ *
+ * @since 2.0.0
+ *
+ * @param array $user_ids Array of user IDs to get display names for.
+ * @return array
+ */
+function bp_core_get_user_displaynames( $user_ids ) {
+
+	// Sanitize.
+	$user_ids = wp_parse_id_list( $user_ids );
+
+	// Remove dupes and empties.
+	$user_ids = array_unique( array_filter( $user_ids ) );
+
+	if ( empty( $user_ids ) ) {
+		return array();
+	}
+
+	$uncached_ids = array();
+	foreach ( $user_ids as $user_id ) {
+		if ( false === wp_cache_get( 'bp_user_fullname_' . $user_id, 'bp' ) ) {
+			$uncached_ids[] = $user_id;
+		}
+	}
+
+	// Prime caches.
+	if ( ! empty( $uncached_ids ) ) {
+		if ( bp_is_active( 'xprofile' ) ) {
+			$fullname_data = BP_XProfile_ProfileData::get_value_byid( 1, $uncached_ids );
+
+			// Key by user_id.
+			$fullnames = array();
+			foreach ( $fullname_data as $fd ) {
+				if ( ! empty( $fd->value ) ) {
+					$fullnames[ intval( $fd->user_id ) ] = $fd->value;
+				}
+			}
+
+			// If xprofiledata is not found for any users,  we'll look
+			// them up separately.
+			$no_xprofile_ids = array_diff( $uncached_ids, array_keys( $fullnames ) );
+		} else {
+			$fullnames = array();
+			$no_xprofile_ids = $user_ids;
+		}
+
+		if ( ! empty( $no_xprofile_ids ) ) {
+			// Use WP_User_Query because we don't need BP information.
+			$query = new WP_User_Query( array(
+				'include'     => $no_xprofile_ids,
+				'fields'      => array( 'ID', 'user_nicename', 'display_name', ),
+				'count_total' => false,
+				'blog_id'     => 0,
+			) );
+
+			foreach ( $query->results as $qr ) {
+				$fullnames[ $qr->ID ] = ! empty( $qr->display_name ) ? $qr->display_name : $qr->user_nicename;
+
+				// If xprofile is active, set this value as the
+				// xprofile display name as well.
+				if ( bp_is_active( 'xprofile' ) ) {
+					xprofile_set_field_data( 1, $qr->ID, $fullnames[ $qr->ID ] );
+				}
+			}
+		}
+
+		foreach ( $fullnames as $fuser_id => $fname ) {
+			wp_cache_set( 'bp_user_fullname_' . $fuser_id, $fname, 'bp' );
+		}
+	}
+
+	$retval = array();
+	foreach ( $user_ids as $user_id ) {
+		$retval[ $user_id ] = wp_cache_get( 'bp_user_fullname_' . $user_id, 'bp' );
+	}
+
+	return $retval;
+}
+
+/**
+ * Fetch the display name for a user.
+ *
+ * @param int|string|bool $user_id_or_username User ID or username.
+ * @return string|bool The display name for the user in question, or false if
+ *                     user not found.
+ */
+function bp_core_get_user_displayname( $user_id_or_username ) {
+	if ( empty( $user_id_or_username ) ) {
+		return false;
+	}
+
+	if ( ! is_numeric( $user_id_or_username ) ) {
+		$user_id = bp_core_get_userid( $user_id_or_username );
+	} else {
+		$user_id = $user_id_or_username;
+	}
+
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	$display_names = bp_core_get_user_displaynames( array( $user_id ) );
+
+	if ( ! isset( $display_names[ $user_id ] ) ) {
+		$fullname = false;
+	} else {
+		$fullname = $display_names[ $user_id ];
+	}
+
+	/**
+	 * Filters the display name for the passed in user.
+	 *
+	 * @since 1.0.1
+	 *
+	 * @param string $fullname Display name for the user.
+	 * @param int    $user_id  ID of the user to check.
+	 */
+	return apply_filters( 'bp_core_get_user_displayname', $fullname, $user_id );
+}
+add_filter( 'bp_core_get_user_displayname', 'strip_tags', 1 );
+add_filter( 'bp_core_get_user_displayname', 'trim'          );
+add_filter( 'bp_core_get_user_displayname', 'stripslashes'  );
+add_filter( 'bp_core_get_user_displayname', 'esc_html'      );
+
+/**
+ * Return the user link for the user based on user email address.
+ *
+ * @param string $email The email address for the user.
+ * @return string The link to the users home base. False on no match.
+ */
+function bp_core_get_userlink_by_email( $email ) {
+	$user = get_user_by( 'email', $email );
+
+	/**
+	 * Filters the user link for the user based on user email address.
+	 *
+	 * @since 1.0.1
+	 *
+	 * @param string|bool $value URL for the user if found, otherwise false.
+	 */
+	return apply_filters( 'bp_core_get_userlink_by_email', bp_core_get_userlink( $user->ID, false, false, true ) );
+}
+
+/**
+ * Return the user link for the user based on the supplied identifier.
+ *
+ * @param string $username If BP_ENABLE_USERNAME_COMPATIBILITY_MODE is set,
+ *                         this should be user_login, otherwise it should
+ *                         be user_nicename.
+ * @return string|bool The link to the user's domain, false on no match.
+ */
+function bp_core_get_userlink_by_username( $username ) {
+	if ( bp_is_username_compatibility_mode() ) {
+		$user_id = bp_core_get_userid( $username );
+	} else {
+		$user_id = bp_core_get_userid_from_nicename( $username );
+	}
+
+	/**
+	 * Filters the user link for the user based on username.
+	 *
+	 * @since 1.0.1
+	 *
+	 * @param string|bool $value URL for the user if found, otherwise false.
+	 */
+	return apply_filters( 'bp_core_get_userlink_by_username', bp_core_get_userlink( $user_id, false, false, true ) );
+}
+
+/**
+ * Return the total number of members for the installation.
+ *
+ * Note that this is a raw count of non-spam, activated users. It does not
+ * account for users who have logged activity (last_active). See
+ * {@link bp_core_get_active_member_count()}.
+ *
+ * @return int The total number of members.
+ */
+function bp_core_get_total_member_count() {
+	global $wpdb;
+
+	$count = wp_cache_get( 'bp_total_member_count', 'bp' );
+
+	if ( false === $count ) {
+		$status_sql = bp_core_get_status_sql();
+		$count = $wpdb->get_var( "SELECT COUNT(ID) FROM {$wpdb->users} WHERE {$status_sql}" );
+		wp_cache_set( 'bp_total_member_count', $count, 'bp' );
+	}
+
+	/**
+	 * Filters the total number of members for the installation.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param int $count Total number of members.
+	 */
+	return apply_filters( 'bp_core_get_total_member_count', $count );
+}
+
+/**
+ * Return the total number of members, limited to those members with last_activity.
+ *
+ * @return int The number of active members.
+ */
+function bp_core_get_active_member_count() {
+	global $wpdb;
+
+	$count = get_transient( 'bp_active_member_count' );
+	if ( false === $count ) {
+		$bp = buddypress();
+
+		// Avoid a costly join by splitting the lookup.
+		if ( is_multisite() ) {
+			$sql = "SELECT ID FROM {$wpdb->users} WHERE (user_status != 0 OR deleted != 0 OR user_status != 0)";
+		} else {
+			$sql = "SELECT ID FROM {$wpdb->users} WHERE user_status != 0";
+		}
+
+		$exclude_users     = $wpdb->get_col( $sql );
+		$exclude_users_sql = !empty( $exclude_users ) ? "AND user_id NOT IN (" . implode( ',', wp_parse_id_list( $exclude_users ) ) . ")" : '';
+		$count             = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(user_id) FROM {$bp->members->table_name_last_activity} WHERE component = %s AND type = 'last_activity' {$exclude_users_sql}", $bp->members->id ) );
+
+		set_transient( 'bp_active_member_count', $count );
+	}
+
+	/**
+	 * Filters the total number of members for the installation limited to those with last_activity.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int $count Total number of active members.
+	 */
+	return apply_filters( 'bp_core_get_active_member_count', $count );
+}
+
+/**
+ * Process a spammed or unspammed user.
+ *
+ * This function is called from three places:
+ *
+ * - in bp_settings_action_capabilities() (from the front-end)
+ * - by bp_core_mark_user_spam_admin()    (from wp-admin)
+ * - bp_core_mark_user_ham_admin()        (from wp-admin)
+ *
+ * @since 1.6.0
+ *
+ * @param int    $user_id       The ID of the user being spammed/hammed.
+ * @param string $status        'spam' if being marked as spam, 'ham' otherwise.
+ * @param bool   $do_wp_cleanup True to force the cleanup of WordPress content
+ *                              and status, otherwise false. Generally, this should
+ *                              only be false if WordPress is expected to have
+ *                              performed this cleanup independently, as when hooked
+ *                              to 'make_spam_user'.
+ * @return bool True on success, false on failure.
+ */
+function bp_core_process_spammer_status( $user_id, $status, $do_wp_cleanup = true ) {
+	global $wpdb;
+
+	// Bail if no user ID.
+	if ( empty( $user_id ) ) {
+		return;
+	}
+
+	// Bail if user ID is super admin.
+	if ( is_super_admin( $user_id ) ) {
+		return;
+	}
+
+	// Get the functions file.
+	if ( is_multisite() ) {
+		require_once( ABSPATH . 'wp-admin/includes/ms.php' );
+	}
+
+	$is_spam = ( 'spam' == $status );
+
+	// Only you can prevent infinite loops.
+	remove_action( 'make_spam_user', 'bp_core_mark_user_spam_admin' );
+	remove_action( 'make_ham_user',  'bp_core_mark_user_ham_admin'  );
+
+	// Force the cleanup of WordPress content and status for multisite configs.
+	if ( $do_wp_cleanup ) {
+
+		// Get the blogs for the user.
+		$blogs = get_blogs_of_user( $user_id, true );
+
+		foreach ( (array) array_values( $blogs ) as $details ) {
+
+			// Do not mark the main or current root blog as spam.
+			if ( 1 == $details->userblog_id || bp_get_root_blog_id() == $details->userblog_id ) {
+				continue;
+			}
+
+			// Update the blog status.
+			update_blog_status( $details->userblog_id, 'spam', $is_spam );
+		}
+
+		// Finally, mark this user as a spammer.
+		if ( is_multisite() ) {
+			update_user_status( $user_id, 'spam', $is_spam );
+		}
+	}
+
+	// Update the user status.
+	$wpdb->update( $wpdb->users, array( 'user_status' => $is_spam ), array( 'ID' => $user_id ) );
+
+	// Clean user cache.
+	clean_user_cache( $user_id );
+
+	if ( ! is_multisite() ) {
+		// Call multisite actions in single site mode for good measure.
+		if ( true === $is_spam ) {
+
+			/**
+			 * Fires at end of processing spammer in Dashboard if not multisite and user is spam.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param int $value user ID.
+			 */
+			do_action( 'make_spam_user', $user_id );
+		} else {
+
+			/**
+			 * Fires at end of processing spammer in Dashboard if not multisite and user is not spam.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param int $value user ID.
+			 */
+			do_action( 'make_ham_user', $user_id );
+		}
+	}
+
+	// Hide this user's activity.
+	if ( ( true === $is_spam ) && bp_is_active( 'activity' ) ) {
+		bp_activity_hide_user_activity( $user_id );
+	}
+
+	// We need a special hook for is_spam so that components can delete data at spam time.
+	if ( true === $is_spam ) {
+
+		/**
+		 * Fires at the end of the process spammer process if the user is spam.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int $value Displayed user ID.
+		 */
+		do_action( 'bp_make_spam_user', $user_id );
+	} else {
+
+		/**
+		 * Fires at the end of the process spammer process if the user is not spam.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int $value Displayed user ID.
+		 */
+		do_action( 'bp_make_ham_user', $user_id );
+	}
+
+	/**
+	 * Fires at the end of the process for hanlding spammer status.
+	 *
+	 * @since 1.5.5
+	 *
+	 * @param int  $user_id ID of the processed user.
+	 * @param bool $is_spam The determined spam status of processed user.
+	 */
+	do_action( 'bp_core_process_spammer_status', $user_id, $is_spam );
+
+	// Put things back how we found them.
+	add_action( 'make_spam_user', 'bp_core_mark_user_spam_admin' );
+	add_action( 'make_ham_user',  'bp_core_mark_user_ham_admin'  );
+
+	return true;
+}
+/**
+ * Hook to WP's make_spam_user and run our custom BP spam functions.
+ *
+ * @since 1.6.0
+ *
+ * @param int $user_id The user ID passed from the make_spam_user hook.
+ */
+function bp_core_mark_user_spam_admin( $user_id ) {
+	bp_core_process_spammer_status( $user_id, 'spam', false );
+}
+add_action( 'make_spam_user', 'bp_core_mark_user_spam_admin' );
+
+/**
+ * Hook to WP's make_ham_user and run our custom BP spam functions.
+ *
+ * @since 1.6.0
+ *
+ * @param int $user_id The user ID passed from the make_ham_user hook.
+ */
+function bp_core_mark_user_ham_admin( $user_id ) {
+	bp_core_process_spammer_status( $user_id, 'ham', false );
+}
+add_action( 'make_ham_user', 'bp_core_mark_user_ham_admin' );
+
+/**
+ * Check whether a user has been marked as a spammer.
+ *
+ * @param int $user_id The ID for the user.
+ * @return bool True if spammer, otherwise false.
+ */
+function bp_is_user_spammer( $user_id = 0 ) {
+
+	// No user to check.
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	$bp = buddypress();
+
+	// Assume user is not spam.
+	$is_spammer = false;
+
+	// Setup our user.
+	$user = false;
+
+	// Get locally-cached data if available.
+	switch ( $user_id ) {
+		case bp_loggedin_user_id() :
+			$user = ! empty( $bp->loggedin_user->userdata ) ? $bp->loggedin_user->userdata : false;
+			break;
+
+		case bp_displayed_user_id() :
+			$user = ! empty( $bp->displayed_user->userdata ) ? $bp->displayed_user->userdata : false;
+			break;
+	}
+
+	// Manually get userdata if still empty.
+	if ( empty( $user ) ) {
+		$user = get_userdata( $user_id );
+	}
+
+	// No user found.
+	if ( empty( $user ) ) {
+		$is_spammer = false;
+
+	// User found.
+	} else {
+
+		// Check if spam.
+		if ( !empty( $user->spam ) ) {
+			$is_spammer = true;
+		}
+
+		if ( 1 == $user->user_status ) {
+			$is_spammer = true;
+		}
+	}
+
+	/**
+	 * Filters whether a user is marked as a spammer.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $is_spammer Whether or not user is marked as spammer.
+	 */
+	return apply_filters( 'bp_is_user_spammer', (bool) $is_spammer );
+}
+
+/**
+ * Check whether a user has been marked as deleted.
+ *
+ * @param int $user_id The ID for the user.
+ * @return bool True if deleted, otherwise false.
+ */
+function bp_is_user_deleted( $user_id = 0 ) {
+
+	// No user to check.
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	$bp = buddypress();
+
+	// Assume user is not deleted.
+	$is_deleted = false;
+
+	// Setup our user.
+	$user = false;
+
+	// Get locally-cached data if available.
+	switch ( $user_id ) {
+		case bp_loggedin_user_id() :
+			$user = ! empty( $bp->loggedin_user->userdata ) ? $bp->loggedin_user->userdata : false;
+			break;
+
+		case bp_displayed_user_id() :
+			$user = ! empty( $bp->displayed_user->userdata ) ? $bp->displayed_user->userdata : false;
+			break;
+	}
+
+	// Manually get userdata if still empty.
+	if ( empty( $user ) ) {
+		$user = get_userdata( $user_id );
+	}
+
+	// No user found.
+	if ( empty( $user ) ) {
+		$is_deleted = true;
+
+	// User found.
+	} else {
+
+		// Check if deleted.
+		if ( !empty( $user->deleted ) ) {
+			$is_deleted = true;
+		}
+
+		if ( 2 == $user->user_status ) {
+			$is_deleted = true;
+		}
+	}
+
+	/**
+	 * Filters whether a user is marked as deleted.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param bool $is_deleted Whether or not user is marked as deleted.
+	 */
+	return apply_filters( 'bp_is_user_deleted', (bool) $is_deleted );
+}
+
+/**
+ * Check whether a user is "active", ie neither deleted nor spammer.
+ *
+ * @since 1.6.0
+ *
+ * @uses is_user_logged_in() To check if user is logged in
+ * @uses bp_loggedin_user_id() To get current user ID
+ * @uses bp_is_user_spammer() To check if user is spammer
+ * @uses bp_is_user_deleted() To check if user is deleted
+ *
+ * @param int $user_id The user ID to check.
+ * @return bool True if active, otherwise false.
+ */
+function bp_is_user_active( $user_id = 0 ) {
+
+	// Default to current user.
+	if ( empty( $user_id ) && is_user_logged_in() ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	// No user to check.
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Check spam.
+	if ( bp_is_user_spammer( $user_id ) ) {
+		return false;
+	}
+
+	// Check deleted.
+	if ( bp_is_user_deleted( $user_id ) ) {
+		return false;
+	}
+
+	// Assume true if not spam or deleted.
+	return true;
+}
+
+/**
+ * Check whether user is not active.
+ *
+ * @since 1.6.0
+ *
+ * @todo No need for the user fallback checks, since they're done in
+ *       bp_is_user_active().
+ *
+ * @uses is_user_logged_in() To check if user is logged in.
+ * @uses bp_get_displayed_user_id() To get current user ID.
+ * @uses bp_is_user_active() To check if user is active.
+ *
+ * @param int $user_id The user ID to check.
+ * @return bool True if inactive, otherwise false.
+ */
+function bp_is_user_inactive( $user_id = 0 ) {
+
+	// Default to current user.
+	if ( empty( $user_id ) && is_user_logged_in() ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	// No user to check.
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Return the inverse of active.
+	return !bp_is_user_active( $user_id );
+}
+
+/**
+ * Update a user's last activity.
+ *
+ * @since 1.9.0
+ *
+ * @param int    $user_id ID of the user being updated.
+ * @param string $time    Time of last activity, in 'Y-m-d H:i:s' format.
+ * @return bool True on success, false on failure.
+ */
+function bp_update_user_last_activity( $user_id = 0, $time = '' ) {
+
+	// Fall back on current user.
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	// Bail if the user id is 0, as there's nothing to update.
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	// Fall back on current time.
+	if ( empty( $time ) ) {
+		$time = bp_core_current_time();
+	}
+
+	// As of BuddyPress 2.0, last_activity is no longer stored in usermeta.
+	// However, we mirror it there for backward compatibility. Do not use!
+	// Remove our warning and re-add.
+	remove_filter( 'update_user_metadata', '_bp_update_user_meta_last_activity_warning', 10, 4 );
+	remove_filter( 'get_user_metadata', '_bp_get_user_meta_last_activity_warning', 10, 3 );
+	bp_update_user_meta( $user_id, 'last_activity', $time );
+	add_filter( 'update_user_metadata', '_bp_update_user_meta_last_activity_warning', 10, 4 );
+	add_filter( 'get_user_metadata', '_bp_get_user_meta_last_activity_warning', 10, 3 );
+
+	return BP_Core_User::update_last_activity( $user_id, $time );
+}
+
+/**
+ * Backward compatibility for 'last_activity' usermeta fetching.
+ *
+ * In BuddyPress 2.0, user last_activity data was moved out of usermeta. For
+ * backward compatibility, we continue to mirror the data there. This function
+ * serves two purposes: it warns plugin authors of the change, and it returns
+ * the data from the proper location.
+ *
+ * @since 2.0.0
+ *
+ * @access private For internal use only.
+ *
+ * @param null   $retval Null retval value.
+ * @param int    $object_id ID of the user.
+ * @param string $meta_key  Meta key being fetched.
+ * @return mixed
+ */
+function _bp_get_user_meta_last_activity_warning( $retval, $object_id, $meta_key ) {
+	static $warned = false;
+
+	if ( 'last_activity' === $meta_key ) {
+		// Don't send the warning more than once per pageload.
+		if ( false === $warned ) {
+			_doing_it_wrong( 'get_user_meta( $user_id, \'last_activity\' )', __( 'User last_activity data is no longer stored in usermeta. Use bp_get_user_last_activity() instead.', 'buddypress' ), '2.0.0' );
+			$warned = true;
+		}
+
+		return bp_get_user_last_activity( $object_id );
+	}
+
+	return $retval;
+}
+add_filter( 'get_user_metadata', '_bp_get_user_meta_last_activity_warning', 10, 3 );
+
+/**
+ * Backward compatibility for 'last_activity' usermeta setting.
+ *
+ * In BuddyPress 2.0, user last_activity data was moved out of usermeta. For
+ * backward compatibility, we continue to mirror the data there. This function
+ * serves two purposes: it warns plugin authors of the change, and it updates
+ * the data in the proper location.
+ *
+ * @since 2.0.0
+ *
+ * @access private For internal use only.
+ *
+ * @param int    $meta_id    ID of the just-set usermeta row.
+ * @param int    $object_id  ID of the user.
+ * @param string $meta_key   Meta key being fetched.
+ * @param string $meta_value Active time.
+ */
+function _bp_update_user_meta_last_activity_warning( $meta_id, $object_id, $meta_key, $meta_value ) {
+	if ( 'last_activity' === $meta_key ) {
+		_doing_it_wrong( 'update_user_meta( $user_id, \'last_activity\' )', __( 'User last_activity data is no longer stored in usermeta. Use bp_update_user_last_activity() instead.', 'buddypress' ), '2.0.0' );
+		bp_update_user_last_activity( $object_id, $meta_value );
+	}
+}
+add_filter( 'update_user_metadata', '_bp_update_user_meta_last_activity_warning', 10, 4 );
+
+/**
+ * Get the last activity for a given user.
+ *
+ * @param int $user_id The ID of the user.
+ * @return string Time of last activity, in 'Y-m-d H:i:s' format, or an empty
+ *                string if none is found.
+ */
+function bp_get_user_last_activity( $user_id = 0 ) {
+	$activity = '';
+
+	$last_activity = BP_Core_User::get_last_activity( $user_id );
+	if ( ! empty( $last_activity[ $user_id ] ) ) {
+		$activity = $last_activity[ $user_id ]['date_recorded'];
+	}
+
+	/**
+	 * Filters the last activity for a given user.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $activity Time of last activity, in 'Y-m-d H:i:s' format or
+	 *                         an empty string if none found.
+	 * @param int    $user_id  ID of the user being checked.
+	 */
+	return apply_filters( 'bp_get_user_last_activity', $activity, $user_id );
+}
+
+/**
+ * Migrate last_activity data from the usermeta table to the activity table.
+ *
+ * Generally, this function is only run when BP is upgraded to 2.0. It can also
+ * be called directly from the BuddyPress Tools panel.
+ *
+ * @since 2.0.0
+ */
+function bp_last_activity_migrate() {
+	global $wpdb;
+
+	$bp = buddypress();
+
+	// Wipe out existing last_activity data in the activity table -
+	// this helps to prevent duplicates when pulling from the usermeta
+	// table.
+	$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->members->table_name_last_activity} WHERE component = %s AND type = 'last_activity'", $bp->members->id ) );
+
+	$sql = "INSERT INTO {$bp->members->table_name_last_activity} (`user_id`, `component`, `type`, `action`, `content`, `primary_link`, `item_id`, `date_recorded` ) (
+		  SELECT user_id, '{$bp->members->id}' as component, 'last_activity' as type, '' as action, '' as content, '' as primary_link, 0 as item_id, meta_value AS date_recorded
+		  FROM {$wpdb->usermeta}
+		  WHERE
+		    meta_key = 'last_activity'
+	);";
+
+	return $wpdb->query( $sql );
+}
+
+/**
+ * Fetch every post that is authored by the given user for the current blog.
+ *
+ * No longer used in BuddyPress.
+ *
+ * @todo Deprecate.
+ *
+ * @param int $user_id ID of the user being queried.
+ * @return array Post IDs.
+ */
+function bp_core_get_all_posts_for_user( $user_id = 0 ) {
+	global $wpdb;
+
+	if ( empty( $user_id ) ) {
+		$user_id = bp_displayed_user_id();
+	}
+
+	return apply_filters( 'bp_core_get_all_posts_for_user', $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_author = %d AND post_status = 'publish' AND post_type = 'post'", $user_id ) ) );
+}
+
+/**
+ * Process account deletion requests.
+ *
+ * Primarily used for self-deletions, as requested through Settings.
+ *
+ * @param int $user_id Optional. ID of the user to be deleted. Default: the
+ *                     logged-in user.
+ * @return bool True on success, false on failure.
+ */
+function bp_core_delete_account( $user_id = 0 ) {
+
+	// Use logged in user ID if none is passed.
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	// Site admins cannot be deleted.
+	if ( is_super_admin( $user_id ) ) {
+		return false;
+	}
+
+	// Extra checks if user is not deleting themselves.
+	if ( bp_loggedin_user_id() !== absint( $user_id ) ) {
+
+		// Bail if current user cannot delete any users.
+		if ( ! bp_current_user_can( 'delete_users' ) ) {
+			return false;
+		}
+
+		// Bail if current user cannot delete this user.
+		if ( ! current_user_can_for_blog( bp_get_root_blog_id(), 'delete_user', $user_id ) ) {
+			return false;
+		}
+	}
+
+	/**
+	 * Fires before the processing of an account deletion.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int $user_id ID of the user account being deleted.
+	 */
+	do_action( 'bp_core_pre_delete_account', $user_id );
+
+	// Specifically handle multi-site environment.
+	if ( is_multisite() ) {
+		require_once( ABSPATH . '/wp-admin/includes/ms.php'   );
+		require_once( ABSPATH . '/wp-admin/includes/user.php' );
+
+		$retval = wpmu_delete_user( $user_id );
+
+	// Single site user deletion.
+	} else {
+		require_once( ABSPATH . '/wp-admin/includes/user.php' );
+		$retval = wp_delete_user( $user_id );
+	}
+
+	/**
+	 * Fires after the deletion of an account.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int $user_id ID of the user account that was deleted.
+	 */
+	do_action( 'bp_core_deleted_account', $user_id );
+
+	return $retval;
+}
+
+/**
+ * Delete a user's avatar when the user is deleted.
+ *
+ * @since 1.9.0
+ *
+ * @param int $user_id ID of the user who is about to be deleted.
+ * @return bool True on success, false on failure.
+ */
+function bp_core_delete_avatar_on_user_delete( $user_id ) {
+	return bp_core_delete_existing_avatar( array(
+		'item_id' => $user_id,
+		'object'  => 'user',
+	) );
+}
+add_action( 'wpmu_delete_user', 'bp_core_delete_avatar_on_user_delete' );
+add_action( 'delete_user', 'bp_core_delete_avatar_on_user_delete' );
+
+/**
+ * Multibyte-safe ucfirst() support.
+ *
+ * Uses multibyte functions when available on the PHP build.
+ *
+ * @param string $str String to be upper-cased.
+ * @return string
+ */
+function bp_core_ucfirst( $str ) {
+	if ( function_exists( 'mb_strtoupper' ) && function_exists( 'mb_substr' ) ) {
+		$fc = mb_strtoupper( mb_substr( $str, 0, 1 ) );
+		return $fc.mb_substr( $str, 1 );
+	} else {
+		return ucfirst( $str );
+	}
+}
+
+/**
+ * Prevent spammers from logging in.
+ *
+ * When a user logs in, check if they have been marked as a spammer. If yes
+ * then simply redirect them to the home page and stop them from logging in.
+ *
+ * @since 1.1.2
+ *
+ * @param WP_User|WP_Error $user Either the WP_User object or the WP_Error
+ *                               object, as passed to the 'authenticate' filter.
+ * @return WP_User|WP_Error If the user is not a spammer, return the WP_User
+ *                          object. Otherwise a new WP_Error object.
+ */
+function bp_core_boot_spammer( $user ) {
+
+	// Check to see if the $user has already failed logging in, if so return $user as-is.
+	if ( is_wp_error( $user ) || empty( $user ) ) {
+		return $user;
+	}
+
+	// The user exists; now do a check to see if the user is a spammer
+	// if the user is a spammer, stop them in their tracks!
+	if ( is_a( $user, 'WP_User' ) && ( ( is_multisite() && (int) $user->spam ) || 1 == $user->user_status ) ) {
+		return new WP_Error( 'invalid_username', __( '<strong>ERROR</strong>: Your account has been marked as a spammer.', 'buddypress' ) );
+	}
+
+	// User is good to go!
+	return $user;
+}
+add_filter( 'authenticate', 'bp_core_boot_spammer', 30 );
+
+/**
+ * Delete last_activity data for the user when the user is deleted.
+ *
+ * @param int $user_id The user ID for the user to delete usermeta for.
+ */
+function bp_core_remove_data( $user_id ) {
+
+	// Remove last_activity data.
+	BP_Core_User::delete_last_activity( $user_id );
+
+	// Flush the cache to remove the user from all cached objects.
+	wp_cache_flush();
+}
+add_action( 'wpmu_delete_user',  'bp_core_remove_data' );
+add_action( 'delete_user',       'bp_core_remove_data' );
+add_action( 'bp_make_spam_user', 'bp_core_remove_data' );
+
+/**
+ * Check whether the logged-in user can edit settings for the displayed user.
+ *
+ * @return bool True if editing is allowed, otherwise false.
+ */
+function bp_core_can_edit_settings() {
+	if ( bp_is_my_profile() ) {
+		return true;
+	}
+
+	if ( is_super_admin( bp_displayed_user_id() ) && ! is_super_admin() ) {
+		return false;
+	}
+
+	if ( bp_current_user_can( 'bp_moderate' ) || current_user_can( 'edit_users' ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+/** Sign-up *******************************************************************/
+
+/**
+ * Flush illegal names by getting and setting 'illegal_names' site option.
+ */
+function bp_core_flush_illegal_names() {
+	$illegal_names = get_site_option( 'illegal_names' );
+	update_site_option( 'illegal_names', $illegal_names );
+}
+
+/**
+ * Add BuddyPress-specific items to the illegal_names array.
+ *
+ * @param array|string $value    Illegal names as being saved defined in
+ *                               Multisite settings.
+ * @param array|string $oldvalue The old value of the option.
+ * @return array Merged and unique array of illegal names.
+ */
+function bp_core_get_illegal_names( $value = '', $oldvalue = '' ) {
+
+	// Make sure $value is array.
+	if ( empty( $value ) ) {
+		$db_illegal_names = array();
+	}
+
+	if ( is_array( $value ) ) {
+		$db_illegal_names = $value;
+	} elseif ( is_string( $value ) ) {
+		$db_illegal_names = explode( ' ', $value );
+	}
+
+	// Add the core components' slugs to the banned list even if their components aren't active.
+	$bp_component_slugs = array(
+		'groups',
+		'members',
+		'forums',
+		'blogs',
+		'activity',
+		'profile',
+		'friends',
+		'search',
+		'settings',
+		'notifications',
+		'register',
+		'activate'
+	);
+
+	// Core constants.
+	$slug_constants = array(
+		'BP_GROUPS_SLUG',
+		'BP_MEMBERS_SLUG',
+		'BP_FORUMS_SLUG',
+		'BP_BLOGS_SLUG',
+		'BP_ACTIVITY_SLUG',
+		'BP_XPROFILE_SLUG',
+		'BP_FRIENDS_SLUG',
+		'BP_SEARCH_SLUG',
+		'BP_SETTINGS_SLUG',
+		'BP_NOTIFICATIONS_SLUG',
+		'BP_REGISTER_SLUG',
+		'BP_ACTIVATION_SLUG',
+	);
+	foreach( $slug_constants as $constant ) {
+		if ( defined( $constant ) ) {
+			$bp_component_slugs[] = constant( $constant );
+		}
+	}
+
+	/**
+	 * Filters the array of default illegal usernames.
+	 *
+	 * @since 1.2.2
+	 *
+	 * @param array $value Merged and unique array of illegal usernames.
+	 */
+	$filtered_illegal_names = apply_filters( 'bp_core_illegal_usernames', array_merge( array( 'www', 'web', 'root', 'admin', 'main', 'invite', 'administrator' ), $bp_component_slugs ) );
+
+	// Merge the arrays together.
+	$merged_names           = array_merge( (array) $filtered_illegal_names, (array) $db_illegal_names );
+
+	// Remove duplicates.
+	$illegal_names          = array_unique( (array) $merged_names );
+
+	/**
+	 * Filters the array of default illegal names.
+	 *
+	 * @since 1.2.5
+	 *
+	 * @param array $value Merged and unique array of illegal names.
+	 */
+	return apply_filters( 'bp_core_illegal_names', $illegal_names );
+}
+add_filter( 'pre_update_site_option_illegal_names', 'bp_core_get_illegal_names', 10, 2 );
+
+/**
+ * Check that an email address is valid for use.
+ *
+ * Performs the following checks:
+ *   - Is the email address well-formed?
+ *   - Is the email address already used?
+ *   - If there's an email domain blacklist, is the current domain on it?
+ *   - If there's an email domain whitelest, is the current domain on it?
+ *
+ * @since 1.6.2
+ *
+ * @param string $user_email The email being checked.
+ * @return bool|array True if the address passes all checks; otherwise an array
+ *                    of error codes.
+ */
+function bp_core_validate_email_address( $user_email ) {
+	$errors = array();
+
+	$user_email = sanitize_email( $user_email );
+
+	// Is the email well-formed?
+	if ( ! is_email( $user_email ) ) {
+		$errors['invalid'] = 1;
+	}
+
+	// Is the email on the Banned Email Domains list?
+	// Note: This check only works on Multisite.
+	if ( function_exists( 'is_email_address_unsafe' ) && is_email_address_unsafe( $user_email ) ) {
+		$errors['domain_banned'] = 1;
+	}
+
+	// Is the email on the Limited Email Domains list?
+	// Note: This check only works on Multisite.
+	$limited_email_domains = get_site_option( 'limited_email_domains' );
+	if ( is_array( $limited_email_domains ) && empty( $limited_email_domains ) == false ) {
+		$emaildomain = substr( $user_email, 1 + strpos( $user_email, '@' ) );
+		if ( ! in_array( $emaildomain, $limited_email_domains ) ) {
+			$errors['domain_not_allowed'] = 1;
+		}
+	}
+
+	// Is the email alreday in use?
+	if ( email_exists( $user_email ) ) {
+		$errors['in_use'] = 1;
+	}
+
+	$retval = ! empty( $errors ) ? $errors : true;
+
+	return $retval;
+}
+
+/**
+ * Add the appropriate errors to a WP_Error object, given results of a validation test.
+ *
+ * Functions like bp_core_validate_email_address() return a structured array
+ * of error codes. bp_core_add_validation_error_messages() takes this array and
+ * parses, adding the appropriate error messages to the WP_Error object.
+ *
+ * @since 1.7.0
+ *
+ * @see bp_core_validate_email_address()
+ *
+ * @param WP_Error $errors             WP_Error object.
+ * @param array    $validation_results The return value of a validation function
+ *                                     like bp_core_validate_email_address().
+ */
+function bp_core_add_validation_error_messages( WP_Error $errors, $validation_results ) {
+	if ( ! empty( $validation_results['invalid'] ) ) {
+		$errors->add( 'user_email', __( 'Please check your email address.', 'buddypress' ) );
+	}
+
+	if ( ! empty( $validation_results['domain_banned'] ) ) {
+		$errors->add( 'user_email',  __( 'Sorry, that email address is not allowed!', 'buddypress' ) );
+	}
+
+	if ( ! empty( $validation_results['domain_not_allowed'] ) ) {
+		$errors->add( 'user_email', __( 'Sorry, that email address is not allowed!', 'buddypress' ) );
+	}
+
+	if ( ! empty( $validation_results['in_use'] ) ) {
+		$errors->add( 'user_email', __( 'Sorry, that email address is already used!', 'buddypress' ) );
+	}
+}
+
+/**
+ * Validate a user name and email address when creating a new user.
+ *
+ * @param string $user_name  Username to validate.
+ * @param string $user_email Email address to validate.
+ * @return array Results of user validation including errors, if any.
+ */
+function bp_core_validate_user_signup( $user_name, $user_email ) {
+
+	// Make sure illegal names include BuddyPress slugs and values.
+	bp_core_flush_illegal_names();
+
+	// WordPress Multisite has its own validation. Use it, so that we
+	// properly mirror restrictions on username, etc.
+	if ( function_exists( 'wpmu_validate_user_signup' ) ) {
+		$result = wpmu_validate_user_signup( $user_name, $user_email );
+
+	// When not running Multisite, we perform our own validation. What
+	// follows reproduces much of the logic of wpmu_validate_user_signup(),
+	// minus the multisite-specific restrictions on user_login.
+	} else {
+		$errors = new WP_Error();
+
+		/**
+		 * Filters the username before being validated.
+		 *
+		 * @since 1.5.5
+		 *
+		 * @param string $user_name Username to validate.
+		 */
+		$user_name = apply_filters( 'pre_user_login', $user_name );
+
+		// User name can't be empty.
+		if ( empty( $user_name ) ) {
+			$errors->add( 'user_name', __( 'Please enter a username', 'buddypress' ) );
+		}
+
+		// User name can't be on the blacklist.
+		$illegal_names = get_site_option( 'illegal_names' );
+		if ( in_array( $user_name, (array) $illegal_names ) ) {
+			$errors->add( 'user_name', __( 'That username is not allowed', 'buddypress' ) );
+		}
+
+		// User name must pass WP's validity check.
+		if ( ! validate_username( $user_name ) ) {
+			$errors->add( 'user_name', __( 'Usernames can contain only letters, numbers, ., -, and @', 'buddypress' ) );
+		}
+
+		// Minimum of 4 characters.
+		if ( strlen( $user_name ) < 4 ) {
+			$errors->add( 'user_name',  __( 'Username must be at least 4 characters', 'buddypress' ) );
+		}
+
+		// No underscores. @todo Why not?
+		if ( false !== strpos( ' ' . $user_name, '_' ) ) {
+			$errors->add( 'user_name', __( 'Sorry, usernames may not contain the character "_"!', 'buddypress' ) );
+		}
+
+		// No usernames that are all numeric. @todo Why?
+		$match = array();
+		preg_match( '/[0-9]*/', $user_name, $match );
+		if ( $match[0] == $user_name ) {
+			$errors->add( 'user_name', __( 'Sorry, usernames must have letters too!', 'buddypress' ) );
+		}
+
+		// Check into signups.
+		$signups = BP_Signup::get( array(
+			'user_login' => $user_name,
+		) );
+
+		$signup = isset( $signups['signups'] ) && ! empty( $signups['signups'][0] ) ? $signups['signups'][0] : false;
+
+		// Check if the username has been used already.
+		if ( username_exists( $user_name ) || ! empty( $signup ) ) {
+			$errors->add( 'user_name', __( 'Sorry, that username already exists!', 'buddypress' ) );
+		}
+
+		// Validate the email address and process the validation results into
+		// error messages.
+		$validate_email = bp_core_validate_email_address( $user_email );
+		bp_core_add_validation_error_messages( $errors, $validate_email );
+
+		// Assemble the return array.
+		$result = array(
+			'user_name'  => $user_name,
+			'user_email' => $user_email,
+			'errors'     => $errors,
+		);
+
+		// Apply WPMU legacy filter.
+		$result = apply_filters( 'wpmu_validate_user_signup', $result );
+	}
+
+	/**
+	 * Filters the result of the user signup validation.
+	 *
+	 * @since 1.2.2
+	 *
+	 * @param array $result Results of user validation including errors, if any.
+	 */
+ 	return apply_filters( 'bp_core_validate_user_signup', $result );
+}
+
+/**
+ * Validate blog URL and title provided at signup.
+ *
+ * @todo Why do we have this wrapper?
+ *
+ * @param string $blog_url   Blog URL requested during registration.
+ * @param string $blog_title Blog title requested during registration.
+ * @return array
+ */
+function bp_core_validate_blog_signup( $blog_url, $blog_title ) {
+	if ( ! is_multisite() || ! function_exists( 'wpmu_validate_blog_signup' ) ) {
+		return false;
+	}
+
+	/**
+	 * Filters the validated blog url and title provided at signup.
+	 *
+	 * @since 1.2.2
+	 *
+	 * @param array $value Array with the new site data and error messages.
+	 */
+	return apply_filters( 'bp_core_validate_blog_signup', wpmu_validate_blog_signup( $blog_url, $blog_title ) );
+}
+
+/**
+ * Process data submitted at user registration and convert to a signup object.
+ *
+ * @todo There appears to be a bug in the return value on success.
+ *
+ * @param string $user_login    Login name requested by the user.
+ * @param string $user_password Password requested by the user.
+ * @param string $user_email    Email address entered by the user.
+ * @param array  $usermeta      Miscellaneous metadata about the user (blog-specific
+ *                              signup data, xprofile data, etc).
+ * @return bool|WP_Error True on success, WP_Error on failure.
+ */
+function bp_core_signup_user( $user_login, $user_password, $user_email, $usermeta ) {
+	$bp = buddypress();
+
+	// We need to cast $user_id to pass to the filters.
+	$user_id = false;
+
+	// Multisite installs have their own install procedure.
+	if ( is_multisite() ) {
+		wpmu_signup_user( $user_login, $user_email, $usermeta );
+
+	} else {
+		// Format data.
+		$user_login     = preg_replace( '/\s+/', '', sanitize_user( $user_login, true ) );
+		$user_email     = sanitize_email( $user_email );
+		$activation_key = substr( md5( time() . rand() . $user_email ), 0, 16 );
+
+		/**
+		 * WordPress's default behavior is to create user accounts
+		 * immediately at registration time. BuddyPress uses a system
+		 * borrowed from WordPress Multisite, where signups are stored
+		 * separately and accounts are only created at the time of
+		 * activation. For backward compatibility with plugins that may
+		 * be anticipating WP's default behavior, BP silently creates
+		 * accounts for registrations (though it does not use them). If
+		 * you know that you are not running any plugins dependent on
+		 * these pending accounts, you may want to save a little DB
+		 * clutter by defining setting the BP_SIGNUPS_SKIP_USER_CREATION
+		 * to true in your wp-config.php file.
+		 */
+		if ( ! defined( 'BP_SIGNUPS_SKIP_USER_CREATION' ) || ! BP_SIGNUPS_SKIP_USER_CREATION ) {
+			$user_id = BP_Signup::add_backcompat( $user_login, $user_password, $user_email, $usermeta );
+
+			if ( is_wp_error( $user_id ) ) {
+				return $user_id;
+			}
+
+			$activation_key = wp_hash( $user_id );
+			bp_update_user_meta( $user_id, 'activation_key', $activation_key );
+		}
+
+		$args = array(
+			'user_login'     => $user_login,
+			'user_email'     => $user_email,
+			'activation_key' => $activation_key,
+			'meta'           => $usermeta,
+		);
+
+		BP_Signup::add( $args );
+
+		/**
+		 * Filters if BuddyPress should send an activation key for a new signup.
+		 *
+		 * @since 1.2.3
+		 *
+		 * @param bool   $value          Whether or not to send the activation key.
+		 * @param int    $user_id        User ID to send activation key to.
+		 * @param string $user_email     User email to send activation key to.
+		 * @param string $activation_key Activation key to be sent.
+		 * @param array  $usermeta       Miscellaneous metadata about the user (blog-specific
+		 *                               signup data, xprofile data, etc).
+		 */
+		if ( apply_filters( 'bp_core_signup_send_activation_key', true, $user_id, $user_email, $activation_key, $usermeta ) ) {
+			bp_core_signup_send_validation_email( $user_id, $user_email, $activation_key );
+		}
+	}
+
+	$bp->signup->username = $user_login;
+
+	/**
+	 * Fires at the end of the process to sign up a user.
+	 *
+	 * @since 1.2.2
+	 *
+	 * @param bool|WP_Error   $user_id       True on success, WP_Error on failure.
+	 * @param string          $user_login    Login name requested by the user.
+	 * @param string          $user_password Password requested by the user.
+	 * @param string          $user_email    Email address requested by the user.
+	 * @param array           $usermeta      Miscellaneous metadata about the user (blog-specific
+	 *                                       signup data, xprofile data, etc).
+	 */
+	do_action( 'bp_core_signup_user', $user_id, $user_login, $user_password, $user_email, $usermeta );
+
+	return $user_id;
+}
+
+/**
+ * Create a blog and user based on data supplied at user registration.
+ *
+ * @param string $blog_domain Domain requested by user.
+ * @param string $blog_path   Path requested by user.
+ * @param string $blog_title  Title as entered by user.
+ * @param string $user_name   user_login of requesting user.
+ * @param string $user_email  Email address of requesting user.
+ * @param string $usermeta    Miscellaneous metadata for the user.
+ * @return bool
+ */
+function bp_core_signup_blog( $blog_domain, $blog_path, $blog_title, $user_name, $user_email, $usermeta ) {
+	if ( ! is_multisite() || ! function_exists( 'wpmu_signup_blog' ) ) {
+		return false;
+	}
+
+	/**
+	 * Filters the result of wpmu_signup_blog().
+	 *
+	 * This filter provides no value and is retained for
+	 * backwards compatibility.
+	 *
+	 * @since 1.2.2
+	 *
+	 * @param void $value
+	 */
+	return apply_filters( 'bp_core_signup_blog', wpmu_signup_blog( $blog_domain, $blog_path, $blog_title, $user_name, $user_email, $usermeta ) );
+}
+
+/**
+ * Activate a signup, as identified by an activation key.
+ *
+ * @param string $key Activation key.
+ * @return int|bool User ID on success, false on failure.
+ */
+function bp_core_activate_signup( $key ) {
+	global $wpdb;
+
+	$user = false;
+
+	// Multisite installs have their own activation routine.
+	if ( is_multisite() ) {
+		$user = wpmu_activate_signup( $key );
+
+		// If there were errors, add a message and redirect.
+		if ( ! empty( $user->errors ) ) {
+			return $user;
+		}
+
+		$user_id = $user['user_id'];
+
+	} else {
+		$signups = BP_Signup::get( array(
+			'activation_key' => $key,
+		) );
+
+		if ( empty( $signups['signups'] ) ) {
+			return new WP_Error( 'invalid_key', __( 'Invalid activation key.', 'buddypress' ) );
+		}
+
+		$signup = $signups['signups'][0];
+
+		if ( $signup->active ) {
+			if ( empty( $signup->domain ) ) {
+				return new WP_Error( 'already_active', __( 'The user is already active.', 'buddypress' ), $signup );
+			} else {
+				return new WP_Error( 'already_active', __( 'The site is already active.', 'buddypress' ), $signup );
+			}
+		}
+
+		// Password is hashed again in wp_insert_user.
+		$password = wp_generate_password( 12, false );
+
+		$user_id = username_exists( $signup->user_login );
+
+		// Create the user.
+		if ( ! $user_id ) {
+			$user_id = wp_create_user( $signup->user_login, $password, $signup->user_email );
+
+		// If a user ID is found, this may be a legacy signup, or one
+		// created locally for backward compatibility. Process it.
+		} elseif ( $key == wp_hash( $user_id ) ) {
+			// Change the user's status so they become active.
+			if ( ! $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->users} SET user_status = 0 WHERE ID = %d", $user_id ) ) ) {
+				return new WP_Error( 'invalid_key', __( 'Invalid activation key.', 'buddypress' ) );
+			}
+
+			bp_delete_user_meta( $user_id, 'activation_key' );
+
+			$member = get_userdata( $user_id );
+			$member->set_role( get_option('default_role') );
+
+			$user_already_created = true;
+
+		} else {
+			$user_already_exists = true;
+		}
+
+		if ( ! $user_id ) {
+			return new WP_Error( 'create_user', __( 'Could not create user', 'buddypress' ), $signup );
+		}
+
+		// Fetch the signup so we have the data later on.
+		$signups = BP_Signup::get( array(
+			'activation_key' => $key,
+		) );
+
+		$signup = isset( $signups['signups'] ) && ! empty( $signups['signups'][0] ) ? $signups['signups'][0] : false;
+
+		// Activate the signup.
+		BP_Signup::validate( $key );
+
+		if ( isset( $user_already_exists ) ) {
+			return new WP_Error( 'user_already_exists', __( 'That username is already activated.', 'buddypress' ), $signup );
+		}
+
+		// Set up data to pass to the legacy filter.
+		$user = array(
+			'user_id'  => $user_id,
+			'password' => $signup->meta['password'],
+			'meta'     => $signup->meta,
+		);
+
+		// Notify the site admin of a new user registration.
+		wp_new_user_notification( $user_id );
+
+		if ( isset( $user_already_created ) ) {
+
+			/**
+			 * Fires if the user has already been created.
+			 *
+			 * @since 1.2.2
+			 *
+			 * @param int    $user_id ID of the user being checked.
+			 * @param string $key     Activation key.
+			 * @param array  $user    Array of user data.
+			 */
+			do_action( 'bp_core_activated_user', $user_id, $key, $user );
+			return $user_id;
+		}
+	}
+
+	// Set any profile data.
+	if ( bp_is_active( 'xprofile' ) ) {
+		if ( ! empty( $user['meta']['profile_field_ids'] ) ) {
+			$profile_field_ids = explode( ',', $user['meta']['profile_field_ids'] );
+
+			foreach( (array) $profile_field_ids as $field_id ) {
+				$current_field = isset( $user['meta']["field_{$field_id}"] ) ? $user['meta']["field_{$field_id}"] : false;
+
+				if ( !empty( $current_field ) ) {
+					xprofile_set_field_data( $field_id, $user_id, $current_field );
+				}
+
+				// Save the visibility level.
+				$visibility_level = ! empty( $user['meta']['field_' . $field_id . '_visibility'] ) ? $user['meta']['field_' . $field_id . '_visibility'] : 'public';
+				xprofile_set_field_visibility_level( $field_id, $user_id, $visibility_level );
+			}
+		}
+	}
+
+	// Update the display_name.
+	wp_update_user( array(
+		'ID'           => $user_id,
+		'display_name' => bp_core_get_user_displayname( $user_id ),
+	) );
+
+	// Set the password on multisite installs.
+	if ( ! empty( $user['meta']['password'] ) ) {
+		$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->users} SET user_pass = %s WHERE ID = %d", $user['meta']['password'], $user_id ) );
+	}
+
+	/**
+	 * Fires at the end of the user activation process.
+	 *
+	 * @since 1.2.2
+	 *
+	 * @param int    $user_id ID of the user being checked.
+	 * @param string $key     Activation key.
+	 * @param array  $user    Array of user data.
+	 */
+	do_action( 'bp_core_activated_user', $user_id, $key, $user );
+
+	return $user_id;
+}
+
+/**
+ * Migrate signups from pre-2.0 configuration to wp_signups.
+ *
+ * @since 2.0.1
+ */
+function bp_members_migrate_signups() {
+	global $wpdb;
+
+	$status_2_ids = $wpdb->get_col( "SELECT ID FROM {$wpdb->users} WHERE user_status = '2'" );
+
+	if ( ! empty( $status_2_ids ) ) {
+		$signups = get_users( array(
+			'fields'  => array(
+				'ID',
+				'user_login',
+				'user_pass',
+				'user_registered',
+				'user_email',
+				'display_name',
+			),
+			'include' => $status_2_ids,
+		) );
+
+		// Fetch activation keys separately, to avoid the all_with_meta
+		// overhead.
+		$status_2_ids_sql = implode( ',', $status_2_ids );
+		$ak_data = $wpdb->get_results( "SELECT user_id, meta_value FROM {$wpdb->usermeta} WHERE meta_key = 'activation_key' AND user_id IN ({$status_2_ids_sql})" );
+
+		// Rekey.
+		$activation_keys = array();
+		foreach ( $ak_data as $ak_datum ) {
+			$activation_keys[ intval( $ak_datum->user_id ) ] = $ak_datum->meta_value;
+		}
+
+		unset( $status_2_ids_sql, $status_2_ids, $ak_data );
+
+		// Merge.
+		foreach ( $signups as &$signup ) {
+			if ( isset( $activation_keys[ $signup->ID ] ) ) {
+				$signup->activation_key = $activation_keys[ $signup->ID ];
+			}
+		}
+
+		// Reset the signup var as we're using it to process the migration.
+		unset( $signup );
+
+	} else {
+		return;
+	}
+
+	foreach ( $signups as $signup ) {
+		$meta = array();
+
+		// Rebuild the activation key, if missing.
+		if ( empty( $signup->activation_key ) ) {
+			$signup->activation_key = wp_hash( $signup->ID );
+		}
+
+		if ( bp_is_active( 'xprofile' ) ) {
+			$meta['field_1'] = $signup->display_name;
+		}
+
+		$meta['password'] = $signup->user_pass;
+
+		$user_login = preg_replace( '/\s+/', '', sanitize_user( $signup->user_login, true ) );
+		$user_email = sanitize_email( $signup->user_email );
+
+		BP_Signup::add( array(
+			'user_login'     => $user_login,
+			'user_email'     => $user_email,
+			'registered'     => $signup->user_registered,
+			'activation_key' => $signup->activation_key,
+			'meta'           => $meta
+		) );
+
+		// Deleting these options will remove signups from users count.
+		delete_user_option( $signup->ID, 'capabilities' );
+		delete_user_option( $signup->ID, 'user_level'   );
+	}
+}
+
+/**
+ * Map a user's WP display name to the XProfile fullname field, if necessary.
+ *
+ * This only happens when a user is registered in wp-admin by an administrator;
+ * during normal registration, XProfile data is provided directly by the user.
+ *
+ * @param int $user_id ID of the user.
+ * @return bool
+ */
+function bp_core_map_user_registration( $user_id ) {
+
+	// Only map data when the site admin is adding users, not on registration.
+	if ( ! is_admin() ) {
+		return false;
+	}
+
+	// Add the user's fullname to Xprofile.
+	if ( bp_is_active( 'xprofile' ) ) {
+		$firstname = bp_get_user_meta( $user_id, 'first_name', true );
+		$lastname = ' ' . bp_get_user_meta( $user_id, 'last_name', true );
+		$name = $firstname . $lastname;
+
+		if ( empty( $name ) || ' ' == $name ) {
+			$name = bp_get_user_meta( $user_id, 'nickname', true );
+		}
+
+		xprofile_set_field_data( 1, $user_id, $name );
+	}
+}
+add_action( 'user_register', 'bp_core_map_user_registration' );
+
+/**
+ * Get the avatar storage directory for use during registration.
+ *
+ * @return string|bool Directory path on success, false on failure.
+ */
+function bp_core_signup_avatar_upload_dir() {
+	$bp = buddypress();
+
+	if ( empty( $bp->signup->avatar_dir ) ) {
+		return false;
+	}
+
+	$directory = 'avatars/signups';
+	$path      = bp_core_avatar_upload_path() . '/' . $directory . '/' . $bp->signup->avatar_dir;
+	$newbdir   = $path;
+	$newurl    = bp_core_avatar_url() . '/' . $directory . '/' . $bp->signup->avatar_dir;
+	$newburl   = $newurl;
+	$newsubdir = '/' . $directory . '/' . $bp->signup->avatar_dir;
+
+	/**
+	 * Filters the avatar storage directory for use during registration.
+	 *
+	 * @since 1.1.1
+	 *
+	 * @param array $value Array of path and URL values for created storage directory.
+	 */
+	return apply_filters( 'bp_core_signup_avatar_upload_dir', array(
+		'path'    => $path,
+		'url'     => $newurl,
+		'subdir'  => $newsubdir,
+		'basedir' => $newbdir,
+		'baseurl' => $newburl,
+		'error'   => false
+	) );
+}
+
+/**
+ * Send activation email to a newly registered user.
+ *
+ * @param int    $user_id    ID of the new user.
+ * @param string $user_email Email address of the new user.
+ * @param string $key        Activation key.
+ */
+function bp_core_signup_send_validation_email( $user_id, $user_email, $key ) {
+	$activate_url = trailingslashit( bp_get_activation_page() ) . "{$key}/";
+	$activate_url = esc_url( $activate_url );
+
+	$message = sprintf( __( "Thanks for registering! To complete the activation of your account please click the following link:\n\n%1\$s\n\n", 'buddypress' ), $activate_url );
+	$subject = bp_get_email_subject( array( 'text' => __( 'Activate Your Account', 'buddypress' ) ) );
+
+	/**
+	 * Filters the user email that the validation email will be sent to.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $user_email User email the notification is being sent to.
+	 * @param int    $user_id    ID of the new user receiving email.
+	 */
+	$to      = apply_filters( 'bp_core_signup_send_validation_email_to',     $user_email, $user_id                );
+
+	/**
+	 * Filters the validation email subject that will be sent to user.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $subject Email validation subject text.
+	 * @param int    $user_id ID of the new user receiving email.
+	 */
+	$subject = apply_filters( 'bp_core_signup_send_validation_email_subject', $subject,    $user_id                );
+
+	/**
+	 * Filters the validation email message that will be sent to user.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $message      Email validation message text.
+	 * @param int    $user_id      ID of the new user receiving email.
+	 * @param string $activate_url URL to use for activating account.
+	 */
+	$message = apply_filters( 'bp_core_signup_send_validation_email_message', $message,    $user_id, $activate_url );
+
+	wp_mail( $to, $subject, $message );
+
+	/**
+	 * Fires after the sending of activation email to a newly registered user.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $subject    Subject for the sent email.
+	 * @param string $message    Message for the sent email.
+	 * @param int    $user_id    ID of the new user.
+	 * @param string $user_email Email address of the new user.
+	 * @param string $key        Activation key.
+	 */
+	do_action( 'bp_core_sent_user_validation_email', $subject, $message, $user_id, $user_email, $key );
+}
+
+/**
+ * Display a "resend email" link when an unregistered user attempts to log in.
+ *
+ * @since 1.2.2
+ *
+ * @param WP_User|WP_Error $user     Either the WP_User or the WP_Error object.
+ * @param string           $username The inputted, attempted username.
+ * @param string           $password The inputted, attempted password.
+ * @return WP_User|WP_Error
+ */
+function bp_core_signup_disable_inactive( $user = null, $username = '', $password ='' ) {
+	// Login form not used.
+	if ( empty( $username ) && empty( $password ) ) {
+		return $user;
+	}
+
+	// An existing WP_User with a user_status of 2 is either a legacy
+	// signup, or is a user created for backward compatibility. See
+	// {@link bp_core_signup_user()} for more details.
+	if ( is_a( $user, 'WP_User' ) && 2 == $user->user_status ) {
+		$user_login = $user->user_login;
+
+	// If no WP_User is found corresponding to the username, this
+	// is a potential signup.
+	} elseif ( is_wp_error( $user ) && 'invalid_username' == $user->get_error_code() ) {
+		$user_login = $username;
+
+	// This is an activated user, so bail.
+	} else {
+		return $user;
+	}
+
+	// Look for the unactivated signup corresponding to the login name.
+	$signup = BP_Signup::get( array( 'user_login' => sanitize_user( $user_login ) ) );
+
+	// No signup or more than one, something is wrong. Let's bail.
+	if ( empty( $signup['signups'][0] ) || $signup['total'] > 1 ) {
+		return $user;
+	}
+
+	// Unactivated user account found!
+	// Set up the feedback message.
+	$signup_id = $signup['signups'][0]->signup_id;
+
+	$resend_url_params = array(
+		'action' => 'bp-resend-activation',
+		'id'     => $signup_id,
+	);
+
+	$resend_url = wp_nonce_url(
+		add_query_arg( $resend_url_params, wp_login_url() ),
+		'bp-resend-activation'
+	);
+
+	$resend_string = '<br /><br />' . sprintf( __( 'If you have not received an email yet, <a href="%s">click here to resend it</a>.', 'buddypress' ), esc_url( $resend_url ) );
+
+	return new WP_Error( 'bp_account_not_activated', __( '<strong>ERROR</strong>: Your account has not been activated. Check your email for the activation link.', 'buddypress' ) . $resend_string );
+}
+add_filter( 'authenticate', 'bp_core_signup_disable_inactive', 30, 3 );
+
+/**
+ * On the login screen, resends the activation email for a user.
+ *
+ * @since 2.0.0
+ *
+ * @see bp_core_signup_disable_inactive()
+ */
+function bp_members_login_resend_activation_email() {
+	global $error;
+
+	if ( empty( $_GET['id'] ) || empty( $_GET['_wpnonce'] ) ) {
+		return;
+	}
+
+	// Verify nonce.
+	if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'bp-resend-activation' ) ) {
+		die( 'Security check' );
+	}
+
+	$signup_id = (int) $_GET['id'];
+
+	// Resend the activation email.
+	// also updates the 'last sent' and '# of emails sent' values.
+	$resend = BP_Signup::resend( array( $signup_id ) );
+
+	// Add feedback message.
+	if ( ! empty( $resend['errors'] ) ) {
+		$error = __( '<strong>ERROR</strong>: Your account has already been activated.', 'buddypress' );
+	} else {
+		$error = __( 'Activation email resent! Please check your inbox or spam folder.', 'buddypress' );
+	}
+}
+add_action( 'login_form_bp-resend-activation', 'bp_members_login_resend_activation_email' );
+
+/**
+ * Redirect away from wp-signup.php if BP registration templates are present.
+ */
+function bp_core_wpsignup_redirect() {
+
+	// Bail in admin or if custom signup page is broken.
+	if ( is_admin() || ! bp_has_custom_signup_page() ) {
+		return;
+	}
+
+	$action = !empty( $_GET['action'] ) ? $_GET['action'] : '';
+
+	// Not at the WP core signup page and action is not register.
+	if ( ! empty( $_SERVER['SCRIPT_NAME'] ) && false === strpos( $_SERVER['SCRIPT_NAME'], 'wp-signup.php' ) && ( 'register' != $action ) ) {
+		return;
+	}
+
+	bp_core_redirect( bp_get_signup_page() );
+}
+add_action( 'bp_init', 'bp_core_wpsignup_redirect' );
+
+/**
+ * Stop a logged-in user who is marked as a spammer.
+ *
+ * When an admin marks a live user as a spammer, that user can still surf
+ * around and cause havoc on the site until that person is logged out.
+ *
+ * This code checks to see if a logged-in user is marked as a spammer.  If so,
+ * we redirect the user back to wp-login.php with the 'reauth' parameter.
+ *
+ * This clears the logged-in spammer's cookies and will ask the spammer to
+ * reauthenticate.
+ *
+ * Note: A spammer cannot log back in - {@see bp_core_boot_spammer()}.
+ *
+ * Runs on 'bp_init' at priority 5 so the members component globals are setup
+ * before we do our spammer checks.
+ *
+ * This is important as the $bp->loggedin_user object is setup at priority 4.
+ *
+ * @since 1.8.0
+ */
+function bp_stop_live_spammer() {
+	// If we're on the login page, stop now to prevent redirect loop.
+	$is_login = false;
+	if ( isset( $GLOBALS['pagenow'] ) && ( false !== strpos( $GLOBALS['pagenow'], 'wp-login.php' ) ) ) {
+		$is_login = true;
+	} elseif ( isset( $_SERVER['SCRIPT_NAME'] ) && false !== strpos( $_SERVER['SCRIPT_NAME'], 'wp-login.php' ) ) {
+		$is_login = true;
+	}
+
+	if ( $is_login ) {
+		return;
+	}
+
+	// User isn't logged in, so stop!
+	if ( ! is_user_logged_in() ) {
+		return;
+	}
+
+	// If spammer, redirect to wp-login.php and reauthorize.
+	if ( bp_is_user_spammer( bp_loggedin_user_id() ) ) {
+		// Setup login args.
+		$args = array(
+			// Custom action used to throw an error message.
+			'action' => 'bp-spam',
+
+			// Reauthorize user to login.
+			'reauth' => 1
+		);
+
+		/**
+		 * Filters the url used for redirection for a logged in user marked as spam.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param string $value URL to redirect user to.
+		 */
+		$login_url = apply_filters( 'bp_live_spammer_redirect', add_query_arg( $args, wp_login_url() ) );
+
+		// Redirect user to login page.
+		wp_redirect( $login_url );
+		die();
+	}
+}
+add_action( 'bp_init', 'bp_stop_live_spammer', 5 );
+
+/**
+ * Show a custom error message when a logged-in user is marked as a spammer.
+ *
+ * @since 1.8.0
+ */
+function bp_live_spammer_login_error() {
+	global $error;
+
+	$error = __( '<strong>ERROR</strong>: Your account has been marked as a spammer.', 'buddypress' );
+
+	// Shake shake shake!
+	add_action( 'login_head', 'wp_shake_js', 12 );
+}
+add_action( 'login_form_bp-spam', 'bp_live_spammer_login_error' );
+
+/** Member Types *************************************************************/
+
+/**
+ * Register a member type.
+ *
+ * @since 2.2.0
+ *
+ * @param string $member_type Unique string identifier for the member type.
+ * @param array  $args {
+ *     Array of arguments describing the member type.
+ *
+ *     @type array       $labels {
+ *         Array of labels to use in various parts of the interface.
+ *
+ *         @type string $name          Default name. Should typically be plural.
+ *         @type string $singular_name Singular name.
+ *     }
+ *     @type bool|string $has_directory Whether the member type should have its own type-specific directory.
+ *                                      Pass `true` to use the `$member_type` string as the type's slug.
+ *                                      Pass a string to customize the slug. Pass `false` to disable.
+ *                                      Default: true.
+ * }
+ * @return object|WP_Error Member type object on success, WP_Error object on failure.
+ */
+function bp_register_member_type( $member_type, $args = array() ) {
+	$bp = buddypress();
+
+	if ( isset( $bp->members->types[ $member_type ] ) ) {
+		return new WP_Error( 'bp_member_type_exists', __( 'Member type already exists.', 'buddypress' ), $member_type );
+	}
+
+	$r = bp_parse_args( $args, array(
+		'labels'        => array(),
+		'has_directory' => true,
+	), 'register_member_type' );
+
+	$member_type = sanitize_key( $member_type );
+
+	/**
+	 * Filters the list of illegal member type names.
+	 *
+	 * - 'any' is a special pseudo-type, representing items unassociated with any member type.
+	 * - 'null' is a special pseudo-type, representing users without any type.
+	 * - '_none' is used internally to denote an item that should not apply to any member types.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param array $illegal_names Array of illegal names.
+	 */
+	$illegal_names = apply_filters( 'bp_member_type_illegal_names', array( 'any', 'null', '_none' ) );
+	if ( in_array( $member_type, $illegal_names, true ) ) {
+		return new WP_Error( 'bp_member_type_illegal_name', __( 'You may not register a member type with this name.', 'buddypress' ), $member_type );
+	}
+
+	// Store the post type name as data in the object (not just as the array key).
+	$r['name'] = $member_type;
+
+	// Make sure the relevant labels have been filled in.
+	$default_name = isset( $r['labels']['name'] ) ? $r['labels']['name'] : ucfirst( $r['name'] );
+	$r['labels'] = array_merge( array(
+		'name'          => $default_name,
+		'singular_name' => $default_name,
+	), $r['labels'] );
+
+	// Directory slug.
+	if ( $r['has_directory'] ) {
+		// A string value is intepreted as the directory slug. Otherwise fall back on member type.
+		if ( is_string( $r['has_directory'] ) ) {
+			$directory_slug = $r['has_directory'];
+		} else {
+			$directory_slug = $member_type;
+		}
+
+		// Sanitize for use in URLs.
+		$r['directory_slug'] = sanitize_title( $directory_slug );
+		$r['has_directory']  = true;
+	} else {
+		$r['directory_slug'] = '';
+		$r['has_directory']  = false;
+	}
+
+	$bp->members->types[ $member_type ] = $type = (object) $r;
+
+	/**
+	 * Fires after a member type is registered.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string $member_type Member type identifier.
+	 * @param object $type        Member type object.
+	 */
+	do_action( 'bp_registered_member_type', $member_type, $type );
+
+	return $type;
+}
+
+/**
+ * Retrieve a member type object by name.
+ *
+ * @since 2.2.0
+ *
+ * @param string $member_type The name of the member type.
+ * @return object A member type object.
+ */
+function bp_get_member_type_object( $member_type ) {
+	$types = bp_get_member_types( array(), 'objects' );
+
+	if ( empty( $types[ $member_type ] ) ) {
+		return null;
+	}
+
+	return $types[ $member_type ];
+}
+
+/**
+ * Get a list of all registered member type objects.
+ *
+ * @since 2.2.0
+ *
+ * @see bp_register_member_type() for accepted arguments.
+ *
+ * @param array|string $args     Optional. An array of key => value arguments to match against
+ *                               the member type objects. Default empty array.
+ * @param string       $output   Optional. The type of output to return. Accepts 'names'
+ *                               or 'objects'. Default 'names'.
+ * @param string       $operator Optional. The logical operation to perform. 'or' means only one
+ *                               element from the array needs to match; 'and' means all elements
+ *                               must match. Accepts 'or' or 'and'. Default 'and'.
+ * @return array A list of member type names or objects.
+ */
+function bp_get_member_types( $args = array(), $output = 'names', $operator = 'and' ) {
+	$types = buddypress()->members->types;
+
+	$types = wp_filter_object_list( $types, $args, $operator );
+
+	/**
+	 * Filters the array of member type objects.
+	 *
+	 * This filter is run before the $output filter has been applied, so that
+	 * filtering functions have access to the entire member type objects.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array  $types     Member type objects, keyed by name.
+	 * @param array  $args      Array of key=>value arguments for filtering.
+	 * @param string $operator  'or' to match any of $args, 'and' to require all.
+	 */
+	$types = apply_filters( 'bp_get_member_types', $types, $args, $operator );
+
+	if ( 'names' === $output ) {
+		$types = wp_list_pluck( $types, 'name' );
+	}
+
+	return $types;
+}
+
+/**
+ * Set type for a member.
+ *
+ * @since 2.2.0
+ *
+ * @param int    $user_id     ID of the user.
+ * @param string $member_type Member type.
+ * @param bool   $append      Optional. True to append this to existing types for user,
+ *                            false to replace. Default: false.
+ * @return array $retval See {@see bp_set_object_terms()}.
+ */
+function bp_set_member_type( $user_id, $member_type, $append = false ) {
+	// Pass an empty $member_type to remove a user's type.
+	if ( ! empty( $member_type ) && ! bp_get_member_type_object( $member_type ) ) {
+		return false;
+	}
+
+	$retval = bp_set_object_terms( $user_id, $member_type, 'bp_member_type', $append );
+
+	// Bust the cache if the type has been updated.
+	if ( ! is_wp_error( $retval ) ) {
+		wp_cache_delete( $user_id, 'bp_member_member_type' );
+
+		/**
+		 * Fires just after a user's member type has been changed.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param int    $user_id     ID of the user whose member type has been updated.
+		 * @param string $member_type Member type.
+		 * @param bool   $append      Whether the type is being appended to existing types.
+		 */
+		do_action( 'bp_set_member_type', $user_id, $member_type, $append );
+	}
+
+	return $retval;
+}
+
+/**
+ * Remove type for a member.
+ *
+ * @since 2.3.0
+ *
+ * @param int    $user_id     ID of the user.
+ * @param string $member_type Member Type.
+ * @return bool|WP_Error
+ */
+function bp_remove_member_type( $user_id, $member_type ) {
+	// Bail if no valid member type was passed.
+	if ( empty( $member_type ) || ! bp_get_member_type_object( $member_type ) ) {
+		return false;
+	}
+
+	$deleted = bp_remove_object_terms( $user_id, $member_type, 'bp_member_type' );
+
+	// Bust the cache if the type has been removed.
+	if ( ! is_wp_error( $deleted ) ) {
+		wp_cache_delete( $user_id, 'bp_member_member_type' );
+
+		/**
+		 * Fires just after a user's member type has been removed.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param int    $user_id     ID of the user whose member type has been updated.
+		 * @param string $member_type Member type.
+		 */
+		do_action( 'bp_remove_member_type', $user_id, $member_type );
+	}
+
+	return $deleted;
+}
+
+/**
+ * Get type for a member.
+ *
+ * @since 2.2.0
+ *
+ * @param int  $user_id ID of the user.
+ * @param bool $single  Optional. Whether to return a single type string. If multiple types are found
+ *                      for the user, the oldest one will be returned. Default: true.
+ * @return string|array|bool On success, returns a single member type (if $single is true) or an array of member
+ *                           types (if $single is false). Returns false on failure.
+ */
+function bp_get_member_type( $user_id, $single = true ) {
+	$types = wp_cache_get( $user_id, 'bp_member_member_type' );
+
+	if ( false === $types ) {
+		$types = bp_get_object_terms( $user_id, 'bp_member_type' );
+
+		if ( ! is_wp_error( $types ) ) {
+			$types = wp_list_pluck( $types, 'name' );
+			wp_cache_set( $user_id, $types, 'bp_member_member_type' );
+		}
+	}
+
+	$type = false;
+	if ( ! empty( $types ) ) {
+		if ( $single ) {
+			$type = array_pop( $types );
+		} else {
+			$type = $types;
+		}
+	}
+
+	/**
+	 * Filters a user's member type(s).
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string $type    Member type.
+	 * @param int    $user_id ID of the user.
+	 * @param bool   $single  Whether to return a single type string, or an array.
+	 */
+	return apply_filters( 'bp_get_member_type', $type, $user_id, $single );
+}
+
+/**
+ * Check whether the given user has a certain member type.
+ *
+ * @since 2.3.0
+ *
+ * @param int    $user_id     $user_id ID of the user.
+ * @param string $member_type Member Type.
+ * @return bool Whether the user has the given member type.
+ */
+function bp_has_member_type( $user_id, $member_type ) {
+	// Bail if no valid member type was passed.
+	if ( empty( $member_type ) || ! bp_get_member_type_object( $member_type ) ) {
+		return false;
+	}
+
+	// Get all user's member types.
+	$types = bp_get_member_type( $user_id, false );
+
+	if ( ! is_array( $types ) ) {
+		return false;
+	}
+
+	return in_array( $member_type, $types );
+}
+
+/**
+ * Delete a user's member type when the user when the user is deleted.
+ *
+ * @since 2.2.0
+ *
+ * @param int $user_id ID of the user.
+ * @return array $value See {@see bp_set_member_type()}.
+ */
+function bp_remove_member_type_on_user_delete( $user_id ) {
+	return bp_set_member_type( $user_id, '' );
+}
+add_action( 'wpmu_delete_user', 'bp_remove_member_type_on_user_delete' );
+add_action( 'delete_user', 'bp_remove_member_type_on_user_delete' );
+
+/**
+ * Get the "current" member type, if one is provided, in member directories.
+ *
+ * @since 2.3.0
+ *
+ * @return string
+ */
+function bp_get_current_member_type() {
+	return apply_filters( 'bp_get_current_member_type', buddypress()->current_member_type );
+}
Index: /tags/2.4.3/src/bp-members/bp-members-loader.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-loader.php	(revision 10410)
@@ -0,0 +1,320 @@
+<?php
+/**
+ * BuddyPress Member Loader.
+ *
+ * @package BuddyPress
+ * @subpackage Members
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Defines the BuddyPress Members Component.
+ */
+class BP_Members_Component extends BP_Component {
+
+	/**
+	 * Member types.
+	 *
+	 * @see bp_register_member_type()
+	 *
+	 * @since 2.2.0
+	 * @var array
+	 */
+	public $types = array();
+
+	/**
+	 * Start the members component creation process.
+	 *
+	 * @since 1.5.0
+	 */
+	public function __construct() {
+		parent::start(
+			'members',
+			__( 'Members', 'buddypress' ),
+			buddypress()->plugin_dir,
+			array(
+				'adminbar_myaccount_order' => 20,
+				'search_query_arg' => 'members_search',
+			)
+		);
+	}
+
+	/**
+	 * Include bp-members files.
+	 *
+	 * @see BP_Component::includes() for description of parameters.
+	 *
+	 * @param array $includes See {@link BP_Component::includes()}.
+	 */
+	public function includes( $includes = array() ) {
+
+		// Always include these files.
+		$includes = array(
+			'actions',
+			'classes',
+			'filters',
+			'screens',
+			'template',
+			'adminbar',
+			'functions',
+			'widgets',
+			'cache',
+		);
+
+		if ( bp_is_active( 'activity' ) ) {
+			$includes[] = 'activity';
+		}
+
+		// Include these only if in admin.
+		if ( is_admin() ) {
+			$includes[] = 'admin';
+		}
+
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Set up bp-members global settings.
+	 *
+	 * The BP_MEMBERS_SLUG constant is deprecated, and only used here for
+	 * backwards compatibility.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_globals() for description of parameters.
+	 *
+	 * @param array $args See {@link BP_Component::setup_globals()}.
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		/** Component Globals ************************************************
+		 */
+
+		// Define a slug, as a fallback for backpat.
+		if ( !defined( 'BP_MEMBERS_SLUG' ) ) {
+			define( 'BP_MEMBERS_SLUG', $this->id );
+		}
+
+		// Override any passed args.
+		$args = array(
+			'slug'            => BP_MEMBERS_SLUG,
+			'root_slug'       => isset( $bp->pages->members->slug ) ? $bp->pages->members->slug : BP_MEMBERS_SLUG,
+			'has_directory'   => true,
+			'directory_title' => _x( 'Members', 'component directory title', 'buddypress' ),
+			'search_string'   => __( 'Search Members...', 'buddypress' ),
+			'global_tables'   => array(
+				'table_name_last_activity' => bp_core_get_table_prefix() . 'bp_activity',
+				'table_name_signups'       => bp_core_get_table_prefix() . 'signups',
+			)
+		);
+
+		parent::setup_globals( $args );
+
+		/** Logged in user ***************************************************
+		 */
+
+		// The core userdata of the user who is currently logged in.
+		$bp->loggedin_user->userdata       = bp_core_get_core_userdata( bp_loggedin_user_id() );
+
+		// Fetch the full name for the logged in user.
+		$bp->loggedin_user->fullname       = isset( $bp->loggedin_user->userdata->display_name ) ? $bp->loggedin_user->userdata->display_name : '';
+
+		// Hits the DB on single WP installs so get this separately.
+		$bp->loggedin_user->is_super_admin = $bp->loggedin_user->is_site_admin = is_super_admin( bp_loggedin_user_id() );
+
+		// The domain for the user currently logged in. eg: http://example.com/members/andy.
+		$bp->loggedin_user->domain         = bp_core_get_user_domain( bp_loggedin_user_id() );
+
+		/** Displayed user ***************************************************
+		 */
+
+		// The core userdata of the user who is currently being displayed.
+		$bp->displayed_user->userdata = bp_core_get_core_userdata( bp_displayed_user_id() );
+
+		// Fetch the full name displayed user.
+		$bp->displayed_user->fullname = isset( $bp->displayed_user->userdata->display_name ) ? $bp->displayed_user->userdata->display_name : '';
+
+		// The domain for the user currently being displayed.
+		$bp->displayed_user->domain   = bp_core_get_user_domain( bp_displayed_user_id() );
+
+		/** Signup ***********************************************************
+		 */
+
+		$bp->signup = new stdClass;
+
+		/** Profiles Fallback ************************************************
+		 */
+
+		if ( ! bp_is_active( 'xprofile' ) ) {
+			$bp->profile       = new stdClass;
+			$bp->profile->slug = 'profile';
+			$bp->profile->id   = 'profile';
+		}
+	}
+
+	/**
+	 * Set up canonical stack for this component.
+	 *
+	 * @since 2.1.0
+	 */
+	public function setup_canonical_stack() {
+		$bp = buddypress();
+
+		/** Default Profile Component ****************************************
+		 */
+
+		if ( defined( 'BP_DEFAULT_COMPONENT' ) && BP_DEFAULT_COMPONENT ) {
+			$bp->default_component = BP_DEFAULT_COMPONENT;
+		} else {
+			if ( bp_is_active( 'activity' ) && isset( $bp->pages->activity ) ) {
+				$bp->default_component = bp_get_activity_slug();
+			} else {
+				$bp->default_component = ( 'xprofile' === $bp->profile->id ) ? 'profile' : $bp->profile->id;
+			}
+		}
+
+		/** Canonical Component Stack ****************************************
+		 */
+
+		if ( bp_displayed_user_id() ) {
+			$bp->canonical_stack['base_url'] = bp_displayed_user_domain();
+
+			if ( bp_current_component() ) {
+				$bp->canonical_stack['component'] = bp_current_component();
+			}
+
+			if ( bp_current_action() ) {
+				$bp->canonical_stack['action'] = bp_current_action();
+			}
+
+			if ( !empty( $bp->action_variables ) ) {
+				$bp->canonical_stack['action_variables'] = bp_action_variables();
+			}
+
+			// Looking at the single member root/home, so assume the default.
+			if ( ! bp_current_component() ) {
+				$bp->current_component = $bp->default_component;
+
+			// The canonical URL will not contain the default component.
+			} elseif ( bp_is_current_component( $bp->default_component ) && ! bp_current_action() ) {
+				unset( $bp->canonical_stack['component'] );
+			}
+
+			// If we're on a spammer's profile page, only users with the 'bp_moderate' cap
+			// can view subpages on the spammer's profile.
+			//
+			// users without the cap trying to access a spammer's subnav page will get
+			// redirected to the root of the spammer's profile page.  this occurs by
+			// by removing the component in the canonical stack.
+			if ( bp_is_user_spammer( bp_displayed_user_id() ) && ! bp_current_user_can( 'bp_moderate' ) ) {
+				unset( $bp->canonical_stack['component'] );
+			}
+		}
+	}
+
+	/**
+	 * Set up fall-back component navigation if XProfile is inactive.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @see BP_Component::setup_nav() for a description of arguments.
+	 *
+	 * @param array $main_nav Optional. See BP_Component::setup_nav() for
+	 *                        description.
+	 * @param array $sub_nav  Optional. See BP_Component::setup_nav() for
+	 *                        description.
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		// Bail if XProfile component is active.
+		if ( bp_is_active( 'xprofile' ) ) {
+			return;
+		}
+
+		// Don't set up navigation if there's no member.
+		if ( ! is_user_logged_in() && ! bp_is_user() ) {
+			return;
+		}
+
+		// Determine user to use.
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		$slug         = bp_get_profile_slug();
+		$profile_link = trailingslashit( $user_domain . $slug );
+
+		// Setup the main navigation.
+		$main_nav = array(
+			'name'                => _x( 'Profile', 'Member profile main navigation', 'buddypress' ),
+			'slug'                => $slug,
+			'position'            => 20,
+			'screen_function'     => 'bp_members_screen_display_profile',
+			'default_subnav_slug' => 'public',
+			'item_css_id'         => buddypress()->profile->id
+		);
+
+		// Setup the subnav items for the member profile.
+		$sub_nav[] = array(
+			'name'            => _x( 'View', 'Member profile view', 'buddypress' ),
+			'slug'            => 'public',
+			'parent_url'      => $profile_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'bp_members_screen_display_profile',
+			'position'        => 10
+		);
+
+		parent::setup_nav( $main_nav, $sub_nav );
+	}
+
+	/**
+	 * Set up the title for pages and <title>.
+	 */
+	public function setup_title() {
+		$bp = buddypress();
+
+		if ( bp_is_my_profile() ) {
+			$bp->bp_options_title = __( 'You', 'buddypress' );
+		} elseif ( bp_is_user() ) {
+			$bp->bp_options_title  = bp_get_displayed_user_fullname();
+			$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+				'item_id' => bp_displayed_user_id(),
+				'type'    => 'thumb',
+				'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), $bp->bp_options_title )
+			) );
+		}
+
+		parent::setup_title();
+	}
+
+	/**
+	 * Setup cache groups.
+	 *
+	 * @since 2.2.0
+	 */
+	public function setup_cache_groups() {
+
+		// Global groups.
+		wp_cache_add_global_groups( array(
+			'bp_last_activity',
+			'bp_member_type'
+		) );
+
+		parent::setup_cache_groups();
+	}
+}
+
+/**
+ * Set up the bp-members component.
+ */
+function bp_setup_members() {
+	buddypress()->members = new BP_Members_Component();
+}
+add_action( 'bp_setup_components', 'bp_setup_members', 1 );
Index: /tags/2.4.3/src/bp-members/bp-members-screens.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-screens.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-screens.php	(revision 10410)
@@ -0,0 +1,714 @@
+<?php
+/**
+ * BuddyPress Member Screens.
+ *
+ * Handlers for member screens that aren't handled elsewhere.
+ *
+ * @package BuddyPress
+ * @subpackage MembersScreens
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Handle the display of the profile page by loading the correct template file.
+ */
+function bp_members_screen_display_profile() {
+
+	/**
+	 * Fires right before the loading of the Member profile screen template file.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_members_screen_display_profile' );
+
+	/**
+	 * Filters the template to load for the Member profile page screen.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $template Path to the Member template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_members_screen_display_profile', 'members/single/home' ) );
+}
+
+/**
+ * Handle the display of the members directory index.
+ */
+function bp_members_screen_index() {
+	if ( bp_is_members_directory() ) {
+		bp_update_is_directory( true, 'members' );
+
+		/**
+		 * Fires right before the loading of the Member directory index screen template file.
+		 *
+		 * @since 1.5.0
+		 */
+		do_action( 'bp_members_screen_index' );
+
+		/**
+		 * Filters the template to load for the Member directory page screen.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Path to the member directory template to load.
+		 */
+		bp_core_load_template( apply_filters( 'bp_members_screen_index', 'members/index' ) );
+	}
+}
+add_action( 'bp_screens', 'bp_members_screen_index' );
+
+/**
+ * Handle the loading of the signup screen.
+ */
+function bp_core_screen_signup() {
+	$bp = buddypress();
+
+	if ( ! bp_is_current_component( 'register' ) || bp_current_action() )
+		return;
+
+	// Not a directory.
+	bp_update_is_directory( false, 'register' );
+
+	// If the user is logged in, redirect away from here.
+	if ( is_user_logged_in() ) {
+
+		$redirect_to = bp_is_component_front_page( 'register' )
+			? bp_get_members_directory_permalink()
+			: bp_get_root_domain();
+
+		/**
+		 * Filters the URL to redirect logged in users to when visiting registration page.
+		 *
+		 * @since 1.5.1
+		 *
+		 * @param string $redirect_to URL to redirect user to.
+		 */
+		bp_core_redirect( apply_filters( 'bp_loggedin_register_page_redirect_to', $redirect_to ) );
+
+		return;
+	}
+
+	$bp->signup->step = 'request-details';
+
+ 	if ( !bp_get_signup_allowed() ) {
+		$bp->signup->step = 'registration-disabled';
+
+	// If the signup page is submitted, validate and save.
+	} elseif ( isset( $_POST['signup_submit'] ) && bp_verify_nonce_request( 'bp_new_signup' ) ) {
+
+	    /**
+		 * Fires before the validation of a new signup.
+		 *
+		 * @since 2.0.0
+		 */
+		do_action( 'bp_signup_pre_validate' );
+
+		// Check the base account details for problems.
+		$account_details = bp_core_validate_user_signup( $_POST['signup_username'], $_POST['signup_email'] );
+
+		// If there are errors with account details, set them for display.
+		if ( !empty( $account_details['errors']->errors['user_name'] ) )
+			$bp->signup->errors['signup_username'] = $account_details['errors']->errors['user_name'][0];
+
+		if ( !empty( $account_details['errors']->errors['user_email'] ) )
+			$bp->signup->errors['signup_email'] = $account_details['errors']->errors['user_email'][0];
+
+		// Check that both password fields are filled in.
+		if ( empty( $_POST['signup_password'] ) || empty( $_POST['signup_password_confirm'] ) )
+			$bp->signup->errors['signup_password'] = __( 'Please make sure you enter your password twice', 'buddypress' );
+
+		// Check that the passwords match.
+		if ( ( !empty( $_POST['signup_password'] ) && !empty( $_POST['signup_password_confirm'] ) ) && $_POST['signup_password'] != $_POST['signup_password_confirm'] )
+			$bp->signup->errors['signup_password'] = __( 'The passwords you entered do not match.', 'buddypress' );
+
+		$bp->signup->username = $_POST['signup_username'];
+		$bp->signup->email = $_POST['signup_email'];
+
+		// Now we've checked account details, we can check profile information.
+		if ( bp_is_active( 'xprofile' ) ) {
+
+			// Make sure hidden field is passed and populated.
+			if ( isset( $_POST['signup_profile_field_ids'] ) && !empty( $_POST['signup_profile_field_ids'] ) ) {
+
+				// Let's compact any profile field info into an array.
+				$profile_field_ids = explode( ',', $_POST['signup_profile_field_ids'] );
+
+				// Loop through the posted fields formatting any datebox values then validate the field.
+				foreach ( (array) $profile_field_ids as $field_id ) {
+					if ( !isset( $_POST['field_' . $field_id] ) ) {
+						if ( !empty( $_POST['field_' . $field_id . '_day'] ) && !empty( $_POST['field_' . $field_id . '_month'] ) && !empty( $_POST['field_' . $field_id . '_year'] ) )
+							$_POST['field_' . $field_id] = date( 'Y-m-d H:i:s', strtotime( $_POST['field_' . $field_id . '_day'] . $_POST['field_' . $field_id . '_month'] . $_POST['field_' . $field_id . '_year'] ) );
+					}
+
+					// Create errors for required fields without values.
+					if ( xprofile_check_is_required_field( $field_id ) && empty( $_POST[ 'field_' . $field_id ] ) && ! bp_current_user_can( 'bp_moderate' ) )
+						$bp->signup->errors['field_' . $field_id] = __( 'This is a required field', 'buddypress' );
+				}
+
+			// This situation doesn't naturally occur so bounce to website root.
+			} else {
+				bp_core_redirect( bp_get_root_domain() );
+			}
+		}
+
+		// Finally, let's check the blog details, if the user wants a blog and blog creation is enabled.
+		if ( isset( $_POST['signup_with_blog'] ) ) {
+			$active_signup = bp_core_get_root_option( 'registration' );
+
+			if ( 'blog' == $active_signup || 'all' == $active_signup ) {
+				$blog_details = bp_core_validate_blog_signup( $_POST['signup_blog_url'], $_POST['signup_blog_title'] );
+
+				// If there are errors with blog details, set them for display.
+				if ( !empty( $blog_details['errors']->errors['blogname'] ) )
+					$bp->signup->errors['signup_blog_url'] = $blog_details['errors']->errors['blogname'][0];
+
+				if ( !empty( $blog_details['errors']->errors['blog_title'] ) )
+					$bp->signup->errors['signup_blog_title'] = $blog_details['errors']->errors['blog_title'][0];
+			}
+		}
+
+	    /**
+		 * Fires after the validation of a new signup.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_signup_validate' );
+
+		// Add any errors to the action for the field in the template for display.
+		if ( !empty( $bp->signup->errors ) ) {
+			foreach ( (array) $bp->signup->errors as $fieldname => $error_message ) {
+				/*
+				 * The addslashes() and stripslashes() used to avoid create_function()
+				 * syntax errors when the $error_message contains quotes.
+				 */
+
+				/**
+				 * Filters the error message in the loop.
+				 *
+				 * @since 1.5.0
+				 *
+				 * @param string $value Error message wrapped in html.
+				 */
+				add_action( 'bp_' . $fieldname . '_errors', create_function( '', 'echo apply_filters(\'bp_members_signup_error_message\', "<div class=\"error\">" . stripslashes( \'' . addslashes( $error_message ) . '\' ) . "</div>" );' ) );
+			}
+		} else {
+			$bp->signup->step = 'save-details';
+
+			// No errors! Let's register those deets.
+			$active_signup = bp_core_get_root_option( 'registration' );
+
+			if ( 'none' != $active_signup ) {
+
+				// Make sure the extended profiles module is enabled.
+				if ( bp_is_active( 'xprofile' ) ) {
+					// Let's compact any profile field info into usermeta.
+					$profile_field_ids = explode( ',', $_POST['signup_profile_field_ids'] );
+
+					// Loop through the posted fields formatting any datebox values then add to usermeta - @todo This logic should be shared with the same in xprofile_screen_edit_profile().
+					foreach ( (array) $profile_field_ids as $field_id ) {
+						if ( ! isset( $_POST['field_' . $field_id] ) ) {
+
+							if ( ! empty( $_POST['field_' . $field_id . '_day'] ) && ! empty( $_POST['field_' . $field_id . '_month'] ) && ! empty( $_POST['field_' . $field_id . '_year'] ) ) {
+								// Concatenate the values.
+								$date_value = $_POST['field_' . $field_id . '_day'] . ' ' . $_POST['field_' . $field_id . '_month'] . ' ' . $_POST['field_' . $field_id . '_year'];
+
+								// Turn the concatenated value into a timestamp.
+								$_POST['field_' . $field_id] = date( 'Y-m-d H:i:s', strtotime( $date_value ) );
+							}
+						}
+
+						if ( !empty( $_POST['field_' . $field_id] ) )
+							$usermeta['field_' . $field_id] = $_POST['field_' . $field_id];
+
+						if ( !empty( $_POST['field_' . $field_id . '_visibility'] ) )
+							$usermeta['field_' . $field_id . '_visibility'] = $_POST['field_' . $field_id . '_visibility'];
+					}
+
+					// Store the profile field ID's in usermeta.
+					$usermeta['profile_field_ids'] = $_POST['signup_profile_field_ids'];
+				}
+
+				// Hash and store the password.
+				$usermeta['password'] = wp_hash_password( $_POST['signup_password'] );
+
+				// If the user decided to create a blog, save those details to usermeta.
+				if ( 'blog' == $active_signup || 'all' == $active_signup )
+					$usermeta['public'] = ( isset( $_POST['signup_blog_privacy'] ) && 'public' == $_POST['signup_blog_privacy'] ) ? true : false;
+
+				/**
+				 * Filters the user meta used for signup.
+				 *
+				 * @since 1.1.0
+				 *
+				 * @param array $usermeta Array of user meta to add to signup.
+				 */
+				$usermeta = apply_filters( 'bp_signup_usermeta', $usermeta );
+
+				// Finally, sign up the user and/or blog.
+				if ( isset( $_POST['signup_with_blog'] ) && is_multisite() )
+					$wp_user_id = bp_core_signup_blog( $blog_details['domain'], $blog_details['path'], $blog_details['blog_title'], $_POST['signup_username'], $_POST['signup_email'], $usermeta );
+				else
+					$wp_user_id = bp_core_signup_user( $_POST['signup_username'], $_POST['signup_password'], $_POST['signup_email'], $usermeta );
+
+				if ( is_wp_error( $wp_user_id ) ) {
+					$bp->signup->step = 'request-details';
+					bp_core_add_message( $wp_user_id->get_error_message(), 'error' );
+				} else {
+					$bp->signup->step = 'completed-confirmation';
+				}
+			}
+
+			/**
+			 * Fires after the completion of a new signup.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'bp_complete_signup' );
+		}
+
+	}
+
+	/**
+	 * Fires right before the loading of the Member registration screen template file.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_core_screen_signup' );
+
+	/**
+	 * Filters the template to load for the Member registration page screen.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string $value Path to the Member registration template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_core_template_register', array( 'register', 'registration/register' ) ) );
+}
+add_action( 'bp_screens', 'bp_core_screen_signup' );
+
+/**
+ * Handle the loading of the Activate screen.
+ *
+ * @todo Move the actual activation process into an action in bp-members-actions.php
+ */
+function bp_core_screen_activation() {
+
+	// Bail if not viewing the activation page.
+	if ( ! bp_is_current_component( 'activate' ) ) {
+		return false;
+	}
+
+	// If the user is already logged in, redirect away from here.
+	if ( is_user_logged_in() ) {
+
+		// If activation page is also front page, set to members directory to
+		// avoid an infinite loop. Otherwise, set to root domain.
+		$redirect_to = bp_is_component_front_page( 'activate' )
+			? bp_get_members_directory_permalink()
+			: bp_get_root_domain();
+
+		// Trailing slash it, as we expect these URL's to be.
+		$redirect_to = trailingslashit( $redirect_to );
+
+		/**
+		 * Filters the URL to redirect logged in users to when visiting activation page.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $redirect_to URL to redirect user to.
+		 */
+		$redirect_to = apply_filters( 'bp_loggedin_activate_page_redirect_to', $redirect_to );
+
+		// Redirect away from the activation page.
+		bp_core_redirect( $redirect_to );
+	}
+
+	// Grab the key (the old way).
+	$key = isset( $_GET['key'] ) ? $_GET['key'] : '';
+
+	// Grab the key (the new way).
+	if ( empty( $key ) ) {
+		$key = bp_current_action();
+	}
+
+	// Get BuddyPress.
+	$bp = buddypress();
+
+	// We've got a key; let's attempt to activate the signup.
+	if ( ! empty( $key ) ) {
+
+		/**
+		 * Filters the activation signup.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param bool|int $value Value returned by activation.
+		 *                        Integer on success, boolean on failure.
+		 */
+		$user = apply_filters( 'bp_core_activate_account', bp_core_activate_signup( $key ) );
+
+		// If there were errors, add a message and redirect.
+		if ( ! empty( $user->errors ) ) {
+			bp_core_add_message( $user->get_error_message(), 'error' );
+			bp_core_redirect( trailingslashit( bp_get_root_domain() . '/' . $bp->pages->activate->slug ) );
+		}
+
+		$hashed_key = wp_hash( $key );
+
+		// Check if the signup avatar folder exists. If it does, move the folder to
+		// the BP user avatars directory.
+		if ( file_exists( bp_core_avatar_upload_path() . '/avatars/signups/' . $hashed_key ) ) {
+			@rename( bp_core_avatar_upload_path() . '/avatars/signups/' . $hashed_key, bp_core_avatar_upload_path() . '/avatars/' . $user );
+		}
+
+		bp_core_add_message( __( 'Your account is now active!', 'buddypress' ) );
+		$bp->activation_complete = true;
+	}
+
+	/**
+	 * Filters the template to load for the Member activation page screen.
+	 *
+	 * @since 1.1.1
+	 *
+	 * @param string $value Path to the Member activation template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_core_template_activate', array( 'activate', 'registration/activate' ) ) );
+}
+add_action( 'bp_screens', 'bp_core_screen_activation' );
+
+/** Theme Compatibility *******************************************************/
+
+/**
+ * The main theme compat class for BuddyPress Members.
+ *
+ * This class sets up the necessary theme compatibility actions to safely output
+ * member template parts to the_title and the_content areas of a theme.
+ *
+ * @since 1.7.0
+ */
+class BP_Members_Theme_Compat {
+
+	/**
+	 * Set up the members component theme compatibility.
+	 *
+	 * @since 1.7.0
+	 */
+	public function __construct() {
+		add_action( 'bp_setup_theme_compat', array( $this, 'is_members' ) );
+	}
+
+	/**
+	 * Are we looking at something that needs members theme compatibility?
+	 *
+	 * @since 1.7.0
+	 */
+	public function is_members() {
+
+		// Bail if not looking at the members component or a user's page.
+		if ( ! bp_is_members_component() && ! bp_is_user() ) {
+			return;
+		}
+
+		// Members Directory.
+		if ( ! bp_current_action() && ! bp_current_item() ) {
+			bp_update_is_directory( true, 'members' );
+
+			/**
+			 * Fires if looking at Members directory when needing theme compat.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'bp_members_screen_index' );
+
+			add_filter( 'bp_get_buddypress_template',                array( $this, 'directory_template_hierarchy' ) );
+			add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'directory_dummy_post' ) );
+			add_filter( 'bp_replace_the_content',                    array( $this, 'directory_content'    ) );
+
+		// User page.
+		} elseif ( bp_is_user() ) {
+
+			// If we're on a single activity permalink page, we shouldn't use the members
+			// template, so stop here!
+			if ( bp_is_active( 'activity' ) && bp_is_single_activity() ) {
+				return;
+			}
+
+			/**
+			 * Fires if looking at Members user page when needing theme compat.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'bp_members_screen_display_profile' );
+
+			add_filter( 'bp_get_buddypress_template',                array( $this, 'single_template_hierarchy' ) );
+			add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'single_dummy_post'    ) );
+			add_filter( 'bp_replace_the_content',                    array( $this, 'single_dummy_content' ) );
+
+		}
+	}
+
+	/** Directory *************************************************************/
+
+	/**
+	 * Add template hierarchy to theme compat for the members directory page.
+	 *
+	 * This is to mirror how WordPress has
+	 * {@link https://codex.wordpress.org/Template_Hierarchy template hierarchy}.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param array $templates The templates from bp_get_theme_compat_templates().
+	 * @return array $templates Array of custom templates to look for.
+	 */
+	public function directory_template_hierarchy( $templates = array() ) {
+
+		// Set up the template hierarchy.
+		$new_templates = array();
+		if ( '' !== bp_get_current_member_type() ) {
+			$new_templates[] = 'members/index-directory-type-' . sanitize_file_name( bp_get_current_member_type() ) . '.php';
+		}
+		$new_templates[] = 'members/index-directory.php';
+
+		/**
+		 * Filters the template hierarchy for theme compat and members directory page.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param array $value Array of template paths to add to hierarchy.
+		 */
+		$new_templates = apply_filters( 'bp_template_hierarchy_members_directory', $new_templates );
+
+		// Merge new templates with existing stack
+		// @see bp_get_theme_compat_templates().
+		$templates = array_merge( (array) $new_templates, $templates );
+
+		return $templates;
+	}
+
+	/**
+	 * Update the global $post with directory data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function directory_dummy_post() {
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => bp_get_directory_title( 'members' ),
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with the members index template part.
+	 *
+	 * @since 1.7.0
+	 */
+	public function directory_content() {
+		return bp_buffer_template_part( 'members/index', null, false );
+	}
+
+	/** Single ****************************************************************/
+
+	/**
+	 * Add custom template hierarchy to theme compat for member pages.
+	 *
+	 * This is to mirror how WordPress has
+	 * {@link https://codex.wordpress.org/Template_Hierarchy template hierarchy}.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $templates The templates from
+	 *                          bp_get_theme_compat_templates().
+	 * @return array $templates Array of custom templates to look for.
+	 */
+	public function single_template_hierarchy( $templates ) {
+		// Setup some variables we're going to reference in our custom templates.
+		$user_nicename = buddypress()->displayed_user->userdata->user_nicename;
+
+		/**
+		 * Filters the template hierarchy for theme compat and member pages.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param array $value Array of template paths to add to hierarchy.
+		 */
+		$new_templates = apply_filters( 'bp_template_hierarchy_members_single_item', array(
+			'members/single/index-id-'        . sanitize_file_name( bp_displayed_user_id() ) . '.php',
+			'members/single/index-nicename-'  . sanitize_file_name( $user_nicename )         . '.php',
+			'members/single/index-action-'    . sanitize_file_name( bp_current_action() )    . '.php',
+			'members/single/index-component-' . sanitize_file_name( bp_current_component() ) . '.php',
+			'members/single/index.php'
+		) );
+
+		// Merge new templates with existing stack
+		// @see bp_get_theme_compat_templates().
+		$templates = array_merge( (array) $new_templates, $templates );
+
+		return $templates;
+	}
+
+	/**
+	 * Update the global $post with the displayed user's data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function single_dummy_post() {
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => bp_get_displayed_user_fullname(),
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with the members' single home template part.
+	 *
+	 * @since 1.7.0
+	 */
+	public function single_dummy_content() {
+		return bp_buffer_template_part( 'members/single/home', null, false );
+	}
+}
+new BP_Members_Theme_Compat();
+
+/**
+ * The main theme compat class for BuddyPress Registration.
+ *
+ * This class sets up the necessary theme compatibility actions to safely output
+ * registration template parts to the_title and the_content areas of a theme.
+ *
+ * @since 1.7.0
+ */
+class BP_Registration_Theme_Compat {
+
+	/**
+	 * Setup the groups component theme compatibility.
+	 *
+	 * @since 1.7.0
+	 */
+	public function __construct() {
+		add_action( 'bp_setup_theme_compat', array( $this, 'is_registration' ) );
+	}
+
+	/**
+	 * Are we looking at either the registration or activation pages?
+	 *
+	 * @since 1.7.0
+	 */
+	public function is_registration() {
+
+		// Bail if not looking at the registration or activation page.
+		if ( ! bp_is_register_page() && ! bp_is_activation_page() ) {
+			return;
+		}
+
+		// Not a directory.
+		bp_update_is_directory( false, 'register' );
+
+		// Setup actions.
+		add_filter( 'bp_get_buddypress_template',                array( $this, 'template_hierarchy' ) );
+		add_action( 'bp_template_include_reset_dummy_post_data', array( $this, 'dummy_post'    ) );
+		add_filter( 'bp_replace_the_content',                    array( $this, 'dummy_content' ) );
+	}
+
+	/** Template ***********************************************************/
+
+	/**
+	 * Add template hierarchy to theme compat for registration/activation pages.
+	 *
+	 * This is to mirror how WordPress has
+	 * {@link https://codex.wordpress.org/Template_Hierarchy template hierarchy}.
+	 *
+	 * @since 1.8.0
+	 *
+	 * @param string $templates The templates from bp_get_theme_compat_templates().
+	 * @return array $templates Array of custom templates to look for.
+	 */
+	public function template_hierarchy( $templates ) {
+		$component = sanitize_file_name( bp_current_component() );
+
+		/**
+		 * Filters the template hierarchy for theme compat and registration/activation pages.
+		 *
+		 * This filter is a variable filter that depends on the current component
+		 * being used.
+		 *
+		 * @since 1.8.0
+		 *
+		 * @param array $value Array of template paths to add to hierarchy.
+		 */
+		$new_templates = apply_filters( "bp_template_hierarchy_{$component}", array(
+			"members/index-{$component}.php"
+		) );
+
+		// Merge new templates with existing stack
+		// @see bp_get_theme_compat_templates().
+		$templates = array_merge( (array) $new_templates, $templates );
+
+		return $templates;
+	}
+
+	/**
+	 * Update the global $post with dummy data.
+	 *
+	 * @since 1.7.0
+	 */
+	public function dummy_post() {
+		// Registration page.
+		if ( bp_is_register_page() ) {
+			$title = __( 'Create an Account', 'buddypress' );
+
+			if ( 'completed-confirmation' == bp_get_current_signup_step() ) {
+				$title = __( 'Check Your Email To Activate Your Account!', 'buddypress' );
+			}
+
+		// Activation page.
+		} else {
+			$title = __( 'Activate Your Account', 'buddypress' );
+
+			if ( bp_account_was_activated() ) {
+				$title = __( 'Account Activated', 'buddypress' );
+			}
+		}
+
+		bp_theme_compat_reset_post( array(
+			'ID'             => 0,
+			'post_title'     => $title,
+			'post_author'    => 0,
+			'post_date'      => 0,
+			'post_content'   => '',
+			'post_type'      => 'page',
+			'post_status'    => 'publish',
+			'is_page'        => true,
+			'comment_status' => 'closed'
+		) );
+	}
+
+	/**
+	 * Filter the_content with either the register or activate templates.
+	 *
+	 * @since 1.7.0
+	 */
+	public function dummy_content() {
+		if ( bp_is_register_page() ) {
+			return bp_buffer_template_part( 'members/register', null, false );
+		} else {
+			return bp_buffer_template_part( 'members/activate', null, false );
+		}
+	}
+}
+new BP_Registration_Theme_Compat();
Index: /tags/2.4.3/src/bp-members/bp-members-template.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-template.php	(revision 10410)
@@ -0,0 +1,2468 @@
+<?php
+/**
+ * BuddyPress Member Template Tags.
+ *
+ * Functions that are safe to use inside your template files and themes.
+ *
+ * @package BuddyPress
+ * @subpackage Members
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the profile component slug.
+ *
+ * @since 2.4.0
+ *
+ * @uses bp_get_profile_slug()
+ */
+function bp_profile_slug() {
+	echo bp_get_profile_slug();
+}
+	/**
+	 * Return the profile component slug.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @return string
+	 */
+	function bp_get_profile_slug() {
+
+		/**
+		 * Filters the profile component slug.
+		 *
+		 * @since 2.4.0
+		 *
+		 * @param string $slug Profile component slug.
+		 */
+		return apply_filters( 'bp_get_profile_slug', buddypress()->profile->slug );
+	}
+
+/**
+ * Output the members component slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_members_slug()
+ */
+function bp_members_slug() {
+	echo bp_get_members_slug();
+}
+	/**
+	 * Return the members component slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string
+	 */
+	function bp_get_members_slug() {
+
+		/**
+		 * Filters the Members component slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $slug Members component slug.
+		 */
+		return apply_filters( 'bp_get_members_slug', buddypress()->members->slug );
+	}
+
+/**
+ * Output the members component root slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_members_root_slug()
+ */
+function bp_members_root_slug() {
+	echo bp_get_members_root_slug();
+}
+	/**
+	 * Return the members component root slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string
+	 */
+	function bp_get_members_root_slug() {
+
+		/**
+		 * Filters the Members component root slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $slug Members component root slug.
+		 */
+		return apply_filters( 'bp_get_members_root_slug', buddypress()->members->root_slug );
+	}
+
+/**
+ * Output member directory permalink.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_members_directory_permalink()
+ */
+function bp_members_directory_permalink() {
+	echo esc_url( bp_get_members_directory_permalink() );
+}
+	/**
+	 * Return member directory permalink.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string
+	 */
+	function bp_get_members_directory_permalink() {
+
+		/**
+		 * Filters the member directory permalink.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Members directory permalink.
+		 */
+		return apply_filters( 'bp_get_members_directory_permalink', trailingslashit( bp_get_root_domain() . '/' . bp_get_members_root_slug() ) );
+	}
+
+/**
+ * Output the sign-up slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_signup_slug()
+ */
+function bp_signup_slug() {
+	echo bp_get_signup_slug();
+}
+	/**
+	 * Return the sign-up slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string
+	 */
+	function bp_get_signup_slug() {
+		$bp = buddypress();
+
+		if ( !empty( $bp->pages->register->slug ) ) {
+			$slug = $bp->pages->register->slug;
+		} elseif ( defined( 'BP_REGISTER_SLUG' ) ) {
+			$slug = BP_REGISTER_SLUG;
+		} else {
+			$slug = 'register';
+		}
+
+		/**
+		 * Filters the sign-up slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $slug Sign-up slug.
+		 */
+		return apply_filters( 'bp_get_signup_slug', $slug );
+	}
+
+/**
+ * Output the activation slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activate_slug()
+ */
+function bp_activate_slug() {
+	echo bp_get_activate_slug();
+}
+	/**
+	 * Return the activation slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string
+	 */
+	function bp_get_activate_slug() {
+		$bp = buddypress();
+
+		if ( !empty( $bp->pages->activate->slug ) ) {
+			$slug = $bp->pages->activate->slug;
+		} elseif ( defined( 'BP_ACTIVATION_SLUG' ) ) {
+			$slug = BP_ACTIVATION_SLUG;
+		} else {
+			$slug = 'activate';
+		}
+
+		/**
+		 * Filters the activation slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $slug Activation slug.
+		 */
+		return apply_filters( 'bp_get_activate_slug', $slug );
+	}
+
+/**
+ * The main member template loop class.
+ *
+ * Responsible for loading a group of members into a loop for display.
+ */
+class BP_Core_Members_Template {
+
+	/**
+	 * The loop iterator.
+	 *
+	 * @var int
+	 */
+	public $current_member = -1;
+
+	/**
+	 * The number of members returned by the paged query.
+	 *
+	 * @var int
+	 */
+	public $member_count;
+
+	/**
+	 * Array of members located by the query.
+	 *
+	 * @var array
+	 */
+	public $members;
+
+	/**
+	 * The member object currently being iterated on.
+	 *
+	 * @var object
+	 */
+	public $member;
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @var bool
+	 */
+	public $in_the_loop;
+
+	/**
+	 * The type of member being requested. Used for ordering results.
+	 *
+	 * @var string
+	 */
+	public $type;
+
+	/**
+	 * The unique string used for pagination queries.
+	 *
+	 * @var string
+	 */
+	public $pag_arg;
+
+	/**
+	 * The page number being requested.
+	 *
+	 * @var string
+	 */
+	public $pag_page;
+
+	/**
+	 * The number of items being requested per page.
+	 *
+	 * @var string
+	 */
+	public $pag_num;
+
+	/**
+	 * An HTML string containing pagination links.
+	 *
+	 * @var string
+	 */
+	public $pag_links;
+
+	/**
+	 * The total number of members matching the query parameters.
+	 *
+	 * @var int
+	 */
+	public $total_member_count;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @see BP_User_Query for an in-depth description of parameters.
+	 *
+	 * @param string       $type                Sort order.
+	 * @param int          $page_number         Page of results.
+	 * @param int          $per_page            Number of results per page.
+	 * @param int          $max                 Max number of results to return.
+	 * @param int          $user_id             Limit to friends of a user.
+	 * @param string       $search_terms        Limit to users matching search terms.
+	 * @param array        $include             Limit results by these user IDs.
+	 * @param bool         $populate_extras     Fetch optional extras.
+	 * @param array        $exclude             Exclude these IDs from results.
+	 * @param array        $meta_key            Limit to users with a meta_key.
+	 * @param array        $meta_value          Limit to users with a meta_value (with meta_key).
+	 * @param string       $page_arg            Optional. The string used as a query parameter in pagination links.
+	 *                                          Default: 'upage'.
+	 * @param array|string $member_type         Array or comma-separated string of member types to limit results to.
+	 * @param array|string $member_type__in     Array or comma-separated string of member types to limit results to.
+	 * @param array|string $member_type__not_in Array or comma-separated string of member types to exclude
+	 *                                          from results.
+	 */
+	function __construct( $type, $page_number, $per_page, $max, $user_id, $search_terms, $include, $populate_extras, $exclude, $meta_key, $meta_value, $page_arg = 'upage', $member_type = '', $member_type__in = '', $member_type__not_in = '' ) {
+
+		$this->pag_arg  = sanitize_key( $page_arg );
+		$this->pag_page = bp_sanitize_pagination_arg( $this->pag_arg, $page_number );
+		$this->pag_num  = bp_sanitize_pagination_arg( 'num',          $per_page    );
+		$this->type     = $type;
+
+		if ( !empty( $_REQUEST['letter'] ) )
+			$this->members = BP_Core_User::get_users_by_letter( $_REQUEST['letter'], $this->pag_num, $this->pag_page, $populate_extras, $exclude );
+		else
+			$this->members = bp_core_get_users( array( 'type' => $this->type, 'per_page' => $this->pag_num, 'page' => $this->pag_page, 'user_id' => $user_id, 'include' => $include, 'search_terms' => $search_terms, 'populate_extras' => $populate_extras, 'exclude' => $exclude, 'meta_key' => $meta_key, 'meta_value' => $meta_value, 'member_type' => $member_type, 'member_type__in' => $member_type__in, 'member_type__not_in' => $member_type__not_in ) );
+
+		if ( !$max || $max >= (int) $this->members['total'] )
+			$this->total_member_count = (int) $this->members['total'];
+		else
+			$this->total_member_count = (int) $max;
+
+		$this->members = $this->members['users'];
+
+		if ( $max ) {
+			if ( $max >= count( $this->members ) ) {
+				$this->member_count = count( $this->members );
+			} else {
+				$this->member_count = (int) $max;
+			}
+		} else {
+			$this->member_count = count( $this->members );
+		}
+
+		if ( (int) $this->total_member_count && (int) $this->pag_num ) {
+			$pag_args = array(
+				$this->pag_arg => '%#%',
+			);
+
+			if ( defined( 'DOING_AJAX' ) && true === (bool) DOING_AJAX ) {
+				$base = remove_query_arg( 's', wp_get_referer() );
+			} else {
+				$base = '';
+			}
+
+			/**
+			 * Defaults to an empty array to make sure paginate_links()
+			 * won't add the $page_arg to the links which would break
+			 * pagination in case JavaScript is disabled.
+			 */
+			$add_args = array();
+
+			if ( ! empty( $search_terms ) ) {
+				$query_arg = bp_core_get_component_search_query_arg( 'members' );
+				$add_args[ $query_arg ] = urlencode( $search_terms );
+			}
+
+			$this->pag_links = paginate_links( array(
+				'base'      => add_query_arg( $pag_args, $base ),
+				'format'    => '',
+				'total'     => ceil( (int) $this->total_member_count / (int) $this->pag_num ),
+				'current'   => (int) $this->pag_page,
+				'prev_text' => _x( '&larr;', 'Member pagination previous text', 'buddypress' ),
+				'next_text' => _x( '&rarr;', 'Member pagination next text', 'buddypress' ),
+				'mid_size'  => 1,
+				'add_args'  => $add_args,
+			) );
+		}
+	}
+
+	/**
+	 * Whether there are members available in the loop.
+	 *
+	 * @see bp_has_members()
+	 *
+	 * @return bool True if there are items in the loop, otherwise false.
+	 */
+	function has_members() {
+		if ( $this->member_count )
+			return true;
+
+		return false;
+	}
+
+	/**
+	 * Set up the next member and iterate index.
+	 *
+	 * @return object The next member to iterate over.
+	 */
+	function next_member() {
+		$this->current_member++;
+		$this->member = $this->members[$this->current_member];
+
+		return $this->member;
+	}
+
+	/**
+	 * Rewind the members and reset member index.
+	 */
+	function rewind_members() {
+		$this->current_member = -1;
+		if ( $this->member_count > 0 ) {
+			$this->member = $this->members[0];
+		}
+	}
+
+	/**
+	 * Whether there are members left in the loop to iterate over.
+	 *
+	 * This method is used by {@link bp_members()} as part of the while loop
+	 * that controls iteration inside the members loop, eg:
+	 *     while ( bp_members() ) { ...
+	 *
+	 * @see bp_members()
+	 *
+	 * @return bool True if there are more members to show, otherwise false.
+	 */
+	function members() {
+		if ( $this->current_member + 1 < $this->member_count ) {
+			return true;
+		} elseif ( $this->current_member + 1 == $this->member_count ) {
+
+			/**
+			 * Fires right before the rewinding of members listing.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action('member_loop_end');
+			// Do some cleaning up after the loop.
+			$this->rewind_members();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	/**
+	 * Set up the current member inside the loop.
+	 *
+	 * Used by {@link bp_the_member()} to set up the current member data
+	 * while looping, so that template tags used during that iteration make
+	 * reference to the current member.
+	 *
+	 * @see bp_the_member()
+	 */
+	function the_member() {
+
+		$this->in_the_loop = true;
+		$this->member      = $this->next_member();
+
+		// Loop has just started.
+		if ( 0 == $this->current_member ) {
+
+			/**
+			 * Fires if the current member is the first in the loop.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'member_loop_start' );
+		}
+
+	}
+}
+
+/**
+ * Rewind the members and reset member index.
+ */
+function bp_rewind_members() {
+	global $members_template;
+
+	return $members_template->rewind_members();
+}
+
+/**
+ * Initialize the members loop.
+ *
+ * Based on the $args passed, bp_has_members() populates the $members_template
+ * global, enabling the use of BuddyPress templates and template functions to
+ * display a list of members.
+ *
+ * @global object $members_template {@link BP_Members_Template}
+ *
+ * @param array|string $args {
+ *     Arguments for limiting the contents of the members loop. Most arguments
+ *     are in the same format as {@link BP_User_Query}. However, because
+ *     the format of the arguments accepted here differs in a number of ways,
+ *     and because bp_has_members() determines some default arguments in a
+ *     dynamic fashion, we list all accepted arguments here as well.
+ *
+ *     Arguments can be passed as an associative array, or as a URL query
+ *     string (eg, 'user_id=4&per_page=3').
+ *
+ *     @type int                   $type                Sort order. Accepts 'active', 'random', 'newest', 'popular',
+ *                                                      'online', 'alphabetical'. Default: 'active'.
+ *     @type int|bool              $page                Page of results to display. Default: 1.
+ *     @type int|bool              $per_page            Number of results per page. Default: 20.
+ *     @type int|bool              $max                 Maximum number of results to return. Default: false (unlimited).
+ *     @type string                $page_arg            The string used as a query parameter in pagination links.
+ *                                                      Default: 'bpage'.
+ *     @type array|int|string|bool $include             Limit results by a list of user IDs. Accepts an array, a
+ *                                                      single integer, a comma-separated list of IDs, or false (to
+ *                                                      disable this limiting). Accepts 'active', 'alphabetical',
+ *                                                      'newest', or 'random'. Default: false.
+ *     @type array|int|string|bool $exclude             Exclude users from results by ID. Accepts an array, a single
+ *                                                      integer, a comma-separated list of IDs, or false (to disable
+ *                                                      this limiting). Default: false.
+ *     @type int                   $user_id             If provided, results are limited to the friends of the specified
+ *                                                      user. When on a user's Friends page, defaults to the ID of the
+ *                                                      displayed user. Otherwise defaults to 0.
+ *     @type string|array          $member_type         Array or comma-separated list of member types to limit
+ *                                                      results to.
+ *     @type string|array          $member_type__in     Array or comma-separated list of member types to limit
+ *                                                      results to.
+ *     @type string|array          $member_type__not_in Array or comma-separated list of member types to exclude
+ *                                                      from results.
+ *     @type string                $search_terms        Limit results by a search term. Default: value of
+ *                                                      `$_REQUEST['members_search']` or `$_REQUEST['s']`, if present.
+ *                                                      Otherwise false.
+ *     @type string                $meta_key            Limit results by the presence of a usermeta key.
+ *                                                      Default: false.
+ *     @type mixed                 $meta_value          When used with meta_key, limits results by the a matching
+ *                                                      usermeta value. Default: false.
+ *     @type bool                  $populate_extras     Whether to fetch optional data, such as friend counts.
+ *                                                      Default: true.
+ * }
+ * @return bool Returns true when blogs are found, otherwise false.
+ */
+function bp_has_members( $args = '' ) {
+	global $members_template;
+
+	// Default user ID.
+	$user_id = 0;
+
+	// User filtering.
+	if ( bp_is_user_friends() && ! bp_is_user_friend_requests() ) {
+		$user_id = bp_displayed_user_id();
+	}
+
+	$member_type = bp_get_current_member_type();
+	if ( ! $member_type && ! empty( $_GET['member_type'] ) ) {
+		if ( is_array( $_GET['member_type'] ) ) {
+			$member_type = $_GET['member_type'];
+		} else {
+			// Can be a comma-separated list.
+			$member_type = explode( ',', $_GET['member_type'] );
+		}
+	}
+
+	$search_terms_default = null;
+	$search_query_arg = bp_core_get_component_search_query_arg( 'members' );
+	if ( ! empty( $_REQUEST[ $search_query_arg ] ) ) {
+		$search_terms_default = stripslashes( $_REQUEST[ $search_query_arg ] );
+	}
+
+	// Type: active ( default ) | random | newest | popular | online | alphabetical.
+	$r = bp_parse_args( $args, array(
+		'type'                => 'active',
+		'page'                => 1,
+		'per_page'            => 20,
+		'max'                 => false,
+
+		'page_arg'            => 'upage',  // See https://buddypress.trac.wordpress.org/ticket/3679.
+
+		'include'             => false,    // Pass a user_id or a list (comma-separated or array) of user_ids to only show these users.
+		'exclude'             => false,    // Pass a user_id or a list (comma-separated or array) of user_ids to exclude these users.
+
+		'user_id'             => $user_id, // Pass a user_id to only show friends of this user.
+		'member_type'         => $member_type,
+		'member_type__in'     => '',
+		'member_type__not_in' => '',
+		'search_terms'        => $search_terms_default,
+
+		'meta_key'            => false,    // Only return users with this usermeta.
+		'meta_value'          => false,    // Only return users where the usermeta value matches. Requires meta_key.
+
+		'populate_extras'     => true      // Fetch usermeta? Friend count, last active etc.
+	), 'has_members' );
+
+	// Pass a filter if ?s= is set.
+	if ( is_null( $r['search_terms'] ) ) {
+		if ( !empty( $_REQUEST['s'] ) ) {
+			$r['search_terms'] = $_REQUEST['s'];
+		} else {
+			$r['search_terms'] = false;
+		}
+	}
+
+	// Set per_page to max if max is larger than per_page.
+	if ( !empty( $r['max'] ) && ( $r['per_page'] > $r['max'] ) ) {
+		$r['per_page'] = $r['max'];
+	}
+
+	// Query for members and populate $members_template global.
+	$members_template = new BP_Core_Members_Template(
+		$r['type'],
+		$r['page'],
+		$r['per_page'],
+		$r['max'],
+		$r['user_id'],
+		$r['search_terms'],
+		$r['include'],
+		$r['populate_extras'],
+		$r['exclude'],
+		$r['meta_key'],
+		$r['meta_value'],
+		$r['page_arg'],
+		$r['member_type'],
+		$r['member_type__in'],
+		$r['member_type__not_in']
+	);
+
+	/**
+	 * Filters whether or not BuddyPress has members to iterate over.
+	 *
+	 * @since 1.2.4
+	 *
+	 * @param bool  $value            Whether or not there are members to iterate over.
+	 * @param array $members_template Populated $members_template global.
+	 */
+	return apply_filters( 'bp_has_members', $members_template->has_members(), $members_template );
+}
+
+/**
+ * Set up the current member inside the loop.
+ *
+ * @return object
+ */
+function bp_the_member() {
+	global $members_template;
+	return $members_template->the_member();
+}
+
+/**
+ * Check whether there are more members to iterate over.
+ *
+ * @return bool
+ */
+function bp_members() {
+	global $members_template;
+	return $members_template->members();
+}
+
+/**
+ * Output the members pagination count.
+ */
+function bp_members_pagination_count() {
+	echo bp_get_members_pagination_count();
+}
+	/**
+	 * Generate the members pagination count.
+	 *
+	 * @return string
+	 */
+	function bp_get_members_pagination_count() {
+		global $members_template;
+
+		if ( empty( $members_template->type ) )
+			$members_template->type = '';
+
+		$start_num = intval( ( $members_template->pag_page - 1 ) * $members_template->pag_num ) + 1;
+		$from_num  = bp_core_number_format( $start_num );
+		$to_num    = bp_core_number_format( ( $start_num + ( $members_template->pag_num - 1 ) > $members_template->total_member_count ) ? $members_template->total_member_count : $start_num + ( $members_template->pag_num - 1 ) );
+		$total     = bp_core_number_format( $members_template->total_member_count );
+
+		if ( 'active' == $members_template->type ) {
+			if ( 1 == $members_template->total_member_count ) {
+				$pag = __( 'Viewing 1 active member', 'buddypress' );
+			} else {
+				$pag = sprintf( _n( 'Viewing %1$s - %2$s of %3$s active member', 'Viewing %1$s - %2$s of %3$s active members', $members_template->total_member_count, 'buddypress' ), $from_num, $to_num, $total );
+			}
+		} elseif ( 'popular' == $members_template->type ) {
+			if ( 1 == $members_template->total_member_count ) {
+				$pag = __( 'Viewing 1 member with friends', 'buddypress' );
+			} else {
+				$pag = sprintf( _n( 'Viewing %1$s - %2$s of %3$s member with friends', 'Viewing %1$s - %2$s of %3$s members with friends', $members_template->total_member_count, 'buddypress' ), $from_num, $to_num, $total );
+			}
+		} elseif ( 'online' == $members_template->type ) {
+			if ( 1 == $members_template->total_member_count ) {
+				$pag = __( 'Viewing 1 online member', 'buddypress' );
+			} else {
+				$pag = sprintf( _n( 'Viewing %1$s - %2$s of %3$s online member', 'Viewing %1$s - %2$s of %3$s online members', $members_template->total_member_count, 'buddypress' ), $from_num, $to_num, $total );
+			}
+		} else {
+			if ( 1 == $members_template->total_member_count ) {
+				$pag = __( 'Viewing 1 member', 'buddypress' );
+			} else {
+				$pag = sprintf( _n( 'Viewing %1$s - %2$s of %3$s member', 'Viewing %1$s - %2$s of %3$s members', $members_template->total_member_count, 'buddypress' ), $from_num, $to_num, $total );
+			}
+		}
+
+		/**
+		 * Filters the members pagination count.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $pag Pagination count string.
+		 */
+		return apply_filters( 'bp_members_pagination_count', $pag );
+	}
+
+/**
+ * Output the members pagination links.
+ */
+function bp_members_pagination_links() {
+	echo bp_get_members_pagination_links();
+}
+	/**
+	 * Fetch the members pagination links.
+	 *
+	 * @return string
+	 */
+	function bp_get_members_pagination_links() {
+		global $members_template;
+
+		/**
+		 * Filters the members pagination link.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $pag_links HTML markup for pagination links.
+		 */
+		return apply_filters( 'bp_get_members_pagination_links', $members_template->pag_links );
+	}
+
+/**
+ * Output the ID of the current member in the loop.
+ *
+ * @uses bp_get_member_user_id()
+ */
+function bp_member_user_id() {
+	echo bp_get_member_user_id();
+}
+	/**
+	 * Get the ID of the current member in the loop.
+	 *
+	 * @return string Member ID.
+	 */
+	function bp_get_member_user_id() {
+		global $members_template;
+		$member_id = isset( $members_template->member->id ) ? (int) $members_template->member->id : false;
+
+		/**
+		 * Filters the ID of the current member in the loop.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $member_id ID of the member being iterated over.
+		 */
+		return apply_filters( 'bp_get_member_user_id', $member_id );
+	}
+
+/**
+ * Output the row class of the current member in the loop.
+ *
+ * @since 1.7.0
+ *
+ * @param array $classes Array of custom classes.
+ */
+function bp_member_class( $classes = array() ) {
+	echo bp_get_member_class( $classes );
+}
+	/**
+	 * Return the row class of the current member in the loop.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $classes Array of custom classes.
+	 *
+	 * @return string Row class of the member
+	 */
+	function bp_get_member_class( $classes = array() ) {
+		global $members_template;
+
+		// Add even/odd classes, but only if there's more than 1 member.
+		if ( $members_template->member_count > 1 ) {
+			$pos_in_loop = (int) $members_template->current_member;
+			$classes[]   = ( $pos_in_loop % 2 ) ? 'even' : 'odd';
+
+		// If we've only one member in the loop, don't bother with odd and even.
+		} else {
+			$classes[] = 'bp-single-member';
+		}
+
+		// Maybe add 'is-online' class.
+		if ( ! empty( $members_template->member->last_activity ) ) {
+
+			// Calculate some times.
+			$current_time  = strtotime( bp_core_current_time() );
+			$last_activity = strtotime( $members_template->member->last_activity );
+			$still_online  = strtotime( '+5 minutes', $last_activity );
+
+			// Has the user been active recently?
+			if ( $current_time <= $still_online ) {
+				$classes[] = 'is-online';
+			}
+		}
+
+		// Add current user class.
+		if ( bp_loggedin_user_id() === (int) $members_template->member->id ) {
+			$classes[] = 'is-current-user';
+		}
+
+		/**
+		 * Filters the determined classes to add to the HTML element.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param string $classes Classes to be added to the HTML element.
+		 */
+		$classes = apply_filters( 'bp_get_member_class', $classes );
+		$classes = array_merge( $classes, array() );
+		$retval  = 'class="' . join( ' ', $classes ) . '"';
+
+		return $retval;
+	}
+
+/**
+ * Output nicename of current member in the loop.
+ */
+function bp_member_user_nicename() {
+	echo bp_get_member_user_nicename();
+}
+	/**
+	 * Get the nicename of the current member in the loop.
+	 *
+	 * @return string Members nicename.
+	 */
+	function bp_get_member_user_nicename() {
+		global $members_template;
+
+		/**
+		 * Filters the nicename of the current member in the loop.
+		 *
+		 * @since 1.2.5
+		 *
+		 * @param string $user_nicename Nicename for the current member.
+		 */
+		return apply_filters( 'bp_get_member_user_nicename', $members_template->member->user_nicename );
+	}
+
+/**
+ * Output login for current member in the loop.
+ */
+function bp_member_user_login() {
+	echo bp_get_member_user_login();
+}
+	/**
+	 * Get the login of the current member in the loop.
+	 *
+	 * @return string Member's login.
+	 */
+	function bp_get_member_user_login() {
+		global $members_template;
+
+		/**
+		 * Filters the login of the current member in the loop.
+		 *
+		 * @since 1.2.5
+		 *
+		 * @param string $user_login Login for the current member.
+		 */
+		return apply_filters( 'bp_get_member_user_login', $members_template->member->user_login );
+	}
+
+/**
+ * Output the email address for the current member in the loop.
+ */
+function bp_member_user_email() {
+	echo bp_get_member_user_email();
+}
+	/**
+	 * Get the email address of the current member in the loop.
+	 *
+	 * @return string Member's email address.
+	 */
+	function bp_get_member_user_email() {
+		global $members_template;
+
+		/**
+		 * Filters the email address of the current member in the loop.
+		 *
+		 * @since 1.2.5
+		 *
+		 * @param string $user_email Email address for the current member.
+		 */
+		return apply_filters( 'bp_get_member_user_email', $members_template->member->user_email );
+	}
+
+/**
+ * Check whether the current member in the loop is the logged-in user.
+ *
+ * @return bool
+ */
+function bp_member_is_loggedin_user() {
+	global $members_template;
+
+	/**
+	 * Filters whether the current member in the loop is the logged-in user.
+	 *
+	 * @since 1.2.5
+	 *
+	 * @param bool $value Whether current member in the loop is logged in.
+	 */
+	return apply_filters( 'bp_member_is_loggedin_user', bp_loggedin_user_id() == $members_template->member->id ? true : false );
+}
+
+/**
+ * Output a member's avatar.
+ *
+ * @see bp_get_member_avatar() for description of arguments.
+ *
+ * @param array|string $args See {@link bp_get_member_avatar()}.
+ */
+function bp_member_avatar( $args = '' ) {
+
+	/**
+	 * Filters a members avatar.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value Formatted HTML <img> element, or raw avatar URL based on $html arg.
+	 */
+	echo apply_filters( 'bp_member_avatar', bp_get_member_avatar( $args ) );
+}
+	/**
+	 * Get a member's avatar.
+	 *
+	 * @see bp_core_fetch_avatar() For a description of arguments and
+	 *      return values.
+	 *
+	 * @param array|string $args  {
+	 *     Arguments are listed here with an explanation of their defaults.
+	 *     For more information about the arguments, see
+	 *     {@link bp_core_fetch_avatar()}.
+	 *     @type string   $alt     Default: 'Profile picture of [user name]'.
+	 *     @type string   $class   Default: 'avatar'.
+	 *     @type string   $type    Default: 'thumb'.
+	 *     @type int|bool $width   Default: false.
+	 *     @type int|bool $height  Default: false.
+	 *     @type bool     $id      Currently unused.
+	 *     @type bool     $no_grav Default: false.
+	 * }
+	 * @return string User avatar string.
+	 */
+	function bp_get_member_avatar( $args = '' ) {
+		global $members_template;
+
+		$fullname = !empty( $members_template->member->fullname ) ? $members_template->member->fullname : $members_template->member->display_name;
+
+		$defaults = array(
+			'type'   => 'thumb',
+			'width'  => false,
+			'height' => false,
+			'class'  => 'avatar',
+			'id'     => false,
+			'alt'    => sprintf( __( 'Profile picture of %s', 'buddypress' ), $fullname )
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		/**
+		 * Filters a members avatar.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Formatted HTML <img> element, or raw avatar URL based on $html arg.
+		 */
+		return apply_filters( 'bp_get_member_avatar', bp_core_fetch_avatar( array( 'item_id' => $members_template->member->id, 'type' => $type, 'alt' => $alt, 'css_id' => $id, 'class' => $class, 'width' => $width, 'height' => $height, 'email' => $members_template->member->user_email ) ) );
+	}
+
+/**
+ * Output the permalink for the current member in the loop.
+ */
+function bp_member_permalink() {
+	echo bp_get_member_permalink();
+}
+	/**
+	 * Get the permalink for the current member in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_member_permalink() {
+		global $members_template;
+
+		/**
+		 * Filters the permalink for the current member in the loop.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Permalink for the current member in the loop.
+		 */
+		return apply_filters( 'bp_get_member_permalink', bp_core_get_user_domain( $members_template->member->id, $members_template->member->user_nicename, $members_template->member->user_login ) );
+	}
+
+	/**
+	 * Alias of {@link bp_member_permalink()}.
+	 */
+	function bp_member_link() { echo bp_get_member_permalink(); }
+
+	/**
+	 * Alias of {@link bp_get_member_permalink()}.
+	 */
+	function bp_get_member_link() { return bp_get_member_permalink(); }
+
+/**
+ * Output display name of current member in the loop.
+ */
+function bp_member_name() {
+
+	/**
+	 * Filters the display name of current member in the loop.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param string $value Display name for current member.
+	 */
+	echo apply_filters( 'bp_member_name', bp_get_member_name() );
+}
+	/**
+	 * Get the display name of the current member in the loop.
+	 *
+	 * Full name is, by default, pulled from xprofile's Full Name field.
+	 * When this field is empty, we try to get an alternative name from the
+	 * WP users table, in the following order of preference: display_name,
+	 * user_nicename, user_login.
+	 *
+	 * @return string The user's fullname for display.
+	 */
+	function bp_get_member_name() {
+		global $members_template;
+
+		// Generally, this only fires when xprofile is disabled.
+		if ( empty( $members_template->member->fullname ) ) {
+			// Our order of preference for alternative fullnames.
+			$name_stack = array(
+				'display_name',
+				'user_nicename',
+				'user_login'
+			);
+
+			foreach ( $name_stack as $source ) {
+				if ( !empty( $members_template->member->{$source} ) ) {
+					// When a value is found, set it as fullname and be done with it.
+					$members_template->member->fullname = $members_template->member->{$source};
+					break;
+				}
+			}
+		}
+
+		/**
+		 * Filters the display name of current member in the loop.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $fullname Display name for current member.
+		 */
+		return apply_filters( 'bp_get_member_name', $members_template->member->fullname );
+	}
+	add_filter( 'bp_get_member_name', 'wp_filter_kses' );
+	add_filter( 'bp_get_member_name', 'stripslashes'   );
+	add_filter( 'bp_get_member_name', 'strip_tags'     );
+	add_filter( 'bp_get_member_name', 'esc_html'       );
+
+/**
+ * Output the current member's last active time.
+ *
+ * @param array $args See {@link bp_get_member_last_active()}.
+ */
+function bp_member_last_active( $args = array() ) {
+	echo bp_get_member_last_active( $args );
+}
+	/**
+	 * Return the current member's last active time.
+	 *
+	 * @param array $args {
+	 *     Array of optional arguments.
+	 *     @type mixed $active_format If true, formatted "active 5 minutes
+	 *                                ago". If false, formatted "5 minutes ago".
+	 *                                If string, should be sprintf'able like
+	 *                                'last seen %s ago'.
+	 * }
+	 * @return string
+	 */
+	function bp_get_member_last_active( $args = array() ) {
+		global $members_template;
+
+		// Parse the activity format.
+		$r = bp_parse_args( $args, array(
+			'active_format' => true
+		) );
+
+		// Backwards compatibility for anyone forcing a 'true' active_format.
+		if ( true === $r['active_format'] ) {
+			$r['active_format'] = __( 'active %s', 'buddypress' );
+		}
+
+		// Member has logged in at least one time.
+		if ( isset( $members_template->member->last_activity ) ) {
+
+			// Backwards compatibility for pre 1.5 'ago' strings.
+			$last_activity = ! empty( $r['active_format'] )
+				? bp_core_get_last_activity( $members_template->member->last_activity, $r['active_format'] )
+				: bp_core_time_since( $members_template->member->last_activity );
+
+		// Member has never logged in or been active.
+		} else {
+			$last_activity = __( 'Never active', 'buddypress' );
+		}
+
+		/**
+		 * Filters the current members last active time.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $last_activity Formatted time since last activity.
+		 * @param array  $r             Array of parsed arguments for query.
+		 */
+		return apply_filters( 'bp_member_last_active', $last_activity, $r );
+	}
+
+/**
+ * Output the latest update of the current member in the loop.
+ *
+ * @param array|string $args Array of arguments for latest update.
+ */
+function bp_member_latest_update( $args = '' ) {
+	echo bp_get_member_latest_update( $args );
+}
+	/**
+	 * Get the latest update from the current member in the loop.
+	 *
+	 * @param array|string $args {
+	 *     Array of optional arguments.
+	 *     @type int  $length    Truncation length. Default: 225.
+	 *     @type bool $view_link Whether to provide a 'View' link for
+	 *                           truncated entries. Default: false.
+	 * }
+	 * @return string
+	 */
+	function bp_get_member_latest_update( $args = '' ) {
+		global $members_template;
+
+		$defaults = array(
+			'length'    => 225,
+			'view_link' => true
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r );
+
+		if ( !bp_is_active( 'activity' ) || empty( $members_template->member->latest_update ) || !$update = maybe_unserialize( $members_template->member->latest_update ) )
+			return false;
+
+		/**
+		 * Filters the excerpt of the latest update for current member in the loop.
+		 *
+		 * @since 1.2.5
+		 *
+		 * @param string $value Excerpt of the latest update for current member in the loop.
+		 */
+		$update_content = apply_filters( 'bp_get_activity_latest_update_excerpt', trim( strip_tags( bp_create_excerpt( $update['content'], $length ) ) ) );
+
+		$update_content = sprintf( _x( '- &quot;%s&quot;', 'member latest update in member directory', 'buddypress' ), $update_content );
+
+		// If $view_link is true and the text returned by bp_create_excerpt() is different from the original text (ie it's
+		// been truncated), add the "View" link.
+		if ( $view_link && ( $update_content != $update['content'] ) ) {
+			$view = __( 'View', 'buddypress' );
+
+			$update_content .= '<span class="activity-read-more"><a href="' . bp_activity_get_permalink( $update['id'] ) . '" rel="nofollow">' . $view . '</a></span>';
+		}
+
+		/**
+		 * Filters the latest update from the current member in the loop.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $update_content Formatted latest update for current member.
+		 */
+		return apply_filters( 'bp_get_member_latest_update', $update_content );
+	}
+
+/**
+ * Output a piece of user profile data.
+ *
+ * @see bp_get_member_profile_data() for a description of params.
+ *
+ * @param array|string $args See {@link bp_get_member_profile_data()}.
+ */
+function bp_member_profile_data( $args = '' ) {
+	echo bp_get_member_profile_data( $args );
+}
+	/**
+	 * Get a piece of user profile data.
+	 *
+	 * When used in a bp_has_members() loop, this function will attempt
+	 * to fetch profile data cached in the template global. It is also safe
+	 * to use outside of the loop.
+	 *
+	 * @param array|string $args {
+	 *     Array of config parameters.
+	 *     @type string $field   Name of the profile field.
+	 *     @type int    $user_id ID of the user whose data is being fetched.
+	 *                           Defaults to the current member in the loop, or if not
+	 *                           present, to the currently displayed user.
+	 * }
+	 * @return string|bool Profile data if found, otherwise false.
+	 */
+	function bp_get_member_profile_data( $args = '' ) {
+		global $members_template;
+
+		if ( ! bp_is_active( 'xprofile' ) ) {
+			return false;
+		}
+
+		// Declare local variables.
+		$data = false;
+
+		// Guess at default $user_id.
+		$default_user_id = 0;
+		if ( ! empty( $members_template->member->id ) ) {
+			$default_user_id = $members_template->member->id;
+		} elseif ( bp_displayed_user_id() ) {
+			$default_user_id = bp_displayed_user_id();
+		}
+
+		$defaults = array(
+			'field'   => false,
+			'user_id' => $default_user_id,
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+
+		// If we're in a members loop, get the data from the global.
+		if ( ! empty( $members_template->member->profile_data ) ) {
+			$profile_data = $members_template->member->profile_data;
+		}
+
+		// Otherwise query for the data.
+		if ( empty( $profile_data ) && method_exists( 'BP_XProfile_ProfileData', 'get_all_for_user' ) ) {
+			$profile_data = BP_XProfile_ProfileData::get_all_for_user( $r['user_id'] );
+		}
+
+		// If we're in the members loop, but the profile data has not
+		// been loaded into the global, cache it there for later use.
+		if ( ! empty( $members_template->member ) && empty( $members_template->member->profile_data ) ) {
+			$members_template->member->profile_data = $profile_data;
+		}
+
+		// Get the data for the specific field requested.
+		if ( ! empty( $profile_data ) && ! empty( $profile_data[ $r['field'] ]['field_type'] ) && ! empty( $profile_data[ $r['field'] ]['field_data'] ) ) {
+			$data = xprofile_format_profile_field( $profile_data[ $r['field'] ]['field_type'], $profile_data[ $r['field'] ]['field_data'] );
+		}
+
+		/**
+		 * Filters resulting piece of member profile data.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string|bool $data Profile data if found, otherwise false.
+		 */
+		return apply_filters( 'bp_get_member_profile_data', $data );
+	}
+
+/**
+ * Output the 'registered [x days ago]' string for the current member.
+ */
+function bp_member_registered() {
+	echo bp_get_member_registered();
+}
+	/**
+	 * Get the 'registered [x days ago]' string for the current member.
+	 *
+	 * @return string
+	 */
+	function bp_get_member_registered() {
+		global $members_template;
+
+		$registered = esc_attr( bp_core_get_last_activity( $members_template->member->user_registered, _x( 'registered %s', 'Records the timestamp that the user registered into the activity stream', 'buddypress' ) ) );
+
+		/**
+		 * Filters the 'registered [x days ago]' string for the current member.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $registered The 'registered [x days ago]' string.
+		 */
+		return apply_filters( 'bp_member_registered', $registered );
+	}
+
+/**
+ * Output a random piece of profile data for the current member in the loop.
+ */
+function bp_member_random_profile_data() {
+	global $members_template;
+
+	if ( bp_is_active( 'xprofile' ) ) { ?>
+		<?php $random_data = xprofile_get_random_profile_data( $members_template->member->id, true ); ?>
+			<strong><?php echo wp_filter_kses( $random_data[0]->name ) ?></strong>
+			<?php echo wp_filter_kses( $random_data[0]->value ) ?>
+	<?php }
+}
+
+/**
+ * Output hidden input for preserving member search params on form submit.
+ */
+function bp_member_hidden_fields() {
+	$query_arg = bp_core_get_component_search_query_arg( 'members' );
+
+	if ( isset( $_REQUEST[ $query_arg ] ) ) {
+		echo '<input type="hidden" id="search_terms" value="' . esc_attr( $_REQUEST[ $query_arg ] ) . '" name="search_terms" />';
+	}
+
+	if ( isset( $_REQUEST['letter'] ) ) {
+		echo '<input type="hidden" id="selected_letter" value="' . esc_attr( $_REQUEST['letter'] ) . '" name="selected_letter" />';
+	}
+
+	if ( isset( $_REQUEST['members_search'] ) ) {
+		echo '<input type="hidden" id="search_terms" value="' . esc_attr( $_REQUEST['members_search'] ) . '" name="search_terms" />';
+	}
+}
+
+/**
+ * Output the Members directory search form.
+ */
+function bp_directory_members_search_form() {
+
+	$query_arg = bp_core_get_component_search_query_arg( 'members' );
+
+	if ( ! empty( $_REQUEST[ $query_arg ] ) ) {
+		$search_value = stripslashes( $_REQUEST[ $query_arg ] );
+	} else {
+		$search_value = bp_get_search_default_text( 'members' );
+	}
+
+	$search_form_html = '<form action="" method="get" id="search-members-form">
+		<label for="members_search"><input type="text" name="' . esc_attr( $query_arg ) . '" id="members_search" placeholder="'. esc_attr( $search_value ) .'" /></label>
+		<input type="submit" id="members_search_submit" name="members_search_submit" value="' . __( 'Search', 'buddypress' ) . '" />
+	</form>';
+
+	/**
+	 * Filters the Members component search form.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $search_form_html HTML markup for the member search form.
+	 */
+	echo apply_filters( 'bp_directory_members_search_form', $search_form_html );
+}
+
+/**
+ * Output the total member count.
+ */
+function bp_total_site_member_count() {
+	echo bp_get_total_site_member_count();
+}
+	/**
+	 * Get the total site member count.
+	 *
+	 * @return int
+	 */
+	function bp_get_total_site_member_count() {
+
+		/**
+		 * Filters the total site member count.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param int $value Number-formatted total site member count.
+		 */
+		return apply_filters( 'bp_get_total_site_member_count', bp_core_number_format( bp_core_get_total_member_count() ) );
+	}
+
+/** Navigation and other misc template tags ***********************************/
+
+/**
+ * Render the navigation markup for the logged-in user.
+ *
+ * Each component adds to this navigation array within its own
+ * [component_name]setup_nav() function.
+ *
+ * This navigation array is the top level navigation, so it contains items such as:
+ *      [Blog, Profile, Messages, Groups, Friends] ...
+ *
+ * The function will also analyze the current component the user is in, to
+ * determine whether or not to highlight a particular nav item.
+ *
+ * @todo Move to a back-compat file?
+ * @deprecated Does not seem to be called anywhere in BP core.
+ */
+function bp_get_loggedin_user_nav() {
+	$bp = buddypress();
+
+	// Loop through each navigation item.
+	foreach( (array) $bp->bp_nav as $nav_item ) {
+
+		$selected = '';
+
+		// If the current component matches the nav item id, then add a highlight CSS class.
+		if ( !bp_is_directory() && !empty( $bp->active_components[bp_current_component()] ) && $bp->active_components[bp_current_component()] == $nav_item['css_id'] ) {
+			$selected = ' class="current selected"';
+		}
+
+		// If we are viewing another person (current_userid does not equal
+		// loggedin_user->id then check to see if the two users are friends.
+		// if they are, add a highlight CSS class to the friends nav item
+		// if it exists.
+		if ( !bp_is_my_profile() && bp_displayed_user_id() ) {
+			$selected = '';
+
+			if ( bp_is_active( 'friends' ) ) {
+				if ( $nav_item['css_id'] == $bp->friends->id ) {
+					if ( friends_check_friendship( bp_loggedin_user_id(), bp_displayed_user_id() ) ) {
+						$selected = ' class="current selected"';
+					}
+				}
+			}
+		}
+
+		// Echo out the final list item.
+		echo apply_filters_ref_array( 'bp_get_loggedin_user_nav_' . $nav_item['css_id'], array( '<li id="li-nav-' . $nav_item['css_id'] . '" ' . $selected . '><a id="my-' . $nav_item['css_id'] . '" href="' . $nav_item['link'] . '">' . $nav_item['name'] . '</a></li>', &$nav_item ) );
+	}
+
+	// Always add a log out list item to the end of the navigation.
+	$logout_link = '<li><a id="wp-logout" href="' .  wp_logout_url( bp_get_root_domain() ) . '">' . __( 'Log Out', 'buddypress' ) . '</a></li>';
+
+	echo apply_filters( 'bp_logout_nav_link', $logout_link );
+}
+
+/**
+ * Render the navigation markup for the displayed user.
+ */
+function bp_get_displayed_user_nav() {
+	$bp = buddypress();
+
+	foreach ( (array) $bp->bp_nav as $user_nav_item ) {
+		if ( empty( $user_nav_item['show_for_displayed_user'] ) && !bp_is_my_profile() )
+			continue;
+
+		$selected = '';
+		if ( bp_is_current_component( $user_nav_item['slug'] ) ) {
+			$selected = ' class="current selected"';
+		}
+
+		if ( bp_loggedin_user_domain() ) {
+			$link = str_replace( bp_loggedin_user_domain(), bp_displayed_user_domain(), $user_nav_item['link'] );
+		} else {
+			$link = trailingslashit( bp_displayed_user_domain() . $user_nav_item['link'] );
+		}
+
+		/**
+		 * Filters the navigation markup for the displayed user.
+		 *
+		 * This is a dynamic filter that is dependent on the navigation tab component being rendered.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value         Markup for the tab list item including link.
+		 * @param array  $user_nav_item Array holding parts used to construct tab list item.
+		 *                              Passed by reference.
+		 */
+		echo apply_filters_ref_array( 'bp_get_displayed_user_nav_' . $user_nav_item['css_id'], array( '<li id="' . $user_nav_item['css_id'] . '-personal-li" ' . $selected . '><a id="user-' . $user_nav_item['css_id'] . '" href="' . $link . '">' . $user_nav_item['name'] . '</a></li>', &$user_nav_item ) );
+	}
+}
+
+/** Cover image ***************************************************************/
+
+/**
+ * Should we use the cover image header
+ *
+ * @since 2.4.0
+ *
+ * @return bool True if the displayed user has a cover image,
+ *              False otherwise
+ */
+function bp_displayed_user_use_cover_image_header() {
+	return (bool) bp_is_active( 'xprofile', 'cover_image' ) && ! bp_disable_cover_image_uploads() && bp_attachments_is_wp_version_supported();
+}
+
+/** Avatars *******************************************************************/
+
+/**
+ * Output the logged-in user's avatar.
+ *
+ * @see bp_get_loggedin_user_avatar() for a description of params.
+ *
+ * @param array|string $args Array of arguments for logged in user avatar.
+ */
+function bp_loggedin_user_avatar( $args = '' ) {
+	echo bp_get_loggedin_user_avatar( $args );
+}
+	/**
+	 * Get the logged-in user's avatar.
+	 *
+	 * @see bp_core_fetch_avatar() For a description of arguments and
+	 *      return values.
+	 *
+	 * @param array|string $args  {
+	 *     Arguments are listed here with an explanation of their defaults.
+	 *     For more information about the arguments, see
+	 *     {@link bp_core_fetch_avatar()}.
+	 *     @type string   $alt    Default: 'Profile picture of [user name]'.
+	 *     @type bool     $html   Default: true.
+	 *     @type string   $type   Default: 'thumb'.
+	 *     @type int|bool $width  Default: false.
+	 *     @type int|bool $height Default: false.
+	 * }
+	 * @return string User avatar string.
+	 */
+	function bp_get_loggedin_user_avatar( $args = '' ) {
+
+		$r = wp_parse_args( $args, array(
+			'item_id' => bp_loggedin_user_id(),
+			'type'    => 'thumb',
+			'width'   => false,
+			'height'  => false,
+			'html'    => true,
+			'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_loggedin_user_fullname() )
+		) );
+
+		/**
+		 * Filters the logged in user's avatar.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value User avatar string.
+		 */
+		return apply_filters( 'bp_get_loggedin_user_avatar', bp_core_fetch_avatar( $r ), $r, $args );
+	}
+
+/**
+ * Output the displayed user's avatar.
+ *
+ * @see bp_get_displayed_user_avatar() for a description of params.
+ *
+ * @param array|string $args Array of arguments for displayed user avatar.
+ */
+function bp_displayed_user_avatar( $args = '' ) {
+	echo bp_get_displayed_user_avatar( $args );
+}
+	/**
+	 * Get the displayed user's avatar.
+	 *
+	 * @see bp_core_fetch_avatar() For a description of arguments and
+	 *      return values.
+	 *
+	 * @param array|string $args  {
+	 *     Arguments are listed here with an explanation of their defaults.
+	 *     For more information about the arguments, see
+	 *     {@link bp_core_fetch_avatar()}.
+	 *     @type string   $alt    Default: 'Profile picture of [user name]'.
+	 *     @type bool     $html   Default: true.
+	 *     @type string   $type   Default: 'thumb'.
+	 *     @type int|bool $width  Default: false.
+	 *     @type int|bool $height Default: false.
+	 * }
+	 * @return string User avatar string.
+	 */
+	function bp_get_displayed_user_avatar( $args = '' ) {
+
+		$r = wp_parse_args( $args, array(
+			'item_id' => bp_displayed_user_id(),
+			'type'    => 'thumb',
+			'width'   => false,
+			'height'  => false,
+			'html'    => true,
+			'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_displayed_user_fullname() )
+		) );
+
+		/**
+		 * Filters the displayed user's avatar.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value User avatar string.
+		 */
+		return apply_filters( 'bp_get_displayed_user_avatar', bp_core_fetch_avatar( $r ), $r, $args );
+	}
+
+/**
+ * Output the email address of the displayed user.
+ */
+function bp_displayed_user_email() {
+	echo bp_get_displayed_user_email();
+}
+	/**
+	 * Get the email address of the displayed user.
+	 *
+	 * @return string
+	 */
+	function bp_get_displayed_user_email() {
+		$bp = buddypress();
+
+		// If displayed user exists, return email address.
+		if ( isset( $bp->displayed_user->userdata->user_email ) )
+			$retval = $bp->displayed_user->userdata->user_email;
+		else
+			$retval = '';
+
+		/**
+		 * Filters the email address of the displayed user.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $retval Email address for displayed user.
+		 */
+		return apply_filters( 'bp_get_displayed_user_email', esc_attr( $retval ) );
+	}
+
+/**
+ * Output the "active [x days ago]" string for a user.
+ *
+ * @see bp_get_last_activity() for a description of parameters.
+ *
+ * @param int $user_id See {@link bp_get_last_activity()}.
+ */
+function bp_last_activity( $user_id = 0 ) {
+
+	/**
+	 * Filters the 'active [x days ago]' string for a user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Formatted 'active [x days ago]' string.
+	 */
+	echo apply_filters( 'bp_last_activity', bp_get_last_activity( $user_id ) );
+}
+	/**
+	 * Get the "active [x days ago]" string for a user.
+	 *
+	 * @param int $user_id ID of the user. Default: displayed user ID.
+	 * @return string
+	 */
+	function bp_get_last_activity( $user_id = 0 ) {
+
+		if ( empty( $user_id ) )
+			$user_id = bp_displayed_user_id();
+
+		$last_activity = bp_core_get_last_activity( bp_get_user_last_activity( $user_id ), __('active %s', 'buddypress') );
+
+		/**
+		 * Filters the 'active [x days ago]' string for a user.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $value Formatted 'active [x days ago]' string.
+		 */
+		return apply_filters( 'bp_get_last_activity', $last_activity );
+	}
+
+/**
+ * Output the calculated first name of the displayed or logged-in user.
+ */
+function bp_user_firstname() {
+	echo bp_get_user_firstname();
+}
+	/**
+	 * Output the first name of a user.
+	 *
+	 * Simply takes all the characters before the first space in a name.
+	 *
+	 * @param string|bool $name Full name to use when generating first name.
+	 *                          Defaults to displayed user's first name, or to
+	 *                          logged-in user's first name if it's unavailable.
+	 * @return string
+	 */
+	function bp_get_user_firstname( $name = false ) {
+
+		// Try to get displayed user.
+		if ( empty( $name ) )
+			$name = bp_get_displayed_user_fullname();
+
+		// Fall back on logged in user.
+		if ( empty( $name ) )
+			$name = bp_get_loggedin_user_fullname();
+
+		$fullname = (array) explode( ' ', $name );
+
+		/**
+		 * Filters the first name of a user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value    First name of user.
+		 * @param string $fullname Full name of user.
+		 */
+		return apply_filters( 'bp_get_user_firstname', $fullname[0], $fullname );
+	}
+
+/**
+ * Output the link for the logged-in user's profile.
+ */
+function bp_loggedin_user_link() {
+	echo bp_get_loggedin_user_link();
+}
+	/**
+	 * Get the link for the logged-in user's profile.
+	 *
+	 * @return string
+	 */
+	function bp_get_loggedin_user_link() {
+
+		/**
+		 * Filters the link for the logged-in user's profile.
+		 *
+		 * @since 1.2.4
+		 *
+		 * @param string $value Link for the logged-in user's profile.
+		 */
+		return apply_filters( 'bp_get_loggedin_user_link', bp_loggedin_user_domain() );
+	}
+
+/**
+ * Output the link for the displayed user's profile.
+ */
+function bp_displayed_user_link() {
+	echo bp_get_displayed_user_link();
+}
+	/**
+	 * Get the link for the displayed user's profile.
+	 *
+	 * @return string
+	 */
+	function bp_get_displayed_user_link() {
+
+		/**
+		 * Filters the link for the displayed user's profile.
+		 *
+		 * @since 1.2.4
+		 *
+		 * @param string $value Link for the displayed user's profile.
+		 */
+		return apply_filters( 'bp_get_displayed_user_link', bp_displayed_user_domain() );
+	}
+
+	/**
+	 * Alias of {@link bp_displayed_user_domain()}.
+	 *
+	 * @deprecated
+	 */
+	function bp_user_link() { bp_displayed_user_domain(); }
+
+/**
+ * Alias of {@link bp_displayed_user_id()}.
+ */
+function bp_current_user_id() { return bp_displayed_user_id(); }
+
+/**
+ * Generate the link for the displayed user's profile.
+ *
+ * @return string
+ */
+function bp_displayed_user_domain() {
+	$bp = buddypress();
+
+	/**
+	 * Filters the generated link for the displayed user's profile.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Generated link for the displayed user's profile.
+	 */
+	return apply_filters( 'bp_displayed_user_domain', isset( $bp->displayed_user->domain ) ? $bp->displayed_user->domain : '' );
+}
+
+/**
+ * Generate the link for the logged-in user's profile.
+ *
+ * @return string
+ */
+function bp_loggedin_user_domain() {
+	$bp = buddypress();
+
+	/**
+	 * Filters the generated link for the logged-in user's profile.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $value Generated link for the logged-in user's profile.
+	 */
+	return apply_filters( 'bp_loggedin_user_domain', isset( $bp->loggedin_user->domain ) ? $bp->loggedin_user->domain : '' );
+}
+
+/**
+ * Output the displayed user's display name.
+ */
+function bp_displayed_user_fullname() {
+	echo bp_get_displayed_user_fullname();
+}
+	/**
+	 * Get the displayed user's display name.
+	 *
+	 * @return string
+	 */
+	function bp_get_displayed_user_fullname() {
+		$bp = buddypress();
+
+		/**
+		 * Filters the displayed user's display name.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $value Displayed user's display name.
+		 */
+		return apply_filters( 'bp_displayed_user_fullname', isset( $bp->displayed_user->fullname ) ? $bp->displayed_user->fullname : '' );
+	}
+
+	/**
+	 * Alias of {@link bp_get_displayed_user_fullname()}.
+	 */
+	function bp_user_fullname() { echo bp_get_displayed_user_fullname(); }
+
+
+/**
+ * Output the logged-in user's display name.
+ */
+function bp_loggedin_user_fullname() {
+	echo bp_get_loggedin_user_fullname();
+}
+	/**
+	 * Get the logged-in user's display name.
+	 *
+	 * @return string
+	 */
+	function bp_get_loggedin_user_fullname() {
+		$bp = buddypress();
+
+		/**
+		 * Filters the logged-in user's display name.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Logged-in user's display name.
+		 */
+		return apply_filters( 'bp_get_loggedin_user_fullname', isset( $bp->loggedin_user->fullname ) ? $bp->loggedin_user->fullname : '' );
+	}
+
+/**
+ * Output the username of the displayed user.
+ */
+function bp_displayed_user_username() {
+	echo bp_get_displayed_user_username();
+}
+	/**
+	 * Get the username of the displayed user.
+	 *
+	 * @return string
+	 */
+	function bp_get_displayed_user_username() {
+		$bp = buddypress();
+
+		if ( bp_displayed_user_id() ) {
+			$username = bp_core_get_username( bp_displayed_user_id(), $bp->displayed_user->userdata->user_nicename, $bp->displayed_user->userdata->user_login );
+		} else {
+			$username = '';
+		}
+
+		/**
+		 * Filters the username of the displayed user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $username Username of the displayed user.
+		 */
+		return apply_filters( 'bp_get_displayed_user_username', $username );
+	}
+
+/**
+ * Output the username of the logged-in user.
+ */
+function bp_loggedin_user_username() {
+	echo bp_get_loggedin_user_username();
+}
+	/**
+	 * Get the username of the logged-in user.
+	 *
+	 * @return string
+	 */
+	function bp_get_loggedin_user_username() {
+		$bp = buddypress();
+
+		if ( bp_loggedin_user_id() ) {
+			$username = bp_core_get_username( bp_loggedin_user_id(), $bp->loggedin_user->userdata->user_nicename, $bp->loggedin_user->userdata->user_login );
+		} else {
+			$username = '';
+		}
+
+		/**
+		 * Filters the username of the logged-in user.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $username Username of the logged-in user.
+		 */
+		return apply_filters( 'bp_get_loggedin_user_username', $username );
+	}
+/**
+ * Echo the current member type message.
+ *
+ * @since 2.3.0
+ */
+function bp_current_member_type_message() {
+	echo bp_get_current_member_type_message();
+}
+	/**
+	 * Generate the current member type message.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @return string
+	 */
+	function bp_get_current_member_type_message() {
+		$type_object = bp_get_member_type_object( bp_get_current_member_type() );
+
+		$message = sprintf( __( 'Viewing members of the type: %s', 'buddypress' ), '<strong>' . $type_object->labels['singular_name'] . '</strong>' );
+
+		return apply_filters( 'bp_get_current_member_type_message', $message );
+	}
+
+/** Signup Form ***************************************************************/
+
+/**
+ * Do we have a working custom sign up page?
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_signup_slug() To make sure there is a slug assigned to the page.
+ * @uses bp_locate_template() To make sure a template exists to provide output.
+ *
+ * @return bool True if page and template exist, false if not.
+ */
+function bp_has_custom_signup_page() {
+	static $has_page = false;
+
+	if ( empty( $has_page ) )
+		$has_page = bp_get_signup_slug() && bp_locate_template( array( 'registration/register.php', 'members/register.php', 'register.php' ), false );
+
+	return (bool) $has_page;
+}
+
+/**
+ * Output the URL to the signup page.
+ */
+function bp_signup_page() {
+	echo esc_url( bp_get_signup_page() );
+}
+	/**
+	 * Get the URL to the signup page.
+	 *
+	 * @return string
+	 */
+	function bp_get_signup_page() {
+		if ( bp_has_custom_signup_page() ) {
+			$page = trailingslashit( bp_get_root_domain() . '/' . bp_get_signup_slug() );
+		} else {
+			$page = bp_get_root_domain() . '/wp-signup.php';
+		}
+
+		/**
+		 * Filters the URL to the signup page.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $page URL to the signup page.
+		 */
+		return apply_filters( 'bp_get_signup_page', $page );
+	}
+
+/**
+ * Do we have a working custom activation page?
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_activate_slug() To make sure there is a slug assigned to the page.
+ * @uses bp_locate_template() To make sure a template exists to provide output.
+ * @return boolean True if page and template exist, false if not.
+ */
+function bp_has_custom_activation_page() {
+	static $has_page = false;
+
+	if ( empty( $has_page ) )
+		$has_page = bp_get_activate_slug() && bp_locate_template( array( 'registration/activate.php', 'members/activate.php', 'activate.php' ), false );
+
+	return (bool) $has_page;
+}
+
+/**
+ * Output the URL of the activation page.
+ */
+function bp_activation_page() {
+	echo esc_url( bp_get_activation_page() );
+}
+	/**
+	 * Get the URL of the activation page.
+	 *
+	 * @return string
+	 */
+	function bp_get_activation_page() {
+		if ( bp_has_custom_activation_page() ) {
+			$page = trailingslashit( bp_get_root_domain() . '/' . bp_get_activate_slug() );
+		} else {
+			$page = trailingslashit( bp_get_root_domain() ) . 'wp-activate.php';
+		}
+
+		/**
+		 * Filters the URL of the activation page.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param string $page URL to the activation page.
+		 */
+		return apply_filters( 'bp_get_activation_page', $page );
+	}
+
+/**
+ * Output the username submitted during signup.
+ */
+function bp_signup_username_value() {
+	echo bp_get_signup_username_value();
+}
+	/**
+	 * Get the username submitted during signup.
+	 *
+	 * @todo This should be properly escaped.
+	 *
+	 * @return string
+	 */
+	function bp_get_signup_username_value() {
+		$value = '';
+		if ( isset( $_POST['signup_username'] ) )
+			$value = $_POST['signup_username'];
+
+		/**
+		 * Filters the username submitted during signup.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Username submitted during signup.
+		 */
+		return apply_filters( 'bp_get_signup_username_value', $value );
+	}
+
+/**
+ * Output the user email address submitted during signup.
+ */
+function bp_signup_email_value() {
+	echo bp_get_signup_email_value();
+}
+	/**
+	 * Get the email address submitted during signup.
+	 *
+	 * @todo This should be properly escaped.
+	 *
+	 * @return string
+	 */
+	function bp_get_signup_email_value() {
+		$value = '';
+		if ( isset( $_POST['signup_email'] ) )
+			$value = $_POST['signup_email'];
+
+		/**
+		 * Filters the email address submitted during signup.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Email address submitted during signup.
+		 */
+		return apply_filters( 'bp_get_signup_email_value', $value );
+	}
+
+/**
+ * Output the 'signup_with_blog' value submitted during signup.
+ */
+function bp_signup_with_blog_value() {
+	echo bp_get_signup_with_blog_value();
+}
+	/**
+	 * Get the 'signup_with_blog' value submitted during signup.
+	 *
+	 * @return string
+	 */
+	function bp_get_signup_with_blog_value() {
+		$value = '';
+		if ( isset( $_POST['signup_with_blog'] ) )
+			$value = $_POST['signup_with_blog'];
+
+		/**
+		 * Filters the 'signup_with_blog' value submitted during signup.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value 'signup_with_blog' value submitted during signup.
+		 */
+		return apply_filters( 'bp_get_signup_with_blog_value', $value );
+	}
+
+/**
+ * Output the 'signup_blog_url' value submitted at signup.
+ */
+function bp_signup_blog_url_value() {
+	echo bp_get_signup_blog_url_value();
+}
+	/**
+	 * Get the 'signup_blog_url' value submitted at signup.
+	 *
+	 * @todo Should be properly escaped.
+	 *
+	 * @return string
+	 */
+	function bp_get_signup_blog_url_value() {
+		$value = '';
+		if ( isset( $_POST['signup_blog_url'] ) )
+			$value = $_POST['signup_blog_url'];
+
+		/**
+		 * Filters the 'signup_blog_url' value submitted during signup.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value 'signup_blog_url' value submitted during signup.
+		 */
+		return apply_filters( 'bp_get_signup_blog_url_value', $value );
+	}
+
+/**
+ * Output the base URL for subdomain installations of WordPress Multisite.
+ *
+ * @since 2.1.0
+ */
+function bp_signup_subdomain_base() {
+	echo bp_signup_get_subdomain_base();
+}
+	/**
+	 * Return the base URL for subdomain installations of WordPress Multisite.
+	 *
+	 * Replaces bp_blogs_get_subdomain_base()
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return string The base URL - eg, 'example.com' for site_url() example.com or www.example.com.
+	 */
+	function bp_signup_get_subdomain_base() {
+		global $current_site;
+
+		// In case plugins are still using this filter.
+		$subdomain_base = apply_filters( 'bp_blogs_subdomain_base', preg_replace( '|^www\.|', '', $current_site->domain ) . $current_site->path );
+
+		/**
+		 * Filters the base URL for subdomain installations of WordPress Multisite.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $subdomain_base The base URL - eg, 'example.com' for
+		 *                               site_url() example.com or www.example.com.
+		 */
+		return apply_filters( 'bp_signup_subdomain_base', $subdomain_base );
+	}
+
+/**
+ * Output the 'signup_blog_titl' value submitted at signup.
+ */
+function bp_signup_blog_title_value() {
+	echo bp_get_signup_blog_title_value();
+}
+	/**
+	 * Get the 'signup_blog_title' value submitted at signup.
+	 *
+	 * @todo Should be properly escaped.
+	 *
+	 * @return string
+	 */
+	function bp_get_signup_blog_title_value() {
+		$value = '';
+		if ( isset( $_POST['signup_blog_title'] ) )
+			$value = $_POST['signup_blog_title'];
+
+		/**
+		 * Filters the 'signup_blog_title' value submitted during signup.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value 'signup_blog_title' value submitted during signup.
+		 */
+		return apply_filters( 'bp_get_signup_blog_title_value', $value );
+	}
+
+/**
+ * Output the 'signup_blog_privacy' value submitted at signup.
+ */
+function bp_signup_blog_privacy_value() {
+	echo bp_get_signup_blog_privacy_value();
+}
+	/**
+	 * Get the 'signup_blog_privacy' value submitted at signup.
+	 *
+	 * @todo Should be properly escaped.
+	 *
+	 * @return string
+	 */
+	function bp_get_signup_blog_privacy_value() {
+		$value = '';
+		if ( isset( $_POST['signup_blog_privacy'] ) )
+			$value = $_POST['signup_blog_privacy'];
+
+		/**
+		 * Filters the 'signup_blog_privacy' value submitted during signup.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value 'signup_blog_privacy' value submitted during signup.
+		 */
+		return apply_filters( 'bp_get_signup_blog_privacy_value', $value );
+	}
+
+/**
+ * Output the avatar dir used during signup.
+ */
+function bp_signup_avatar_dir_value() {
+	echo bp_get_signup_avatar_dir_value();
+}
+	/**
+	 * Get the avatar dir used during signup.
+	 *
+	 * @return string
+	 */
+	function bp_get_signup_avatar_dir_value() {
+		$bp = buddypress();
+
+		// Check if signup_avatar_dir is passed.
+		if ( !empty( $_POST['signup_avatar_dir'] ) )
+			$signup_avatar_dir = $_POST['signup_avatar_dir'];
+
+		// If not, check if global is set.
+		elseif ( !empty( $bp->signup->avatar_dir ) )
+			$signup_avatar_dir = $bp->signup->avatar_dir;
+
+		// If not, set false.
+		else
+			$signup_avatar_dir = false;
+
+		/**
+		 * Filters the avatar dir used during signup.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string|bool $signup_avatar_dir Avatar dir used during signup or false.
+		 */
+		return apply_filters( 'bp_get_signup_avatar_dir_value', $signup_avatar_dir );
+	}
+
+/**
+ * Output the current signup step.
+ */
+function bp_current_signup_step() {
+	echo bp_get_current_signup_step();
+}
+	/**
+	 * Get the current signup step.
+	 *
+	 * @return string
+	 */
+	function bp_get_current_signup_step() {
+		return buddypress()->signup->step;
+	}
+
+/**
+ * Output the user avatar during signup.
+ *
+ * @see bp_get_signup_avatar() for description of arguments.
+ *
+ * @param array|string $args See {@link bp_get_signup_avatar(}.
+ */
+function bp_signup_avatar( $args = '' ) {
+	echo bp_get_signup_avatar( $args );
+}
+	/**
+	 * Get the user avatar during signup.
+	 *
+	 * @see bp_core_fetch_avatar() for description of arguments.
+	 *
+	 * @param array|string $args {
+	 *     Array of optional arguments.
+	 *     @type int    $size  Height/weight in pixels. Default: value of
+	 *                         bp_core_avatar_full_width().
+	 *     @type string $class CSS class. Default: 'avatar'.
+	 *     @type string $alt   HTML 'alt' attribute. Default: 'Your Avatar'.
+	 * }
+	 * @return string
+	 */
+	function bp_get_signup_avatar( $args = '' ) {
+		$bp = buddypress();
+
+		$defaults = array(
+			'size' => bp_core_avatar_full_width(),
+			'class' => 'avatar',
+			'alt' => __( 'Your Profile Photo', 'buddypress' )
+		);
+
+		$r = wp_parse_args( $args, $defaults );
+		extract( $r, EXTR_SKIP );
+
+		// Avatar DIR is found.
+		if ( $signup_avatar_dir = bp_get_signup_avatar_dir_value() ) {
+			$gravatar_img = bp_core_fetch_avatar( array(
+				'item_id'    => $signup_avatar_dir,
+				'object'     => 'signup',
+				'avatar_dir' => 'avatars/signups',
+				'type'       => 'full',
+				'width'      => $size,
+				'height'     => $size,
+				'alt'        => $alt,
+				'class'      => $class
+			) );
+
+		// No avatar DIR was found.
+		} else {
+
+			// Set default gravatar type.
+			if ( empty( $bp->grav_default->user ) )
+				$default_grav = 'wavatar';
+			elseif ( 'mystery' == $bp->grav_default->user )
+				$default_grav = $bp->plugin_url . 'bp-core/images/mystery-man.jpg';
+			else
+				$default_grav = $bp->grav_default->user;
+
+			/**
+			 * Filters the base Gravatar url used for signup avatars when no avatar dir found.
+			 *
+			 * @since 1.0.2
+			 *
+			 * @param string $value Gravatar url to use.
+			 */
+			$gravatar_url    = apply_filters( 'bp_gravatar_url', '//www.gravatar.com/avatar/' );
+			$md5_lcase_email = md5( strtolower( bp_get_signup_email_value() ) );
+			$gravatar_img    = '<img src="' . $gravatar_url . $md5_lcase_email . '?d=' . $default_grav . '&amp;s=' . $size . '" width="' . $size . '" height="' . $size . '" alt="' . $alt . '" class="' . $class . '" />';
+		}
+
+		/**
+		 * Filters the user avatar during signup.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $gravatar_img Avatar HTML image tag.
+		 * @param array  $args         Array of parsed args for avatar query.
+		 */
+		return apply_filters( 'bp_get_signup_avatar', $gravatar_img, $args );
+	}
+
+/**
+ * Output whether signup is allowed.
+ *
+ * @todo Remove this function. Echoing a bool is pointless.
+ */
+function bp_signup_allowed() {
+	echo bp_get_signup_allowed();
+}
+	/**
+	 * Is user signup allowed?
+	 *
+	 * @return bool
+	 */
+	function bp_get_signup_allowed() {
+		$bp = buddypress();
+
+		$signup_allowed = false;
+
+		if ( is_multisite() ) {
+			$registration = bp_core_get_root_option( 'registration' );
+
+			if ( in_array( $registration, array( 'all', 'user' ) ) ) {
+				$signup_allowed = true;
+			}
+
+		} else {
+			if ( bp_get_option( 'users_can_register') ) {
+				$signup_allowed = true;
+			}
+		}
+
+		/**
+		 * Filters whether or not new signups are allowed.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param bool $signup_allowed Whether or not new signups are allowed.
+		 */
+		return apply_filters( 'bp_get_signup_allowed', $signup_allowed );
+	}
+
+/**
+ * Hook member activity feed to <head>.
+ *
+ * @since 1.5.0
+ */
+function bp_members_activity_feed() {
+	if ( !bp_is_active( 'activity' ) || !bp_is_user() )
+		return; ?>
+
+	<link rel="alternate" type="application/rss+xml" title="<?php bloginfo( 'name' ) ?> | <?php bp_displayed_user_fullname() ?> | <?php _e( 'Activity RSS Feed', 'buddypress' ) ?>" href="<?php bp_member_activity_feed_link() ?>" />
+
+<?php
+}
+add_action( 'bp_head', 'bp_members_activity_feed' );
+
+/**
+ * Output a link to a members component subpage.
+ *
+ * @see bp_get_members_component_link() for description of parameters.
+ *
+ * @param string      $component See {@bp_get_members_component_link()}.
+ * @param string      $action See {@bp_get_members_component_link()}.
+ * @param string      $query_args See {@bp_get_members_component_link()}.
+ * @param string|bool $nonce See {@bp_get_members_component_link()}.
+ */
+function bp_members_component_link( $component, $action = '', $query_args = '', $nonce = false ) {
+	echo esc_url( bp_get_members_component_link( $component, $action, $query_args, $nonce ) );
+}
+	/**
+	 * Generate a link to a members component subpage.
+	 *
+	 * @param string       $component  ID of the component (eg 'friends').
+	 * @param string       $action     Optional. 'action' slug (eg 'invites').
+	 * @param array|string $query_args Optional. Array of URL params to add to the
+	 *                                 URL. See {@link add_query_arg()} for format.
+	 * @param array|bool   $nonce      Optional. If provided, the URL will be passed
+	 *                                 through wp_nonce_url() with $nonce as the
+	 *                                 action string.
+	 * @return string
+	 */
+	function bp_get_members_component_link( $component, $action = '', $query_args = '', $nonce = false ) {
+
+		// Must be displayed user.
+		if ( !bp_displayed_user_id() )
+			return;
+
+		$bp = buddypress();
+
+		// Append $action to $url if there is no $type.
+		if ( !empty( $action ) )
+			$url = bp_displayed_user_domain() . $bp->{$component}->slug . '/' . $action;
+		else
+			$url = bp_displayed_user_domain() . $bp->{$component}->slug;
+
+		// Add a slash at the end of our user url.
+		$url = trailingslashit( $url );
+
+		// Add possible query arg.
+		if ( !empty( $query_args ) && is_array( $query_args ) )
+			$url = add_query_arg( $query_args, $url );
+
+		// To nonce, or not to nonce...
+		if ( true === $nonce )
+			$url = wp_nonce_url( $url );
+		elseif ( is_string( $nonce ) )
+			$url = wp_nonce_url( $url, $nonce );
+
+		// Return the url, if there is one.
+		if ( !empty( $url ) )
+			return $url;
+	}
Index: /tags/2.4.3/src/bp-members/bp-members-widgets.php
===================================================================
--- /tags/2.4.3/src/bp-members/bp-members-widgets.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/bp-members-widgets.php	(revision 10410)
@@ -0,0 +1,622 @@
+<?php
+/**
+ * BuddyPress Members Widgets.
+ *
+ * @package BuddyPress
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register bp-members widgets.
+ *
+ * Previously, these widgets were registered in bp-core.
+ *
+ * @since 2.2.0
+ */
+function bp_members_register_widgets() {
+	add_action( 'widgets_init', create_function( '', 'return register_widget("BP_Core_Members_Widget");'         ) );
+	add_action( 'widgets_init', create_function( '', 'return register_widget("BP_Core_Whos_Online_Widget");'     ) );
+	add_action( 'widgets_init', create_function( '', 'return register_widget("BP_Core_Recently_Active_Widget");' ) );
+}
+add_action( 'bp_register_widgets', 'bp_members_register_widgets' );
+
+/**
+ * Members Widget.
+ *
+ * @since 1.0.3
+ */
+class BP_Core_Members_Widget extends WP_Widget {
+
+	/**
+	 * Constructor method.
+	 */
+	public function __construct() {
+
+		// Setup widget name & description.
+		$name        = _x( '(BuddyPress) Members', 'widget name', 'buddypress' );
+		$description = __( 'A dynamic list of recently active, popular, and newest members', 'buddypress' );
+
+		// Call WP_Widget constructor.
+		parent::__construct( false, $name, array(
+			'description' => $description,
+			'classname'   => 'widget_bp_core_members_widget buddypress widget',
+		) );
+
+		// Maybe enqueue JS for widget.
+		if ( is_active_widget( false, false, $this->id_base ) && ! is_admin() && ! is_network_admin() ) {
+			wp_enqueue_script( 'bp-widget-members' );
+		}
+	}
+
+	/**
+	 * Display the Members widget.
+	 *
+	 * @see WP_Widget::widget() for description of parameters.
+	 *
+	 * @param array $args     Widget arguments.
+	 * @param array $instance Widget settings, as saved by the user.
+	 */
+	public function widget( $args, $instance ) {
+
+		// Get widget settings.
+		$settings = $this->parse_settings( $instance );
+
+		/**
+		 * Filters the title of the Members widget.
+		 *
+		 * @since 1.8.0
+		 * @since 2.3.0 Added 'instance' and 'id_base' to arguments passed to filter.
+		 *
+		 * @param string $title    The widget title.
+		 * @param array  $settings The settings for the particular instance of the widget.
+		 * @param string $id_base  Root ID for all widgets of this type.
+		 */
+		$title = apply_filters( 'widget_title', $settings['title'], $settings, $this->id_base );
+		$title = $settings['link_title'] ? '<a href="' . bp_get_members_directory_permalink() . '">' . $title . '</a>' : $title;
+
+		/**
+		 * Filters the separator of the member widget links.
+		 *
+		 * @since 2.4.0
+		 *
+		 * @param string $separator Separator string. Default '|'.
+		 */
+		$separator = apply_filters( 'bp_members_widget_separator', '|' );
+
+		// Output before widget HTMl, title (and maybe content before & after it).
+		echo $args['before_widget']
+		   . $args['before_title']
+		   . $title
+		   . $args['after_title'];
+
+		// Setup args for querying members.
+		$members_args = array(
+			'user_id'         => 0,
+			'type'            => $settings['member_default'],
+			'per_page'        => $settings['max_members'],
+			'max'             => $settings['max_members'],
+			'populate_extras' => true,
+			'search_terms'    => false,
+		); ?>
+
+		<?php if ( bp_has_members( $members_args ) ) : ?>
+
+			<div class="item-options" id="members-list-options">
+				<a href="<?php bp_members_directory_permalink(); ?>" id="newest-members" <?php if ( 'newest' === $settings['member_default'] ) : ?>class="selected"<?php endif; ?>><?php esc_html_e( 'Newest', 'buddypress' ); ?></a>
+				<span class="bp-separator" role="separator"><?php echo esc_html( $separator ); ?></span>
+				<a href="<?php bp_members_directory_permalink(); ?>" id="recently-active-members" <?php if ( 'active' === $settings['member_default'] ) : ?>class="selected"<?php endif; ?>><?php esc_html_e( 'Active', 'buddypress' ); ?></a>
+
+				<?php if ( bp_is_active( 'friends' ) ) : ?>
+					<span class="bp-separator" role="separator"><?php echo esc_html( $separator ); ?></span>
+					<a href="<?php bp_members_directory_permalink(); ?>" id="popular-members" <?php if ( 'popular' === $settings['member_default'] ) : ?>class="selected"<?php endif; ?>><?php esc_html_e( 'Popular', 'buddypress' ); ?></a>
+
+				<?php endif; ?>
+
+			</div>
+
+			<ul id="members-list" class="item-list">
+
+				<?php while ( bp_members() ) : bp_the_member(); ?>
+
+					<li class="vcard">
+						<div class="item-avatar">
+							<a href="<?php bp_member_permalink() ?>" title="<?php bp_member_name(); ?>"><?php bp_member_avatar(); ?></a>
+						</div>
+
+						<div class="item">
+							<div class="item-title fn"><a href="<?php bp_member_permalink(); ?>" title="<?php bp_member_name(); ?>"><?php bp_member_name(); ?></a></div>
+							<div class="item-meta">
+								<span class="activity"><?php
+									if ( 'newest' === $settings['member_default'] ) :
+										bp_member_registered();
+									elseif ( 'active' === $settings['member_default'] ) :
+										bp_member_last_active();
+									elseif ( 'popular' === $settings['member_default'] ) :
+										bp_member_total_friend_count();
+									endif; ?></span>
+							</div>
+						</div>
+					</li>
+
+				<?php endwhile; ?>
+
+			</ul>
+
+			<?php wp_nonce_field( 'bp_core_widget_members', '_wpnonce-members', false ); ?>
+
+			<input type="hidden" name="members_widget_max" id="members_widget_max" value="<?php echo esc_attr( $settings['max_members'] ); ?>" />
+
+		<?php else: ?>
+
+			<div class="widget-error">
+				<?php esc_html_e( 'No one has signed up yet!', 'buddypress' ); ?>
+			</div>
+
+		<?php endif; ?>
+
+		<?php echo $args['after_widget'];
+	}
+
+	/**
+	 * Update the Members widget options.
+	 *
+	 * @param array $new_instance The new instance options.
+	 * @param array $old_instance The old instance options.
+	 * @return array $instance The parsed options to be saved.
+	 */
+	public function update( $new_instance, $old_instance ) {
+		$instance = $old_instance;
+
+		$instance['title']          = strip_tags( $new_instance['title'] );
+		$instance['max_members']    = strip_tags( $new_instance['max_members'] );
+		$instance['member_default'] = strip_tags( $new_instance['member_default'] );
+		$instance['link_title']	    = (bool) $new_instance['link_title'];
+
+		return $instance;
+	}
+
+	/**
+	 * Output the Members widget options form.
+	 *
+	 * @param array $instance Widget instance settings.
+	 *
+	 * @return void
+	 */
+	public function form( $instance ) {
+
+		// Get widget settings.
+		$settings       = $this->parse_settings( $instance );
+		$title          = strip_tags( $settings['title'] );
+		$max_members    = strip_tags( $settings['max_members'] );
+		$member_default = strip_tags( $settings['member_default'] );
+		$link_title     = (bool) $settings['link_title']; ?>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>">
+				<?php esc_html_e( 'Title:', 'buddypress' ); ?>
+				<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( $title ); ?>" style="width: 100%" />
+			</label>
+		</p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'link_title' ) ?>">
+				<input type="checkbox" name="<?php echo $this->get_field_name( 'link_title' ) ?>" id="<?php echo $this->get_field_id( 'link_title' ) ?>" value="1" <?php checked( $link_title ) ?> />
+				<?php esc_html_e( 'Link widget title to Members directory', 'buddypress' ); ?>
+			</label>
+		</p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'max_members' ); ?>">
+				<?php esc_html_e( 'Max members to show:', 'buddypress' ); ?>
+				<input class="widefat" id="<?php echo $this->get_field_id( 'max_members' ); ?>" name="<?php echo $this->get_field_name( 'max_members' ); ?>" type="text" value="<?php echo esc_attr( $max_members ); ?>" style="width: 30%" />
+			</label>
+		</p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'member_default' ) ?>"><?php esc_html_e( 'Default members to show:', 'buddypress' ); ?></label>
+			<select name="<?php echo $this->get_field_name( 'member_default' ) ?>" id="<?php echo $this->get_field_id( 'member_default' ) ?>">
+				<option value="newest"  <?php if ( 'newest'  === $member_default ) : ?>selected="selected"<?php endif; ?>><?php esc_html_e( 'Newest',  'buddypress' ); ?></option>
+				<option value="active"  <?php if ( 'active'  === $member_default ) : ?>selected="selected"<?php endif; ?>><?php esc_html_e( 'Active',  'buddypress' ); ?></option>
+				<option value="popular" <?php if ( 'popular' === $member_default ) : ?>selected="selected"<?php endif; ?>><?php esc_html_e( 'Popular', 'buddypress' ); ?></option>
+			</select>
+		</p>
+
+	<?php
+	}
+
+	/**
+	 * Merge the widget settings into defaults array.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @uses bp_parse_args() To merge widget settings into defaults.
+	 *
+	 * @param array $instance Widget instance settings.
+	 * @return array
+	 */
+	public function parse_settings( $instance = array() ) {
+		return bp_parse_args( $instance, array(
+			'title' 	     => __( 'Members', 'buddypress' ),
+			'max_members' 	 => 5,
+			'member_default' => 'active',
+			'link_title' 	 => false
+		), 'members_widget_settings' );
+	}
+}
+
+/**
+ * Who's Online Widget.
+ *
+ * @since 1.0.3
+ */
+class BP_Core_Whos_Online_Widget extends WP_Widget {
+
+	/**
+	 * Constructor method.
+	 */
+	public function __construct() {
+		$name        = _x( "(BuddyPress) Who's Online", 'widget name', 'buddypress' );
+		$description = __( 'Profile photos of online users', 'buddypress' );
+		parent::__construct( false, $name, array(
+			'description' => $description,
+			'classname' => 'widget_bp_core_whos_online_widget buddypress widget',
+		) );
+	}
+
+	/**
+	 * Display the Who's Online widget.
+	 *
+	 * @see WP_Widget::widget() for description of parameters.
+	 *
+	 * @param array $args     Widget arguments.
+	 * @param array $instance Widget settings, as saved by the user.
+	 */
+	public function widget( $args, $instance ) {
+
+		// Get widget settings.
+		$settings = $this->parse_settings( $instance );
+
+		/**
+		 * Filters the title of the Who's Online widget.
+		 *
+		 * @since 1.8.0
+		 * @since 2.3.0 Added 'instance' and 'id_base' to arguments passed to filter.
+		 *
+		 * @param string $title    The widget title.
+		 * @param array  $settings The settings for the particular instance of the widget.
+		 * @param string $id_base  Root ID for all widgets of this type.
+		 */
+		$title = apply_filters( 'widget_title', $settings['title'], $settings, $this->id_base );
+
+		echo $args['before_widget']
+		   . $args['before_title']
+		   . $title
+		   . $args['after_title'];
+
+		// Setup args for querying members.
+		$members_args = array(
+			'user_id'         => 0,
+			'type'            => 'online',
+			'per_page'        => $settings['max_members'],
+			'max'             => $settings['max_members'],
+			'populate_extras' => true,
+			'search_terms'    => false,
+		); ?>
+
+		<?php if ( bp_has_members( $members_args ) ) : ?>
+
+			<div class="avatar-block">
+
+				<?php while ( bp_members() ) : bp_the_member(); ?>
+
+					<div class="item-avatar">
+						<a href="<?php bp_member_permalink(); ?>" title="<?php bp_member_name(); ?>"><?php bp_member_avatar(); ?></a>
+					</div>
+
+				<?php endwhile; ?>
+
+			</div>
+
+		<?php else: ?>
+
+			<div class="widget-error">
+				<?php esc_html_e( 'There are no users currently online', 'buddypress' ); ?>
+			</div>
+
+		<?php endif; ?>
+
+		<?php echo $args['after_widget'];
+	}
+
+	/**
+	 * Update the Who's Online widget options.
+	 *
+	 * @param array $new_instance The new instance options.
+	 * @param array $old_instance The old instance options.
+	 * @return array $instance The parsed options to be saved.
+	 */
+	public function update( $new_instance, $old_instance ) {
+		$instance                = $old_instance;
+		$instance['title']       = strip_tags( $new_instance['title'] );
+		$instance['max_members'] = strip_tags( $new_instance['max_members'] );
+
+		return $instance;
+	}
+
+	/**
+	 * Output the Who's Online widget options form.
+	 *
+	 * @param array $instance Widget instance settings.
+	 * @return void
+	 */
+	public function form( $instance ) {
+
+		// Get widget settings.
+		$settings    = $this->parse_settings( $instance );
+		$title       = strip_tags( $settings['title'] );
+		$max_members = strip_tags( $settings['max_members'] ); ?>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>">
+				<?php esc_html_e( 'Title:', 'buddypress' ); ?>
+				<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( $title ); ?>" style="width: 100%" />
+			</label>
+		</p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'max_members' ); ?>">
+				<?php esc_html_e( 'Max members to show:', 'buddypress' ); ?>
+				<input class="widefat" id="<?php echo $this->get_field_id( 'max_members' ); ?>" name="<?php echo $this->get_field_name( 'max_members' ); ?>" type="text" value="<?php echo esc_attr( $max_members ); ?>" style="width: 30%" />
+			</label>
+		</p>
+
+	<?php
+	}
+
+	/**
+	 * Merge the widget settings into defaults array.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @uses bp_parse_args() To merge widget settings into defaults.
+	 *
+	 * @param array $instance Widget instance settings.
+	 * @return array
+	 */
+	public function parse_settings( $instance = array() ) {
+		return bp_parse_args( $instance, array(
+			'title' 	     => __( "Who's Online", 'buddypress' ),
+			'max_members' 	 => 15,
+		), 'members_widget_settings' );
+	}
+}
+
+/**
+ * Recently Active Members Widget.
+ *
+ * @since 1.0.3
+ */
+class BP_Core_Recently_Active_Widget extends WP_Widget {
+
+	/**
+	 * Constructor method.
+	 */
+	public function __construct() {
+		$name        = _x( '(BuddyPress) Recently Active Members', 'widget name', 'buddypress' );
+		$description = __( 'Profile photos of recently active members', 'buddypress' );
+		parent::__construct( false, $name, array(
+			'description' => $description,
+			'classname'   => 'widget_bp_core_recently_active_widget buddypress widget',
+		) );
+	}
+
+	/**
+	 * Display the Recently Active widget.
+	 *
+	 * @see WP_Widget::widget() for description of parameters.
+	 *
+	 * @param array $args     Widget arguments.
+	 * @param array $instance Widget settings, as saved by the user.
+	 */
+	public function widget( $args, $instance ) {
+
+		// Get widget settings.
+		$settings = $this->parse_settings( $instance );
+
+		/**
+		 * Filters the title of the Recently Active widget.
+		 *
+		 * @since 1.8.0
+		 * @since 2.3.0 Added 'instance' and 'id_base' to arguments passed to filter.
+		 *
+		 * @param string $title    The widget title.
+		 * @param array  $settings The settings for the particular instance of the widget.
+		 * @param string $id_base  Root ID for all widgets of this type.
+		 */
+		$title = apply_filters( 'widget_title', $settings['title'], $settings, $this->id_base );
+
+		echo $args['before_widget'];
+		echo $args['before_title']
+		   . $title
+		   . $args['after_title'];
+
+		// Setup args for querying members.
+		$members_args = array(
+			'user_id'         => 0,
+			'type'            => 'active',
+			'per_page'        => $settings['max_members'],
+			'max'             => $settings['max_members'],
+			'populate_extras' => true,
+			'search_terms'    => false,
+		); ?>
+
+		<?php if ( bp_has_members( $members_args ) ) : ?>
+
+			<div class="avatar-block">
+
+				<?php while ( bp_members() ) : bp_the_member(); ?>
+
+					<div class="item-avatar">
+						<a href="<?php bp_member_permalink(); ?>" title="<?php bp_member_name(); ?>"><?php bp_member_avatar(); ?></a>
+					</div>
+
+				<?php endwhile; ?>
+
+			</div>
+
+		<?php else: ?>
+
+			<div class="widget-error">
+				<?php esc_html_e( 'There are no recently active members', 'buddypress' ); ?>
+			</div>
+
+		<?php endif; ?>
+
+		<?php echo $args['after_widget'];
+	}
+
+	/**
+	 * Update the Recently Active widget options.
+	 *
+	 * @param array $new_instance The new instance options.
+	 * @param array $old_instance The old instance options.
+	 * @return array $instance The parsed options to be saved.
+	 */
+	public function update( $new_instance, $old_instance ) {
+		$instance                = $old_instance;
+		$instance['title']       = strip_tags( $new_instance['title'] );
+		$instance['max_members'] = strip_tags( $new_instance['max_members'] );
+
+		return $instance;
+	}
+
+	/**
+	 * Output the Recently Active widget options form.
+	 *
+	 * @param array $instance Widget instance settings.
+	 * @return void
+	 */
+	public function form( $instance ) {
+
+		// Get widget settings.
+		$settings    = $this->parse_settings( $instance );
+		$title       = strip_tags( $settings['title'] );
+		$max_members = strip_tags( $settings['max_members'] ); ?>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>">
+				<?php esc_html_e( 'Title:', 'buddypress' ); ?>
+				<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( $title ); ?>" style="width: 100%" />
+			</label>
+		</p>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'max_members' ); ?>">
+				<?php esc_html_e( 'Max members to show:', 'buddypress' ); ?>
+				<input class="widefat" id="<?php echo $this->get_field_id( 'max_members' ); ?>" name="<?php echo $this->get_field_name( 'max_members' ); ?>" type="text" value="<?php echo esc_attr( $max_members ); ?>" style="width: 30%" />
+			</label>
+		</p>
+
+	<?php
+	}
+
+	/**
+	 * Merge the widget settings into defaults array.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @uses bp_parse_args() To merge widget settings into defaults.
+	 *
+	 * @param array $instance Widget instance settings.
+	 * @return array
+	 */
+	public function parse_settings( $instance = array() ) {
+		return bp_parse_args( $instance, array(
+			'title' 	     => __( 'Recently Active Members', 'buddypress' ),
+			'max_members' 	 => 15,
+		), 'recently_active_members_widget_settings' );
+	}
+}
+
+/**
+ * AJAX request handler for Members widgets.
+ *
+ * @since 1.0.0
+ *
+ * @see BP_Core_Members_Widget
+ */
+function bp_core_ajax_widget_members() {
+
+	check_ajax_referer( 'bp_core_widget_members' );
+
+	// Setup some variables to check.
+	$filter      = ! empty( $_POST['filter']      ) ? $_POST['filter']                : 'recently-active-members';
+	$max_members = ! empty( $_POST['max-members'] ) ? absint( $_POST['max-members'] ) : 5;
+
+	// Determine the type of members query to perform.
+	switch ( $filter ) {
+
+		// Newest activated.
+		case 'newest-members' :
+			$type = 'newest';
+			break;
+
+		// Popular by friends.
+		case 'popular-members' :
+			if ( bp_is_active( 'friends' ) ) {
+				$type = 'popular';
+			} else {
+				$type = 'active';
+			}
+			break;
+
+		// Default.
+		case 'recently-active-members' :
+		default :
+			$type = 'active';
+			break;
+	}
+
+	// Setup args for querying members.
+	$members_args = array(
+		'user_id'         => 0,
+		'type'            => $type,
+		'per_page'        => $max_members,
+		'max'             => $max_members,
+		'populate_extras' => true,
+		'search_terms'    => false,
+	);
+
+	// Query for members.
+	if ( bp_has_members( $members_args ) ) : ?>
+		<?php echo '0[[SPLIT]]'; // Return valid result. TODO: remove this. ?>
+		<?php while ( bp_members() ) : bp_the_member(); ?>
+			<li class="vcard">
+				<div class="item-avatar">
+					<a href="<?php bp_member_permalink(); ?>"><?php bp_member_avatar(); ?></a>
+				</div>
+
+				<div class="item">
+					<div class="item-title fn"><a href="<?php bp_member_permalink(); ?>" title="<?php bp_member_name(); ?>"><?php bp_member_name(); ?></a></div>
+					<?php if ( 'active' === $type ) : ?>
+						<div class="item-meta"><span class="activity"><?php bp_member_last_active(); ?></span></div>
+					<?php elseif ( 'newest' === $type ) : ?>
+						<div class="item-meta"><span class="activity"><?php bp_member_registered(); ?></span></div>
+					<?php elseif ( bp_is_active( 'friends' ) ) : ?>
+						<div class="item-meta"><span class="activity"><?php bp_member_total_friend_count(); ?></span></div>
+					<?php endif; ?>
+				</div>
+			</li>
+
+		<?php endwhile; ?>
+
+	<?php else: ?>
+		<?php echo "-1[[SPLIT]]<li>"; ?>
+		<?php esc_html_e( 'There were no members found, please try another filter.', 'buddypress' ) ?>
+		<?php echo "</li>"; ?>
+	<?php endif;
+}
+add_action( 'wp_ajax_widget_members',        'bp_core_ajax_widget_members' );
+add_action( 'wp_ajax_nopriv_widget_members', 'bp_core_ajax_widget_members' );
Index: /tags/2.4.3/src/bp-members/classes/class-bp-signup.php
===================================================================
--- /tags/2.4.3/src/bp-members/classes/class-bp-signup.php	(revision 10410)
+++ /tags/2.4.3/src/bp-members/classes/class-bp-signup.php	(revision 10410)
@@ -0,0 +1,789 @@
+<?php
+/**
+ * Signups Management class.
+ *
+ * @since 2.0.0
+ *
+ * @package BuddyPress
+ * @subpackage coreClasses
+ */
+
+/**
+ * Class used to handle Signups.
+ */
+class BP_Signup {
+
+	/**
+	 * ID of the signup which the object relates to.
+	 *
+	 * @var integer
+	 */
+	public $id;
+
+	/**
+	 * The URL to the full size of the avatar for the user.
+	 *
+	 * @var string
+	 */
+	public $avatar;
+
+	/**
+	 * The username for the user.
+	 *
+	 * @var string
+	 */
+	public $user_login;
+
+	/**
+	 * The email for the user.
+	 *
+	 * @var string
+	 */
+	public $user_email;
+
+	/**
+	 * The full name of the user.
+	 *
+	 * @var string
+	 */
+	public $user_name;
+
+	/**
+	 * Metadata associated with the signup.
+	 *
+	 * @var array
+	 */
+	public $meta;
+
+	/**
+	 * The registered date for the user.
+	 *
+	 * @var string
+	 */
+	public $registered;
+
+	/**
+	 * The activation key for the user.
+	 *
+	 * @var string
+	 */
+	public $activation_key;
+
+
+	/** Public Methods *******************************************************/
+
+	/**
+	 * Class constructor.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param integer $signup_id The ID for the signup being queried.
+	 */
+	public function __construct( $signup_id = 0 ) {
+		if ( !empty( $signup_id ) ) {
+			$this->id = $signup_id;
+			$this->populate();
+		}
+	}
+
+	/**
+	 * Populate the instantiated class with data based on the signup_id provided.
+	 *
+	 * @since 2.0.0
+	 */
+	public function populate() {
+		global $wpdb;
+
+		$signups_table = buddypress()->members->table_name_signups;
+		$signup        = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$signups_table} WHERE signup_id = %d AND active = 0", $this->id ) );
+
+		$this->avatar         = get_avatar( $signup->user_email, 32 );
+		$this->user_login     = $signup->user_login;
+		$this->user_email     = $signup->user_email;
+		$this->meta           = maybe_unserialize( $signup->meta );
+		$this->user_name      = ! empty( $this->meta['field_1'] ) ? wp_unslash( $this->meta['field_1'] ) : '';
+		$this->registered     = $signup->registered;
+		$this->activation_key = $signup->activation_key;
+	}
+
+	/** Static Methods *******************************************************/
+
+	/**
+	 * Fetch signups based on parameters.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $args the argument to retrieve desired signups.
+	 * @return array {
+	 *     @type array $signups Located signups.
+	 *     @type int   $total   Total number of signups matching params.
+	 * }
+	 */
+	public static function get( $args = array() ) {
+		global $wpdb;
+
+		$r = bp_parse_args( $args,
+			array(
+				'offset'         => 0,
+				'number'         => 1,
+				'usersearch'     => false,
+				'orderby'        => 'signup_id',
+				'order'          => 'DESC',
+				'include'        => false,
+				'activation_key' => '',
+				'user_login'     => '',
+			),
+			'bp_core_signups_get_args'
+		);
+
+		// @todo whitelist sanitization
+		if ( $r['orderby'] !== 'signup_id' ) {
+			$r['orderby'] = 'user_' . $r['orderby'];
+		}
+
+		$r['orderby'] = sanitize_title( $r['orderby'] );
+
+		$sql = array();
+		$signups_table  = buddypress()->members->table_name_signups;
+		$sql['select']  = "SELECT * FROM {$signups_table}";
+		$sql['where']   = array();
+		$sql['where'][] = "active = 0";
+
+		if ( empty( $r['include'] ) ) {
+
+			// Search terms.
+			if ( ! empty( $r['usersearch'] ) ) {
+				$search_terms_like = '%' . bp_esc_like( $r['usersearch'] ) . '%';
+				$sql['where'][]    = $wpdb->prepare( "( user_login LIKE %s OR user_email LIKE %s OR meta LIKE %s )", $search_terms_like, $search_terms_like, $search_terms_like );
+			}
+
+			// Activation key.
+			if ( ! empty( $r['activation_key'] ) ) {
+				$sql['where'][] = $wpdb->prepare( "activation_key = %s", $r['activation_key'] );
+			}
+
+			// User login.
+			if ( ! empty( $r['user_login'] ) ) {
+				$sql['where'][] = $wpdb->prepare( "user_login = %s", $r['user_login'] );
+			}
+
+			$sql['orderby'] = "ORDER BY {$r['orderby']}";
+			$sql['order']	= bp_esc_sql_order( $r['order'] );
+			$sql['limit']	= $wpdb->prepare( "LIMIT %d, %d", $r['offset'], $r['number'] );
+		} else {
+			$in = implode( ',', wp_parse_id_list( $r['include'] ) );
+			$sql['in'] = "AND signup_id IN ({$in})";
+		}
+
+		// Implode WHERE clauses.
+		$sql['where'] = 'WHERE ' . implode( ' AND ', $sql['where'] );
+
+		/**
+		 * Filters the Signups paged query.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $value SQL statement.
+		 * @param array  $sql   Array of SQL statement parts.
+		 * @param array  $args  Array of original arguments for get() method.
+		 * @param array  $r     Array of parsed arguments for get() method.
+		 */
+		$paged_signups = $wpdb->get_results( apply_filters( 'bp_members_signups_paged_query', join( ' ', $sql ), $sql, $args, $r ) );
+
+		if ( empty( $paged_signups ) ) {
+			return array( 'signups' => false, 'total' => false );
+		}
+
+		// Used to calculate a diff between now & last
+		// time an activation link has been resent.
+		$now = current_time( 'timestamp', true );
+
+		foreach ( (array) $paged_signups as $key => $signup ) {
+
+			$signup->id   = intval( $signup->signup_id );
+
+			$signup->meta = ! empty( $signup->meta ) ? maybe_unserialize( $signup->meta ) : false;
+
+			$signup->user_name = '';
+			if ( ! empty( $signup->meta['field_1'] ) ) {
+				$signup->user_name = wp_unslash( $signup->meta['field_1'] );
+			}
+
+			// Sent date defaults to date of registration.
+			if ( ! empty( $signup->meta['sent_date'] ) ) {
+				$signup->date_sent = $signup->meta['sent_date'];
+			} else {
+				$signup->date_sent = $signup->registered;
+			}
+
+			$sent_at = mysql2date('U', $signup->date_sent );
+			$diff    = $now - $sent_at;
+
+			/**
+			 * Add a boolean in case the last time an activation link
+			 * has been sent happened less than a day ago.
+			 */
+			if ( $diff < 1 * DAY_IN_SECONDS ) {
+				$signup->recently_sent = true;
+			}
+
+			if ( ! empty( $signup->meta['count_sent'] ) ) {
+				$signup->count_sent = absint( $signup->meta['count_sent'] );
+			} else {
+				$signup->count_sent = 1;
+			}
+
+			$paged_signups[ $key ] = $signup;
+		}
+
+		unset( $sql['limit'] );
+		$sql['select'] = preg_replace( "/SELECT.*?FROM/", "SELECT COUNT(*) FROM", $sql['select'] );
+
+		/**
+		 * Filters the Signups count query.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $value SQL statement.
+		 * @param array  $sql   Array of SQL statement parts.
+		 * @param array  $args  Array of original arguments for get() method.
+		 * @param array  $r     Array of parsed arguments for get() method.
+		 */
+		$total_signups = $wpdb->get_var( apply_filters( 'bp_members_signups_count_query', join( ' ', $sql ), $sql, $args, $r ) );
+
+		return array( 'signups' => $paged_signups, 'total' => $total_signups );
+	}
+
+	/**
+	 * Add a signup.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $args Array of arguments for signup addition.
+	 * @return int|bool ID of newly created signup on success, false on
+	 *                  failure.
+	 */
+	public static function add( $args = array() ) {
+		global $wpdb;
+
+		$r = bp_parse_args( $args,
+			array(
+				'domain'         => '',
+				'path'           => '',
+				'title'          => '',
+				'user_login'     => '',
+				'user_email'     => '',
+				'registered'     => current_time( 'mysql', true ),
+				'activation_key' => '',
+				'meta'           => '',
+			),
+			'bp_core_signups_add_args'
+		);
+
+		$r['meta'] = maybe_serialize( $r['meta'] );
+
+		$inserted = $wpdb->insert(
+			buddypress()->members->table_name_signups,
+			$r,
+			array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
+		);
+
+		if ( $inserted ) {
+			$retval = $wpdb->insert_id;
+		} else {
+			$retval = false;
+		}
+
+		/**
+		 * Filters the result of a signup addition.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param int|bool $retval Newly added user ID on success, false on failure.
+		 */
+		return apply_filters( 'bp_core_signups_add', $retval );
+	}
+
+	/**
+	 * Create a WP user at signup.
+	 *
+	 * Since BP 2.0, non-multisite configurations have stored signups in
+	 * the same way as Multisite configs traditionally have: in the
+	 * wp_signups table. However, because some plugins may be looking
+	 * directly in the wp_users table for non-activated signups, we
+	 * mirror signups there by creating "phantom" users, mimicking WP's
+	 * default behavior.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $user_login    User login string.
+	 * @param string $user_password User password.
+	 * @param string $user_email    User email address.
+	 * @param array  $usermeta      Metadata associated with the signup.
+	 * @return int User id.
+	 */
+	public static function add_backcompat( $user_login = '', $user_password = '', $user_email = '', $usermeta = array() ) {
+		global $wpdb;
+
+		$user_id = wp_insert_user( array(
+			'user_login'   => $user_login,
+			'user_pass'    => $user_password,
+			'display_name' => sanitize_title( $user_login ),
+			'user_email'   => $user_email
+		) );
+
+		if ( is_wp_error( $user_id ) || empty( $user_id ) ) {
+			return $user_id;
+		}
+
+		// Update the user status to '2', ie "not activated"
+		// (0 = active, 1 = spam, 2 = not active).
+		$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->users} SET user_status = 2 WHERE ID = %d", $user_id ) );
+
+		// WordPress creates these options automatically on
+		// wp_insert_user(), but we delete them so that inactive
+		// signups don't appear in various user counts.
+		delete_user_option( $user_id, 'capabilities' );
+		delete_user_option( $user_id, 'user_level'   );
+
+		// Set any profile data.
+		if ( bp_is_active( 'xprofile' ) ) {
+			if ( ! empty( $usermeta['profile_field_ids'] ) ) {
+				$profile_field_ids = explode( ',', $usermeta['profile_field_ids'] );
+
+				foreach ( (array) $profile_field_ids as $field_id ) {
+					if ( empty( $usermeta["field_{$field_id}"] ) ) {
+						continue;
+					}
+
+					$current_field = $usermeta["field_{$field_id}"];
+					xprofile_set_field_data( $field_id, $user_id, $current_field );
+
+					// Save the visibility level.
+					$visibility_level = ! empty( $usermeta['field_' . $field_id . '_visibility'] ) ? $usermeta['field_' . $field_id . '_visibility'] : 'public';
+					xprofile_set_field_visibility_level( $field_id, $user_id, $visibility_level );
+				}
+			}
+		}
+
+		/**
+		 * Filters the user ID for the backcompat functionality.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param int $user_id User ID being registered.
+		 */
+		return apply_filters( 'bp_core_signups_add_backcompat', $user_id );
+	}
+
+	/**
+	 * Check a user status (from wp_users) on a non-multisite config.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param int $user_id ID of the user being checked.
+	 * @return int|bool The status if found, otherwise false.
+	 */
+	public static function check_user_status( $user_id = 0 ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) ) {
+			return false;
+		}
+
+		$user_status = $wpdb->get_var( $wpdb->prepare( "SELECT user_status FROM {$wpdb->users} WHERE ID = %d", $user_id ) );
+
+		/**
+		 * Filters the user status of a provided user ID.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param int $value User status of the provided user ID.
+		 */
+		return apply_filters( 'bp_core_signups_check_user_status', intval( $user_status ) );
+	}
+
+	/**
+	 * Activate a signup.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $key Activation key.
+	 * @return bool True on success, false on failure.
+	 */
+	public static function validate( $key = '' ) {
+		global $wpdb;
+
+		if ( empty( $key ) ) {
+			return;
+		}
+
+		$activated = $wpdb->update(
+			// Signups table.
+			buddypress()->members->table_name_signups,
+			array(
+				'active' => 1,
+				'activated' => current_time( 'mysql', true ),
+			),
+			array(
+				'activation_key' => $key,
+			),
+			// Data sanitization format.
+			array(
+				'%d',
+				'%s',
+			),
+			// WHERE sanitization format.
+			array(
+				'%s',
+			)
+		);
+
+		/**
+		 * Filters the status of the activated user.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param bool $activated Whether or not the activation was successful.
+		 */
+		return apply_filters( 'bp_core_signups_validate', $activated );
+	}
+
+	/**
+	 * How many inactive signups do we have?
+	 *
+	 * @since 2.0.0
+	 *
+	 * @return int The number of signups.
+	 */
+	public static function count_signups() {
+		global $wpdb;
+
+		$signups_table = buddypress()->members->table_name_signups;
+		$count_signups = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) AS total FROM {$signups_table} WHERE active = %d", 0 ) );
+
+		/**
+		 * Filters the total inactive signups.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param int $count_signups How many total signups there are.
+		 */
+		return apply_filters( 'bp_core_signups_count', (int) $count_signups );
+	}
+
+	/**
+	 * Update the meta for a signup.
+	 *
+	 * This is the way we use to "trace" the last date an activation
+	 * email was sent and how many times activation was sent.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $args Array of arguments for the signup update.
+	 * @return int The signup id.
+	 */
+	public static function update( $args = array() ) {
+		global $wpdb;
+
+		$r = bp_parse_args( $args,
+			array(
+				'signup_id'  => 0,
+				'meta'       => array(),
+			),
+			'bp_core_signups_update_args'
+		);
+
+		if ( empty( $r['signup_id'] ) || empty( $r['meta'] ) ) {
+			return false;
+		}
+
+		$wpdb->update(
+			// Signups table.
+			buddypress()->members->table_name_signups,
+			// Data to update.
+			array(
+				'meta' => serialize( $r['meta'] ),
+			),
+			// WHERE.
+			array(
+				'signup_id' => $r['signup_id'],
+			),
+			// Data sanitization format.
+			array(
+				'%s',
+			),
+			// WHERE sanitization format.
+			array(
+				'%d',
+			)
+		);
+
+		/**
+		 * Filters the signup ID which received a meta update.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param int $value The signup ID.
+		 */
+		return apply_filters( 'bp_core_signups_update', $r['signup_id'] );
+	}
+
+	/**
+	 * Resend an activation email.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $signup_ids Single ID or list of IDs to resend.
+	 * @return array
+	 */
+	public static function resend( $signup_ids = array() ) {
+		if ( empty( $signup_ids ) || ! is_array( $signup_ids ) ) {
+			return false;
+		}
+
+		$to_resend = self::get( array(
+			'include' => $signup_ids,
+		) );
+
+		if ( ! $signups = $to_resend['signups'] ) {
+			return false;
+		}
+
+		$result = array();
+
+		/**
+		 * Fires before activation emails are resent.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $signup_ids Array of IDs to resend activation emails to.
+		 */
+		do_action( 'bp_core_signup_before_resend', $signup_ids );
+
+		foreach ( $signups as $signup ) {
+
+			$meta               = $signup->meta;
+			$meta['sent_date']  = current_time( 'mysql', true );
+			$meta['count_sent'] = $signup->count_sent + 1;
+
+			// Send activation email.
+			if ( is_multisite() ) {
+				wpmu_signup_user_notification( $signup->user_login, $signup->user_email, $signup->activation_key, serialize( $meta ) );
+			} else {
+
+				// Check user status before sending email.
+				$user_id = email_exists( $signup->user_email );
+
+				if ( ! empty( $user_id ) && 2 != self::check_user_status( $user_id ) ) {
+
+					// Status is not 2, so user's account has been activated.
+					$result['errors'][ $signup->signup_id ] = array( $signup->user_login, esc_html__( 'the sign-up has already been activated.', 'buddypress' ) );
+
+					// Repair signups table.
+					self::validate( $signup->activation_key );
+
+					continue;
+
+				// Send the validation email.
+				} else {
+					bp_core_signup_send_validation_email( false, $signup->user_email, $signup->activation_key );
+				}
+			}
+
+			// Update metas.
+			$result['resent'][] = self::update( array(
+				'signup_id' => $signup->signup_id,
+				'meta'      => $meta,
+			) );
+		}
+
+		/**
+		 * Fires after activation emails are resent.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $signup_ids Array of IDs to resend activation emails to.
+		 * @param array $result     Updated metadata related to activation emails.
+		 */
+		do_action( 'bp_core_signup_after_resend', $signup_ids, $result );
+
+		/**
+		 * Filters the result of the metadata for signup activation email resends.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $result Updated metadata related to activation emails.
+		 */
+		return apply_filters( 'bp_core_signup_resend', $result );
+	}
+
+	/**
+	 * Activate a pending account.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $signup_ids Single ID or list of IDs to activate.
+	 * @return array
+	 */
+	public static function activate( $signup_ids = array() ) {
+		if ( empty( $signup_ids ) || ! is_array( $signup_ids ) ) {
+			return false;
+		}
+
+		$to_activate = self::get( array(
+			'include' => $signup_ids,
+		) );
+
+		if ( ! $signups = $to_activate['signups'] ) {
+			return false;
+		}
+
+		$result = array();
+
+		/**
+		 * Fires before activation of user accounts.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $signup_ids Array of IDs to activate.
+		 */
+		do_action( 'bp_core_signup_before_activate', $signup_ids );
+
+		foreach ( $signups as $signup ) {
+
+			$user = bp_core_activate_signup( $signup->activation_key );
+
+			if ( ! empty( $user->errors ) ) {
+
+				$user_id = username_exists( $signup->user_login );
+
+				if ( 2 !== self::check_user_status( $user_id ) ) {
+					$user_id = false;
+				}
+
+				if ( empty( $user_id ) ) {
+
+					// Status is not 2, so user's account has been activated.
+					$result['errors'][ $signup->signup_id ] = array( $signup->user_login, esc_html__( 'the sign-up has already been activated.', 'buddypress' ) );
+
+					// Repair signups table.
+					self::validate( $signup->activation_key );
+
+				// We have a user id, account is not active, let's delete it.
+				} else {
+					$result['errors'][ $signup->signup_id ] = array( $signup->user_login, $user->get_error_message() );
+				}
+
+			} else {
+				$result['activated'][] = $user;
+			}
+		}
+
+		/**
+		 * Fires after activation of user accounts.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $signup_ids Array of IDs activated activate.
+		 * @param array $result     Array of data for activated accounts.
+		 */
+		do_action( 'bp_core_signup_after_activate', $signup_ids, $result );
+
+		/**
+		 * Filters the result of the metadata after user activation.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $result Updated metadata related to user activation.
+		 */
+		return apply_filters( 'bp_core_signup_activate', $result );
+	}
+
+	/**
+	 * Delete a pending account.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $signup_ids Single ID or list of IDs to delete.
+	 * @return array
+	 */
+	public static function delete( $signup_ids = array() ) {
+		global $wpdb;
+
+		if ( empty( $signup_ids ) || ! is_array( $signup_ids ) ) {
+			return false;
+		}
+
+		$to_delete = self::get( array(
+			'include' => $signup_ids,
+		) );
+
+		if ( ! $signups = $to_delete['signups'] ) {
+			return false;
+		}
+
+		$result = array();
+
+		/**
+		 * Fires before deletion of pending accounts.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $signup_ids Array of pending IDs to delete.
+		 */
+		do_action( 'bp_core_signup_before_delete', $signup_ids );
+
+		foreach ( $signups as $signup ) {
+			$user_id = username_exists( $signup->user_login );
+
+			if ( ! empty( $user_id ) && $signup->activation_key == wp_hash( $user_id ) ) {
+
+				if ( 2 != self::check_user_status( $user_id ) ) {
+
+					// Status is not 2, so user's account has been activated.
+					$result['errors'][ $signup->signup_id ] = array( $signup->user_login, esc_html__( 'the sign-up has already been activated.', 'buddypress' ) );
+
+					// Repair signups table.
+					self::validate( $signup->activation_key );
+
+				// We have a user id, account is not active, let's delete it.
+				} else {
+					bp_core_delete_account( $user_id );
+				}
+			}
+
+			if ( empty( $result['errors'][ $signup->signup_id ] ) ) {
+				$wpdb->delete(
+					// Signups table.
+					buddypress()->members->table_name_signups,
+					// Where.
+					array( 'signup_id' => $signup->signup_id, ),
+					// WHERE sanitization format.
+					array( '%d', )
+				);
+
+				$result['deleted'][] = $signup->signup_id;
+			}
+		}
+
+		/**
+		 * Fires after deletion of pending accounts.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $signup_ids Array of pending IDs to delete.
+		 * @param array $result     Array of data for deleted accounts.
+		 */
+		do_action( 'bp_core_signup_after_delete', $signup_ids, $result );
+
+		/**
+		 * Filters the result of the metadata for deleted pending accounts.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $result Updated metadata related to deleted pending accounts.
+		 */
+		return apply_filters( 'bp_core_signup_delete', $result );
+	}
+}
Index: /tags/2.4.3/src/bp-messages/bp-messages-actions.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-actions.php	(revision 10410)
@@ -0,0 +1,470 @@
+<?php
+/**
+ * BuddyPress Messages Actions.
+ *
+ * Action functions are exactly the same as screen functions, however they do
+ * not have a template screen associated with them. Usually they will send the
+ * user back to the default screen after execution.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesActions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Handle creating of private messages or sitewide notices
+ *
+ * @since 2.4.0 This function was split from messages_screen_compose(). See #6505.
+ *
+ * @return boolean
+ */
+function bp_messages_action_create_message() {
+
+	// Bail if not posting to the compose message screen.
+	if ( ! bp_is_post_request() || ! bp_is_messages_component() || ! bp_is_current_action( 'compose' ) ) {
+		return false;
+	}
+
+	// Check the nonce.
+	check_admin_referer( 'messages_send_message' );
+
+	// Define local variables.
+	$redirect_to = '';
+	$feedback    = '';
+	$success     = false;
+
+	// Missing subject or content.
+	if ( empty( $_POST['subject'] ) || empty( $_POST['content'] ) ) {
+		$success  = false;
+
+		if ( empty( $_POST['subject'] ) ) {
+			$feedback = __( 'Your message was not sent. Please enter a subject line.', 'buddypress' );
+		} else {
+			$feedback = __( 'Your message was not sent. Please enter some content.', 'buddypress' );
+		}
+
+	// Subject and content present.
+	} else {
+
+		// Setup the link to the logged-in user's messages.
+		$member_messages = trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() );
+
+		// Site-wide notice.
+		if ( isset( $_POST['send-notice'] ) ) {
+
+			// Attempt to save the notice and redirect to notices.
+			if ( messages_send_notice( $_POST['subject'], $_POST['content'] ) ) {
+				$success     = true;
+				$feedback    = __( 'Notice successfully created.', 'buddypress' );
+				$redirect_to = trailingslashit( $member_messages . 'notices' );
+
+			// Notice could not be sent.
+			} else {
+				$success  = false;
+				$feedback = __( 'Notice was not created. Please try again.', 'buddypress' );
+			}
+
+		// Private conversation.
+		} else {
+
+			// Filter recipients into the format we need - array( 'username/userid', 'username/userid' ).
+			$autocomplete_recipients = (array) explode( ',', $_POST['send-to-input']     );
+			$typed_recipients        = (array) explode( ' ', $_POST['send_to_usernames'] );
+			$recipients              = array_merge( $autocomplete_recipients, $typed_recipients );
+
+			/**
+			 * Filters the array of recipients to receive the composed message.
+			 *
+			 * @since 1.2.10
+			 *
+			 * @param array $recipients Array of recipients to receive message.
+			 */
+			$recipients = apply_filters( 'bp_messages_recipients', $recipients );
+
+			// Attempt to send the message.
+			$send = messages_new_message( array(
+				'recipients' => $recipients,
+				'subject'    => $_POST['subject'],
+				'content'    => $_POST['content'],
+				'error_type' => 'wp_error'
+			) );
+
+			// Send the message and redirect to it.
+			if ( true === is_int( $send ) ) {
+				$success     = true;
+				$feedback    = __( 'Message successfully sent.', 'buddypress' );
+				$view        = trailingslashit( $member_messages . 'view' );
+				$redirect_to = trailingslashit( $view . $send );
+
+			// Message could not be sent.
+			} else {
+				$success  = false;
+				$feedback = $send->get_error_message();
+			}
+		}
+	}
+
+	// Feedback.
+	if ( ! empty( $feedback ) ) {
+
+		// Determine message type.
+		$type = ( true === $success )
+			? 'success'
+			: 'error';
+
+		// Add feedback message.
+		bp_core_add_message( $feedback, $type );
+	}
+
+	// Maybe redirect.
+	if ( ! empty( $redirect_to ) ) {
+		bp_core_redirect( $redirect_to );
+	}
+}
+add_action( 'bp_actions', 'bp_messages_action_create_message' );
+
+/**
+ * Handle editing of sitewide notices.
+ *
+ * @since 2.4.0 This function was split from messages_screen_notices(). See #6505.
+ *
+ * @global int $notice_id
+ *
+ * @return boolean
+ */
+function bp_messages_action_edit_notice() {
+	global $notice_id;
+
+	// Bail if not viewing a single notice URL.
+	if ( ! bp_is_messages_component() || ! bp_is_current_action( 'notices' ) || ! bp_action_variable( 1 ) ) {
+		return false;
+	}
+
+	// Get action variables.
+	$action    = bp_action_variable( 0 ); // deactivate|activate|delete.
+	$notice_id = bp_action_variable( 1 ); // 1|2|3|etc...
+
+	// Bail if notice ID is not numeric.
+	if ( ! is_numeric( $notice_id ) ) {
+		return;
+	}
+
+	// Define local variables.
+	$redirect_to = '';
+	$feedback    = '';
+	$success     = false;
+
+	// Get the notice from database.
+	$notice = new BP_Messages_Notice( $notice_id );
+
+	// Take action.
+	switch ( $action ) {
+
+		// Deactivate.
+		case 'deactivate' :
+			$success  = $notice->deactivate();
+			$feedback = true === $success
+				? __( 'Notice deactivated successfully.',              'buddypress' )
+				: __( 'There was a problem deactivating that notice.', 'buddypress' );
+			break;
+
+		// Activate.
+		case 'activate' :
+			$success  = $notice->activate();
+			$feedback = true === $success
+				? __( 'Notice activated successfully.',              'buddypress' )
+				: __( 'There was a problem activating that notice.', 'buddypress' );
+			break;
+
+		// Delete.
+		case 'delete' :
+			$success  = $notice->delete();
+			$feedback = true === $success
+				? __( 'Notice deleted successfully.',              'buddypress' )
+				: __( 'There was a problem deleting that notice.', 'buddypress' );
+			break;
+	}
+
+	// Feedback.
+	if ( ! empty( $feedback ) ) {
+
+		// Determine message type.
+		$type = ( true === $success )
+			? 'success'
+			: 'error';
+
+		// Add feedback message.
+		bp_core_add_message( $feedback, $type );
+	}
+
+	// Redirect.
+	$member_notices = trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() );
+	$redirect_to    = trailingslashit( $member_notices . 'notices' );
+
+	bp_core_redirect( $redirect_to );
+}
+add_action( 'bp_actions', 'bp_messages_action_edit_notice' );
+
+/**
+ * Process a request to view a single message thread.
+ */
+function messages_action_conversation() {
+
+	// Bail if not viewing a single conversation.
+	if ( ! bp_is_messages_component() || ! bp_is_current_action( 'view' ) ) {
+		return false;
+	}
+
+	// Get the thread ID from the action variable.
+	$thread_id = (int) bp_action_variable( 0 );
+
+	if ( ! messages_is_valid_thread( $thread_id ) || ( ! messages_check_thread_access( $thread_id ) && ! bp_current_user_can( 'bp_moderate' ) ) ) {
+		bp_core_redirect( trailingslashit( bp_displayed_user_domain() . bp_get_messages_slug() ) );
+	}
+
+	// Check if a new reply has been submitted.
+	if ( isset( $_POST['send'] ) ) {
+
+		// Check the nonce.
+		check_admin_referer( 'messages_send_message', 'send_message_nonce' );
+
+		$new_reply = messages_new_message( array(
+			'thread_id' => $thread_id,
+			'subject'   => ! empty( $_POST['subject'] ) ? $_POST['subject'] : false,
+			'content'   => $_POST['content']
+		) );
+
+		// Send the reply.
+		if ( ! empty( $new_reply ) ) {
+			bp_core_add_message( __( 'Your reply was sent successfully', 'buddypress' ) );
+		} else {
+			bp_core_add_message( __( 'There was a problem sending your reply. Please try again.', 'buddypress' ), 'error' );
+		}
+
+		bp_core_redirect( bp_displayed_user_domain() . bp_get_messages_slug() . '/view/' . $thread_id . '/' );
+	}
+
+	// Mark message read.
+	messages_mark_thread_read( $thread_id );
+
+	/**
+	 * Fires after processing a view request for a single message thread.
+	 *
+	 * @since 1.7.0
+	 */
+	do_action( 'messages_action_conversation' );
+}
+add_action( 'bp_actions', 'messages_action_conversation' );
+
+/**
+ * Process a request to delete a message.
+ *
+ * @return bool False on failure.
+ */
+function messages_action_delete_message() {
+
+	if ( ! bp_is_messages_component() || bp_is_current_action( 'notices' ) || ! bp_is_action_variable( 'delete', 0 ) ) {
+		return false;
+	}
+
+	$thread_id = bp_action_variable( 1 );
+
+	if ( !$thread_id || !is_numeric( $thread_id ) || !messages_check_thread_access( $thread_id ) ) {
+		bp_core_redirect( trailingslashit( bp_displayed_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() ) );
+	} else {
+		if ( ! check_admin_referer( 'messages_delete_thread' ) ) {
+			return false;
+		}
+
+		// Delete message.
+		if ( !messages_delete_thread( $thread_id ) ) {
+			bp_core_add_message( __('There was an error deleting that message.', 'buddypress'), 'error' );
+		} else {
+			bp_core_add_message( __('Message deleted.', 'buddypress') );
+		}
+		bp_core_redirect( trailingslashit( bp_displayed_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() ) );
+	}
+}
+add_action( 'bp_actions', 'messages_action_delete_message' );
+
+/**
+ * Handle marking a single message thread as read.
+ *
+ * @since 2.2.0
+ *
+ * @return bool|null Returns false on failure. Otherwise redirects back to the
+ *                   message box URL.
+ */
+function bp_messages_action_mark_read() {
+
+	if ( ! bp_is_messages_component() || bp_is_current_action( 'notices' ) || ! bp_is_action_variable( 'read', 0 ) ) {
+		return false;
+	}
+
+	$action = ! empty( $_GET['action'] ) ? $_GET['action'] : '';
+	$nonce  = ! empty( $_GET['_wpnonce'] ) ? $_GET['_wpnonce'] : '';
+	$id     = ! empty( $_GET['message_id'] ) ? intval( $_GET['message_id'] ) : '';
+
+	// Bail if no action or no ID.
+	if ( 'read' !== $action || empty( $id ) || empty( $nonce ) ) {
+		return false;
+	}
+
+	// Check the nonce.
+	if ( ! bp_verify_nonce_request( 'bp_message_thread_mark_read_' . $id ) ) {
+		return false;
+	}
+
+	// Check access to the message and mark as read.
+	if ( messages_check_thread_access( $id ) ) {
+		messages_mark_thread_read( $id );
+		bp_core_add_message( __( 'Message marked as read.', 'buddypress' ) );
+	} else {
+		bp_core_add_message( __( 'There was a problem marking that message.', 'buddypress' ), 'error' );
+	}
+
+	// Redirect back to the message box.
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() );
+}
+add_action( 'bp_actions', 'bp_messages_action_mark_read' );
+
+/**
+ * Handle marking a single message thread as unread.
+ *
+ * @since 2.2.0
+ *
+ * @return bool|null Returns false on failure. Otherwise redirects back to the
+ *                   message box URL.
+ */
+function bp_messages_action_mark_unread() {
+
+	if ( ! bp_is_messages_component() || bp_is_current_action( 'notices' ) || ! bp_is_action_variable( 'unread', 0 ) ) {
+		return false;
+	}
+
+	$action = ! empty( $_GET['action'] ) ? $_GET['action'] : '';
+	$nonce  = ! empty( $_GET['_wpnonce'] ) ? $_GET['_wpnonce'] : '';
+	$id     = ! empty( $_GET['message_id'] ) ? intval( $_GET['message_id'] ) : '';
+
+	// Bail if no action or no ID.
+	if ( 'unread' !== $action || empty( $id ) || empty( $nonce ) ) {
+		return false;
+	}
+
+	// Check the nonce.
+	if ( ! bp_verify_nonce_request( 'bp_message_thread_mark_unread_' . $id ) ) {
+		return false;
+	}
+
+	// Check access to the message and mark unread.
+	if ( messages_check_thread_access( $id ) ) {
+		messages_mark_thread_unread( $id );
+		bp_core_add_message( __( 'Message marked unread.', 'buddypress' ) );
+	} else {
+		bp_core_add_message( __( 'There was a problem marking that message.', 'buddypress' ), 'error' );
+	}
+
+	// Redirect back to the message box URL.
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() );
+}
+add_action( 'bp_actions', 'bp_messages_action_mark_unread' );
+
+/**
+ * Handle bulk management (mark as read/unread, delete) of message threads.
+ *
+ * @since 2.2.0
+ *
+ * @return bool Returns false on failure. Otherwise redirects back to the
+ *              message box URL.
+ */
+function bp_messages_action_bulk_manage() {
+
+	if ( ! bp_is_messages_component() || bp_is_current_action( 'notices' ) || ! bp_is_action_variable( 'bulk-manage', 0 ) ) {
+		return false;
+	}
+
+	$action   = ! empty( $_POST['messages_bulk_action'] ) ? $_POST['messages_bulk_action'] : '';
+	$nonce    = ! empty( $_POST['messages_bulk_nonce'] ) ? $_POST['messages_bulk_nonce'] : '';
+	$messages = ! empty( $_POST['message_ids'] ) ? $_POST['message_ids'] : '';
+
+	$messages = wp_parse_id_list( $messages );
+
+	// Bail if no action or no IDs.
+	if ( ( ! in_array( $action, array( 'delete', 'read', 'unread' ) ) ) || empty( $messages ) || empty( $nonce ) ) {
+		bp_core_redirect( bp_displayed_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() . '/' );
+	}
+
+	// Check the nonce.
+	if ( ! wp_verify_nonce( $nonce, 'messages_bulk_nonce' ) ) {
+		return false;
+	}
+
+	// Make sure the user has access to all notifications before managing them.
+	foreach ( $messages as $message ) {
+		if ( ! messages_check_thread_access( $message ) ) {
+			bp_core_add_message( __( 'There was a problem managing your messages.', 'buddypress' ), 'error' );
+			bp_core_redirect( bp_displayed_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() . '/' );
+		}
+	}
+
+	// Delete, mark as read or unread depending on the user 'action'.
+	switch ( $action ) {
+		case 'delete' :
+			foreach ( $messages as $message ) {
+				messages_delete_thread( $message );
+			}
+			bp_core_add_message( __( 'Messages deleted.', 'buddypress' ) );
+		break;
+
+		case 'read' :
+			foreach ( $messages as $message ) {
+				messages_mark_thread_read( $message );
+			}
+			bp_core_add_message( __( 'Messages marked as read', 'buddypress' ) );
+		break;
+
+		case 'unread' :
+			foreach ( $messages as $message ) {
+				messages_mark_thread_unread( $message );
+			}
+			bp_core_add_message( __( 'Messages marked as unread.', 'buddypress' ) );
+		break;
+	}
+
+	// Redirect back to message box.
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() . '/' );
+}
+add_action( 'bp_actions', 'bp_messages_action_bulk_manage' );
+
+/**
+ * Process a request to bulk delete messages.
+ *
+ * @return bool False on failure.
+ */
+function messages_action_bulk_delete() {
+
+	if ( ! bp_is_messages_component() || ! bp_is_action_variable( 'bulk-delete', 0 ) ) {
+		return false;
+	}
+
+	$thread_ids = $_POST['thread_ids'];
+
+	if ( !$thread_ids || !messages_check_thread_access( $thread_ids ) ) {
+		bp_core_redirect( trailingslashit( bp_displayed_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() ) );
+	} else {
+		if ( !check_admin_referer( 'messages_delete_thread' ) ) {
+			return false;
+		}
+
+		if ( !messages_delete_thread( $thread_ids ) ) {
+			bp_core_add_message( __('There was an error deleting messages.', 'buddypress'), 'error' );
+		} else {
+			bp_core_add_message( __('Messages deleted.', 'buddypress') );
+		}
+
+		bp_core_redirect( trailingslashit( bp_displayed_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() ) );
+	}
+}
+add_action( 'bp_actions', 'messages_action_bulk_delete' );
Index: /tags/2.4.3/src/bp-messages/bp-messages-cache.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-cache.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-cache.php	(revision 10410)
@@ -0,0 +1,102 @@
+<?php
+/**
+ * BuddyPress Messages Caching.
+ *
+ * Caching functions handle the clearing of cached objects and pages on specific
+ * actions throughout BuddyPress.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesCache
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Slurp up metadata for a set of messages.
+ *
+ * It grabs all message meta associated with all of the messages passed in
+ * $message_ids and adds it to WP cache. This improves efficiency when using
+ * message meta within a loop context.
+ *
+ * @since 2.2.0
+ *
+ * @param int|string|array|bool $message_ids Accepts a single message_id, or a
+ *                                           comma-separated list or array of message ids.
+ */
+function bp_messages_update_meta_cache( $message_ids = false ) {
+	bp_update_meta_cache( array(
+		'object_ids' 	   => $message_ids,
+		'object_type' 	   => buddypress()->messages->id,
+		'cache_group'      => 'message_meta',
+		'object_column'    => 'message_id',
+		'meta_table' 	   => buddypress()->messages->table_name_meta,
+		'cache_key_prefix' => 'bp_messages_meta'
+	) );
+}
+
+// List actions to clear super cached pages on, if super cache is installed.
+add_action( 'messages_delete_thread',  'bp_core_clear_cache' );
+add_action( 'messages_send_notice',    'bp_core_clear_cache' );
+add_action( 'messages_message_sent',   'bp_core_clear_cache' );
+
+// Don't cache message inbox/sentbox/compose as it's too problematic.
+add_action( 'messages_screen_compose', 'bp_core_clear_cache' );
+add_action( 'messages_screen_sentbox', 'bp_core_clear_cache' );
+add_action( 'messages_screen_inbox',   'bp_core_clear_cache' );
+
+/**
+ * Clear message cache after a message is saved.
+ *
+ * @since 2.0.0
+ *
+ * @param BP_Messages_Message $message Message being saved.
+ */
+function bp_messages_clear_cache_on_message_save( BP_Messages_Message $message ) {
+	// Delete thread cache.
+	wp_cache_delete( $message->thread_id, 'bp_messages_threads' );
+
+	// Delete unread count for each recipient.
+	foreach ( (array) $message->recipients as $recipient ) {
+		wp_cache_delete( $recipient->user_id, 'bp_messages_unread_count' );
+	}
+
+	// Delete thread recipient cache.
+	wp_cache_delete( 'thread_recipients_' . $message->thread_id, 'bp_messages' );
+}
+add_action( 'messages_message_after_save', 'bp_messages_clear_cache_on_message_save' );
+
+/**
+ * Clear message cache after a message thread is deleted.
+ *
+ * @since 2.0.0
+ *
+ * @param int|array $thread_ids If single thread, the thread ID.
+ *                              Otherwise, an array of thread IDs.
+ */
+function bp_messages_clear_cache_on_message_delete( $thread_ids ) {
+	// Delete thread and thread recipient cache.
+	foreach( (array) $thread_ids as $thread_id ) {
+		wp_cache_delete( $thread_id, 'bp_messages_threads' );
+		wp_cache_delete( "thread_recipients_{$thread_id}", 'bp_messages' );
+	}
+
+	// Delete unread count for logged-in user.
+	wp_cache_delete( bp_loggedin_user_id(), 'bp_messages_unread_count' );
+}
+add_action( 'messages_delete_thread', 'bp_messages_clear_cache_on_message_delete' );
+
+/**
+ * Invalidate cache for notices.
+ *
+ * Currently, invalidates active notice cache.
+ *
+ * @since 2.0.0
+ *
+ * @param BP_Messages_Notice $notice Notice that was saved.
+ */
+function bp_notices_clear_cache( $notice ) {
+	wp_cache_delete( 'active_notice', 'bp_messages' );
+}
+add_action( 'messages_notice_after_save',    'bp_notices_clear_cache' );
+add_action( 'messages_notice_before_delete', 'bp_notices_clear_cache' );
Index: /tags/2.4.3/src/bp-messages/bp-messages-classes.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-classes.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-classes.php	(revision 10410)
@@ -0,0 +1,14 @@
+<?php
+/**
+ * BuddyPress Messages Classes.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+require dirname( __FILE__ ) . '/classes/class-bp-messages-thread.php';
+require dirname( __FILE__ ) . '/classes/class-bp-messages-message.php';
+require dirname( __FILE__ ) . '/classes/class-bp-messages-notice.php';
Index: /tags/2.4.3/src/bp-messages/bp-messages-cssjs.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-cssjs.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-cssjs.php	(revision 10410)
@@ -0,0 +1,62 @@
+<?php
+/**
+ * BuddyPress Messages CSS and JS.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesScripts
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Enqueue the JS for messages autocomplete.
+ */
+function messages_add_autocomplete_js() {
+
+	// Include the autocomplete JS for composing a message.
+	if ( bp_is_messages_component() && bp_is_current_action( 'compose' ) ) {
+		add_action( 'wp_head', 'messages_autocomplete_init_jsblock' );
+
+		wp_enqueue_script( 'bp-jquery-autocomplete' );
+		wp_enqueue_script( 'bp-jquery-autocomplete-fb' );
+		wp_enqueue_script( 'bp-jquery-bgiframe' );
+		wp_enqueue_script( 'bp-jquery-dimensions' );
+	}
+}
+add_action( 'bp_enqueue_scripts', 'messages_add_autocomplete_js' );
+
+/**
+ * Enqueue the CSS for messages autocomplete.
+ *
+ * @todo Why do we call wp_print_styles()?
+ */
+function messages_add_autocomplete_css() {
+	if ( bp_is_messages_component() && bp_is_current_action( 'compose' ) ) {
+		wp_enqueue_style( 'bp-messages-autocomplete' );
+		wp_print_styles();
+	}
+}
+add_action( 'wp_head', 'messages_add_autocomplete_css' );
+
+/**
+ * Print inline JS for initializing the messages autocomplete.
+ *
+ * @todo Why is this here and not in a properly enqueued file?
+ */
+function messages_autocomplete_init_jsblock() {
+?>
+
+	<script type="text/javascript">
+		jQuery(document).ready(function() {
+			var acfb = jQuery("ul.first").autoCompletefb({urlLookup: ajaxurl});
+
+			jQuery('#send_message_form').submit( function() {
+				var users = document.getElementById('send-to-usernames').className;
+				document.getElementById('send-to-usernames').value = String(users);
+			});
+		});
+	</script>
+
+<?php
+}
Index: /tags/2.4.3/src/bp-messages/bp-messages-filters.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-filters.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-filters.php	(revision 10410)
@@ -0,0 +1,92 @@
+<?php
+/**
+ * BuddyPress Messages Filters.
+ *
+ * Apply WordPress defined filters to private messages.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesFilters
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+add_filter( 'bp_get_message_notice_subject',        'wp_filter_kses', 1 );
+add_filter( 'bp_get_message_notice_text',           'wp_filter_kses', 1 );
+add_filter( 'bp_get_message_thread_subject',        'wp_filter_kses', 1 );
+add_filter( 'bp_get_message_thread_excerpt',        'wp_filter_kses', 1 );
+add_filter( 'bp_get_messages_subject_value',        'wp_filter_kses', 1 );
+add_filter( 'bp_get_messages_content_value',        'wp_filter_kses', 1 );
+add_filter( 'bp_get_the_thread_message_content',    'wp_filter_kses', 1 );
+
+add_filter( 'messages_message_content_before_save', 'wp_filter_kses', 1 );
+add_filter( 'messages_message_subject_before_save', 'wp_filter_kses', 1 );
+add_filter( 'messages_notice_message_before_save',  'wp_filter_kses', 1 );
+add_filter( 'messages_notice_subject_before_save',  'wp_filter_kses', 1 );
+
+add_filter( 'bp_get_the_thread_message_content',    'wp_filter_kses', 1 );
+add_filter( 'bp_get_the_thread_subject',            'wp_filter_kses', 1 );
+
+add_filter( 'messages_message_content_before_save', 'force_balance_tags' );
+add_filter( 'messages_message_subject_before_save', 'force_balance_tags' );
+add_filter( 'messages_notice_message_before_save',  'force_balance_tags' );
+add_filter( 'messages_notice_subject_before_save',  'force_balance_tags' );
+
+add_filter( 'bp_get_message_notice_subject',     'wptexturize' );
+add_filter( 'bp_get_message_notice_text',        'wptexturize' );
+add_filter( 'bp_get_message_thread_subject',     'wptexturize' );
+add_filter( 'bp_get_message_thread_excerpt',     'wptexturize' );
+add_filter( 'bp_get_the_thread_message_content', 'wptexturize' );
+
+add_filter( 'bp_get_message_notice_subject',     'convert_smilies', 2 );
+add_filter( 'bp_get_message_notice_text',        'convert_smilies', 2 );
+add_filter( 'bp_get_message_thread_subject',     'convert_smilies', 2 );
+add_filter( 'bp_get_message_thread_excerpt',     'convert_smilies', 2 );
+add_filter( 'bp_get_the_thread_message_content', 'convert_smilies', 2 );
+
+add_filter( 'bp_get_message_notice_subject',     'convert_chars' );
+add_filter( 'bp_get_message_notice_text',        'convert_chars' );
+add_filter( 'bp_get_message_thread_subject',     'convert_chars' );
+add_filter( 'bp_get_message_thread_excerpt',     'convert_chars' );
+add_filter( 'bp_get_the_thread_message_content', 'convert_chars' );
+
+add_filter( 'bp_get_message_notice_text',        'make_clickable', 9 );
+add_filter( 'bp_get_the_thread_message_content', 'make_clickable', 9 );
+
+add_filter( 'bp_get_message_notice_text',        'wpautop' );
+add_filter( 'bp_get_the_thread_message_content', 'wpautop' );
+
+add_filter( 'bp_get_message_notice_subject',          'stripslashes_deep' );
+add_filter( 'bp_get_message_notice_text',             'stripslashes_deep' );
+add_filter( 'bp_get_message_thread_subject',          'stripslashes_deep' );
+add_filter( 'bp_get_message_thread_excerpt',          'stripslashes_deep' );
+add_filter( 'bp_get_message_get_recipient_usernames', 'stripslashes_deep' );
+add_filter( 'bp_get_messages_subject_value',          'stripslashes_deep' );
+add_filter( 'bp_get_messages_content_value',          'stripslashes_deep' );
+add_filter( 'bp_get_the_thread_message_content',      'stripslashes_deep' );
+add_filter( 'bp_get_the_thread_subject',              'stripslashes_deep' );
+
+/**
+ * Enforce limitations on viewing private message contents
+ *
+ * @since 2.3.2
+ *
+ * @see bp_has_message_threads() for description of parameters
+ *
+ * @param array|string $args See {@link bp_has_message_threads()}.
+ * @return array
+ */
+function bp_messages_enforce_current_user( $args = array() ) {
+
+	// Non-community moderators can only ever see their own messages.
+	if ( is_user_logged_in() && ! bp_current_user_can( 'bp_moderate' ) ) {
+		$_user_id = (int) bp_loggedin_user_id();
+		if ( $_user_id !== (int) $args['user_id'] ) {
+			$args['user_id'] = $_user_id;
+		}
+	}
+
+	// Return possibly modified $args array.
+	return $args;
+}
+add_filter( 'bp_after_has_message_threads_parse_args', 'bp_messages_enforce_current_user', 5 );
Index: /tags/2.4.3/src/bp-messages/bp-messages-functions.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-functions.php	(revision 10410)
@@ -0,0 +1,516 @@
+<?php
+/**
+ * BuddyPress Messages Functions.
+ *
+ * Business functions are where all the magic happens in BuddyPress. They will
+ * handle the actual saving or manipulation of information. Usually they will
+ * hand off to a database class for data access, then return
+ * true or false on success or failure.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Create a new message.
+ *
+ * @since 2.4.0 Added 'error_type' as an additional $args parameter.
+ *
+ * @param array|string $args {
+ *     Array of arguments.
+ *     @type int    $sender_id  Optional. ID of the user who is sending the
+ *                              message. Default: ID of the logged-in user.
+ *     @type int    $thread_id  Optional. ID of the parent thread. Leave blank to
+ *                              create a new thread for the message.
+ *     @type array  $recipients IDs or usernames of message recipients. If this
+ *                              is an existing thread, it is unnecessary to pass a $recipients
+ *                              argument - existing thread recipients will be assumed.
+ *     @type string $subject    Optional. Subject line for the message. For
+ *                              existing threads, the existing subject will be used. For new
+ *                              threads, 'No Subject' will be used if no $subject is provided.
+ *     @type string $content    Content of the message. Cannot be empty.
+ *     @type string $date_sent  Date sent, in 'Y-m-d H:i:s' format. Default: current date/time.
+ *     @type string $error_type Optional. Error type. Either 'bool' or 'wp_error'. Default: 'bool'.
+ * }
+ * @return int|bool ID of the message thread on success, false on failure.
+ */
+function messages_new_message( $args = '' ) {
+
+	// Parse the default arguments.
+	$r = bp_parse_args( $args, array(
+		'sender_id'  => bp_loggedin_user_id(),
+		'thread_id'  => false,   // False for a new message, thread id for a reply to a thread.
+		'recipients' => array(), // Can be an array of usernames, user_ids or mixed.
+		'subject'    => false,
+		'content'    => false,
+		'date_sent'  => bp_core_current_time(),
+		'error_type' => 'bool'
+	), 'messages_new_message' );
+
+	// Bail if no sender or no content.
+	if ( empty( $r['sender_id'] ) || empty( $r['content'] ) ) {
+		if ( 'wp_error' === $r['error_type'] ) {
+			if ( empty( $r['sender_id'] ) ) {
+				$error_code = 'messages_empty_sender';
+				$feedback = __( 'Your message was not sent. Please use a valid sender.', 'buddypress' );
+			} else {
+				$error_code = 'messages_empty_content';
+				$feedback = __( 'Your message was not sent. Please enter some content.', 'buddypress' );
+			}
+
+			return new WP_Error( $error_code, $feedback );
+
+		} else {
+			return false;
+		}
+	}
+
+	// Create a new message object.
+	$message            = new BP_Messages_Message;
+	$message->thread_id = $r['thread_id'];
+	$message->sender_id = $r['sender_id'];
+	$message->subject   = $r['subject'];
+	$message->message   = $r['content'];
+	$message->date_sent = $r['date_sent'];
+
+	// If we have a thread ID...
+	if ( ! empty( $r['thread_id'] ) ) {
+
+		// ...use the existing recipients
+		$thread              = new BP_Messages_Thread( $r['thread_id'] );
+		$message->recipients = $thread->get_recipients();
+
+		// Strip the sender from the recipient list, and unset them if they are
+		// not alone. If they are alone, let them talk to themselves.
+		if ( isset( $message->recipients[ $r['sender_id'] ] ) && ( count( $message->recipients ) > 1 ) ) {
+			unset( $message->recipients[ $r['sender_id'] ] );
+		}
+
+		// Set a default reply subject if none was sent.
+		if ( empty( $message->subject ) ) {
+			$message->subject = sprintf( __( 'Re: %s', 'buddypress' ), $thread->messages[0]->subject );
+		}
+
+	// ...otherwise use the recipients passed
+	} else {
+
+		// Bail if no recipients.
+		if ( empty( $r['recipients'] ) ) {
+			if ( 'wp_error' === $r['error_type'] ) {
+				return new WP_Error( 'message_empty_recipients', __( 'Message could not be sent. Please enter a recipient.', 'buddypress' ) );
+			} else {
+				return false;
+			}
+		}
+
+		// Set a default subject if none exists.
+		if ( empty( $message->subject ) ) {
+			$message->subject = __( 'No Subject', 'buddypress' );
+		}
+
+		// Setup the recipients array.
+		$recipient_ids 	    = array();
+
+		// Invalid recipients are added to an array, for future enhancements.
+		$invalid_recipients = array();
+
+		// Loop the recipients and convert all usernames to user_ids where needed.
+		foreach( (array) $r['recipients'] as $recipient ) {
+
+			// Trim spaces and skip if empty.
+			$recipient = trim( $recipient );
+			if ( empty( $recipient ) ) {
+				continue;
+			}
+
+			// Check user_login / nicename columns first
+			// @see http://buddypress.trac.wordpress.org/ticket/5151.
+			if ( bp_is_username_compatibility_mode() ) {
+				$recipient_id = bp_core_get_userid( urldecode( $recipient ) );
+			} else {
+				$recipient_id = bp_core_get_userid_from_nicename( $recipient );
+			}
+
+			// Check against user ID column if no match and if passed recipient is numeric.
+			if ( empty( $recipient_id ) && is_numeric( $recipient ) ) {
+				if ( bp_core_get_core_userdata( (int) $recipient ) ) {
+					$recipient_id = (int) $recipient;
+				}
+			}
+
+			// Decide which group to add this recipient to.
+			if ( empty( $recipient_id ) ) {
+				$invalid_recipients[] = $recipient;
+			} else {
+				$recipient_ids[] = (int) $recipient_id;
+			}
+		}
+
+		// Strip the sender from the recipient list, and unset them if they are
+		// not alone. If they are alone, let them talk to themselves.
+		$self_send = array_search( $r['sender_id'], $recipient_ids );
+		if ( ! empty( $self_send ) && ( count( $recipient_ids ) > 1 ) ) {
+			unset( $recipient_ids[ $self_send ] );
+		}
+
+		// Remove duplicates & bail if no recipients.
+		$recipient_ids = array_unique( $recipient_ids );
+		if ( empty( $recipient_ids ) ) {
+			if ( 'wp_error' === $r['error_type'] ) {
+				return new WP_Error( 'message_invalid_recipients', __( 'Message could not be sent because you have entered an invalid username. Please try again.', 'buddypress' ) );
+			} else {
+				return false;
+			}
+		}
+
+		// Format this to match existing recipients.
+		foreach( (array) $recipient_ids as $i => $recipient_id ) {
+			$message->recipients[$i]          = new stdClass;
+			$message->recipients[$i]->user_id = $recipient_id;
+		}
+	}
+
+	// Bail if message failed to send.
+	if ( ! $message->send() ) {
+		return false;
+	}
+
+	/**
+	 * Fires after a message has been successfully sent.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param BP_Messages_Message $message Message object. Passed by reference.
+	 */
+	do_action_ref_array( 'messages_message_sent', array( &$message ) );
+
+	// Return the thread ID.
+	return $message->thread_id;
+}
+
+/**
+ * Send a notice.
+ *
+ * @param string $subject Subject of the notice.
+ * @param string $message Content of the notice.
+ * @return bool True on success, false on failure.
+ */
+function messages_send_notice( $subject, $message ) {
+	if ( !bp_current_user_can( 'bp_moderate' ) || empty( $subject ) || empty( $message ) ) {
+		return false;
+
+	// Has access to send notices, lets do it.
+	} else {
+		$notice            = new BP_Messages_Notice;
+		$notice->subject   = $subject;
+		$notice->message   = $message;
+		$notice->date_sent = bp_core_current_time();
+		$notice->is_active = 1;
+		$notice->save(); // Send it.
+
+		/**
+		 * Fires after a notice has been successfully sent.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $subject Subject of the notice.
+		 * @param string $message Content of the notice.
+		 */
+		do_action_ref_array( 'messages_send_notice', array( $subject, $message ) );
+
+		return true;
+	}
+}
+
+/**
+ * Delete message thread(s).
+ *
+ * @param int|array $thread_ids Thread ID or array of thread IDs.
+ * @return bool True on success, false on failure.
+ */
+function messages_delete_thread( $thread_ids ) {
+
+	/**
+	 * Fires before specified thread IDs have been deleted.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int|array Thread ID or array of thread IDs that were deleted.
+	 */
+	do_action( 'messages_before_delete_thread', $thread_ids );
+
+	if ( is_array( $thread_ids ) ) {
+		$error = 0;
+		for ( $i = 0, $count = count( $thread_ids ); $i < $count; ++$i ) {
+			if ( ! BP_Messages_Thread::delete( $thread_ids[$i] ) ) {
+				$error = 1;
+			}
+		}
+
+		if ( ! empty( $error ) ) {
+			return false;
+		}
+
+		/**
+		 * Fires after specified thread IDs have been deleted.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int|array Thread ID or array of thread IDs that were deleted.
+		 */
+		do_action( 'messages_delete_thread', $thread_ids );
+
+		return true;
+	} else {
+		if ( ! BP_Messages_Thread::delete( $thread_ids ) ) {
+			return false;
+		}
+
+		/** This action is documented in bp-messages/bp-messages-functions.php */
+		do_action( 'messages_delete_thread', $thread_ids );
+
+		return true;
+	}
+}
+
+/**
+ * Check whether a user has access to a thread.
+ *
+ * @param int $thread_id ID of the thread.
+ * @param int $user_id   Optional. ID of the user. Default: ID of the logged-in user.
+ * @return int|null Message ID if the user has access, otherwise null.
+ */
+function messages_check_thread_access( $thread_id, $user_id = 0 ) {
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	return BP_Messages_Thread::check_access( $thread_id, $user_id );
+}
+
+/**
+ * Mark a thread as read.
+ *
+ * Wrapper for {@link BP_Messages_Thread::mark_as_read()}.
+ *
+ * @param int $thread_id ID of the thread.
+ */
+function messages_mark_thread_read( $thread_id ) {
+	return BP_Messages_Thread::mark_as_read( $thread_id );
+}
+
+/**
+ * Mark a thread as unread.
+ *
+ * Wrapper for {@link BP_Messages_Thread::mark_as_unread()}.
+ *
+ * @param int $thread_id ID of the thread.
+ */
+function messages_mark_thread_unread( $thread_id ) {
+	return BP_Messages_Thread::mark_as_unread( $thread_id );
+}
+
+/**
+ * Set messages-related cookies.
+ *
+ * Saves the 'bp_messages_send_to', 'bp_messages_subject', and
+ * 'bp_messages_content' cookies, which are used when setting up the default
+ * values on the messages page.
+ *
+ * @param string $recipients Comma-separated list of recipient usernames.
+ * @param string $subject    Subject of the message.
+ * @param string $content    Content of the message.
+ */
+function messages_add_callback_values( $recipients, $subject, $content ) {
+	@setcookie( 'bp_messages_send_to', $recipients, time() + 60 * 60 * 24, COOKIEPATH );
+	@setcookie( 'bp_messages_subject', $subject,    time() + 60 * 60 * 24, COOKIEPATH );
+	@setcookie( 'bp_messages_content', $content,    time() + 60 * 60 * 24, COOKIEPATH );
+}
+
+/**
+ * Unset messages-related cookies.
+ *
+ * @see messages_add_callback_values()
+ */
+function messages_remove_callback_values() {
+	@setcookie( 'bp_messages_send_to', false, time() - 1000, COOKIEPATH );
+	@setcookie( 'bp_messages_subject', false, time() - 1000, COOKIEPATH );
+	@setcookie( 'bp_messages_content', false, time() - 1000, COOKIEPATH );
+}
+
+/**
+ * Get the unread messages count for a user.
+ *
+ * @param int $user_id Optional. ID of the user. Default: ID of the logged-in user.
+ * @return int
+ */
+function messages_get_unread_count( $user_id = 0 ) {
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	return BP_Messages_Thread::get_inbox_count( $user_id );
+}
+
+/**
+ * Check whether a user is the sender of a message.
+ *
+ * @param int $user_id    ID of the user.
+ * @param int $message_id ID of the message.
+ * @return int|null Returns the ID of the message if the user is the
+ *                  sender, otherwise null.
+ */
+function messages_is_user_sender( $user_id, $message_id ) {
+	return BP_Messages_Message::is_user_sender( $user_id, $message_id );
+}
+
+/**
+ * Get the ID of the sender of a message.
+ *
+ * @param int $message_id ID of the message.
+ * @return int|null The ID of the sender if found, otherwise null.
+ */
+function messages_get_message_sender( $message_id ) {
+	return BP_Messages_Message::get_message_sender( $message_id );
+}
+
+/**
+ * Check whether a message thread exists.
+ *
+ * @param int $thread_id ID of the thread.
+ * @return int|null The message thread ID on success, null on failure.
+ */
+function messages_is_valid_thread( $thread_id ) {
+	return BP_Messages_Thread::is_valid( $thread_id );
+}
+
+/**
+ * Get the thread ID from a message ID.
+ *
+ * @since 2.3.0
+ *
+ * @param  int $message_id ID of the message.
+ * @return int The ID of the thread if found, otherwise 0.
+ */
+function messages_get_message_thread_id( $message_id = 0 ) {
+	global $wpdb;
+
+	$bp = buddypress();
+
+	return (int) $wpdb->get_var( $wpdb->prepare( "SELECT thread_id FROM {$bp->messages->table_name_messages} WHERE id = %d", $message_id ) );
+}
+
+/** Messages Meta *******************************************************/
+
+/**
+ * Delete metadata for a message.
+ *
+ * If $meta_key is false, this will delete all meta for the message ID.
+ *
+ * @since 2.2.0
+ *
+ * @see delete_metadata() for full documentation excluding $meta_type variable.
+ *
+ * @param int         $message_id ID of the message to have meta deleted for.
+ * @param string|bool $meta_key   Meta key to delete. Default false.
+ * @param string|bool $meta_value Meta value to delete. Default false.
+ * @param bool        $delete_all Whether or not to delete all meta data.
+ * @return bool
+ */
+function bp_messages_delete_meta( $message_id, $meta_key = false, $meta_value = false, $delete_all = false ) {
+	// Legacy - if no meta_key is passed, delete all for the item.
+	if ( empty( $meta_key ) ) {
+		global $wpdb;
+
+		$keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->messagemeta} WHERE message_id = %d", $message_id ) );
+
+		// With no meta_key, ignore $delete_all.
+		$delete_all = false;
+	} else {
+		$keys = array( $meta_key );
+	}
+
+	// No keys, so stop now!
+	if ( empty( $keys ) ) {
+		return false;
+	}
+
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	foreach ( $keys as $key ) {
+		$retval = delete_metadata( 'message', $message_id, $key, $meta_value, $delete_all );
+	}
+
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Get a piece of message metadata.
+ *
+ * @since 2.2.0
+ *
+ * @see get_metadata() for full documentation excluding $meta_type variable.
+ *
+ * @param int    $message_id ID of the message to retrieve meta for.
+ * @param string $meta_key   Meta key to retrieve. Default empty string.
+ * @param bool   $single     Whether or not to fetch all or a single value.
+ * @return mixed
+ */
+function bp_messages_get_meta( $message_id, $meta_key = '', $single = true ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = get_metadata( 'message', $message_id, $meta_key, $single );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Update a piece of message metadata.
+ *
+ * @since 2.2.0
+ *
+ * @see update_metadata() for full documentation excluding $meta_type variable.
+ *
+ * @param int         $message_id ID of the message to have meta deleted for.
+ * @param string|bool $meta_key   Meta key to update.
+ * @param string|bool $meta_value Meta value to update.
+ * @param string      $prev_value If specified, only update existing metadata entries with
+ *                                the specified value. Otherwise, update all entries.
+ * @return mixed
+ */
+function bp_messages_update_meta( $message_id, $meta_key, $meta_value, $prev_value = '' ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = update_metadata( 'message', $message_id, $meta_key, $meta_value, $prev_value );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Add a piece of message metadata.
+ *
+ * @since 2.2.0
+ *
+ * @see add_metadata() for full documentation excluding $meta_type variable.
+ *
+ * @param int         $message_id ID of the message to have meta deleted for.
+ * @param string|bool $meta_key   Meta key to update.
+ * @param string|bool $meta_value Meta value to update.
+ * @param bool        $unique     Whether the specified metadata key should be
+ *                                unique for the object. If true, and the object
+ *                                already has a value for the specified metadata key,
+ *                                no change will be made.
+ * @return mixed
+ */
+function bp_messages_add_meta( $message_id, $meta_key, $meta_value, $unique = false ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = add_metadata( 'message', $message_id, $meta_key, $meta_value, $unique );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
Index: /tags/2.4.3/src/bp-messages/bp-messages-loader.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-loader.php	(revision 10410)
@@ -0,0 +1,349 @@
+<?php
+/**
+ * BuddyPress Messages Loader.
+ *
+ * A private messages component, for users to send messages to each other.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesLoader
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Implementation of BP_Component for the Messages component.
+ *
+ * @since 1.5.0
+ */
+class BP_Messages_Component extends BP_Component {
+
+	/**
+	 * If this is true, the Message autocomplete will return friends only, unless
+	 * this is set to false, in which any matching users will be returned.
+	 *
+	 * @since 1.5.0
+	 * @var bool
+	 */
+	public $autocomplete_all;
+
+	/**
+	 * Start the messages component creation process.
+	 *
+	 * @since 1.5.0
+	 */
+	public function __construct() {
+		parent::start(
+			'messages',
+			__( 'Private Messages', 'buddypress' ),
+			buddypress()->plugin_dir,
+			array(
+				'adminbar_myaccount_order' => 50,
+				'features'                 => array( 'star' )
+			)
+		);
+	}
+
+	/**
+	 * Include files.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $includes See {BP_Component::includes()} for details.
+	 */
+	public function includes( $includes = array() ) {
+
+		// Files to include.
+		$includes = array(
+			'cssjs',
+			'cache',
+			'actions',
+			'screens',
+			'classes',
+			'filters',
+			'template',
+			'functions',
+			'notifications',
+			'widgets',
+		);
+
+		// Conditional includes.
+		if ( bp_is_active( $this->id, 'star' ) ) {
+			$includes[] = 'star';
+		}
+
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Set up globals for the Messages component.
+	 *
+	 * The BP_MESSAGES_SLUG constant is deprecated, and only used here for
+	 * backwards compatibility.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $args Not used.
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		// Define a slug, if necessary.
+		if ( ! defined( 'BP_MESSAGES_SLUG' ) ) {
+			define( 'BP_MESSAGES_SLUG', $this->id );
+		}
+
+		// Global tables for messaging component.
+		$global_tables = array(
+			'table_name_notices'    => $bp->table_prefix . 'bp_messages_notices',
+			'table_name_messages'   => $bp->table_prefix . 'bp_messages_messages',
+			'table_name_recipients' => $bp->table_prefix . 'bp_messages_recipients',
+			'table_name_meta'       => $bp->table_prefix . 'bp_messages_meta',
+		);
+
+		// Metadata tables for messaging component.
+		$meta_tables = array(
+			'message' => $bp->table_prefix . 'bp_messages_meta',
+		);
+
+		$this->autocomplete_all = defined( 'BP_MESSAGES_AUTOCOMPLETE_ALL' );
+
+		// All globals for messaging component.
+		// Note that global_tables is included in this array.
+		parent::setup_globals( array(
+			'slug'                  => BP_MESSAGES_SLUG,
+			'has_directory'         => false,
+			'notification_callback' => 'messages_format_notifications',
+			'search_string'         => __( 'Search Messages...', 'buddypress' ),
+			'global_tables'         => $global_tables,
+			'meta_tables'           => $meta_tables
+		) );
+	}
+
+	/**
+	 * Set up navigation for user pages.
+	 *
+	 * @param array $main_nav See {BP_Component::setup_nav()} for details.
+	 * @param array $sub_nav  See {BP_Component::setup_nav()} for details.
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		// Determine user to use.
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		$access        = bp_core_can_edit_settings();
+		$slug          = bp_get_messages_slug();
+		$messages_link = trailingslashit( $user_domain . $slug );
+
+		// Only grab count if we're on a user page and current user has access.
+		if ( bp_is_user() && bp_user_has_access() ) {
+			$count    = bp_get_total_unread_messages_count();
+			$class    = ( 0 === $count ) ? 'no-count' : 'count';
+			$nav_name = sprintf( __( 'Messages <span class="%s">%s</span>', 'buddypress' ), esc_attr( $class ), bp_core_number_format( $count ) );
+		} else {
+			$nav_name = __( 'Messages', 'buddypress' );
+		}
+
+		// Add 'Messages' to the main navigation.
+		$main_nav = array(
+			'name'                    => $nav_name,
+			'slug'                    => $slug,
+			'position'                => 50,
+			'show_for_displayed_user' => $access,
+			'screen_function'         => 'messages_screen_inbox',
+			'default_subnav_slug'     => 'inbox',
+			'item_css_id'             => $this->id
+		);
+
+		// Add the subnav items to the profile.
+		$sub_nav[] = array(
+			'name'            => __( 'Inbox', 'buddypress' ),
+			'slug'            => 'inbox',
+			'parent_url'      => $messages_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'messages_screen_inbox',
+			'position'        => 10,
+			'user_has_access' => $access
+		);
+
+		if ( bp_is_active( $this->id, 'star' ) ) {
+			$sub_nav[] = array(
+				'name'            => __( 'Starred', 'buddypress' ),
+				'slug'            => bp_get_messages_starred_slug(),
+				'parent_url'      => $messages_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'bp_messages_star_screen',
+				'position'        => 11,
+				'user_has_access' => $access
+			);
+		}
+
+		$sub_nav[] = array(
+			'name'            => __( 'Sent', 'buddypress' ),
+			'slug'            => 'sentbox',
+			'parent_url'      => $messages_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'messages_screen_sentbox',
+			'position'        => 20,
+			'user_has_access' => $access
+		);
+
+		$sub_nav[] = array(
+			'name'            => __( 'Compose', 'buddypress' ),
+			'slug'            => 'compose',
+			'parent_url'      => $messages_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'messages_screen_compose',
+			'position'        => 30,
+			'user_has_access' => $access
+		);
+
+		if ( bp_current_user_can( 'bp_moderate' ) ) {
+			$sub_nav[] = array(
+				'name'            => __( 'Notices', 'buddypress' ),
+				'slug'            => 'notices',
+				'parent_url'      => $messages_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'messages_screen_notices',
+				'position'        => 90,
+				'user_has_access' => true
+			);
+		}
+
+		parent::setup_nav( $main_nav, $sub_nav );
+	}
+
+	/**
+	 * Set up the Toolbar.
+	 *
+	 * @param array $wp_admin_nav See {BP_Component::setup_admin_bar()} for details.
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		// Menus for logged in user.
+		if ( is_user_logged_in() ) {
+
+			// Setup the logged in user variables.
+			$messages_link = trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() );
+
+			// Unread message count.
+			$count = messages_get_unread_count();
+			if ( !empty( $count ) ) {
+				$title = sprintf( __( 'Messages <span class="count">%s</span>', 'buddypress' ), bp_core_number_format( $count ) );
+				$inbox = sprintf( __( 'Inbox <span class="count">%s</span>',    'buddypress' ), bp_core_number_format( $count ) );
+			} else {
+				$title = __( 'Messages', 'buddypress' );
+				$inbox = __( 'Inbox',    'buddypress' );
+			}
+
+			// Add main Messages menu.
+			$wp_admin_nav[] = array(
+				'parent' => buddypress()->my_account_menu_id,
+				'id'     => 'my-account-' . $this->id,
+				'title'  => $title,
+				'href'   => $messages_link
+			);
+
+			// Inbox.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-inbox',
+				'title'  => $inbox,
+				'href'   => $messages_link
+			);
+
+			// Starred.
+			if ( bp_is_active( $this->id, 'star' ) ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-starred',
+					'title'  => __( 'Starred', 'buddypress' ),
+					'href'   => trailingslashit( $messages_link . bp_get_messages_starred_slug() )
+				);
+			}
+
+			// Sent Messages.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-sentbox',
+				'title'  => __( 'Sent', 'buddypress' ),
+				'href'   => trailingslashit( $messages_link . 'sentbox' )
+			);
+
+			// Compose Message.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-compose',
+				'title'  => __( 'Compose', 'buddypress' ),
+				'href'   => trailingslashit( $messages_link . 'compose' )
+			);
+
+			// Site Wide Notices.
+			if ( bp_current_user_can( 'bp_moderate' ) ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-notices',
+					'title'  => __( 'All Member Notices', 'buddypress' ),
+					'href'   => trailingslashit( $messages_link . 'notices' )
+				);
+			}
+		}
+
+		parent::setup_admin_bar( $wp_admin_nav );
+	}
+
+	/**
+	 * Set up the title for pages and <title>.
+	 */
+	public function setup_title() {
+
+		if ( bp_is_messages_component() ) {
+			$bp = buddypress();
+
+			if ( bp_is_my_profile() ) {
+				$bp->bp_options_title = __( 'My Messages', 'buddypress' );
+			} else {
+				$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+					'item_id' => bp_displayed_user_id(),
+					'type'    => 'thumb',
+					'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_displayed_user_fullname() )
+				) );
+				$bp->bp_options_title = bp_get_displayed_user_fullname();
+			}
+		}
+
+		parent::setup_title();
+	}
+
+	/**
+	 * Setup cache groups
+	 *
+	 * @since 2.2.0
+	 */
+	public function setup_cache_groups() {
+
+		// Global groups.
+		wp_cache_add_global_groups( array(
+			'bp_messages',
+			'bp_messages_threads',
+			'bp_messages_unread_count',
+			'message_meta'
+		) );
+
+		parent::setup_cache_groups();
+	}
+}
+
+/**
+ * Bootstrap the Messages component.
+ */
+function bp_setup_messages() {
+	buddypress()->messages = new BP_Messages_Component();
+}
+add_action( 'bp_setup_components', 'bp_setup_messages', 6 );
Index: /tags/2.4.3/src/bp-messages/bp-messages-notifications.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-notifications.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-notifications.php	(revision 10410)
@@ -0,0 +1,315 @@
+<?php
+/**
+ * BuddyPress Messages Notifications.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesNotifications
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/** Email *********************************************************************/
+
+/**
+ * Email message recipients to alert them of a new unread private message.
+ *
+ * @since 1.0.0
+ *
+ * @param array|BP_Messages_Message $raw_args {
+ *     Array of arguments. Also accepts a BP_Messages_Message object.
+ *     @type array  $recipients    User IDs of recipients.
+ *     @type string $email_subject Subject line of message.
+ *     @type string $email_content Content of message.
+ *     @type int    $sender_id     User ID of sender.
+ * }
+ */
+function messages_notification_new_message( $raw_args = array() ) {
+
+	// Cast possible $message object as an array.
+	if ( is_object( $raw_args ) ) {
+		$args = (array) $raw_args;
+	} else {
+		$args = $raw_args;
+	}
+
+	// These should be extracted below.
+	$recipients    = array();
+	$email_subject = $email_content = '';
+	$sender_id     = 0;
+
+	// Barf.
+	extract( $args );
+
+	// Get the sender display name.
+	$sender_name = bp_core_get_user_displayname( $sender_id );
+
+	// Bail if no recipients.
+	if ( ! empty( $recipients ) ) {
+
+		foreach ( $recipients as $recipient ) {
+
+			if ( $sender_id == $recipient->user_id || 'no' == bp_get_user_meta( $recipient->user_id, 'notification_messages_new_message', true ) ) {
+				continue;
+			}
+
+			// User data and links.
+			$ud = get_userdata( $recipient->user_id );
+
+			// Bail if user cannot be found.
+			if ( empty( $ud ) ) {
+				continue;
+			}
+
+			$message_link  = bp_core_get_user_domain( $recipient->user_id ) . bp_get_messages_slug() .'/';
+			$settings_slug = function_exists( 'bp_get_settings_slug' ) ? bp_get_settings_slug() : 'settings';
+			$settings_link = bp_core_get_user_domain( $recipient->user_id ) . $settings_slug . '/notifications/';
+
+			// Sender info.
+			$sender_name   = stripslashes( $sender_name );
+			$subject       = stripslashes( wp_filter_kses( $subject ) );
+			$content       = stripslashes( wp_filter_kses( $message ) );
+
+			// Set up and send the message.
+			$email_to      = $ud->user_email;
+			$email_subject = bp_get_email_subject( array( 'text' => sprintf( __( 'New message from %s', 'buddypress' ), $sender_name ) ) );
+
+			$email_content = sprintf( __(
+'%1$s sent you a new message:
+
+Subject: %2$s
+
+"%3$s"
+
+To view and read your messages please log in and visit: %4$s
+
+---------------------
+', 'buddypress' ), $sender_name, $subject, $content, $message_link );
+
+			// Only show the disable notifications line if the settings component is enabled.
+			if ( bp_is_active( 'settings' ) ) {
+				$email_content .= sprintf( __( 'To disable these notifications, please log in and go to: %s', 'buddypress' ), $settings_link );
+			}
+
+			/**
+			 * Filters the user email that the message notification will be sent to.
+			 *
+			 * @since 1.2.0
+			 *
+			 * @param string  $email_to User email the notification is being sent to.
+			 * @param WP_User $ud       WP_User object of who is receiving the message.
+			 */
+			$email_to      = apply_filters( 'messages_notification_new_message_to',      $email_to, $ud );
+
+			/**
+			 * Filters the message notification subject that will be sent to user.
+			 *
+			 * @since 1.2.0
+			 *
+			 * @param string  $email_subject Email notification subject text.
+			 * @param string  $sender_name   Name of the person who sent the message.
+			 * @param WP_User $ud            WP_User object of who is receiving the message.
+			 */
+			$email_subject = apply_filters( 'messages_notification_new_message_subject', $email_subject, $sender_name, $ud );
+
+			/**
+			 * Filters the message notification message that will be sent to user.
+			 *
+			 * @since 1.2.0
+			 *
+			 * @param string  $email_content Email notification message text.
+			 * @param string  $sender_name   Name of the person who sent the message.
+			 * @param string  $subject       Email notification subject text.
+			 * @param string  $content       Content of the message.
+			 * @param string  $message_link  URL permalink for the message.
+			 * @param string  $settings_link URL permalink for the user's notification settings area.
+			 * @param WP_User $ud            WP_User object of who is receiving the message.
+			 */
+			$email_content = apply_filters( 'messages_notification_new_message_message', $email_content, $sender_name, $subject, $content, $message_link, $settings_link, $ud );
+
+			wp_mail( $email_to, $email_subject, $email_content );
+		}
+	}
+
+	/**
+	 * Fires after the sending of a new message email notification.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array  $recipients    User IDs of recipients.
+	 * @param string $email_subject Email notification subject text.
+	 * @param string $email_content Email notification message text.
+	 * @param array  $$args         Array of originally provided arguments.
+	 */
+	do_action( 'bp_messages_sent_notification_email', $recipients, $email_subject, $email_content, $args );
+}
+add_action( 'messages_message_sent', 'messages_notification_new_message', 10 );
+
+/** Notifications *************************************************************/
+
+/**
+ * Format notifications for the Messages component.
+ *
+ * @since 1.0.0
+ *
+ * @param string $action            The kind of notification being rendered.
+ * @param int    $item_id           The primary item id.
+ * @param int    $secondary_item_id The secondary item id.
+ * @param int    $total_items       The total number of messaging-related notifications
+ *                                  waiting for the user.
+ * @param string $format            Return value format. 'string' for BuddyBar-compatible
+ *                                  notifications; 'array' for WP Toolbar. Default: 'string'.
+ * @return string|array Formatted notifications.
+ */
+function messages_format_notifications( $action, $item_id, $secondary_item_id, $total_items, $format = 'string' ) {
+	$total_items = (int) $total_items;
+	$link        = trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() . '/inbox' );
+	$title       = __( 'Inbox', 'buddypress' );
+	$amount      = 'single';
+
+	if ( 'new_message' === $action ) {
+		if ( $total_items > 1 ) {
+			$amount = 'multiple';
+			$text   = sprintf( __( 'You have %d new messages', 'buddypress' ), $total_items );
+		} else {
+			$amount = 'single';
+
+			// Get message thread ID.
+			$message   = new BP_Messages_Message( $item_id );
+			$thread_id = $message->thread_id;
+			$link      = ( ! empty( $thread_id ) )
+				? bp_get_message_thread_view_link( $thread_id )
+				: false;
+
+			if ( ! empty( $secondary_item_id ) ) {
+				$text = sprintf( __( '%s sent you a new private message', 'buddypress' ), bp_core_get_user_displayname( $secondary_item_id ) );
+			} else {
+				$text = sprintf( _n( 'You have %s new private message', 'You have %s new private messages', $total_items, 'buddypress' ), bp_core_number_format( $total_items ) );
+			}
+		}
+	}
+
+	if ( 'string' === $format ) {
+		if ( ! empty( $link ) ) {
+			$retval = '<a href="' . esc_url( $link ) . '" title="' . esc_attr( $title ) . '">' . esc_html( $text ) . '</a>';
+		} else {
+			$retval = esc_html( $text );
+		}
+
+		/**
+		 * Filters the new message notification text before the notification is created.
+		 *
+		 * This is a dynamic filter. Possible filter names are:
+		 *   - 'bp_messages_multiple_new_message_notification'.
+		 *   - 'bp_messages_single_new_message_notification'.
+		 *
+		 * @param string $retval            Notification text.
+		 * @param int    $total_items       Number of messages referred to by the notification.
+		 * @param string $text              The raw notification test (ie, not wrapped in a link).
+		 * @param int    $item_id           ID of the associated item.
+		 * @param int    $secondary_item_id ID of the secondary associated item.
+		 */
+		$return = apply_filters( 'bp_messages_' . $amount . '_new_message_notification', $retval, (int) $total_items, $text, $link, $item_id, $secondary_item_id );
+	} else {
+		/** This filter is documented in bp-messages/bp-messages-notifications.php */
+		$return = apply_filters( 'bp_messages_' . $amount . '_new_message_notification', array(
+			'text' => $text,
+			'link' => $link
+		), $link, (int) $total_items, $text, $link, $item_id, $secondary_item_id );
+	}
+
+	/**
+	 * Fires right before returning the formatted message notifications.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $action            The type of message notification.
+	 * @param int    $item_id           The primary item ID.
+	 * @param int    $secondary_item_id The secondary item ID.
+	 * @param int    $total_items       Total amount of items to format.
+	 */
+	do_action( 'messages_format_notifications', $action, $item_id, $secondary_item_id, $total_items );
+
+	return $return;
+}
+
+/**
+ * Send notifications to message recipients.
+ *
+ * @since 1.9.0
+ *
+ * @param BP_Messages_Message $message Message object.
+ */
+function bp_messages_message_sent_add_notification( $message ) {
+	if ( bp_is_active( 'notifications' ) && ! empty( $message->recipients ) ) {
+		foreach ( (array) $message->recipients as $recipient ) {
+			bp_notifications_add_notification( array(
+				'user_id'           => $recipient->user_id,
+				'item_id'           => $message->id,
+				'secondary_item_id' => $message->sender_id,
+				'component_name'    => buddypress()->messages->id,
+				'component_action'  => 'new_message',
+				'date_notified'     => bp_core_current_time(),
+				'is_new'            => 1,
+			) );
+		}
+	}
+}
+add_action( 'messages_message_sent', 'bp_messages_message_sent_add_notification', 10 );
+
+/**
+ * Mark new message notification when member reads a message thread directly.
+ *
+ * @since 1.9.0
+ */
+function bp_messages_screen_conversation_mark_notifications() {
+	if ( bp_is_active( 'notifications' ) ) {
+		global $thread_template;
+
+		// Get unread PM notifications for the user.
+		$new_pm_notifications = BP_Notifications_Notification::get( array(
+			'user_id'           => bp_loggedin_user_id(),
+			'component_name'    => buddypress()->messages->id,
+			'component_action'  => 'new_message',
+			'is_new'            => 1,
+		) );
+		$unread_message_ids = wp_list_pluck( $new_pm_notifications, 'item_id' );
+
+		// No unread PMs, so stop!
+		if ( empty( $unread_message_ids ) ) {
+			return;
+		}
+
+		// Get the unread message ids for this thread only.
+		$message_ids = array_intersect( $unread_message_ids, wp_list_pluck( $thread_template->thread->messages, 'id' ) );
+
+		// Mark each notification for each PM message as read.
+		foreach ( $message_ids as $message_id ) {
+			bp_notifications_mark_notifications_by_item_id( bp_loggedin_user_id(), (int) $message_id, buddypress()->messages->id, 'new_message' );
+		}
+	}
+}
+add_action( 'thread_loop_start', 'bp_messages_screen_conversation_mark_notifications', 10 );
+
+/**
+ * When a message is deleted, delete corresponding notifications.
+ *
+ * @since 2.0.0
+ *
+ * @param int   $thread_id   ID of the thread.
+ * @param array $message_ids IDs of the messages.
+ */
+function bp_messages_message_delete_notifications( $thread_id, $message_ids ) {
+	if ( ! bp_is_active( 'notifications' ) ) {
+		return;
+	}
+
+	// For each recipient, delete notifications corresponding to each message.
+	$thread = new BP_Messages_Thread( $thread_id );
+	foreach ( $thread->get_recipients() as $recipient ) {
+		foreach ( $message_ids as $message_id ) {
+			bp_notifications_delete_notifications_by_item_id( $recipient->user_id, (int) $message_id, buddypress()->messages->id, 'new_message' );
+		}
+	}
+}
+add_action( 'bp_messages_thread_after_delete', 'bp_messages_message_delete_notifications', 10, 2 );
Index: /tags/2.4.3/src/bp-messages/bp-messages-screens.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-screens.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-screens.php	(revision 10410)
@@ -0,0 +1,232 @@
+<?php
+/**
+ * BuddyPress Messages Screens.
+ *
+ * Screen functions are the controllers of BuddyPress. They will execute when
+ * their specific URL is caught. They will first save or manipulate data using
+ * business functions, then pass on the user to a template file.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesScreens
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Load the Messages > Inbox screen.
+ *
+ * @since 1.0.0
+ */
+function messages_screen_inbox() {
+
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	/**
+	 * Fires right before the loading of the Messages inbox screen template file.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'messages_screen_inbox' );
+
+	/**
+	 * Filters the template to load for the Messages inbox screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the messages template to load.
+	 */
+	bp_core_load_template( apply_filters( 'messages_template_inbox', 'members/single/home' ) );
+}
+
+/**
+ * Load the Messages > Sent screen.
+ *
+ * @since 1.0.0
+ */
+function messages_screen_sentbox() {
+
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	/**
+	 * Fires right before the loading of the Messages sentbox screen template file.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'messages_screen_sentbox' );
+
+	/**
+	 * Filters the template to load for the Messages sentbox screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the messages template to load.
+	 */
+	bp_core_load_template( apply_filters( 'messages_template_sentbox', 'members/single/home' ) );
+}
+
+/**
+ * Load the Messages > Compose screen.
+ *
+ * @since 1.0.0
+ */
+function messages_screen_compose() {
+
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	// Remove any saved message data from a previous session.
+	messages_remove_callback_values();
+
+	/**
+	 * Fires right before the loading of the Messages compose screen template file.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'messages_screen_compose' );
+
+	/**
+	 * Filters the template to load for the Messages compose screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the messages template to load.
+	 */
+	bp_core_load_template( apply_filters( 'messages_template_compose', 'members/single/home' ) );
+}
+
+/**
+ * Load an individual conversation screen.
+ *
+ * @since 1.0.0
+ *
+ * @return bool|null False on failure.
+ */
+function messages_screen_conversation() {
+
+	// Bail if not viewing a single message.
+	if ( ! bp_is_messages_component() || ! bp_is_current_action( 'view' ) ) {
+		return false;
+	}
+
+	$thread_id = (int) bp_action_variable( 0 );
+
+	if ( empty( $thread_id ) || ! messages_is_valid_thread( $thread_id ) || ( ! messages_check_thread_access( $thread_id ) && ! bp_current_user_can( 'bp_moderate' ) ) ) {
+		bp_core_redirect( trailingslashit( bp_displayed_user_domain() . bp_get_messages_slug() ) );
+	}
+
+	// Load up BuddyPress one time.
+	$bp = buddypress();
+
+	// Decrease the unread count in the nav before it's rendered.
+	$count    = bp_get_total_unread_messages_count();
+	$class    = ( 0 === $count ) ? 'no-count' : 'count';
+	$nav_name = sprintf( __( 'Messages <span class="%s">%s</span>', 'buddypress' ), esc_attr( $class ), bp_core_number_format( $count ) );
+
+	$bp->bp_nav[ $bp->messages->slug ]['name'] = $nav_name;
+
+	/**
+	 * Fires right before the loading of the Messages view screen template file.
+	 *
+	 * @since 1.7.0
+	 */
+	do_action( 'messages_screen_conversation' );
+
+	/**
+	 * Filters the template to load for the Messages view screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the messages template to load.
+	 */
+	bp_core_load_template( apply_filters( 'messages_template_view_message', 'members/single/home' ) );
+}
+add_action( 'bp_screens', 'messages_screen_conversation' );
+
+/**
+ * Load the Messages > Notices screen.
+ *
+ * @since 1.0.0
+ *
+ * @return false|null False on failure.
+ */
+function messages_screen_notices() {
+
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	/**
+	 * Fires right before the loading of the Messages notices screen template file.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'messages_screen_notices' );
+
+	/**
+	 * Filters the template to load for the Messages notices screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the messages template to load.
+	 */
+	bp_core_load_template( apply_filters( 'messages_template_notices', 'members/single/home' ) );
+}
+
+/**
+ * Render the markup for the Messages section of Settings > Notifications.
+ *
+ * @since 1.0.0
+ */
+function messages_screen_notification_settings() {
+
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	if ( !$new_messages = bp_get_user_meta( bp_displayed_user_id(), 'notification_messages_new_message', true ) ) {
+		$new_messages = 'yes';
+	} ?>
+
+	<table class="notification-settings" id="messages-notification-settings">
+		<thead>
+			<tr>
+				<th class="icon"></th>
+				<th class="title"><?php _e( 'Messages', 'buddypress' ) ?></th>
+				<th class="yes"><?php _e( 'Yes', 'buddypress' ) ?></th>
+				<th class="no"><?php _e( 'No', 'buddypress' )?></th>
+			</tr>
+		</thead>
+
+		<tbody>
+			<tr id="messages-notification-settings-new-message">
+				<td></td>
+				<td><?php _e( 'A member sends you a new message', 'buddypress' ) ?></td>
+				<td class="yes"><input type="radio" name="notifications[notification_messages_new_message]" id="notification-messages-new-messages-yes" value="yes" <?php checked( $new_messages, 'yes', true ) ?>/><label for="notification-messages-new-messages-yes" class="bp-screen-reader-text"><?php _e( 'Yes, send email', 'buddypress' ); ?></label></td>
+				<td class="no"><input type="radio" name="notifications[notification_messages_new_message]" id="notification-messages-new-messages-no" value="no" <?php checked( $new_messages, 'no', true ) ?>/><label for="notification-messages-new-messages-no" class="bp-screen-reader-text"><?php _e( 'No, do not send email', 'buddypress' ); ?></label></td>
+			</tr>
+
+			<?php
+
+			/**
+			 * Fires inside the closing </tbody> tag for messages screen notification settings.
+			 *
+			 * @since 1.0.0
+			 */
+			do_action( 'messages_screen_notification_settings' ); ?>
+		</tbody>
+	</table>
+
+<?php
+}
+add_action( 'bp_notification_settings', 'messages_screen_notification_settings', 2 );
Index: /tags/2.4.3/src/bp-messages/bp-messages-star.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-star.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-star.php	(revision 10410)
@@ -0,0 +1,529 @@
+<?php
+/**
+ * Functions related to starring private messages.
+ *
+ * @since 2.3.0
+ *
+ * @package BuddyPress
+ * @subpackage MessagesStar
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/** UTILITY **************************************************************/
+
+/**
+ * Return the starred messages slug. Defaults to 'starred'.
+ *
+ * @since 2.3.0
+ *
+ * @return string
+ */
+function bp_get_messages_starred_slug() {
+	/**
+	 * Filters the starred message slug.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string
+	 */
+	return sanitize_title( apply_filters( 'bp_get_messages_starred_slug', 'starred' ) );
+}
+
+/**
+ * Function to determine if a message ID is starred.
+ *
+ * @since 2.3.0
+ *
+ * @param  int $mid     The message ID. Please note that this isn't the message thread ID.
+ * @param  int $user_id The user ID.
+ * @return bool
+ */
+function bp_messages_is_message_starred( $mid = 0, $user_id = 0 ) {
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	if ( empty( $mid ) ) {
+		return false;
+	}
+
+	$starred = array_flip( (array) bp_messages_get_meta( $mid, 'starred_by_user', false ) );
+
+	if ( isset( $starred[$user_id] ) ) {
+		return true;
+	} else {
+		return false;
+	}
+}
+
+/**
+ * Output the link or raw URL for starring or unstarring a message.
+ *
+ * @since 2.3.0
+ *
+ * @param array $args See bp_get_the_message_star_action_link() for full documentation.
+ */
+function bp_the_message_star_action_link( $args = array() ) {
+	echo bp_get_the_message_star_action_link( $args );
+}
+	/**
+	 * Return the link or raw URL for starring or unstarring a message.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param array $args {
+	 *     Array of arguments.
+	 *     @type int    $user_id       The user ID. Defaults to the logged-in user ID.
+	 *     @type int    $thread_id     The message thread ID. Default: 0. If not zero, this takes precedence over
+	 *                                 $message_id.
+	 *     @type int    $message_id    The individual message ID. If on a single thread page, defaults to the
+	 *                                 current message ID in the message loop.
+	 *     @type bool   $url_only      Whether to return the URL only. If false, returns link with markup.
+	 *                                 Default: false.
+	 *     @type string $text_unstar   Link text for the 'unstar' action. Only applicable if $url_only is false.
+	 *     @type string $text_star     Link text for the 'star' action. Only applicable if $url_only is false.
+	 *     @type string $title_unstar  Link title for the 'unstar' action. Only applicable if $url_only is false.
+	 *     @type string $title_star    Link title for the 'star' action. Only applicable if $url_only is false.
+	 *     @type string $title_unstar_thread Link title for the 'unstar' action when displayed in a thread loop.
+	 *                                       Only applicable if $message_id is set and if $url_only is false.
+	 *     @type string $title_star_thread   Link title for the 'star' action when displayed in a thread loop.
+	 *                                       Only applicable if $message_id is set and if $url_only is false.
+	 * }
+	 * @return string
+	 */
+	function bp_get_the_message_star_action_link( $args = array() ) {
+
+		// Default user ID.
+		$user_id = bp_displayed_user_id()
+			? bp_displayed_user_id()
+			: bp_loggedin_user_id();
+
+		$r = bp_parse_args( $args, array(
+			'user_id'             => (int) $user_id,
+			'thread_id'           => 0,
+			'message_id'          => (int) bp_get_the_thread_message_id(),
+			'url_only'            => false,
+			'text_unstar'         => __( 'Unstar',      'buddypress' ),
+			'text_star'           => __( 'Star',        'buddypress' ),
+			'title_unstar'        => __( 'Starred',     'buddypress' ),
+			'title_star'          => __( 'Not starred', 'buddypress' ),
+			'title_unstar_thread' => __( 'Remove all starred messages in this thread', 'buddypress' ),
+			'title_star_thread'   => __( 'Star the first message in this thread',      'buddypress' ),
+		), 'messages_star_action_link' );
+
+		// Check user ID and determine base user URL.
+		switch ( $r['user_id'] ) {
+
+			// Current user.
+			case bp_loggedin_user_id() :
+				$user_domain = bp_loggedin_user_domain();
+				break;
+
+			// Displayed user.
+			case bp_displayed_user_id() :
+				$user_domain = bp_displayed_user_domain();
+				break;
+
+			// Empty or other.
+			default :
+				$user_domain = bp_core_get_user_domain( $r['user_id'] );
+				break;
+		}
+
+		// Bail if no user domain was calculated.
+		if ( empty( $user_domain ) ) {
+			return '';
+		}
+
+		// Define local variables.
+		$retval = $bulk_attr = '';
+
+		// Thread ID.
+		if ( (int) $r['thread_id'] > 0 ) {
+
+			// See if we're in the loop.
+			if ( bp_get_message_thread_id() == $r['thread_id'] ) {
+
+				// Grab all message ids.
+				$mids = wp_list_pluck( $GLOBALS['messages_template']->thread->messages, 'id' );
+
+				// Make sure order is ASC.
+				// Order is DESC when used in the thread loop by default.
+				$mids = array_reverse( $mids );
+
+			// Pull up the thread.
+			} else {
+				$thread = new BP_Messages_Thread( $r['thread_id'] );
+				$mids   = wp_list_pluck( $thread->messages, 'id' );
+			}
+
+			$is_starred = false;
+			$message_id = 0;
+			foreach ( $mids as $mid ) {
+
+				// Try to find the first msg that is starred in a thread.
+				if ( true === bp_messages_is_message_starred( $mid ) ) {
+					$is_starred = true;
+					$message_id = $mid;
+					break;
+				}
+			}
+
+			// No star, so default to first message in thread.
+			if ( empty( $message_id ) ) {
+				$message_id = $mids[0];
+			}
+
+			$message_id = (int) $message_id;
+
+			// Nonce.
+			$nonce = wp_create_nonce( "bp-messages-star-{$message_id}" );
+
+			if ( true === $is_starred ) {
+				$action    = 'unstar';
+				$bulk_attr = ' data-star-bulk="1"';
+				$retval    = $user_domain . bp_get_messages_slug() . '/unstar/' . $message_id . '/' . $nonce . '/all/';
+			} else {
+				$action    = 'star';
+				$retval    = $user_domain . bp_get_messages_slug() . '/star/' . $message_id . '/' . $nonce . '/';
+			}
+
+			$title = $r["title_{$action}_thread"];
+
+		// Message ID.
+		} else {
+			$message_id = (int) $r['message_id'];
+			$is_starred = bp_messages_is_message_starred( $message_id );
+			$nonce      = wp_create_nonce( "bp-messages-star-{$message_id}" );
+
+			if ( true === $is_starred ) {
+				$action = 'unstar';
+				$retval = $user_domain . bp_get_messages_slug() . '/unstar/' . $message_id . '/' . $nonce . '/';
+			} else {
+				$action = 'star';
+				$retval = $user_domain . bp_get_messages_slug() . '/star/' . $message_id . '/' . $nonce . '/';
+			}
+
+			$title = $r["title_{$action}"];
+		}
+
+		/**
+		 * Filters the star action URL for starring / unstarring a message.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param string $retval URL for starring / unstarring a message.
+		 * @param array  $r      Parsed link arguments. See $args in bp_get_the_message_star_action_link().
+		 */
+		$retval = esc_url( apply_filters( 'bp_get_the_message_star_action_urlonly', $retval, $r ) );
+		if ( true === (bool) $r['url_only'] ) {
+			return $retval;
+		}
+
+		/**
+		 * Filters the star action link, including markup.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param string $retval Link for starring / unstarring a message, including markup.
+		 * @param array  $r      Parsed link arguments. See $args in bp_get_the_message_star_action_link().
+		 */
+		return apply_filters( 'bp_get_the_message_star_action_link', '<a title="' . esc_attr( $title ) . '" class="message-action-' . esc_attr( $action ) . '" data-star-status="' . esc_attr( $action ) .'" data-star-nonce="' . esc_attr( $nonce ) . '"' . $bulk_attr . ' data-message-id="' . esc_attr( (int) $message_id ) . '" href="' . $retval . '"><span class="icon"></span> <span class="bp-screen-reader-text">' . $r['text_' . $action] . '</span></a>', $r );
+	}
+
+/**
+ * Save or delete star message meta according to a message's star status.
+ *
+ * @since 2.3.0
+ *
+ * @param array $args {
+ *     Array of arguments.
+ *     @type string $action     The star action. Either 'star' or 'unstar'. Default: 'star'.
+ *     @type int    $thread_id  The message thread ID. Default: 0. If not zero, this takes precedence over
+ *                              $message_id.
+ *     @type int    $message_id The indivudal message ID to star or unstar.  Default: 0.
+ *     @type int    $user_id    The user ID. Defaults to the logged-in user ID.
+ *     @type bool   $bulk       Whether to mark all messages in a thread as a certain action. Only relevant
+ *                              when $action is 'unstar' at the moment. Default: false.
+ * }
+ * @return bool
+ */
+function bp_messages_star_set_action( $args = array() ) {
+	$r = wp_parse_args( $args, array(
+		'action'     => 'star',
+		'thread_id'  => 0,
+		'message_id' => 0,
+		'user_id'    => bp_loggedin_user_id(),
+		'bulk'       => false
+	) );
+
+	// Set thread ID.
+	if ( ! empty( $r['thread_id'] ) ) {
+		$thread_id = (int) $r['thread_id'];
+	} else {
+		$thread_id = messages_get_message_thread_id( $r['message_id'] );
+	}
+	if ( empty( $thread_id ) ) {
+		return false;
+	}
+
+	// Check if user has access to thread.
+	if( ! messages_check_thread_access( $thread_id, $r['user_id'] ) ) {
+		return false;
+	}
+
+	$is_starred = bp_messages_is_message_starred( $r['message_id'], $r['user_id'] );
+
+	// Star.
+	if ( 'star' == $r['action'] ) {
+		if ( true === $is_starred ) {
+			return true;
+		} else {
+			bp_messages_add_meta( $r['message_id'], 'starred_by_user', $r['user_id'] );
+			return true;
+		}
+	// Unstar.
+	} else {
+		// Unstar one message.
+		if ( false === $r['bulk'] ) {
+			if ( false === $is_starred ) {
+				return true;
+			} else {
+				bp_messages_delete_meta( $r['message_id'], 'starred_by_user', $r['user_id'] );
+				return true;
+			}
+
+		// Unstar all messages in a thread.
+		} else {
+			$thread = new BP_Messages_Thread( $thread_id );
+			$mids = wp_list_pluck( $thread->messages, 'id' );
+
+			foreach ( $mids as $mid ) {
+				if ( true === bp_messages_is_message_starred( $mid, $r['user_id'] ) ) {
+					bp_messages_delete_meta( $mid, 'starred_by_user', $r['user_id'] );
+				}
+			}
+
+			return true;
+		}
+	}
+}
+
+/** SCREENS **************************************************************/
+
+/**
+ * Screen handler to display a user's "Starred" private messages page.
+ *
+ * @since 2.3.0
+ */
+function bp_messages_star_screen() {
+	add_action( 'bp_template_content', 'bp_messages_star_content' );
+
+	/**
+	 * Fires right before the loading of the "Starred" messages box.
+	 *
+	 * @since 2.3.0
+	 */
+	do_action( 'bp_messages_screen_star' );
+
+	bp_core_load_template( 'members/single/plugins' );
+}
+
+/**
+ * Screen content callback to display a user's "Starred" messages page.
+ *
+ * @since 2.3.0
+ */
+function bp_messages_star_content() {
+	// Add our message thread filter.
+	add_filter( 'bp_after_has_message_threads_parse_args', 'bp_messages_filter_starred_message_threads' );
+
+	// Load the message loop template part.
+	bp_get_template_part( 'members/single/messages/messages-loop' );
+
+	// Remove our filter.
+	remove_filter( 'bp_after_has_message_threads_parse_args', 'bp_messages_filter_starred_message_threads' );
+}
+
+/**
+ * Filter message threads by those starred by the logged-in user.
+ *
+ * @since 2.3.0
+ *
+ * @param  array $r Current message thread arguments.
+ * @return array $r Array of starred message threads.
+ */
+function bp_messages_filter_starred_message_threads( $r = array() ) {
+	$r['box'] = 'starred';
+	$r['meta_query'] = array( array(
+		'key'   => 'starred_by_user',
+		'value' => $r['user_id']
+	) );
+
+	return $r;
+}
+
+/** ACTIONS **************************************************************/
+
+/**
+ * Action handler to set a message's star status for those not using JS.
+ *
+ * @since 2.3.0
+ */
+function bp_messages_star_action_handler() {
+	if ( ! bp_is_user_messages() ) {
+		return;
+	}
+
+	if ( false === ( bp_is_current_action( 'unstar' ) || bp_is_current_action( 'star' ) ) ) {
+		return;
+	}
+
+	if ( ! wp_verify_nonce( bp_action_variable( 1 ), 'bp-messages-star-' . bp_action_variable( 0 ) ) ) {
+		wp_die( "Oops!  That's a no-no!" );
+	}
+
+	// Check capability.
+	if ( ! is_user_logged_in() || ! bp_core_can_edit_settings() ) {
+		return;
+	}
+
+	// Mark the star.
+	bp_messages_star_set_action( array(
+		'action'     => bp_current_action(),
+		'message_id' => bp_action_variable(),
+		'bulk'       => (bool) bp_action_variable( 2 )
+	) );
+
+	// Redirect back to previous screen.
+	$redirect = wp_get_referer() ? wp_get_referer() : bp_loggedin_user_domain() . bp_get_messages_slug();
+	bp_core_redirect( $redirect );
+	die();
+}
+add_action( 'bp_actions', 'bp_messages_star_action_handler' );
+
+/**
+ * Bulk manage handler to set the star status for multiple messages.
+ *
+ * @since 2.3.0
+ */
+function bp_messages_star_bulk_manage_handler() {
+	if ( empty( $_POST['messages_bulk_nonce' ] ) ) {
+		return;
+	}
+
+	// Check the nonce.
+	if ( ! wp_verify_nonce( $_POST['messages_bulk_nonce'], 'messages_bulk_nonce' ) ) {
+		return;
+	}
+
+	// Check capability.
+	if ( ! is_user_logged_in() || ! bp_core_can_edit_settings() ) {
+		return;
+	}
+
+	$action  = ! empty( $_POST['messages_bulk_action'] ) ? $_POST['messages_bulk_action'] : '';
+	$threads = ! empty( $_POST['message_ids'] ) ? $_POST['message_ids'] : '';
+	$threads = wp_parse_id_list( $threads );
+
+	// Bail if action doesn't match our star actions or no IDs.
+	if ( false === in_array( $action, array( 'star', 'unstar' ), true ) || empty( $threads ) ) {
+		return;
+	}
+
+	// It's star time!
+	switch ( $action ) {
+		case 'star' :
+			$count = count( $threads );
+
+			// If we're starring a thread, we only star the first message in the thread.
+			foreach ( $threads as $thread ) {
+				$thread = new BP_Messages_thread( $thread );
+				$mids = wp_list_pluck( $thread->messages, 'id' );
+
+				bp_messages_star_set_action( array(
+					'action'     => 'star',
+					'message_id' => $mids[0],
+				) );
+			}
+
+			bp_core_add_message( sprintf( _n( '%s message was successfully starred', '%s messages were successfully starred', $count, 'buddypress' ), $count ) );
+			break;
+
+		case 'unstar' :
+			$count = count( $threads );
+
+			foreach ( $threads as $thread ) {
+				bp_messages_star_set_action( array(
+					'action'    => 'unstar',
+					'thread_id' => $thread,
+					'bulk'      => true
+				) );
+			}
+
+			bp_core_add_message( sprintf( _n( '%s message was successfully unstarred', '%s messages were successfully unstarred', $count, 'buddypress' ), $count ) );
+			break;
+	}
+
+	// Redirect back to message box.
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() . '/' );
+	die();
+}
+add_action( 'bp_actions', 'bp_messages_star_bulk_manage_handler', 5 );
+
+/** HOOKS ****************************************************************/
+
+/**
+ * Enqueues the dashicons font.
+ *
+ * The dashicons font is used for the star / unstar icon.
+ *
+ * @since 2.3.0
+ */
+function bp_messages_star_enqueue_scripts() {
+	if ( ! bp_is_user_messages() ) {
+		return;
+	}
+
+	wp_enqueue_style( 'dashicons' );
+}
+add_action( 'bp_enqueue_scripts', 'bp_messages_star_enqueue_scripts' );
+
+/**
+ * Add the "Add star" and "Remove star" options to the bulk management list.
+ *
+ * @since 2.3.0
+ */
+function bp_messages_star_bulk_management_dropdown() {
+?>
+
+	<option value="star"><?php _e( 'Add star', 'buddypress' ); ?></option>
+	<option value="unstar"><?php _e( 'Remove star', 'buddypress' ); ?></option>
+
+<?php
+}
+add_action( 'bp_messages_bulk_management_dropdown', 'bp_messages_star_bulk_management_dropdown', 1 );
+
+/**
+ * Add CSS class for the current message depending on starred status.
+ *
+ * @since 2.3.0
+ *
+ * @param  array $retval Current CSS classes.
+ * @return array
+ */
+function bp_messages_star_message_css_class( $retval = array() ) {
+	if ( true === bp_messages_is_message_starred( bp_get_the_thread_message_id() ) ) {
+		$status = 'starred';
+	} else {
+		$status = 'not-starred';
+	}
+
+	// Add css class based on star status for the current message.
+	$retval[] = "message-{$status}";
+
+	return $retval;
+}
+add_filter( 'bp_get_the_thread_message_css_class', 'bp_messages_star_message_css_class' );
Index: /tags/2.4.3/src/bp-messages/bp-messages-template.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-template.php	(revision 10410)
@@ -0,0 +1,2543 @@
+<?php
+/**
+ * BuddyPress Messages Template Tags.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesTemplate
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Message Box Template Class
+ */
+class BP_Messages_Box_Template {
+
+	/**
+	 * The loop iterator.
+	 *
+	 * @var int
+	 */
+	public $current_thread = -1;
+
+	/**
+	 * The number of threads returned by the paged query.
+	 *
+	 * @var int
+	 */
+	public $current_thread_count = 0;
+
+	/**
+	 * Total number of threads matching the query params.
+	 *
+	 * @var int
+	 */
+	public $total_thread_count = 0;
+
+	/**
+	 * Array of threads located by the query.
+	 *
+	 * @var array
+	 */
+	public $threads = array();
+
+	/**
+	 * The thread object currently being iterated on.
+	 *
+	 * @var object
+	 */
+	public $thread = false;
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @var bool
+	 */
+	public $in_the_loop = false;
+
+	/**
+	 * User ID of the current inbox.
+	 *
+	 * @var int
+	 */
+	public $user_id = 0;
+
+	/**
+	 * The current "box" view ('notices', 'sentbox', 'inbox').
+	 *
+	 * @var string
+	 */
+	public $box = 'inbox';
+
+	/**
+	 * The page number being requested.
+	 *
+	 * @var int
+	 */
+	public $pag_page = 1;
+
+	/**
+	 * The number of items being requested per page.
+	 *
+	 * @var int
+	 */
+	public $pag_num = 10;
+
+	/**
+	 * An HTML string containing pagination links.
+	 *
+	 * @var string
+	 */
+	public $pag_links = '';
+
+	/**
+	 * Search terms for limiting the thread query.
+	 *
+	 * @var string
+	 */
+	public $search_terms = '';
+
+	/**
+	 * Constructor method.
+	 *
+	 * @param array $args {
+	 *     Array of arguments. See bp_has_message_threads() for full description.
+	 * }
+	 */
+	public function __construct( $args = array() ) {
+
+		// Backward compatibility with old method of passing arguments.
+		if ( ! is_array( $args ) || func_num_args() > 1 ) {
+			_deprecated_argument( __METHOD__, '2.2.0', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+			$old_args_keys = array(
+				0 => 'user_id',
+				1 => 'box',
+				2 => 'per_page',
+				3 => 'max',
+				4 => 'type',
+				5 => 'search_terms',
+				6 => 'page_arg'
+			);
+
+			$func_args = func_get_args();
+			$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
+		}
+
+		$r = wp_parse_args( $args, array(
+			'page'         => 1,
+			'per_page'     => 10,
+			'page_arg'     => 'mpage',
+			'box'          => 'inbox',
+			'type'         => 'all',
+			'user_id'      => bp_loggedin_user_id(),
+			'max'          => false,
+			'search_terms' => '',
+			'meta_query'   => array(),
+		) );
+
+		$this->pag_arg      = sanitize_key( $r['page_arg'] );
+		$this->pag_page     = bp_sanitize_pagination_arg( $this->pag_arg, $r['page']     );
+		$this->pag_num      = bp_sanitize_pagination_arg( 'num',          $r['per_page'] );
+		$this->user_id      = $r['user_id'];
+		$this->box          = $r['box'];
+		$this->type         = $r['type'];
+		$this->search_terms = $r['search_terms'];
+
+		if ( 'notices' === $this->box ) {
+			$this->threads = BP_Messages_Notice::get_notices( array(
+				'pag_num'  => $this->pag_num,
+				'pag_page' => $this->pag_page
+			) );
+		} else {
+			$threads = BP_Messages_Thread::get_current_threads_for_user( array(
+				'user_id'      => $this->user_id,
+				'box'          => $this->box,
+				'type'         => $this->type,
+				'limit'        => $this->pag_num,
+				'page'         => $this->pag_page,
+				'search_terms' => $this->search_terms,
+				'meta_query'   => $r['meta_query'],
+			) );
+
+			$this->threads            = $threads['threads'];
+			$this->total_thread_count = $threads['total'];
+		}
+
+		if ( !$this->threads ) {
+			$this->thread_count       = 0;
+			$this->total_thread_count = 0;
+		} else {
+			$total_notice_count = BP_Messages_Notice::get_total_notice_count();
+
+			if ( empty( $r['max'] ) || ( (int) $r['max'] >= (int) $total_notice_count ) ) {
+				if ( 'notices' === $this->box ) {
+					$this->total_thread_count = (int) $total_notice_count;
+				}
+			} else {
+				$this->total_thread_count = (int) $r['max'];
+			}
+
+			if ( ! empty( $r['max'] ) ) {
+				if ( (int) $r['max'] >= count( $this->threads ) ) {
+					$this->thread_count = count( $this->threads );
+				} else {
+					$this->thread_count = (int) $r['max'];
+				}
+			} else {
+				$this->thread_count = count( $this->threads );
+			}
+		}
+
+		if ( (int) $this->total_thread_count && (int) $this->pag_num ) {
+			$pag_args = array(
+				$r['page_arg'] => '%#%',
+			);
+
+			if ( defined( 'DOING_AJAX' ) && true === (bool) DOING_AJAX ) {
+				$base = remove_query_arg( 's', wp_get_referer() );
+			} else {
+				$base = '';
+			}
+
+			$add_args = array();
+
+			if ( ! empty( $this->search_terms ) ) {
+				$add_args['s'] = $this->search_terms;
+			}
+
+			$this->pag_links = paginate_links( array(
+				'base'      => add_query_arg( $pag_args, $base ),
+				'format'    => '',
+				'total'     => ceil( (int) $this->total_thread_count / (int) $this->pag_num ),
+				'current'   => $this->pag_page,
+				'prev_text' => _x( '&larr;', 'Message pagination previous text', 'buddypress' ),
+				'next_text' => _x( '&rarr;', 'Message pagination next text', 'buddypress' ),
+				'mid_size'  => 1,
+				'add_args'  => $add_args,
+			) );
+		}
+	}
+
+	/**
+	 * Whether there are threads available in the loop.
+	 *
+	 * @see bp_has_message_threads()
+	 *
+	 * @return bool True if there are items in the loop, otherwise false.
+	 */
+	public function has_threads() {
+		if ( $this->thread_count ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Set up the next member and iterate index.
+	 *
+	 * @return object The next member to iterate over.
+	 */
+	public function next_thread() {
+		$this->current_thread++;
+		$this->thread = $this->threads[$this->current_thread];
+
+		return $this->thread;
+	}
+
+	/**
+	 * Rewind the threads and reset thread index.
+	 */
+	public function rewind_threads() {
+		$this->current_thread = -1;
+		if ( $this->thread_count > 0 ) {
+			$this->thread = $this->threads[0];
+		}
+	}
+
+	/**
+	 * Whether there are threads left in the loop to iterate over.
+	 *
+	 * This method is used by {@link bp_message_threads()} as part of the
+	 * while loop that controls iteration inside the threads loop, eg:
+	 *     while ( bp_message_threads() ) { ...
+	 *
+	 * @see bp_message_threads()
+	 *
+	 * @return bool True if there are more threads to show, otherwise false.
+	 */
+	function message_threads() {
+		if ( $this->current_thread + 1 < $this->thread_count ) {
+			return true;
+		} elseif ( $this->current_thread + 1 == $this->thread_count ) {
+
+			/**
+			 * Fires when at the end of threads to iterate over.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'messages_box_loop_end' );
+			// Do some cleaning up after the loop.
+			$this->rewind_threads();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	/**
+	 * Set up the current thread inside the loop.
+	 *
+	 * Used by {@link bp_message_thread()} to set up the current thread data
+	 * while looping, so that template tags used during that iteration make
+	 * reference to the current thread.
+	 *
+	 * @see bp_message_thread()
+	 */
+	public function the_message_thread() {
+
+		$this->in_the_loop = true;
+		$this->thread      = $this->next_thread();
+
+		if ( ! bp_is_current_action( 'notices' ) ) {
+			$last_message_index     = count( $this->thread->messages ) - 1;
+			$this->thread->messages = array_reverse( (array) $this->thread->messages );
+
+			// Set up the last message data.
+			if ( count($this->thread->messages) > 1 ) {
+				if ( 'inbox' == $this->box ) {
+					foreach ( (array) $this->thread->messages as $key => $message ) {
+						if ( bp_loggedin_user_id() != $message->sender_id ) {
+							$last_message_index = $key;
+							break;
+						}
+					}
+
+				} elseif ( 'sentbox' == $this->box ) {
+					foreach ( (array) $this->thread->messages as $key => $message ) {
+						if ( bp_loggedin_user_id() == $message->sender_id ) {
+							$last_message_index = $key;
+							break;
+						}
+					}
+				}
+			}
+
+			$this->thread->last_message_id      = $this->thread->messages[ $last_message_index ]->id;
+			$this->thread->last_message_date    = $this->thread->messages[ $last_message_index ]->date_sent;
+			$this->thread->last_sender_id       = $this->thread->messages[ $last_message_index ]->sender_id;
+			$this->thread->last_message_subject = $this->thread->messages[ $last_message_index ]->subject;
+			$this->thread->last_message_content = $this->thread->messages[ $last_message_index ]->message;
+		}
+
+		// Loop has just started.
+		if ( 0 == $this->current_thread ) {
+
+			/**
+			 * Fires if at the start of the message thread loop.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'messages_box_loop_start' );
+		}
+	}
+}
+
+/**
+ * Retrieve private message threads for display in inbox/sentbox/notices.
+ *
+ * Similar to WordPress's have_posts() function, this function is responsible
+ * for querying the database and retrieving private messages for display inside
+ * the theme via individual template parts for a member's inbox/sentbox/notices.
+ *
+ * @since 1.0.0
+ *
+ * @global BP_Messages_Box_Template $messages_template
+ *
+ * @param array|string $args {
+ *     Array of arguments. All are optional.
+ *     @type int    $user_id      ID of the user whose threads are being loaded.
+ *                                Default: ID of the logged-in user.
+ *     @type string $box          Current "box" view. If not provided here, the current
+ *                                view will be inferred from the URL.
+ *     @type int    $per_page     Number of results to return per page. Default: 10.
+ *     @type int    $max          Max results to return. Default: false.
+ *     @type string $type         Type of messages to return. Values: 'all', 'read', 'unread'
+ *                                Default: 'all'
+ *     @type string $search_terms Terms to which to limit results. Default:
+ *                                the value of $_REQUEST['s'].
+ *     @type string $page_arg     URL argument used for the pagination param.
+ *                                Default: 'mpage'.
+ *     @type array  $meta_query   Meta query arguments. Only applicable if $box is
+ *                                not 'notices'. See WP_Meta_Query more details.
+ * }
+ * @return bool True if there are threads to display, otherwise false.
+ */
+function bp_has_message_threads( $args = array() ) {
+	global $messages_template;
+
+	// The default box the user is looking at.
+	$current_action = bp_current_action();
+	switch ( $current_action ) {
+		case 'sentbox' :
+		case 'notices' :
+		case 'inbox'   :
+			$default_box = $current_action;
+			break;
+		default :
+			$default_box = 'inbox';
+			break;
+	}
+
+	// User ID
+	// @todo displayed user for moderators that get this far?
+	$user_id = bp_loggedin_user_id();
+
+	// Search Terms.
+	$search_terms = isset( $_REQUEST['s'] ) ? stripslashes( $_REQUEST['s'] ) : '';
+
+	// Parse the arguments.
+	$r = bp_parse_args( $args, array(
+		'user_id'      => $user_id,
+		'box'          => $default_box,
+		'per_page'     => 10,
+		'max'          => false,
+		'type'         => 'all',
+		'search_terms' => $search_terms,
+		'page_arg'     => 'mpage', // See https://buddypress.trac.wordpress.org/ticket/3679.
+		'meta_query'   => array()
+	), 'has_message_threads' );
+
+	// Load the messages loop global up with messages.
+	$messages_template = new BP_Messages_Box_Template( $r );
+
+	/**
+	 * Filters if there are any message threads to display in inbox/sentbox/notices.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool                     $value             Whether or not the message has threads.
+	 * @param BP_Messages_Box_Template $messages_template Current message box template object.
+	 * @param array                    $r                 Array of parsed arguments passed into function.
+	 */
+	return apply_filters( 'bp_has_message_threads', $messages_template->has_threads(), $messages_template, $r );
+}
+
+/**
+ * Check whether there are more threads to iterate over.
+ *
+ * @return bool
+ */
+function bp_message_threads() {
+	global $messages_template;
+	return $messages_template->message_threads();
+}
+
+/**
+ * Set up the current thread inside the loop.
+ *
+ * @return object
+ */
+function bp_message_thread() {
+	global $messages_template;
+	return $messages_template->the_message_thread();
+}
+
+/**
+ * Output the ID of the current thread in the loop.
+ */
+function bp_message_thread_id() {
+	echo bp_get_message_thread_id();
+}
+	/**
+	 * Get the ID of the current thread in the loop.
+	 *
+	 * @return int
+	 */
+	function bp_get_message_thread_id() {
+		global $messages_template;
+
+		/**
+		 * Filters the ID of the current thread in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $thread_id ID of the current thread in the loop.
+		 */
+		return apply_filters( 'bp_get_message_thread_id', $messages_template->thread->thread_id );
+	}
+
+/**
+ * Output the subject of the current thread in the loop.
+ */
+function bp_message_thread_subject() {
+	echo bp_get_message_thread_subject();
+}
+	/**
+	 * Get the subject of the current thread in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_thread_subject() {
+		global $messages_template;
+
+		/**
+		 * Filters the subject of the current thread in the loop.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Subject of the current thread in the loop.
+		 */
+		return apply_filters( 'bp_get_message_thread_subject', $messages_template->thread->last_message_subject );
+	}
+
+/**
+ * Output an excerpt from the current message in the loop.
+ */
+function bp_message_thread_excerpt() {
+	echo bp_get_message_thread_excerpt();
+}
+	/**
+	 * Generate an excerpt from the current message in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_thread_excerpt() {
+		global $messages_template;
+
+		/**
+		 * Filters the excerpt of the current thread in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Excerpt of the current thread in the loop.
+		 */
+		return apply_filters( 'bp_get_message_thread_excerpt', strip_tags( bp_create_excerpt( $messages_template->thread->last_message_content, 75 ) ) );
+	}
+
+/**
+ * Output the thread's last message content.
+ *
+ * When viewing your Inbox, the last message is the most recent message in
+ * the thread of which you are *not* the author.
+ *
+ * When viewing your Sentbox, last message is the most recent message in
+ * the thread of which you *are* the member.
+ *
+ * @since 2.0.0
+ */
+function bp_message_thread_content() {
+	echo bp_get_message_thread_content();
+}
+	/**
+	 * Return the thread's last message content.
+	 *
+	 * When viewing your Inbox, the last message is the most recent message in
+	 * the thread of which you are *not* the author.
+	 *
+	 * When viewing your Sentbox, last message is the most recent message in
+	 * the thread of which you *are* the member.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @return string The raw content of the last message in the thread.
+	 */
+	function bp_get_message_thread_content() {
+		global $messages_template;
+
+		/**
+		 * Filters the content of the last message in the thread.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $last_message_content Content of the last message in the thread.
+		 */
+		return apply_filters( 'bp_get_message_thread_content', $messages_template->thread->last_message_content );
+	}
+
+/**
+ * Output a link to the page of the current thread's last author.
+ */
+function bp_message_thread_from() {
+	echo bp_get_message_thread_from();
+}
+	/**
+	 * Get a link to the page of the current thread's last author.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_thread_from() {
+		global $messages_template;
+
+		/**
+		 * Filters the link to the page of the current thread's last author.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Link to the page of the current thread's last author.
+		 */
+		return apply_filters( 'bp_get_message_thread_from', bp_core_get_userlink( $messages_template->thread->last_sender_id ) );
+	}
+
+/**
+ * Output links to the pages of the current thread's recipients.
+ */
+function bp_message_thread_to() {
+	echo bp_get_message_thread_to();
+}
+	/**
+	 * Generate HTML links to the pages of the current thread's recipients.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_thread_to() {
+		global $messages_template;
+
+		/**
+		 * Filters the HTML links to the pages of the current thread's recipients.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value HTML links to the pages of the current thread's recipients.
+		 */
+		return apply_filters( 'bp_message_thread_to', BP_Messages_Thread::get_recipient_links($messages_template->thread->recipients ) );
+	}
+
+/**
+ * Output the permalink for a particular thread.
+ *
+ * @param int $thread_id Optional. ID of the thread. Default: current thread
+ *                       being iterated on in the loop.
+ */
+function bp_message_thread_view_link( $thread_id = 0 ) {
+	echo bp_get_message_thread_view_link( $thread_id );
+}
+	/**
+	 * Get the permalink of a particular thread.
+	 *
+	 * @param int $thread_id Optional. ID of the thread. Default: current
+	 *                       thread being iterated on in the loop.
+	 * @return string
+	 */
+	function bp_get_message_thread_view_link( $thread_id = 0 ) {
+		global $messages_template;
+
+		if ( empty( $messages_template ) && (int) $thread_id > 0 ) {
+			$thread_id = (int) $thread_id;
+		} elseif ( ! empty( $messages_template->thread->thread_id ) ) {
+			$thread_id = $messages_template->thread->thread_id;
+		}
+
+		/**
+		 * Filters the permalink of a particular thread.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value permalink of a particular thread.
+		 */
+		return apply_filters( 'bp_get_message_thread_view_link', trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() . '/view/' . $thread_id ) );
+	}
+
+/**
+ * Output the URL for deleting the current thread.
+ */
+function bp_message_thread_delete_link() {
+	echo esc_url( bp_get_message_thread_delete_link() );
+}
+	/**
+	 * Generate the URL for deleting the current thread.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_thread_delete_link() {
+		global $messages_template;
+
+		/**
+		 * Filters the URL for deleting the current thread.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL for deleting the current thread.
+		 * @param string $value Text indicating action being executed.
+		 */
+		return apply_filters( 'bp_get_message_thread_delete_link', wp_nonce_url( trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() . '/delete/' . $messages_template->thread->thread_id ), 'messages_delete_thread' ) );
+	}
+
+/**
+ * Output the URL used for marking a single message thread as unread.
+ *
+ * Since this function directly outputs a URL, it is escaped.
+ *
+ * @since 2.2.0
+ */
+function bp_the_message_thread_mark_unread_url() {
+	echo esc_url( bp_get_the_message_thread_mark_unread_url() );
+}
+	/**
+	 * Return the URL used for marking a single message thread as unread.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_message_thread_mark_unread_url() {
+
+		// Get the message ID.
+		$id = bp_get_message_thread_id();
+
+		// Get the args to add to the URL.
+		$args = array(
+			'action'     => 'unread',
+			'message_id' => $id
+		);
+
+		// Base unread URL.
+		$url = trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() . '/unread' );
+
+		// Add the args to the URL.
+		$url = add_query_arg( $args, $url );
+
+		// Add the nonce.
+		$url = wp_nonce_url( $url, 'bp_message_thread_mark_unread_' . $id );
+
+		/**
+		 * Filters the URL used for marking a single message thread as unread.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param string $url URL used for marking a single message thread as unread.
+		 */
+		return apply_filters( 'bp_get_the_message_thread_mark_unread_url', $url );
+	}
+
+/**
+ * Output the URL used for marking a single message thread as read.
+ *
+ * Since this function directly outputs a URL, it is escaped.
+ *
+ * @since 2.2.0
+ */
+function bp_the_message_thread_mark_read_url() {
+	echo esc_url( bp_get_the_message_thread_mark_read_url() );
+}
+	/**
+	 * Return the URL used for marking a single message thread as read.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_message_thread_mark_read_url() {
+
+		// Get the message ID.
+		$id = bp_get_message_thread_id();
+
+		// Get the args to add to the URL.
+		$args = array(
+			'action'     => 'read',
+			'message_id' => $id
+		);
+
+		// Base read URL.
+		$url = trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() . '/read' );
+
+		// Add the args to the URL.
+		$url = add_query_arg( $args, $url );
+
+		// Add the nonce.
+		$url = wp_nonce_url( $url, 'bp_message_thread_mark_read_' . $id );
+
+		/**
+		 * Filters the URL used for marking a single message thread as read.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param string $url URL used for marking a single message thread as read.
+		 */
+		return apply_filters( 'bp_get_the_message_thread_mark_read_url', $url );
+	}
+
+/**
+ * Output the CSS class for the current thread.
+ */
+function bp_message_css_class() {
+	echo esc_attr( bp_get_message_css_class() );
+}
+	/**
+	 * Generate the CSS class for the current thread.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_css_class() {
+		global $messages_template;
+
+		$class = false;
+
+		if ( $messages_template->current_thread % 2 == 1 ) {
+			$class .= 'alt';
+		}
+
+		/**
+		 * Filters the CSS class for the current thread.
+		 *
+		 * @since 1.2.10
+		 *
+		 * @param string $class Class string to be added to the list of classes.
+		 */
+		return apply_filters( 'bp_get_message_css_class', trim( $class ) );
+	}
+
+/**
+ * Check whether the current thread has unread items.
+ *
+ * @return bool True if there are unread items, otherwise false.
+ */
+function bp_message_thread_has_unread() {
+	global $messages_template;
+
+	$retval = ! empty( $messages_template->thread->unread_count )
+		? true
+		: false;
+
+	/**
+	 * Filters whether or not a message thread has unread items.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $retval Whether or not a message thread has unread items.
+	 */
+	return apply_filters( 'bp_message_thread_has_unread', $retval );
+}
+
+/**
+ * Output the current thread's unread count.
+ */
+function bp_message_thread_unread_count() {
+	echo esc_html( bp_get_message_thread_unread_count() );
+}
+	/**
+	 * Get the current thread's unread count.
+	 *
+	 * @return int
+	 */
+	function bp_get_message_thread_unread_count() {
+		global $messages_template;
+
+		$count = ! empty( $messages_template->thread->unread_count )
+			? (int) $messages_template->thread->unread_count
+			: false;
+
+		/**
+		 * Filters the current thread's unread count.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $count Current thread unread count.
+		 */
+		return apply_filters( 'bp_get_message_thread_unread_count', $count );
+	}
+
+/**
+ * Output a thread's total message count.
+ *
+ * @since 2.2.0
+ *
+ * @param int|bool $thread_id Optional. ID of the thread. Defaults to current thread ID.
+ */
+function bp_message_thread_total_count( $thread_id = false ) {
+	echo bp_get_message_thread_total_count( $thread_id );
+}
+	/**
+	 * Get the current thread's total message count.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param int|bool $thread_id Optional. ID of the thread.
+	 *                            Defaults to current thread ID.
+	 * @return int
+	 */
+	function bp_get_message_thread_total_count( $thread_id = false ) {
+		if ( false === $thread_id ) {
+			$thread_id = bp_get_message_thread_id();
+		}
+
+		$thread_template = new BP_Messages_Thread_Template( $thread_id, 'ASC', array(
+			'update_meta_cache' => false
+		) );
+
+		$count = 0;
+		if ( ! empty( $thread_template->message_count ) ) {
+			$count = intval( $thread_template->message_count );
+		}
+
+		/**
+		 * Filters the current thread's total message count.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param int $count Current thread total message count.
+		 */
+		return apply_filters( 'bp_get_message_thread_total_count', $count );
+	}
+
+/**
+ * Output markup for the current thread's total and unread count.
+ *
+ * @since 2.2.0
+ *
+ * @param int|bool $thread_id Optional. ID of the thread. Default: current thread ID.
+ */
+function bp_message_thread_total_and_unread_count( $thread_id = false ) {
+	echo bp_get_message_thread_total_and_unread_count( $thread_id );
+}
+	/**
+	 * Get markup for the current thread's total and unread count.
+	 *
+	 * @param int|bool $thread_id Optional. ID of the thread. Default: current thread ID.
+	 * @return string Markup displaying the total and unread count for the thread.
+	 */
+	function bp_get_message_thread_total_and_unread_count( $thread_id = false ) {
+		if ( false === $thread_id ) {
+			$thread_id = bp_get_message_thread_id();
+		}
+
+		$total  = bp_get_message_thread_total_count( $thread_id );
+		$unread = bp_get_message_thread_unread_count( $thread_id );
+
+		return sprintf(
+			'<span class="thread-count">(%1$s)</span> <span class="bp-screen-reader-text">%2$s</span>',
+			number_format_i18n( $total ),
+			sprintf( _n( '%d unread', '%d unread', $unread, 'buddypress' ), number_format_i18n( $unread ) )
+		);
+	}
+
+/**
+ * Output the unformatted date of the last post in the current thread.
+ */
+function bp_message_thread_last_post_date_raw() {
+	echo bp_get_message_thread_last_post_date_raw();
+}
+	/**
+	 * Get the unformatted date of the last post in the current thread.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_thread_last_post_date_raw() {
+		global $messages_template;
+
+		/**
+		 * Filters the unformatted date of the last post in the current thread.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $last_message_date Unformatted date of the last post in the current thread.
+		 */
+		return apply_filters( 'bp_get_message_thread_last_message_date', $messages_template->thread->last_message_date );
+	}
+
+/**
+ * Output the nicely formatted date of the last post in the current thread.
+ */
+function bp_message_thread_last_post_date() {
+	echo bp_get_message_thread_last_post_date();
+}
+	/**
+	 * Get the nicely formatted date of the last post in the current thread.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_thread_last_post_date() {
+
+		/**
+		 * Filters the nicely formatted date of the last post in the current thread.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $value Formatted date of the last post in the current thread.
+		 */
+		return apply_filters( 'bp_get_message_thread_last_post_date', bp_format_time( strtotime( bp_get_message_thread_last_post_date_raw() ) ) );
+	}
+
+/**
+ * Output the avatar for the last sender in the current message thread.
+ *
+ * @see bp_get_message_thread_avatar() for a description of arguments.
+ *
+ * @param array|string $args See {@link bp_get_message_thread_avatar()}.
+ */
+function bp_message_thread_avatar( $args = '' ) {
+	echo bp_get_message_thread_avatar( $args );
+}
+	/**
+	 * Return the avatar for the last sender in the current message thread.
+	 *
+	 * @see bp_core_fetch_avatar() For a description of arguments and
+	 *      return values.
+	 *
+	 * @param array|string $args {
+	 *     Arguments are listed here with an explanation of their defaults.
+	 *     For more information about the arguments, see
+	 *     {@link bp_core_fetch_avatar()}.
+	 *     @type string      $type   Default: 'thumb'.
+	 *     @type int|bool    $width  Default: false.
+	 *     @type int|bool    $height Default: false.
+	 *     @type string      $class  Default: 'avatar'.
+	 *     @type string|bool $id     Default: false.
+	 *     @type string      $alt    Default: 'Profile picture of [display name]'.
+	 * }
+	 * @return string User avatar string.
+	 */
+	function bp_get_message_thread_avatar( $args = '' ) {
+		global $messages_template;
+
+		$fullname = bp_core_get_user_displayname( $messages_template->thread->last_sender_id );
+		$alt      = sprintf( __( 'Profile picture of %s', 'buddypress' ), $fullname );
+
+		$r = bp_parse_args( $args, array(
+			'type'   => 'thumb',
+			'width'  => false,
+			'height' => false,
+			'class'  => 'avatar',
+			'id'     => false,
+			'alt'    => $alt
+		) );
+
+		/**
+		 * Filters the avatar for the last sender in the current message thread.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value User avatar string.
+		 */
+		return apply_filters( 'bp_get_message_thread_avatar', bp_core_fetch_avatar( array(
+			'item_id' => $messages_template->thread->last_sender_id,
+			'type'    => $r['type'],
+			'alt'     => $r['alt'],
+			'css_id'  => $r['id'],
+			'class'   => $r['class'],
+			'width'   => $r['width'],
+			'height'  => $r['height'],
+		) ) );
+	}
+
+/**
+ * Output the unread messages count for the current inbox.
+ */
+function bp_total_unread_messages_count() {
+	echo bp_get_total_unread_messages_count();
+}
+	/**
+	 * Get the unread messages count for the current inbox.
+	 *
+	 * @return int
+	 */
+	function bp_get_total_unread_messages_count() {
+
+		/**
+		 * Filters the unread messages count for the current inbox.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $value Unread messages count for the current inbox.
+		 */
+		return apply_filters( 'bp_get_total_unread_messages_count', BP_Messages_Thread::get_inbox_count() );
+	}
+
+/**
+ * Output the pagination HTML for the current thread loop.
+ */
+function bp_messages_pagination() {
+	echo bp_get_messages_pagination();
+}
+	/**
+	 * Get the pagination HTML for the current thread loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_messages_pagination() {
+		global $messages_template;
+
+		/**
+		 * Filters the pagination HTML for the current thread loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param int $pag_links Pagination HTML for the current thread loop.
+		 */
+		return apply_filters( 'bp_get_messages_pagination', $messages_template->pag_links );
+	}
+
+/**
+ * Generate the "Viewing message x to y (of z messages)" string for a loop.
+ */
+function bp_messages_pagination_count() {
+	global $messages_template;
+
+	$start_num = intval( ( $messages_template->pag_page - 1 ) * $messages_template->pag_num ) + 1;
+	$from_num  = bp_core_number_format( $start_num );
+	$to_num    = bp_core_number_format( ( $start_num + ( $messages_template->pag_num - 1 ) > $messages_template->total_thread_count ) ? $messages_template->total_thread_count : $start_num + ( $messages_template->pag_num - 1 ) );
+	$total     = bp_core_number_format( $messages_template->total_thread_count );
+
+	if ( 1 == $messages_template->total_thread_count ) {
+		$message = __( 'Viewing 1 message', 'buddypress' );
+	} else {
+		$message = sprintf( _n( 'Viewing %1$s - %2$s of %3$s message', 'Viewing %1$s - %2$s of %3$s messages', $messages_template->total_thread_count, 'buddypress' ), $from_num, $to_num, $total );
+	}
+
+	echo esc_html( $message );
+}
+
+/**
+ * Output the Private Message search form.
+ *
+ * @todo  Move markup to template part in: /members/single/messages/search.php
+ * @since 1.6.0
+ */
+function bp_message_search_form() {
+
+	// Get the default search text.
+	$default_search_value = bp_get_search_default_text( 'messages' );
+
+	// Setup a few values based on what's being searched for.
+	$search_submitted     = ! empty( $_REQUEST['s'] ) ? stripslashes( $_REQUEST['s'] ) : $default_search_value;
+	$search_placeholder   = ( $search_submitted === $default_search_value ) ? ' placeholder="' .  esc_attr( $search_submitted ) . '"' : '';
+	$search_value         = ( $search_submitted !== $default_search_value ) ? ' value="'       .  esc_attr( $search_submitted ) . '"' : '';
+
+	// Start the output buffer, so form can be filtered.
+	ob_start(); ?>
+
+	<form action="" method="get" id="search-message-form">
+		<label for="messages_search" class="bp-screen-reader-text"><?php esc_html_e( 'Search Messages', 'buddypress' ); ?></label>
+		<input type="text" name="s" id="messages_search"<?php echo $search_placeholder . $search_value; ?> />
+		<input type="submit" class="button" id="messages_search_submit" name="messages_search_submit" value="<?php esc_html_e( 'Search', 'buddypress' ); ?>" />
+	</form>
+
+	<?php
+
+	// Get the search form from the above output buffer.
+	$search_form_html = ob_get_clean();
+
+	/**
+	 * Filters the private message component search form.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string $search_form_html HTML markup for the message search form.
+	 */
+	echo apply_filters( 'bp_message_search_form', $search_form_html );
+}
+
+/**
+ * Echo the form action for Messages HTML forms.
+ */
+function bp_messages_form_action() {
+	echo esc_url( bp_get_messages_form_action() );
+}
+	/**
+	 * Return the form action for Messages HTML forms.
+	 *
+	 * @return string The form action.
+	 */
+	function bp_get_messages_form_action() {
+
+		/**
+		 * Filters the form action for Messages HTML forms.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value The form action.
+		 */
+		return apply_filters( 'bp_get_messages_form_action', trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() . '/' . bp_action_variable( 0 ) ) );
+	}
+
+/**
+ * Output the default username for the recipient box.
+ */
+function bp_messages_username_value() {
+	echo esc_attr( bp_get_messages_username_value() );
+}
+	/**
+	 * Get the default username for the recipient box.
+	 *
+	 * @return string
+	 */
+	function bp_get_messages_username_value() {
+		if ( isset( $_COOKIE['bp_messages_send_to'] ) ) {
+
+			/**
+			 * Filters the default username for the recipient box.
+			 *
+			 * Value passed into filter is dependent on if the 'bp_messages_send_to'
+			 * cookie or 'r' $_GET parameter is set.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param string $value Default user name.
+			 */
+			return apply_filters( 'bp_get_messages_username_value', $_COOKIE['bp_messages_send_to'] );
+		} elseif ( isset( $_GET['r'] ) && !isset( $_COOKIE['bp_messages_send_to'] ) ) {
+			/** This filter is documented in bp-messages-template.php */
+			return apply_filters( 'bp_get_messages_username_value', $_GET['r'] );
+		}
+	}
+
+/**
+ * Output the default value for the Subject field.
+ */
+function bp_messages_subject_value() {
+	echo esc_attr( bp_get_messages_subject_value() );
+}
+	/**
+	 * Get the default value for the Subject field.
+	 *
+	 * Will get a value out of $_POST['subject'] if available (ie after a
+	 * failed submission).
+	 *
+	 * @return string
+	 */
+	function bp_get_messages_subject_value() {
+
+		// Sanitized in bp-messages-filters.php.
+		$subject = ! empty( $_POST['subject'] )
+			? $_POST['subject']
+			: '';
+
+		/**
+		 * Filters the default value for the subject field.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $subject The default value for the subject field.
+		 */
+		return apply_filters( 'bp_get_messages_subject_value', $subject );
+	}
+
+/**
+ * Output the default value for the Compose content field.
+ */
+function bp_messages_content_value() {
+	echo esc_textarea( bp_get_messages_content_value() );
+}
+	/**
+	 * Get the default value fo the Compose content field.
+	 *
+	 * Will get a value out of $_POST['content'] if available (ie after a
+	 * failed submission).
+	 *
+	 * @return string
+	 */
+	function bp_get_messages_content_value() {
+
+		// Sanitized in bp-messages-filters.php.
+		$content = ! empty( $_POST['content'] )
+			? $_POST['content']
+			: '';
+
+		/**
+		 * Filters the default value for the content field.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $content The default value for the content field.
+		 */
+		return apply_filters( 'bp_get_messages_content_value', $content );
+	}
+
+/**
+ * Output the markup for the message type dropdown.
+ */
+function bp_messages_options() {
+?>
+
+	<label for="message-type-select" class="bp-screen-reader-text">
+		<?php _e( 'Select:', 'buddypress' ) ?>
+	 </label>
+
+	<select name="message-type-select" id="message-type-select">
+		<option value=""><?php _e( 'Select', 'buddypress' ); ?></option>
+		<option value="read"><?php _ex('Read', 'Message dropdown filter', 'buddypress') ?></option>
+		<option value="unread"><?php _ex('Unread', 'Message dropdown filter', 'buddypress') ?></option>
+		<option value="all"><?php _ex('All', 'Message dropdown filter', 'buddypress') ?></option>
+	</select> &nbsp;
+
+	<?php if ( ! bp_is_current_action( 'sentbox' ) && ! bp_is_current_action( 'notices' ) ) : ?>
+
+		<a href="#" id="mark_as_read"><?php _ex('Mark as Read', 'Message management markup', 'buddypress') ?></a> &nbsp;
+		<a href="#" id="mark_as_unread"><?php _ex('Mark as Unread', 'Message management markup', 'buddypress') ?></a> &nbsp;
+
+	<?php endif; ?>
+
+	<a href="#" id="delete_<?php echo bp_current_action(); ?>_messages"><?php _e( 'Delete Selected', 'buddypress' ); ?></a> &nbsp;
+
+<?php
+}
+
+/**
+ * Output the dropdown for bulk management of messages.
+ *
+ * @since 2.2.0
+ */
+function bp_messages_bulk_management_dropdown() {
+	?>
+	<label class="bp-screen-reader-text" for="messages-select"><?php _e( 'Select Bulk Action', 'buddypress' ); ?></label>
+	<select name="messages_bulk_action" id="messages-select">
+		<option value="" selected="selected"><?php _e( 'Bulk Actions', 'buddypress' ); ?></option>
+		<option value="read"><?php _e( 'Mark read', 'buddypress' ); ?></option>
+		<option value="unread"><?php _e( 'Mark unread', 'buddypress' ); ?></option>
+		<option value="delete"><?php _e( 'Delete', 'buddypress' ); ?></option>
+		<?php
+			/**
+			 * Action to add additional options to the messages bulk management dropdown.
+			 *
+			 * @since 2.3.0
+			 */
+			do_action( 'bp_messages_bulk_management_dropdown' );
+		?>
+	</select>
+	<input type="submit" id="messages-bulk-manage" class="button action" value="<?php esc_attr_e( 'Apply', 'buddypress' ); ?>">
+	<?php
+}
+
+/**
+ * Return whether or not the notice is currently active.
+ *
+ * @since 1.6.0
+ */
+function bp_messages_is_active_notice() {
+	global $messages_template;
+
+	$retval = ! empty( $messages_template->thread->is_active )
+		? true
+		: false;
+
+	/**
+	 * Filters whether or not the notice is currently active.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $retval Whether or not the notice is currently active.
+	 */
+	return apply_filters( 'bp_messages_is_active_notice', $retval );
+}
+
+/**
+ * Output a string for the active notice.
+ *
+ * Since 1.6 this function has been deprecated in favor of text in the theme.
+ *
+ * @since 1.0.0
+ * @deprecated 1.6.0
+ * @uses bp_get_message_is_active_notice()
+ */
+function bp_message_is_active_notice() {
+	echo bp_get_message_is_active_notice();
+}
+	/**
+	 * Returns a string for the active notice.
+	 *
+	 * Since 1.6 this function has been deprecated in favor of text in the
+	 * theme.
+	 *
+	 * @since 1.0.0
+	 * @deprecated 1.6.0
+	 * @uses bp_messages_is_active_notice()
+	 */
+	function bp_get_message_is_active_notice() {
+
+		$string = bp_messages_is_active_notice()
+			? __( 'Currently Active', 'buddypress' )
+			: '';
+
+		return apply_filters( 'bp_get_message_is_active_notice', $string );
+	}
+
+/**
+ * Output the ID of the current notice in the loop.
+ */
+function bp_message_notice_id() {
+	echo (int) bp_get_message_notice_id();
+}
+	/**
+	 * Get the ID of the current notice in the loop.
+	 *
+	 * @return int
+	 */
+	function bp_get_message_notice_id() {
+		global $messages_template;
+
+		/**
+		 * Filters the ID of the current notice in the loop.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param int $id ID of the current notice in the loop.
+		 */
+		return apply_filters( 'bp_get_message_notice_id', $messages_template->thread->id );
+	}
+
+/**
+ * Output the post date of the current notice in the loop.
+ */
+function bp_message_notice_post_date() {
+	echo bp_get_message_notice_post_date();
+}
+	/**
+	 * Get the post date of the current notice in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_notice_post_date() {
+		global $messages_template;
+
+		/**
+		 * Filters the post date of the current notice in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Formatted post date of the current notice in the loop.
+		 */
+		return apply_filters( 'bp_get_message_notice_post_date', bp_format_time( strtotime( $messages_template->thread->date_sent ) ) );
+	}
+
+/**
+ * Output the subject of the current notice in the loop.
+ */
+function bp_message_notice_subject() {
+	echo bp_get_message_notice_subject();
+}
+	/**
+	 * Get the subject of the current notice in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_notice_subject() {
+		global $messages_template;
+
+		/**
+		 * Filters the subject of the current notice in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $subject Subject of the current notice in the loop.
+		 */
+		return apply_filters( 'bp_get_message_notice_subject', $messages_template->thread->subject );
+	}
+
+/**
+ * Output the text of the current notice in the loop.
+ */
+function bp_message_notice_text() {
+	echo bp_get_message_notice_text();
+}
+	/**
+	 * Get the text of the current notice in the loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_notice_text() {
+		global $messages_template;
+
+		/**
+		 * Filters the text of the current notice in the loop.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $message Text for the current notice in the loop.
+		 */
+		return apply_filters( 'bp_get_message_notice_text', $messages_template->thread->message );
+	}
+
+/**
+ * Output the URL for deleting the current notice.
+ */
+function bp_message_notice_delete_link() {
+	echo esc_url( bp_get_message_notice_delete_link() );
+}
+	/**
+	 * Get the URL for deleting the current notice.
+	 *
+	 * @return string Delete URL.
+	 */
+	function bp_get_message_notice_delete_link() {
+		global $messages_template;
+
+		/**
+		 * Filters the URL for deleting the current notice.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL for deleting the current notice.
+		 * @param string $value Text indicating action being executed.
+		 */
+		return apply_filters( 'bp_get_message_notice_delete_link', wp_nonce_url( bp_loggedin_user_domain() . bp_get_messages_slug() . '/notices/delete/' . $messages_template->thread->id, 'messages_delete_thread' ) );
+	}
+
+/**
+ * Output the URL for deactivating the current notice.
+ */
+function bp_message_activate_deactivate_link() {
+	echo esc_url( bp_get_message_activate_deactivate_link() );
+}
+	/**
+	 * Get the URL for deactivating the current notice.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_activate_deactivate_link() {
+		global $messages_template;
+
+		if ( 1 === (int) $messages_template->thread->is_active ) {
+			$link = wp_nonce_url( trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() . '/notices/deactivate/' . $messages_template->thread->id ), 'messages_deactivate_notice' );
+		} else {
+			$link = wp_nonce_url( trailingslashit( bp_loggedin_user_domain() . bp_get_messages_slug() . '/notices/activate/' . $messages_template->thread->id ), 'messages_activate_notice' );
+		}
+
+		/**
+		 * Filters the URL for deactivating the current notice.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $link URL for deactivating the current notice.
+		 */
+		return apply_filters( 'bp_get_message_activate_deactivate_link', $link );
+	}
+
+/**
+ * Output the Deactivate/Activate text for the notice action link.
+ */
+function bp_message_activate_deactivate_text() {
+	echo esc_html( bp_get_message_activate_deactivate_text() );
+}
+	/**
+	 * Generate the text ('Deactivate' or 'Activate') for the notice action link.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_activate_deactivate_text() {
+		global $messages_template;
+
+		if ( 1 === (int) $messages_template->thread->is_active  ) {
+			$text = __('Deactivate', 'buddypress');
+		} else {
+			$text = __('Activate', 'buddypress');
+		}
+
+		/**
+		 * Filters the "Deactivate" or "Activate" text for notice action links.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $text Text used for notice action links.
+		 */
+		return apply_filters( 'bp_message_activate_deactivate_text', $text );
+	}
+
+/**
+ * Output the messages component slug.
+ *
+ * @since 1.5.0
+ *
+ * @uses bp_get_messages_slug()
+ */
+function bp_messages_slug() {
+	echo bp_get_messages_slug();
+}
+	/**
+	 * Return the messages component slug.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string
+	 */
+	function bp_get_messages_slug() {
+
+		/**
+		 * Filters the messages component slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $slug Messages component slug.
+		 */
+		return apply_filters( 'bp_get_messages_slug', buddypress()->messages->slug );
+	}
+
+/**
+ * Generate markup for currently active notices.
+ */
+function bp_message_get_notices() {
+	$notice = BP_Messages_Notice::get_active();
+
+	if ( empty( $notice ) ) {
+		return false;
+	}
+
+	$closed_notices = bp_get_user_meta( bp_loggedin_user_id(), 'closed_notices', true );
+
+	if ( empty( $closed_notices ) ) {
+		$closed_notices = array();
+	}
+
+	if ( is_array( $closed_notices ) ) {
+		if ( !in_array( $notice->id, $closed_notices ) && $notice->id ) {
+			?>
+			<div id="message" class="info notice" rel="n-<?php echo esc_attr( $notice->id ); ?>">
+				<p>
+					<strong><?php echo stripslashes( wp_filter_kses( $notice->subject ) ) ?></strong><br />
+					<?php echo stripslashes( wp_filter_kses( $notice->message) ) ?>
+					<a href="#" id="close-notice"><?php _e( 'Close', 'buddypress' ) ?></a>
+				</p>
+			</div>
+			<?php
+		}
+	}
+}
+
+/**
+ * Output the URL for the Private Message link in member profile headers.
+ */
+function bp_send_private_message_link() {
+	echo esc_url( bp_get_send_private_message_link() );
+}
+	/**
+	 * Generate the URL for the Private Message link in member profile headers.
+	 *
+	 * @return bool|string False on failure, otherwise the URL.
+	 */
+	function bp_get_send_private_message_link() {
+
+		if ( bp_is_my_profile() || ! is_user_logged_in() ) {
+			return false;
+		}
+
+		/**
+		 * Filters the URL for the Private Message link in member profile headers.
+		 *
+		 * @since 1.2.10
+		 *
+		 * @param string $value URL for the Private Message link in member profile headers.
+		 */
+		return apply_filters( 'bp_get_send_private_message_link', wp_nonce_url( bp_loggedin_user_domain() . bp_get_messages_slug() . '/compose/?r=' . bp_core_get_username( bp_displayed_user_id() ) ) );
+	}
+
+/**
+ * Output the 'Private Message' button for member profile headers.
+ *
+ * Explicitly named function to avoid confusion with public messages.
+ *
+ * @since 1.2.6
+ *
+ * @uses bp_get_send_message_button()
+ */
+function bp_send_private_message_button() {
+	echo bp_get_send_message_button();
+}
+
+/**
+ * Output the 'Private Message' button for member profile headers.
+ */
+function bp_send_message_button() {
+	echo bp_get_send_message_button();
+}
+	/**
+	 * Generate the 'Private Message' button for member profile headers.
+	 *
+	 * @return string
+	 */
+	function bp_get_send_message_button() {
+		// Note: 'bp_get_send_message_button' is a legacy filter. Use
+		// 'bp_get_send_message_button_args' instead. See #4536.
+		return apply_filters( 'bp_get_send_message_button',
+
+			/**
+			 * Filters the "Private Message" button for member profile headers.
+			 *
+			 * @since 1.8.0
+			 *
+			 * @param array $value See {@link BP_Button}.
+			 */
+			bp_get_button( apply_filters( 'bp_get_send_message_button_args', array(
+				'id'                => 'private_message',
+				'component'         => 'messages',
+				'must_be_logged_in' => true,
+				'block_self'        => true,
+				'wrapper_id'        => 'send-private-message',
+				'link_href'         => bp_get_send_private_message_link(),
+				'link_title'        => __( 'Send a private message to this user.', 'buddypress' ),
+				'link_text'         => __( 'Private Message', 'buddypress' ),
+				'link_class'        => 'send-message',
+			) ) )
+		);
+	}
+
+/**
+ * Output the URL of the Messages AJAX loader gif.
+ */
+function bp_message_loading_image_src() {
+	echo esc_url( bp_get_message_loading_image_src() );
+}
+	/**
+	 * Get the URL of the Messages AJAX loader gif.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_loading_image_src() {
+
+		/**
+		 * Filters the URL of the Messages AJAX loader gif.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL of the Messages AJAX loader gif.
+		 */
+		return apply_filters( 'bp_get_message_loading_image_src', buddypress()->messages->image_base . '/ajax-loader.gif' );
+	}
+
+/**
+ * Output the markup for the message recipient tabs.
+ */
+function bp_message_get_recipient_tabs() {
+	$recipients = explode( ' ', bp_get_message_get_recipient_usernames() );
+
+	foreach ( $recipients as $recipient ) {
+
+		$user_id = bp_is_username_compatibility_mode()
+			? bp_core_get_userid( $recipient )
+			: bp_core_get_userid_from_nicename( $recipient );
+
+		if ( ! empty( $user_id ) ) : ?>
+
+			<li id="un-<?php echo esc_attr( $recipient ); ?>" class="friend-tab">
+				<span><?php
+					echo bp_core_fetch_avatar( array( 'item_id' => $user_id, 'type' => 'thumb', 'width' => 15, 'height' => 15 ) );
+					echo bp_core_get_userlink( $user_id );
+				?></span>
+			</li>
+
+		<?php endif;
+	}
+}
+
+/**
+ * Output recipient usernames for prefilling the 'To' field on the Compose screen.
+ */
+function bp_message_get_recipient_usernames() {
+	echo esc_attr( bp_get_message_get_recipient_usernames() );
+}
+	/**
+	 * Get the recipient usernames for prefilling the 'To' field on the Compose screen.
+	 *
+	 * @return string
+	 */
+	function bp_get_message_get_recipient_usernames() {
+
+		// Sanitized in bp-messages-filters.php.
+		$recipients = isset( $_GET['r'] )
+			? $_GET['r']
+			: '';
+
+		/**
+		 * Filters the recipients usernames for prefilling the 'To' field on the Compose screen.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $recipients Recipients usernames for 'To' field prefilling.
+		 */
+		return apply_filters( 'bp_get_message_get_recipient_usernames', $recipients );
+	}
+
+/**
+ * Message Thread Template Class
+ */
+class BP_Messages_Thread_Template {
+
+	/**
+	 * The loop iterator.
+	 *
+	 * @var int
+	 */
+	public $current_message = -1;
+
+	/**
+	 * Number of messages returned by the paged query.
+	 *
+	 * @var int
+	 */
+	public $message_count = 0;
+
+	/**
+	 * The message object currently being iterated on.
+	 *
+	 * @var object
+	 */
+	public $message;
+
+	/**
+	 * Thread that the current messages belong to.
+	 *
+	 * @var BP_Messages_Thread
+	 */
+	public $thread;
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @var bool
+	 */
+	public $in_the_loop = false;
+
+	/**
+	 * The page number being requested.
+	 *
+	 * @var int
+	 */
+	public $pag_page = 1;
+
+	/**
+	 * The number of items being requested per page.
+	 *
+	 * @var int
+	 */
+	public $pag_num = 10;
+
+	/**
+	 * An HTML string containing pagination links.
+	 *
+	 * @var string
+	 */
+	public $pag_links = '';
+
+	/**
+	 * The total number of messages matching the query.
+	 *
+	 * @var int
+	 */
+	public $total_message_count = 0;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @see BP_Messages_Thread::populate() for full parameter info.
+	 *
+	 * @param int    $thread_id ID of the message thread to display.
+	 * @param string $order     Order to show the thread's messages in.
+	 * @param array  $args      Array of arguments for the query.
+	 */
+	public function __construct( $thread_id = 0, $order = 'ASC', $args = array() ) {
+		$this->thread        = new BP_Messages_Thread( $thread_id, $order, $args );
+		$this->message_count = count( $this->thread->messages );
+	}
+
+	/**
+	 * Whether there are messages available in the loop.
+	 *
+	 * @see bp_thread_has_messages()
+	 *
+	 * @return bool True if there are items in the loop, otherwise false.
+	 */
+	public function has_messages() {
+		if ( ! empty( $this->message_count ) ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Set up the next member and iterate index.
+	 *
+	 * @return object The next member to iterate over.
+	 */
+	public function next_message() {
+		$this->current_message++;
+		$this->message = $this->thread->messages[ $this->current_message ];
+
+		return $this->message;
+	}
+
+	/**
+	 * Rewind the messages and reset message index.
+	 */
+	public function rewind_messages() {
+		$this->current_message = -1;
+		if ( $this->message_count > 0 ) {
+			$this->message = $this->thread->messages[0];
+		}
+	}
+
+	/**
+	 * Whether there are messages left in the loop to iterate over.
+	 *
+	 * This method is used by {@link bp_thread_messages()} as part of the
+	 * while loop that controls iteration inside the messages loop, eg:
+	 *     while ( bp_thread_messages() ) { ...
+	 *
+	 * @see bp_thread_messages()
+	 *
+	 * @return bool True if there are more messages to show, otherwise false.
+	 */
+	public function messages() {
+		if ( ( $this->current_message + 1 ) < $this->message_count ) {
+			return true;
+		} elseif ( ( $this->current_message + 1 ) === $this->message_count ) {
+
+			/**
+			 * Fires when at the end of messages to iterate over.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'thread_loop_end' );
+			// Do some cleaning up after the loop.
+			$this->rewind_messages();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	/**
+	 * Set up the current message inside the loop.
+	 *
+	 * Used by {@link bp_thread_the_message()} to set up the current
+	 * message data while looping, so that template tags used during
+	 * that iteration make reference to the current message.
+	 *
+	 * @see bp_thread_the_message()
+	 */
+	public function the_message() {
+		$this->in_the_loop = true;
+		$this->message     = $this->next_message();
+
+		// Loop has just started.
+		if ( 0 === $this->current_message ) {
+
+			/**
+			 * Fires if at the start of the message loop.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'thread_loop_start' );
+		}
+	}
+}
+
+/**
+ * Initialize the messages template loop for a specific thread.
+ *
+ * @param array|string $args {
+ *     Array of arguments. All are optional.
+ *     @type int    $thread_id         ID of the thread whose messages you are displaying.
+ *                                     Default: if viewing a thread, the thread ID will be parsed from
+ *                                     the URL (bp_action_variable( 0 )).
+ *     @type string $order             'ASC' or 'DESC'. Default: 'ASC'.
+ *     @type bool   $update_meta_cache Whether to pre-fetch metadata for
+ *                                     queried message items. Default: true.
+ * }
+ * @return bool True if there are messages to display, otherwise false.
+ */
+function bp_thread_has_messages( $args = '' ) {
+	global $thread_template;
+
+	$r = bp_parse_args( $args, array(
+		'thread_id'         => false,
+		'order'             => 'ASC',
+		'update_meta_cache' => true,
+	), 'thread_has_messages' );
+
+	if ( empty( $r['thread_id'] ) && bp_is_messages_component() && bp_is_current_action( 'view' ) ) {
+		$r['thread_id'] = (int) bp_action_variable( 0 );
+	}
+
+	// Set up extra args.
+	$extra_args = $r;
+	unset( $extra_args['thread_id'], $extra_args['order'] );
+
+	$thread_template = new BP_Messages_Thread_Template( $r['thread_id'], $r['order'], $extra_args );
+
+	return $thread_template->has_messages();
+}
+
+/**
+ * Output the 'ASC' or 'DESC' messages order string for this loop.
+ */
+function bp_thread_messages_order() {
+	echo esc_attr( bp_get_thread_messages_order() );
+}
+	/**
+	 * Get the 'ASC' or 'DESC' messages order string for this loop.
+	 *
+	 * @return string
+	 */
+	function bp_get_thread_messages_order() {
+		global $thread_template;
+		return $thread_template->thread->messages_order;
+	}
+
+/**
+ * Check whether there are more messages to iterate over.
+ *
+ * @return bool
+ */
+function bp_thread_messages() {
+	global $thread_template;
+
+	return $thread_template->messages();
+}
+
+/**
+ * Set up the current thread inside the loop.
+ *
+ * @return object
+ */
+function bp_thread_the_message() {
+	global $thread_template;
+
+	return $thread_template->the_message();
+}
+
+/**
+ * Output the ID of the thread that the current loop belongs to.
+ */
+function bp_the_thread_id() {
+	echo (int) bp_get_the_thread_id();
+}
+	/**
+	 * Get the ID of the thread that the current loop belongs to.
+	 *
+	 * @return int
+	 */
+	function bp_get_the_thread_id() {
+		global $thread_template;
+
+		/**
+		 * Filters the ID of the thread that the current loop belongs to.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int $thread_id ID of the thread.
+		 */
+		return apply_filters( 'bp_get_the_thread_id', $thread_template->thread->thread_id );
+	}
+
+/**
+ * Output the subject of the thread currently being iterated over.
+ */
+function bp_the_thread_subject() {
+	echo bp_get_the_thread_subject();
+}
+	/**
+	 * Get the subject of the thread currently being iterated over.
+	 *
+	 * @return string
+	 */
+	function bp_get_the_thread_subject() {
+		global $thread_template;
+
+		/**
+		 * Filters the subject of the thread currently being iterated over.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @return string $last_message_subject Subject of the thread currently being iterated over.
+		 */
+		return apply_filters( 'bp_get_the_thread_subject', $thread_template->thread->last_message_subject );
+	}
+
+/**
+ * Get a list of thread recipients or a "x recipients" string.
+ *
+ * In BuddyPress 2.2.0, this parts of this functionality were moved into the
+ * members/single/messages/single.php template. This function is no longer used
+ * by BuddyPress.
+ *
+ * @return string
+ */
+function bp_get_the_thread_recipients() {
+	if ( 5 <= bp_get_thread_recipients_count() ) {
+		$recipients = sprintf( __( '%s recipients', 'buddypress' ), number_format_i18n( bp_get_thread_recipients_count() ) );
+	} else {
+		$recipients = bp_get_thread_recipients_list();
+	}
+
+	return apply_filters( 'bp_get_the_thread_recipients', $recipients );
+}
+
+/**
+ * Get the number of recipients in the current thread.
+ *
+ * @since 2.2.0
+ *
+ * @return int
+ */
+function bp_get_thread_recipients_count() {
+	global $thread_template;
+	return count( $thread_template->thread->recipients );
+}
+
+/**
+ * Get the max number of recipients to list in the 'Conversation between...' gloss.
+ *
+ * @since 2.3.0
+ *
+ * @return int
+ */
+function bp_get_max_thread_recipients_to_list() {
+	/**
+	 * Filters the max number of recipients to list in the 'Conversation between...' gloss.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param int $count Recipient count. Default: 5.
+	 */
+	return (int) apply_filters( 'bp_get_max_thread_recipients_to_list', 5 );
+}
+
+/**
+ * Output HTML links to recipients in the current thread.
+ *
+ * @since 2.2.0
+ */
+function bp_the_thread_recipients_list() {
+	echo bp_get_thread_recipients_list();
+}
+	/**
+	 * Generate HTML links to the profiles of recipients in the current thread.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @return string
+	 */
+	function bp_get_thread_recipients_list() {
+		global $thread_template;
+
+		$recipient_links = array();
+
+		foreach( (array) $thread_template->thread->recipients as $recipient ) {
+			if ( (int) $recipient->user_id !== bp_loggedin_user_id() ) {
+				$recipient_link = bp_core_get_userlink( $recipient->user_id );
+
+				if ( empty( $recipient_link ) ) {
+					$recipient_link = __( 'Deleted User', 'buddypress' );
+				}
+
+				$recipient_links[] = $recipient_link;
+			}
+		}
+
+		/**
+		 * Filters the HTML links to the profiles of recipients in the current thread.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param string $value Comma-separated list of recipient HTML links for current thread.
+		 */
+		return apply_filters( 'bp_get_the_thread_recipients_list', implode( ', ', $recipient_links ) );
+	}
+
+/**
+ * Echo the ID of the current message in the thread.
+ *
+ * @since 1.9.0
+ */
+function bp_the_thread_message_id() {
+	echo (int) bp_get_the_thread_message_id();
+}
+	/**
+	 * Get the ID of the current message in the thread.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return int
+	 */
+	function bp_get_the_thread_message_id() {
+		global $thread_template;
+
+		$thread_message_id = isset( $thread_template->message->id )
+			? (int) $thread_template->message->id
+			: null;
+
+		/**
+		 * Filters the ID of the current message in the thread.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param int $thread_message_id ID of the current message in the thread.
+		 */
+		return apply_filters( 'bp_get_the_thread_message_id', $thread_message_id );
+	}
+
+/**
+ * Output the CSS classes for messages within a single thread.
+ *
+ * @since 2.1.0
+ */
+function bp_the_thread_message_css_class() {
+	echo esc_attr( bp_get_the_thread_message_css_class() );
+}
+	/**
+	 * Generate the CSS classes for messages within a single thread.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_thread_message_css_class() {
+		global $thread_template;
+
+		$classes = array();
+
+		// Zebra-striping.
+		$classes[] = bp_get_the_thread_message_alt_class();
+
+		// ID of the sender.
+		$classes[] = 'sent-by-' . intval( $thread_template->message->sender_id );
+
+		// Whether the sender is the same as the logged-in user.
+		if ( bp_loggedin_user_id() == $thread_template->message->sender_id ) {
+			$classes[] = 'sent-by-me';
+		}
+
+		/**
+		 * Filters the CSS classes for messages within a single thread.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param array $classes Array of classes to add to the HTML class attribute.
+		 */
+		$classes = apply_filters( 'bp_get_the_thread_message_css_class', $classes );
+
+		return implode( ' ', $classes );
+	}
+
+/**
+ * Output the CSS class used for message zebra striping.
+ */
+function bp_the_thread_message_alt_class() {
+	echo esc_attr( bp_get_the_thread_message_alt_class() );
+}
+	/**
+	 * Get the CSS class used for message zebra striping.
+	 *
+	 * @return string
+	 */
+	function bp_get_the_thread_message_alt_class() {
+		global $thread_template;
+
+		if ( $thread_template->current_message % 2 == 1 ) {
+			$class = 'even alt';
+		} else {
+			$class = 'odd';
+		}
+
+		/**
+		 * Filters the CSS class used for message zebra striping.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $class Class determined to be next for zebra striping effect.
+		 */
+		return apply_filters( 'bp_get_the_thread_message_alt_class', $class );
+	}
+
+/**
+ * Output the ID for message sender within a single thread.
+ *
+ * @since 2.1.0
+ */
+function bp_the_thread_message_sender_id() {
+	echo (int) bp_get_the_thread_message_sender_id();
+}
+	/**
+	 * Return the ID for message sender within a single thread.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_thread_message_sender_id() {
+		global $thread_template;
+
+		$user_id = ! empty( $thread_template->message->sender_id )
+			? $thread_template->message->sender_id
+			: 0;
+
+		/**
+		 * Filters the ID for message sender within a single thread.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param int $user_id ID of the message sender.
+		 */
+		return (int) apply_filters( 'bp_get_the_thread_message_sender_id', (int) $user_id );
+	}
+
+/**
+ * Output the avatar for the current message sender.
+ *
+ * @param array|string $args See {@link bp_get_the_thread_message_sender_avatar_thumb()}
+ *                           for a description.
+ */
+function bp_the_thread_message_sender_avatar( $args = '' ) {
+	echo bp_get_the_thread_message_sender_avatar_thumb( $args );
+}
+	/**
+	 * Get the avatar for the current message sender.
+	 *
+	 * @param array|string $args {
+	 *     Array of arguments. See {@link bp_core_fetch_avatar()} for more
+	 *     complete details. All arguments are optional.
+	 *     @type string $type   Avatar type. Default: 'thumb'.
+	 *     @type int    $width  Avatar width. Default: default for your $type.
+	 *     @type int    $height Avatar height. Default: default for your $type.
+	 * }
+	 * @return string <img> tag containing the avatar.
+	 */
+	function bp_get_the_thread_message_sender_avatar_thumb( $args = '' ) {
+		global $thread_template;
+
+		$r = bp_parse_args( $args, array(
+			'type'   => 'thumb',
+			'width'  => false,
+			'height' => false,
+		) );
+
+		/**
+		 * Filters the avatar for the current message sender.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value <img> tag containing the avatar value.
+		 */
+		return apply_filters( 'bp_get_the_thread_message_sender_avatar_thumb', bp_core_fetch_avatar( array(
+			'item_id' => $thread_template->message->sender_id,
+			'type'    => $r['type'],
+			'width'   => $r['width'],
+			'height'  => $r['height'],
+			'alt'     => bp_core_get_user_displayname( $thread_template->message->sender_id )
+		) ) );
+	}
+
+/**
+ * Output a link to the sender of the current message.
+ *
+ * @since 1.1.0
+ */
+function bp_the_thread_message_sender_link() {
+	echo esc_url( bp_get_the_thread_message_sender_link() );
+}
+	/**
+	 * Get a link to the sender of the current message.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_thread_message_sender_link() {
+		global $thread_template;
+
+		/**
+		 * Filters the link to the sender of the current message.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Link to the sender of the current message.
+		 */
+		return apply_filters( 'bp_get_the_thread_message_sender_link', bp_core_get_userlink( $thread_template->message->sender_id, false, true ) );
+	}
+
+/**
+ * Output the display name of the sender of the current message.
+ *
+ * @since 1.1.0
+ */
+function bp_the_thread_message_sender_name() {
+	echo esc_html( bp_get_the_thread_message_sender_name() );
+}
+	/**
+	 * Get the display name of the sender of the current message.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_thread_message_sender_name() {
+		global $thread_template;
+
+		$display_name = bp_core_get_user_displayname( $thread_template->message->sender_id );
+
+		if ( empty( $display_name ) ) {
+			$display_name = __( 'Deleted User', 'buddypress' );
+		}
+
+		/**
+		 * Filters the display name of the sender of the current message.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $display_name Display name of the sender of the current message.
+		 */
+		return apply_filters( 'bp_get_the_thread_message_sender_name', $display_name );
+	}
+
+/**
+ * Output the URL for deleting the current thread.
+ *
+ * @since 1.5.0
+ */
+function bp_the_thread_delete_link() {
+	echo esc_url( bp_get_the_thread_delete_link() );
+}
+	/**
+	 * Get the URL for deleting the current thread.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @return string URL
+	 */
+	function bp_get_the_thread_delete_link() {
+
+		/**
+		 * Filters the URL for deleting the current thread.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value URL for deleting the current thread.
+		 * @param string $value Text indicating action being executed.
+		 */
+		return apply_filters( 'bp_get_message_thread_delete_link', wp_nonce_url( bp_loggedin_user_domain() . bp_get_messages_slug() . '/inbox/delete/' . bp_get_the_thread_id(), 'messages_delete_thread' ) );
+	}
+
+/**
+ * Output the 'Sent x hours ago' string for the current message.
+ *
+ * @since 1.1.0
+ */
+function bp_the_thread_message_time_since() {
+	echo bp_get_the_thread_message_time_since();
+}
+	/**
+	 * Generate the 'Sent x hours ago' string for the current message.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_thread_message_time_since() {
+
+		/**
+		 * Filters the 'Sent x hours ago' string for the current message.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Default text of 'Sent x hours ago'.
+		 */
+		return apply_filters( 'bp_get_the_thread_message_time_since', sprintf( __( 'Sent %s', 'buddypress' ), bp_core_time_since( bp_get_the_thread_message_date_sent() ) ) );
+	}
+
+/**
+ * Output the timestamp for the current message.
+ *
+ * @since 2.1.0
+ */
+function bp_the_thread_message_date_sent() {
+	echo bp_get_the_thread_message_date_sent();
+}
+	/**
+	 * Generate the 'Sent x hours ago' string for the current message.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @uses strtotime() To convert the message string into a usable timestamp.
+	 *
+	 * @return int
+	 */
+	function bp_get_the_thread_message_date_sent() {
+		global $thread_template;
+
+		/**
+		 * Filters the date sent value for the current message as a timestamp.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $value Timestamp of the date sent value for the current message.
+		 */
+		return apply_filters( 'bp_get_the_thread_message_date_sent', strtotime( $thread_template->message->date_sent ) );
+	}
+
+/**
+ * Output the content of the current message in the loop.
+ *
+ * @since 1.1.0
+ */
+function bp_the_thread_message_content() {
+	echo bp_get_the_thread_message_content();
+}
+	/**
+	 * Get the content of the current message in the loop.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_thread_message_content() {
+		global $thread_template;
+
+		/**
+		 * Filters the content of the current message in the loop.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $message The content of the current message in the loop.
+		 */
+		return apply_filters( 'bp_get_the_thread_message_content', $thread_template->message->message );
+	}
+
+/** Embeds *******************************************************************/
+
+/**
+ * Enable oEmbed support for Messages.
+ *
+ * @since 1.5.0
+ *
+ * @see BP_Embed
+ */
+function bp_messages_embed() {
+	add_filter( 'embed_post_id',         'bp_get_the_thread_message_id' );
+	add_filter( 'bp_embed_get_cache',    'bp_embed_message_cache',      10, 3 );
+	add_action( 'bp_embed_update_cache', 'bp_embed_message_save_cache', 10, 3 );
+}
+add_action( 'thread_loop_start', 'bp_messages_embed' );
+
+/**
+ * Fetch a private message item's cached embeds.
+ *
+ * Used during {@link BP_Embed::parse_oembed()} via {@link bp_messages_embed()}.
+ *
+ * @since 2.2.0
+ *
+ * @param string $cache    An empty string passed by BP_Embed::parse_oembed() for
+ *                         functions like this one to filter.
+ * @param int    $id       The ID of the message item.
+ * @param string $cachekey The cache key generated in BP_Embed::parse_oembed().
+ * @return mixed The cached embeds for this message item.
+ */
+function bp_embed_message_cache( $cache, $id, $cachekey ) {
+	return bp_messages_get_meta( $id, $cachekey );
+}
+
+/**
+ * Set a private message item's embed cache.
+ *
+ * Used during {@link BP_Embed::parse_oembed()} via {@link bp_messages_embed()}.
+ *
+ * @since 2.2.0
+ *
+ * @param string $cache    An empty string passed by BP_Embed::parse_oembed() for
+ *                         functions like this one to filter.
+ * @param string $cachekey The cache key generated in BP_Embed::parse_oembed().
+ * @param int    $id       The ID of the message item.
+ */
+function bp_embed_message_save_cache( $cache, $cachekey, $id ) {
+	bp_messages_update_meta( $id, $cachekey, $cache );
+}
Index: /tags/2.4.3/src/bp-messages/bp-messages-widgets.php
===================================================================
--- /tags/2.4.3/src/bp-messages/bp-messages-widgets.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/bp-messages-widgets.php	(revision 10410)
@@ -0,0 +1,130 @@
+<?php
+/**
+ * BuddyPress Messages Widgets.
+ *
+ * @package BuddyPress
+ * @subpackage Messages
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register widgets for the Messages component.
+ *
+ * @since 1.9.0
+ */
+function bp_messages_register_widgets() {
+	add_action( 'widgets_init', create_function('', 'return register_widget( "BP_Messages_Sitewide_Notices_Widget" );') );
+}
+add_action( 'bp_register_widgets', 'bp_messages_register_widgets' );
+
+/**
+ * A widget that displays sitewide notices.
+ *
+ * @since 1.9.0
+ */
+class BP_Messages_Sitewide_Notices_Widget extends WP_Widget {
+
+	/**
+	 * Constructor method.
+	 */
+	function __construct() {
+		parent::__construct(
+			'bp_messages_sitewide_notices_widget',
+			__( '(BuddyPress) Sitewide Notices', 'buddypress' ),
+			array(
+				'classname'   => 'widget_bp_core_sitewide_messages buddypress widget',
+				'description' => __( 'Display Sitewide Notices posted by the site administrator', 'buddypress' ),
+			)
+		);
+	}
+
+	/**
+	 * Render the widget.
+	 *
+	 * @see WP_Widget::widget() for a description of parameters.
+	 *
+	 * @param array $args     See {@WP_Widget::widget()}.
+	 * @param array $instance See {@WP_Widget::widget()}.
+	 */
+	public function widget( $args, $instance ) {
+
+		if ( ! is_user_logged_in() ) {
+			return;
+		}
+
+		// Don't display the widget if there are no Notices to show.
+		$notices = BP_Messages_Notice::get_active();
+		if ( empty( $notices ) ) {
+			return;
+		}
+
+		extract( $args );
+
+		$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
+
+		/**
+		 * Filters the title of the Messages widget.
+		 *
+		 * @since 1.9.0
+		 * @since 2.3.0 Added 'instance' and 'id_base' to arguments passed to filter.
+		 *
+		 * @param string $title    The widget title.
+		 * @param array  $instance The settings for the particular instance of the widget.
+		 * @param string $id_base  Root ID for all widgets of this type.
+		 */
+		$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
+
+		echo $before_widget;
+		echo $before_title . $title . $after_title; ?>
+
+		<div class="bp-site-wide-message">
+			<?php bp_message_get_notices(); ?>
+		</div>
+
+		<?php
+
+		echo $after_widget;
+	}
+
+	/**
+	 * Process the saved settings for the widget.
+	 *
+	 * @see WP_Widget::update() for a description of parameters and
+	 *      return values.
+	 *
+	 * @param array $new_instance See {@WP_Widget::update()}.
+	 * @param array $old_instance See {@WP_Widget::update()}.
+	 * @return array $instance See {@WP_Widget::update()}.
+	 */
+	public function update( $new_instance, $old_instance ) {
+		$instance = $old_instance;
+		$instance['title'] = strip_tags( $new_instance['title'] );
+		return $instance;
+	}
+
+	/**
+	 * Render the settings form for Appearance > Widgets.
+	 *
+	 * @see WP_Widget::form() for a description of parameters.
+	 *
+	 * @param array $instance See {@WP_Widget::form()}.
+	 *
+	 * @return string Widget form output.
+	 */
+	public function form( $instance ) {
+		$instance = wp_parse_args( (array) $instance, array(
+			'title' => '',
+		) );
+
+		$title = strip_tags( $instance['title'] ); ?>
+
+		<p>
+			<label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:', 'buddypress' ); ?></label>
+			<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( $title ); ?>" />
+		</p>
+
+		<?php
+	}
+}
Index: /tags/2.4.3/src/bp-messages/classes/class-bp-messages-message.php
===================================================================
--- /tags/2.4.3/src/bp-messages/classes/class-bp-messages-message.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/classes/class-bp-messages-message.php	(revision 10410)
@@ -0,0 +1,254 @@
+<?php
+/**
+ * BuddyPress Messages Classes.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Single message class.
+ */
+class BP_Messages_Message {
+	/**
+	 * ID of the message.
+	 *
+	 * @var int
+	 */
+	public $id;
+
+	/**
+	 * ID of the message thread.
+	 *
+	 * @var int
+	 */
+	public $thread_id;
+
+	/**
+	 * ID of the sender.
+	 *
+	 * @var int
+	 */
+	public $sender_id;
+
+	/**
+	 * Subject line of the message.
+	 *
+	 * @var string
+	 */
+	public $subject;
+
+	/**
+	 * Content of the message.
+	 *
+	 * @var string
+	 */
+	public $message;
+
+	/**
+	 * Date the message was sent.
+	 *
+	 * @var string
+	 */
+	public $date_sent;
+
+	/**
+	 * Message recipients.
+	 *
+	 * @var bool|array
+	 */
+	public $recipients = false;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param int $id Optional. ID of the message.
+	 */
+	public function __construct( $id = null ) {
+		$this->date_sent = bp_core_current_time();
+		$this->sender_id = bp_loggedin_user_id();
+
+		if ( !empty( $id ) ) {
+			$this->populate( $id );
+		}
+	}
+
+	/**
+	 * Set up data related to a specific message object.
+	 *
+	 * @param int $id ID of the message.
+	 */
+	public function populate( $id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( $message = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->messages->table_name_messages} WHERE id = %d", $id ) ) ) {
+			$this->id        = $message->id;
+			$this->thread_id = $message->thread_id;
+			$this->sender_id = $message->sender_id;
+			$this->subject   = $message->subject;
+			$this->message   = $message->message;
+			$this->date_sent = $message->date_sent;
+		}
+	}
+
+	/**
+	 * Send a message.
+	 *
+	 * @return int|bool ID of the newly created message on success, false on failure.
+	 */
+	public function send() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$this->sender_id = apply_filters( 'messages_message_sender_id_before_save', $this->sender_id, $this->id );
+		$this->thread_id = apply_filters( 'messages_message_thread_id_before_save', $this->thread_id, $this->id );
+		$this->subject   = apply_filters( 'messages_message_subject_before_save',   $this->subject,   $this->id );
+		$this->message   = apply_filters( 'messages_message_content_before_save',   $this->message,   $this->id );
+		$this->date_sent = apply_filters( 'messages_message_date_sent_before_save', $this->date_sent, $this->id );
+
+		/**
+		 * Fires before the current message item gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Messages_Message $this Current instance of the message item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'messages_message_before_save', array( &$this ) );
+
+		// Make sure we have at least one recipient before sending.
+		if ( empty( $this->recipients ) )
+			return false;
+
+		$new_thread = false;
+
+		// If we have no thread_id then this is the first message of a new thread.
+		if ( empty( $this->thread_id ) ) {
+			$this->thread_id = (int) $wpdb->get_var( "SELECT MAX(thread_id) FROM {$bp->messages->table_name_messages}" ) + 1;
+			$new_thread = true;
+		}
+
+		// First insert the message into the messages table.
+		if ( !$wpdb->query( $wpdb->prepare( "INSERT INTO {$bp->messages->table_name_messages} ( thread_id, sender_id, subject, message, date_sent ) VALUES ( %d, %d, %s, %s, %s )", $this->thread_id, $this->sender_id, $this->subject, $this->message, $this->date_sent ) ) )
+			return false;
+
+		$this->id = $wpdb->insert_id;
+
+		$recipient_ids = array();
+
+		if ( $new_thread ) {
+			// Add an recipient entry for all recipients.
+			foreach ( (array) $this->recipients as $recipient ) {
+				$wpdb->query( $wpdb->prepare( "INSERT INTO {$bp->messages->table_name_recipients} ( user_id, thread_id, unread_count ) VALUES ( %d, %d, 1 )", $recipient->user_id, $this->thread_id ) );
+				$recipient_ids[] = $recipient->user_id;
+			}
+
+			// Add a sender recipient entry if the sender is not in the list of recipients.
+			if ( !in_array( $this->sender_id, $recipient_ids ) )
+				$wpdb->query( $wpdb->prepare( "INSERT INTO {$bp->messages->table_name_recipients} ( user_id, thread_id, sender_only ) VALUES ( %d, %d, 1 )", $this->sender_id, $this->thread_id ) );
+		} else {
+			// Update the unread count for all recipients.
+			$wpdb->query( $wpdb->prepare( "UPDATE {$bp->messages->table_name_recipients} SET unread_count = unread_count + 1, sender_only = 0, is_deleted = 0 WHERE thread_id = %d AND user_id != %d", $this->thread_id, $this->sender_id ) );
+		}
+
+		messages_remove_callback_values();
+
+		/**
+		 * Fires after the current message item has been saved.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Messages_Message $this Current instance of the message item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'messages_message_after_save', array( &$this ) );
+
+		return $this->id;
+	}
+
+	/**
+	 * Get a list of recipients for a message.
+	 *
+	 * @return object $value List of recipients for a message.
+	 */
+	public function get_recipients() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_results( $wpdb->prepare( "SELECT user_id FROM {$bp->messages->table_name_recipients} WHERE thread_id = %d", $this->thread_id ) );
+	}
+
+	/** Static Functions **************************************************/
+
+	/**
+	 * Get list of recipient IDs from their usernames.
+	 *
+	 * @param array $recipient_usernames Usernames of recipients.
+	 * @return array $recipient_ids Array of Recepient IDs.
+	 */
+	public static function get_recipient_ids( $recipient_usernames ) {
+		if ( !$recipient_usernames )
+			return false;
+
+		if ( is_array( $recipient_usernames ) ) {
+			for ( $i = 0, $count = count( $recipient_usernames ); $i < $count; ++$i ) {
+				if ( $rid = bp_core_get_userid( trim($recipient_usernames[$i]) ) ) {
+					$recipient_ids[] = $rid;
+				}
+			}
+		}
+
+		return $recipient_ids;
+	}
+
+	/**
+	 * Get the ID of the message last sent by the logged-in user for a given thread.
+	 *
+	 * @param int $thread_id ID of the thread.
+	 * @return int|null ID of the message if found, otherwise null.
+	 */
+	public static function get_last_sent_for_user( $thread_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->messages->table_name_messages} WHERE sender_id = %d AND thread_id = %d ORDER BY date_sent DESC LIMIT 1", bp_loggedin_user_id(), $thread_id ) );
+	}
+
+	/**
+	 * Check whether a user is the sender of a message.
+	 *
+	 * @param int $user_id    ID of the user.
+	 * @param int $message_id ID of the message.
+	 * @return int|null Returns the ID of the message if the user is the
+	 *                  sender, otherwise null.
+	 */
+	public static function is_user_sender( $user_id, $message_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->messages->table_name_messages} WHERE sender_id = %d AND id = %d", $user_id, $message_id ) );
+	}
+
+	/**
+	 * Get the ID of the sender of a message.
+	 *
+	 * @param int $message_id ID of the message.
+	 * @return int|null The ID of the sender if found, otherwise null.
+	 */
+	public static function get_message_sender( $message_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT sender_id FROM {$bp->messages->table_name_messages} WHERE id = %d", $message_id ) );
+	}
+}
Index: /tags/2.4.3/src/bp-messages/classes/class-bp-messages-notice.php
===================================================================
--- /tags/2.4.3/src/bp-messages/classes/class-bp-messages-notice.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/classes/class-bp-messages-notice.php	(revision 10410)
@@ -0,0 +1,277 @@
+<?php
+/**
+ * BuddyPress Messages Classes.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BuddyPress Notices class.
+ *
+ * Use this class to create, activate, deactivate or delete notices.
+ *
+ * @since 1.0.0
+ */
+class BP_Messages_Notice {
+	/**
+	 * The notice ID.
+	 *
+	 * @var int
+	 */
+	public $id = null;
+
+	/**
+	 * The subject line for the notice.
+	 *
+	 * @var string
+	 */
+	public $subject;
+
+	/**
+	 * The content of the notice.
+	 *
+	 * @var string
+	 */
+	public $message;
+
+	/**
+	 * The date the notice was created.
+	 *
+	 * @var string
+	 */
+	public $date_sent;
+
+	/**
+	 * Whether the notice is active or not.
+	 *
+	 * @var int
+	 */
+	public $is_active;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $id Optional. The ID of the current notice.
+	 */
+	public function __construct( $id = null ) {
+		if ( $id ) {
+			$this->id = $id;
+			$this->populate();
+		}
+	}
+
+	/**
+	 * Populate method.
+	 *
+	 * Runs during constructor.
+	 *
+	 * @since 1.0.0
+	 */
+	public function populate() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$notice = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->messages->table_name_notices} WHERE id = %d", $this->id ) );
+
+		if ( $notice ) {
+			$this->subject   = $notice->subject;
+			$this->message   = $notice->message;
+			$this->date_sent = $notice->date_sent;
+			$this->is_active = $notice->is_active;
+		}
+	}
+
+	/**
+	 * Saves a notice.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @return bool
+	 */
+	public function save() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$this->subject = apply_filters( 'messages_notice_subject_before_save', $this->subject, $this->id );
+		$this->message = apply_filters( 'messages_notice_message_before_save', $this->message, $this->id );
+
+		/**
+		 * Fires before the current message notice item gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Messages_Notice $this Current instance of the message notice item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'messages_notice_before_save', array( &$this ) );
+
+		if ( empty( $this->id ) ) {
+			$sql = $wpdb->prepare( "INSERT INTO {$bp->messages->table_name_notices} (subject, message, date_sent, is_active) VALUES (%s, %s, %s, %d)", $this->subject, $this->message, $this->date_sent, $this->is_active );
+		} else {
+			$sql = $wpdb->prepare( "UPDATE {$bp->messages->table_name_notices} SET subject = %s, message = %s, is_active = %d WHERE id = %d", $this->subject, $this->message, $this->is_active, $this->id );
+		}
+
+		if ( ! $wpdb->query( $sql ) ) {
+			return false;
+		}
+
+		if ( ! $id = $this->id ) {
+			$id = $wpdb->insert_id;
+		}
+
+		// Now deactivate all notices apart from the new one.
+		$wpdb->query( $wpdb->prepare( "UPDATE {$bp->messages->table_name_notices} SET is_active = 0 WHERE id != %d", $id ) );
+
+		bp_update_user_last_activity( bp_loggedin_user_id(), bp_core_current_time() );
+
+		/**
+		 * Fires after the current message notice item has been saved.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Messages_Notice $this Current instance of the message item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'messages_notice_after_save', array( &$this ) );
+
+		return true;
+	}
+
+	/**
+	 * Activates a notice.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @return bool
+	 */
+	public function activate() {
+		$this->is_active = 1;
+		return (bool) $this->save();
+	}
+
+	/**
+	 * Deactivates a notice.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @return bool
+	 */
+	public function deactivate() {
+		$this->is_active = 0;
+		return (bool) $this->save();
+	}
+
+	/**
+	 * Deletes a notice.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @return bool
+	 */
+	public function delete() {
+		global $wpdb;
+
+		/**
+		 * Fires before the current message item has been deleted.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_Messages_Notice $this Current instance of the message notice item being deleted.
+		 */
+		do_action( 'messages_notice_before_delete', $this );
+
+		$bp  = buddypress();
+		$sql = $wpdb->prepare( "DELETE FROM {$bp->messages->table_name_notices} WHERE id = %d", $this->id );
+
+		if ( ! $wpdb->query( $sql ) ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/** Static Methods ********************************************************/
+
+	/**
+	 * Pulls up a list of notices.
+	 *
+	 * To get all notices, pass a value of -1 to pag_num.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param array $args {
+	 *     Array of parameters.
+	 *     @type int $pag_num  Number of notices per page. Defaults to 20.
+	 *     @type int $pag_page The page number.  Defaults to 1.
+	 * }
+	 * @return object List of notices to display.
+	 */
+	public static function get_notices( $args = array() ) {
+		global $wpdb;
+
+		$r = wp_parse_args( $args, array(
+			'pag_num'  => 20, // Number of notices per page.
+			'pag_page' => 1   // Page number.
+		) );
+
+		$limit_sql = '';
+		if ( (int) $r['pag_num'] >= 0 ) {
+			$limit_sql = $wpdb->prepare( "LIMIT %d, %d", (int) ( ( $r['pag_page'] - 1 ) * $r['pag_num'] ), (int) $r['pag_num'] );
+		}
+
+		$bp = buddypress();
+
+		$notices = $wpdb->get_results( "SELECT * FROM {$bp->messages->table_name_notices} ORDER BY date_sent DESC {$limit_sql}" );
+
+		return $notices;
+	}
+
+	/**
+	 * Returns the total number of recorded notices.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @return int
+	 */
+	public static function get_total_notice_count() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$notice_count = $wpdb->get_var( "SELECT COUNT(id) FROM {$bp->messages->table_name_notices}" );
+
+		return $notice_count;
+	}
+
+	/**
+	 * Returns the active notice that should be displayed on the frontend.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @return object The BP_Messages_Notice object.
+	 */
+	public static function get_active() {
+		$notice = wp_cache_get( 'active_notice', 'bp_messages' );
+
+		if ( false === $notice ) {
+			global $wpdb;
+
+			$bp = buddypress();
+
+			$notice_id = $wpdb->get_var( "SELECT id FROM {$bp->messages->table_name_notices} WHERE is_active = 1" );
+			$notice    = new BP_Messages_Notice( $notice_id );
+
+			wp_cache_set( 'active_notice', $notice, 'bp_messages' );
+		}
+
+		return $notice;
+	}
+}
Index: /tags/2.4.3/src/bp-messages/classes/class-bp-messages-thread.php
===================================================================
--- /tags/2.4.3/src/bp-messages/classes/class-bp-messages-thread.php	(revision 10410)
+++ /tags/2.4.3/src/bp-messages/classes/class-bp-messages-thread.php	(revision 10410)
@@ -0,0 +1,845 @@
+<?php
+/**
+ * BuddyPress Messages Classes.
+ *
+ * @package BuddyPress
+ * @subpackage MessagesClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BuddyPress Message Thread class.
+ *
+ * @since 1.0.0
+ */
+class BP_Messages_Thread {
+
+	/**
+	 * The message thread ID.
+	 *
+	 * @since 1.0.0
+	 * @var int
+	 */
+	public $thread_id;
+
+	/**
+	 * The current messages.
+	 *
+	 * @since 1.0.0
+	 * @var array
+	 */
+	public $messages;
+
+	/**
+	 * The current recipients in the message thread.
+	 *
+	 * @since 1.0.0
+	 * @var array
+	 */
+	public $recipients;
+
+	/**
+	 * The user IDs of all messages in the message thread.
+	 *
+	 * @since 1.2.0
+	 * @var array
+	 */
+	public $sender_ids;
+
+	/**
+	 * The unread count for the logged-in user.
+	 *
+	 * @since 1.2.0
+	 * @var int
+	 */
+	public $unread_count;
+
+	/**
+	 * The content of the last message in this thread.
+	 *
+	 * @since 1.2.0
+	 * @var string
+	 */
+	public $last_message_content;
+
+	/**
+	 * The date of the last message in this thread.
+	 *
+	 * @since 1.2.0
+	 * @var string
+	 */
+	public $last_message_date;
+
+	/**
+	 * The ID of the last message in this thread.
+	 *
+	 * @since 1.2.0
+	 * @var int
+	 */
+	public $last_message_id;
+
+	/**
+	 * The subject of the last message in this thread.
+	 *
+	 * @since 1.2.0
+	 * @var string
+	 */
+	public $last_message_subject;
+
+	/**
+	 * The user ID of the author of the last message in this thread.
+	 *
+	 * @since 1.2.0
+	 * @var int
+	 */
+	public $last_sender_id;
+
+	/**
+	 * Sort order of the messages in this thread (ASC or DESC).
+	 *
+	 * @since 1.5.0
+	 * @var string
+	 */
+	public $messages_order;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @see BP_Messages_Thread::populate() for full description of parameters.
+	 *
+	 * @param bool   $thread_id ID for the message thread.
+	 * @param string $order     Order to display the messages in.
+	 * @param array  $args      Array of arguments for thread querying.
+	 */
+	public function __construct( $thread_id = false, $order = 'ASC', $args = array() ) {
+		if ( $thread_id ) {
+			$this->populate( $thread_id, $order, $args );
+		}
+	}
+
+	/**
+	 * Populate method.
+	 *
+	 * Used in constructor.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int    $thread_id The message thread ID.
+	 * @param string $order     The order to sort the messages. Either 'ASC' or 'DESC'.
+	 * @param array  $args {
+	 *     Array of arguments.
+	 *     @type bool $update_meta_cache Whether to pre-fetch metadata for
+	 *                                   queried message items. Default: true.
+	 * }
+	 * @return bool False on failure.
+	 */
+	public function populate( $thread_id = 0, $order = 'ASC', $args = array() ) {
+
+		if ( 'ASC' !== $order && 'DESC' !== $order ) {
+			$order = 'ASC';
+		}
+
+		// Merge $args with our defaults.
+		$r = wp_parse_args( $args, array(
+			'user_id'           => bp_loggedin_user_id(),
+			'update_meta_cache' => true
+		) );
+
+		$this->messages_order = $order;
+		$this->thread_id      = (int) $thread_id;
+
+		// Get messages for thread.
+		$this->messages = self::get_messages( $this->thread_id );
+
+		if ( empty( $this->messages ) || is_wp_error( $this->messages ) ) {
+			return false;
+		}
+
+		// Flip if order is DESC.
+		if ( 'DESC' === $order ) {
+			$this->messages = array_reverse( $this->messages );
+		}
+
+		$last_message_index         = count( $this->messages ) - 1;
+		$this->last_message_id      = $this->messages[ $last_message_index ]->id;
+		$this->last_message_date    = $this->messages[ $last_message_index ]->date_sent;
+		$this->last_sender_id       = $this->messages[ $last_message_index ]->sender_id;
+		$this->last_message_subject = $this->messages[ $last_message_index ]->subject;
+		$this->last_message_content = $this->messages[ $last_message_index ]->message;
+
+		foreach ( (array) $this->messages as $key => $message ) {
+			$this->sender_ids[ $message->sender_id ] = $message->sender_id;
+		}
+
+		// Fetch the recipients.
+		$this->recipients = $this->get_recipients();
+
+		// Get the unread count for the logged in user.
+		if ( isset( $this->recipients[ $r['user_id'] ] ) ) {
+			$this->unread_count = $this->recipients[ $r['user_id'] ]->unread_count;
+		}
+
+		// Grab all message meta.
+		if ( true === (bool) $r['update_meta_cache'] ) {
+			bp_messages_update_meta_cache( wp_list_pluck( $this->messages, 'id' ) );
+		}
+
+		/**
+		 * Fires after a BP_Messages_Thread object has been populated.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param BP_Messages_Thread $this Message thread object.
+		 */
+		do_action( 'bp_messages_thread_post_populate', $this );
+	}
+
+	/**
+	 * Mark a thread initialized in this class as read.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @see BP_Messages_Thread::mark_as_read()
+	 */
+	public function mark_read() {
+		BP_Messages_Thread::mark_as_read( $this->thread_id );
+	}
+
+	/**
+	 * Mark a thread initialized in this class as unread.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @see BP_Messages_Thread::mark_as_unread()
+	 */
+	public function mark_unread() {
+		BP_Messages_Thread::mark_as_unread( $this->thread_id );
+	}
+
+	/**
+	 * Returns recipients for a message thread.
+	 *
+	 * @since 1.0.0
+	 * @since 2.3.0 Added $thread_id as a parameter.
+	 *
+	 * @param int $thread_id The thread ID.
+	 * @return array
+	 */
+	public function get_recipients( $thread_id = 0 ) {
+		global $wpdb;
+
+		if ( empty( $thread_id ) ) {
+			$thread_id = $this->thread_id;
+		}
+
+		$thread_id = (int) $thread_id;
+
+		$recipients = wp_cache_get( 'thread_recipients_' . $thread_id, 'bp_messages' );
+		if ( false === $recipients ) {
+			$bp = buddypress();
+
+			$recipients = array();
+			$sql        = $wpdb->prepare( "SELECT * FROM {$bp->messages->table_name_recipients} WHERE thread_id = %d", $thread_id );
+			$results    = $wpdb->get_results( $sql );
+
+			foreach ( (array) $results as $recipient ) {
+				$recipients[ $recipient->user_id ] = $recipient;
+			}
+
+			wp_cache_set( 'thread_recipients_' . $thread_id, $recipients, 'bp_messages' );
+		}
+
+		/**
+		 * Filters the recipients of a message thread.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param array $recipients Array of recipient objects.
+		 * @param int   $thread_id  ID of the current thread.
+		 */
+		return apply_filters( 'bp_messages_thread_get_recipients', $recipients, $thread_id );
+	}
+
+	/** Static Functions ******************************************************/
+
+	/**
+	 * Get all messages associated with a thread.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param int $thread_id The message thread ID.
+	 *
+	 * @return object List of messages associated with a thread.
+	 */
+	public static function get_messages( $thread_id = 0 ) {
+		$thread_id = (int) $thread_id;
+		$messages  = wp_cache_get( $thread_id, 'bp_messages_threads' );
+
+		if ( false === $messages ) {
+			global $wpdb;
+
+			$bp = buddypress();
+
+			// Always sort by ASC by default.
+			$messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$bp->messages->table_name_messages} WHERE thread_id = %d ORDER BY date_sent ASC", $thread_id ) );
+
+			wp_cache_set( $thread_id, (array) $messages, 'bp_messages_threads' );
+		}
+
+		return $messages;
+	}
+
+	/**
+	 * Static method to get message recipients by thread ID.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param int $thread_id The thread ID.
+	 * @return array
+	 */
+	public static function get_recipients_for_thread( $thread_id = 0 ) {
+		$thread = new self( false );
+		return $thread->get_recipients( $thread_id );
+	}
+
+	/**
+	 * Mark messages in a thread as deleted or delete all messages in a thread.
+	 *
+	 * Note: All messages in a thread are deleted once every recipient in a thread
+	 * has marked the thread as deleted.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $thread_id The message thread ID.
+	 * @return bool
+	 */
+	public static function delete( $thread_id = 0 ) {
+		global $wpdb;
+
+		$thread_id = (int) $thread_id;
+
+		/**
+		 * Fires before a message thread is marked as deleted.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param int $thread_id ID of the thread being deleted.
+		 */
+		do_action( 'bp_messages_thread_before_mark_delete', $thread_id );
+
+		$bp = buddypress();
+
+		// Mark messages as deleted
+		//
+		// @todo the reliance on bp_loggedin_user_id() sucks for plugins
+		// refactor this method to accept a $user_id parameter.
+		$wpdb->query( $wpdb->prepare( "UPDATE {$bp->messages->table_name_recipients} SET is_deleted = 1 WHERE thread_id = %d AND user_id = %d", $thread_id, bp_loggedin_user_id() ) );
+
+		// Get the message ids in order to pass to the action.
+		$message_ids = $wpdb->get_col( $wpdb->prepare( "SELECT id FROM {$bp->messages->table_name_messages} WHERE thread_id = %d", $thread_id ) );
+
+		// Check to see if any more recipients remain for this message.
+		$recipients = $wpdb->get_results( $wpdb->prepare( "SELECT id FROM {$bp->messages->table_name_recipients} WHERE thread_id = %d AND is_deleted = 0", $thread_id ) );
+
+		// No more recipients so delete all messages associated with the thread.
+		if ( empty( $recipients ) ) {
+
+			/**
+			 * Fires before an entire message thread is deleted.
+			 *
+			 * @since 2.2.0
+			 *
+			 * @param int   $thread_id   ID of the thread being deleted.
+			 * @param array $message_ids IDs of messages being deleted.
+			 */
+			do_action( 'bp_messages_thread_before_delete', $thread_id, $message_ids );
+
+			// Delete all the messages.
+			$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->messages->table_name_messages} WHERE thread_id = %d", $thread_id ) );
+
+			// Do something for each message ID.
+			foreach ( $message_ids as $message_id ) {
+
+				// Delete message meta.
+				bp_messages_delete_meta( $message_id );
+
+				/**
+				 * Fires after a message is deleted. This hook is poorly named.
+				 *
+				 * @since 1.0.0
+				 *
+				 * @param int $message_id ID of the message.
+				 */
+				do_action( 'messages_thread_deleted_thread', $message_id );
+			}
+
+			// Delete all the recipients.
+			$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->messages->table_name_recipients} WHERE thread_id = %d", $thread_id ) );
+		}
+
+		/**
+		 * Fires after a message thread is either marked as deleted or deleted.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param int   $thread_id   ID of the thread being deleted.
+		 * @param array $message_ids IDs of messages being deleted.
+		 */
+		do_action( 'bp_messages_thread_after_delete', $thread_id, $message_ids );
+
+		return true;
+	}
+
+	/**
+	 * Get current message threads for a user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param array $args {
+	 *     Array of arguments.
+	 *     @type int    $user_id      The user ID.
+	 *     @type string $box          The type of mailbox to get. Either 'inbox' or 'sentbox'.
+	 *                                Defaults to 'inbox'.
+	 *     @type string $type         The type of messages to get. Either 'all' or 'unread'
+	 *                                or 'read'. Defaults to 'all'.
+	 *     @type int    $limit        The number of messages to get. Defaults to null.
+	 *     @type int    $page         The page number to get. Defaults to null.
+	 *     @type string $search_terms The search term to use. Defaults to ''.
+	 *     @type array  $meta_query   Meta query arguments. See WP_Meta_Query for more details.
+	 * }
+	 * @return array|bool Array on success. Boolean false on failure.
+	 */
+	public static function get_current_threads_for_user( $args = array() ) {
+		global $wpdb;
+
+		// Backward compatibility with old method of passing arguments.
+		if ( ! is_array( $args ) || func_num_args() > 1 ) {
+			_deprecated_argument( __METHOD__, '2.2.0', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+			$old_args_keys = array(
+				0 => 'user_id',
+				1 => 'box',
+				2 => 'type',
+				3 => 'limit',
+				4 => 'page',
+				5 => 'search_terms',
+			);
+
+			$func_args = func_get_args();
+			$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
+		}
+
+		$r = bp_parse_args( $args, array(
+			'user_id'      => false,
+			'box'          => 'inbox',
+			'type'         => 'all',
+			'limit'        => null,
+			'page'         => null,
+			'search_terms' => '',
+			'meta_query'   => array()
+		) );
+
+		$pag_sql = $type_sql = $search_sql = $user_id_sql = $sender_sql = '';
+		$meta_query_sql = array(
+			'join'  => '',
+			'where' => ''
+		);
+
+		if ( $r['limit'] && $r['page'] ) {
+			$pag_sql = $wpdb->prepare( " LIMIT %d, %d", intval( ( $r['page'] - 1 ) * $r['limit'] ), intval( $r['limit'] ) );
+		}
+
+		if ( $r['type'] == 'unread' ) {
+			$type_sql = " AND r.unread_count != 0 ";
+		} elseif ( $r['type'] == 'read' ) {
+			$type_sql = " AND r.unread_count = 0 ";
+		}
+
+		if ( ! empty( $r['search_terms'] ) ) {
+			$search_terms_like = '%' . bp_esc_like( $r['search_terms'] ) . '%';
+			$search_sql        = $wpdb->prepare( "AND ( subject LIKE %s OR message LIKE %s )", $search_terms_like, $search_terms_like );
+		}
+
+		$r['user_id'] = (int) $r['user_id'];
+
+		// Default deleted SQL.
+		$deleted_sql = 'r.is_deleted = 0';
+
+		switch ( $r['box'] ) {
+			case 'sentbox' :
+				$user_id_sql = 'AND ' . $wpdb->prepare( 'm.sender_id = %d', $r['user_id'] );
+				$sender_sql  = 'AND m.sender_id = r.user_id';
+				break;
+
+			case 'inbox' :
+				$user_id_sql = 'AND ' . $wpdb->prepare( 'r.user_id = %d', $r['user_id'] );
+				$sender_sql  = 'AND r.sender_only = 0';
+				break;
+
+			default :
+				// Omit user-deleted threads from all other custom message boxes.
+				$deleted_sql = $wpdb->prepare( '( r.user_id = %d AND r.is_deleted = 0 )', $r['user_id'] );
+				break;
+		}
+
+		// Process meta query into SQL.
+		$meta_query = self::get_meta_query_sql( $r['meta_query'] );
+		if ( ! empty( $meta_query['join'] ) ) {
+			$meta_query_sql['join'] = $meta_query['join'];
+		}
+		if ( ! empty( $meta_query['where'] ) ) {
+			$meta_query_sql['where'] = $meta_query['where'];
+		}
+
+		$bp = buddypress();
+
+		// Set up SQL array.
+		$sql = array();
+		$sql['select'] = 'SELECT m.thread_id, MAX(m.date_sent) AS date_sent';
+		$sql['from']   = "FROM {$bp->messages->table_name_recipients} r INNER JOIN {$bp->messages->table_name_messages} m ON m.thread_id = r.thread_id {$meta_query_sql['join']}";
+		$sql['where']  = "WHERE {$deleted_sql} {$user_id_sql} {$sender_sql} {$type_sql} {$search_sql} {$meta_query_sql['where']}";
+		$sql['misc']   = "GROUP BY m.thread_id ORDER BY date_sent DESC {$pag_sql}";
+
+		// Get thread IDs.
+		$thread_ids = $wpdb->get_results( implode( ' ', $sql ) );
+		if ( empty( $thread_ids ) ) {
+			return false;
+		}
+
+		// Adjust $sql to work for thread total.
+		$sql['select'] = 'SELECT COUNT( DISTINCT m.thread_id )';
+		unset( $sql['misc'] );
+		$total_threads = $wpdb->get_var( implode( ' ', $sql ) );
+
+		// Sort threads by date_sent.
+		foreach( (array) $thread_ids as $thread ) {
+			$sorted_threads[ $thread->thread_id ] = strtotime( $thread->date_sent );
+		}
+
+		arsort( $sorted_threads );
+
+		$threads = array();
+		foreach ( (array) $sorted_threads as $thread_id => $date_sent ) {
+			$threads[] = new BP_Messages_Thread( $thread_id, 'ASC', array(
+				'update_meta_cache' => false
+			) );
+		}
+
+		/**
+		 * Filters the results of the query for a user's message threads.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param array $value {
+		 *     @type array $threads       Array of threads. Passed by reference.
+		 *     @type int   $total_threads Number of threads found by the query.
+		 * }
+		 */
+		return apply_filters( 'bp_messages_thread_current_threads', array(
+			'threads' => &$threads,
+			'total'   => (int) $total_threads
+		) );
+	}
+
+	/**
+	 * Get the SQL for the 'meta_query' param in BP_Messages_Thread::get_current_threads_for_user().
+	 *
+	 * We use WP_Meta_Query to do the heavy lifting of parsing the meta_query array
+	 * and creating the necessary SQL clauses.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $meta_query An array of meta_query filters. See the
+	 *                          documentation for WP_Meta_Query for details.
+	 * @return array $sql_array 'join' and 'where' clauses.
+	 */
+	public static function get_meta_query_sql( $meta_query = array() ) {
+		global $wpdb;
+
+		$sql_array = array(
+			'join'  => '',
+			'where' => '',
+		);
+
+		if ( ! empty( $meta_query ) ) {
+			$meta_query = new WP_Meta_Query( $meta_query );
+
+			// WP_Meta_Query expects the table name at
+			// $wpdb->messagemeta.
+			$wpdb->messagemeta = buddypress()->messages->table_name_meta;
+
+			return $meta_query->get_sql( 'message', 'm', 'id' );
+		}
+
+		return $sql_array;
+	}
+
+	/**
+	 * Mark a thread as read.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $thread_id The message thread ID.
+	 */
+	public static function mark_as_read( $thread_id = 0 ) {
+		global $wpdb;
+
+		$bp  = buddypress();
+		$sql = $wpdb->prepare( "UPDATE {$bp->messages->table_name_recipients} SET unread_count = 0 WHERE user_id = %d AND thread_id = %d", bp_loggedin_user_id(), $thread_id );
+		$wpdb->query( $sql );
+
+		wp_cache_delete( 'thread_recipients_' . $thread_id, 'bp_messages' );
+		wp_cache_delete( bp_loggedin_user_id(), 'bp_messages_unread_count' );
+	}
+
+	/**
+	 * Mark a thread as unread.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $thread_id The message thread ID.
+	 */
+	public static function mark_as_unread( $thread_id = 0 ) {
+		global $wpdb;
+
+		$bp  = buddypress();
+		$sql = $wpdb->prepare( "UPDATE {$bp->messages->table_name_recipients} SET unread_count = 1 WHERE user_id = %d AND thread_id = %d", bp_loggedin_user_id(), $thread_id );
+		$wpdb->query( $sql );
+
+		wp_cache_delete( 'thread_recipients_' . $thread_id, 'bp_messages' );
+		wp_cache_delete( bp_loggedin_user_id(), 'bp_messages_unread_count' );
+	}
+
+	/**
+	 * Returns the total number of message threads for a user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int    $user_id The user ID.
+	 * @param string $box     The type of mailbox to get. Either 'inbox' or 'sentbox'.
+	 *                        Defaults to 'inbox'.
+	 * @param string $type    The type of messages to get. Either 'all' or 'unread'.
+	 *                        or 'read'. Defaults to 'all'.
+	 * @return int $value Total thread count for the provided user.
+	 */
+	public static function get_total_threads_for_user( $user_id, $box = 'inbox', $type = 'all' ) {
+		global $wpdb;
+
+		$exclude_sender = $type_sql = '';
+		if ( $box !== 'sentbox' ) {
+			$exclude_sender = 'AND sender_only != 1';
+		}
+
+		if ( $type === 'unread' ) {
+			$type_sql = 'AND unread_count != 0';
+		} elseif ( $type === 'read' ) {
+			$type_sql = 'AND unread_count = 0';
+		}
+
+		$bp = buddypress();
+
+		return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(thread_id) FROM {$bp->messages->table_name_recipients} WHERE user_id = %d AND is_deleted = 0 {$exclude_sender} {$type_sql}", $user_id ) );
+	}
+
+	/**
+	 * Determine if the logged-in user is a sender of any message in a thread.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $thread_id The message thread ID.
+	 * @return bool
+	 */
+	public static function user_is_sender( $thread_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$sender_ids = $wpdb->get_col( $wpdb->prepare( "SELECT sender_id FROM {$bp->messages->table_name_messages} WHERE thread_id = %d", $thread_id ) );
+
+		if ( empty( $sender_ids ) ) {
+			return false;
+		}
+
+		return in_array( bp_loggedin_user_id(), $sender_ids );
+	}
+
+	/**
+	 * Returns the userlink of the last sender in a message thread.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $thread_id The message thread ID.
+	 * @return string|bool The user link on success. Boolean false on failure.
+	 */
+	public static function get_last_sender( $thread_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( ! $sender_id = $wpdb->get_var( $wpdb->prepare( "SELECT sender_id FROM {$bp->messages->table_name_messages} WHERE thread_id = %d GROUP BY sender_id ORDER BY date_sent LIMIT 1", $thread_id ) ) ) {
+			return false;
+		}
+
+		return bp_core_get_userlink( $sender_id, true );
+	}
+
+	/**
+	 * Gets the unread message count for a user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $user_id The user ID.
+	 * @return int $unread_count Total inbox unread count for user.
+	 */
+	public static function get_inbox_count( $user_id = 0 ) {
+		global $wpdb;
+
+		if ( empty( $user_id ) ) {
+			$user_id = bp_loggedin_user_id();
+		}
+
+		$unread_count = wp_cache_get( $user_id, 'bp_messages_unread_count' );
+
+		if ( false === $unread_count ) {
+			$bp = buddypress();
+
+			$unread_count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT SUM(unread_count) FROM {$bp->messages->table_name_recipients} WHERE user_id = %d AND is_deleted = 0 AND sender_only = 0", $user_id ) );
+
+			wp_cache_set( $user_id, $unread_count, 'bp_messages_unread_count' );
+		}
+
+		/**
+		 * Filters a user's unread message count.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param int $unread_count Unread message count.
+		 * @param int $user_id      ID of the user.
+		 */
+		return apply_filters( 'messages_thread_get_inbox_count', (int) $unread_count, $user_id );
+	}
+
+	/**
+	 * Checks whether a user is a part of a message thread discussion.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $thread_id The message thread ID.
+	 * @param int $user_id   The user ID.
+	 * @return int|null The recorded recipient ID on success, null on failure.
+	 */
+	public static function check_access( $thread_id, $user_id = 0 ) {
+
+		if ( empty( $user_id ) ) {
+			$user_id = bp_loggedin_user_id();
+		}
+
+		$recipients = self::get_recipients_for_thread( $thread_id );
+
+		if ( isset( $recipients[ $user_id ] ) && 0 == $recipients[ $user_id ]->is_deleted ) {
+			return $recipients[ $user_id ]->id;
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 * Checks whether a message thread exists.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param int $thread_id The message thread ID.
+	 * @return int|null The message thread ID on success, null on failure.
+	 */
+	public static function is_valid( $thread_id = 0 ) {
+
+		// Bail if no thread ID is passed.
+		if ( empty( $thread_id ) ) {
+			return false;
+		}
+
+		$thread = self::get_messages( $thread_id );
+
+		if ( ! empty( $thread ) ) {
+			return $thread_id;
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 * Returns a string containing all the message recipient userlinks.
+	 *
+	 * String is comma-delimited.
+	 *
+	 * If a message thread has more than four users, the returned string is simply
+	 * "X Recipients" where "X" is the number of recipients in the message thread.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param array $recipients Array containing the message recipients (array of objects).
+	 * @return string $value String of message recipent userlinks.
+	 */
+	public static function get_recipient_links( $recipients ) {
+
+		if ( count( $recipients ) >= 5 ) {
+			return sprintf( __( '%s Recipients', 'buddypress' ), number_format_i18n( count( $recipients ) ) );
+		}
+
+		$recipient_links = array();
+
+		foreach ( (array) $recipients as $recipient ) {
+			$recipient_link = bp_core_get_userlink( $recipient->user_id );
+
+			if ( empty( $recipient_link ) ) {
+				$recipient_link = __( 'Deleted User', 'buddypress' );
+			}
+
+			$recipient_links[] = $recipient_link;
+		}
+
+		return implode( ', ', (array) $recipient_links );
+	}
+
+	/**
+	 * Upgrade method for the older BP message thread DB table.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @todo We should remove this.  No one is going to upgrade from v1.1, right?
+	 * @return bool
+	 */
+	public static function update_tables() {
+		global $wpdb;
+
+		$bp_prefix = bp_core_get_table_prefix();
+		$errors    = false;
+		$threads   = $wpdb->get_results( "SELECT * FROM {$bp_prefix}bp_messages_threads" );
+
+		// Nothing to update, just return true to remove the table.
+		if ( empty( $threads ) ) {
+			return true;
+		}
+
+		$bp = buddypress();
+
+		foreach( (array) $threads as $thread ) {
+			$message_ids = maybe_unserialize( $thread->message_ids );
+
+			if ( ! empty( $message_ids ) ) {
+				$message_ids = implode( ',', $message_ids );
+
+				// Add the thread_id to the messages table.
+				if ( ! $wpdb->query( $wpdb->prepare( "UPDATE {$bp->messages->table_name_messages} SET thread_id = %d WHERE id IN ({$message_ids})", $thread->id ) ) ) {
+					$errors = true;
+				}
+			}
+		}
+
+		return (bool) ! $errors;
+	}
+}
Index: /tags/2.4.3/src/bp-notifications/bp-notifications-actions.php
===================================================================
--- /tags/2.4.3/src/bp-notifications/bp-notifications-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-notifications/bp-notifications-actions.php	(revision 10410)
@@ -0,0 +1,183 @@
+<?php
+/**
+ * BuddyPress Notifications Actions.
+ *
+ * Action functions are exactly the same as screen functions, however they do not
+ * have a template screen associated with them. Usually they will send the user
+ * back to the default screen after execution.
+ *
+ * @package BuddyPress
+ * @subpackage NotificationsActions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Handle marking single notifications as read.
+ *
+ * @since 1.9.0
+ *
+ * @return bool
+ */
+function bp_notifications_action_mark_read() {
+
+	// Bail if not the unread screen.
+	if ( ! bp_is_notifications_component() || ! bp_is_current_action( 'unread' ) ) {
+		return false;
+	}
+
+	// Get the action.
+	$action = !empty( $_GET['action']          ) ? $_GET['action']          : '';
+	$nonce  = !empty( $_GET['_wpnonce']        ) ? $_GET['_wpnonce']        : '';
+	$id     = !empty( $_GET['notification_id'] ) ? $_GET['notification_id'] : '';
+
+	// Bail if no action or no ID.
+	if ( ( 'read' !== $action ) || empty( $id ) || empty( $nonce ) ) {
+		return false;
+	}
+
+	// Check the nonce and mark the notification.
+	if ( bp_verify_nonce_request( 'bp_notification_mark_read_' . $id ) && bp_notifications_mark_notification( $id, false ) ) {
+		bp_core_add_message( __( 'Notification successfully marked read.',         'buddypress' )          );
+	} else {
+		bp_core_add_message( __( 'There was a problem marking that notification.', 'buddypress' ), 'error' );
+	}
+
+	// Redirect.
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_notifications_slug() . '/unread/' );
+}
+add_action( 'bp_actions', 'bp_notifications_action_mark_read' );
+
+/**
+ * Handle marking single notifications as unread.
+ *
+ * @since 1.9.0
+ *
+ * @return bool
+ */
+function bp_notifications_action_mark_unread() {
+
+	// Bail if not the read screen.
+	if ( ! bp_is_notifications_component() || ! bp_is_current_action( 'read' ) ) {
+		return false;
+	}
+
+	// Get the action.
+	$action = !empty( $_GET['action']          ) ? $_GET['action']          : '';
+	$nonce  = !empty( $_GET['_wpnonce']        ) ? $_GET['_wpnonce']        : '';
+	$id     = !empty( $_GET['notification_id'] ) ? $_GET['notification_id'] : '';
+
+	// Bail if no action or no ID.
+	if ( ( 'unread' !== $action ) || empty( $id ) || empty( $nonce ) ) {
+		return false;
+	}
+
+	// Check the nonce and mark the notification.
+	if ( bp_verify_nonce_request( 'bp_notification_mark_unread_' . $id ) && bp_notifications_mark_notification( $id, true ) ) {
+		bp_core_add_message( __( 'Notification successfully marked unread.',       'buddypress' )          );
+	} else {
+		bp_core_add_message( __( 'There was a problem marking that notification.', 'buddypress' ), 'error' );
+	}
+
+	// Redirect.
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_notifications_slug() . '/read/' );
+}
+add_action( 'bp_actions', 'bp_notifications_action_mark_unread' );
+
+/**
+ * Handle deleting single notifications.
+ *
+ * @since 1.9.0
+ *
+ * @return bool
+ */
+function bp_notifications_action_delete() {
+
+	// Bail if not the read or unread screen.
+	if ( ! bp_is_notifications_component() || ! ( bp_is_current_action( 'read' ) || bp_is_current_action( 'unread' ) ) ) {
+		return false;
+	}
+
+	// Get the action.
+	$action = !empty( $_GET['action']          ) ? $_GET['action']          : '';
+	$nonce  = !empty( $_GET['_wpnonce']        ) ? $_GET['_wpnonce']        : '';
+	$id     = !empty( $_GET['notification_id'] ) ? $_GET['notification_id'] : '';
+
+	// Bail if no action or no ID.
+	if ( ( 'delete' !== $action ) || empty( $id ) || empty( $nonce ) ) {
+		return false;
+	}
+
+	// Check the nonce and delete the notification.
+	if ( bp_verify_nonce_request( 'bp_notification_delete_' . $id ) && bp_notifications_delete_notification( $id ) ) {
+		bp_core_add_message( __( 'Notification successfully deleted.',              'buddypress' )          );
+	} else {
+		bp_core_add_message( __( 'There was a problem deleting that notification.', 'buddypress' ), 'error' );
+	}
+
+	// Redirect.
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_notifications_slug() . '/' . bp_current_action() . '/' );
+}
+add_action( 'bp_actions', 'bp_notifications_action_delete' );
+
+/**
+ * Handles bulk management (mark as read/unread, delete) of notifications.
+ *
+ * @since 2.2.0
+ *
+ * @return bool
+ */
+function bp_notifications_action_bulk_manage() {
+
+	// Bail if not the read or unread screen.
+	if ( ! bp_is_notifications_component() || ! ( bp_is_current_action( 'read' ) || bp_is_current_action( 'unread' ) ) ) {
+		return false;
+	}
+
+	// Get the action.
+	$action = !empty( $_POST['notification_bulk_action'] ) ? $_POST['notification_bulk_action'] : '';
+	$nonce  = !empty( $_POST['notifications_bulk_nonce'] ) ? $_POST['notifications_bulk_nonce'] : '';
+	$notifications = !empty( $_POST['notifications'] ) ? $_POST['notifications'] : '';
+
+	// Bail if no action or no IDs.
+	if ( ( ! in_array( $action, array( 'delete', 'read', 'unread' ) ) ) || empty( $notifications ) || empty( $nonce ) ) {
+		return false;
+	}
+
+	// Check the nonce.
+	if ( ! wp_verify_nonce( $nonce, 'notifications_bulk_nonce' ) ) {
+		bp_core_add_message( __( 'There was a problem managing your notifications.', 'buddypress' ), 'error' );
+		return false;
+	}
+
+	$notifications = wp_parse_id_list( $notifications );
+
+	// Delete, mark as read or unread depending on the user 'action'.
+	switch ( $action ) {
+		case 'delete' :
+			foreach ( $notifications as $notification ) {
+				bp_notifications_delete_notification( $notification );
+			}
+			bp_core_add_message( __( 'Notifications deleted.', 'buddypress' ) );
+		break;
+
+		case 'read' :
+			foreach ( $notifications as $notification ) {
+				bp_notifications_mark_notification( $notification, false );
+			}
+			bp_core_add_message( __( 'Notifications marked as read', 'buddypress' ) );
+		break;
+
+		case 'unread' :
+			foreach ( $notifications as $notification ) {
+				bp_notifications_mark_notification( $notification, true );
+			}
+			bp_core_add_message( __( 'Notifications marked as unread.', 'buddypress' ) );
+		break;
+	}
+
+	// Redirect.
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_notifications_slug() . '/' . bp_current_action() . '/' );
+}
+add_action( 'bp_actions', 'bp_notifications_action_bulk_manage' );
Index: /tags/2.4.3/src/bp-notifications/bp-notifications-adminbar.php
===================================================================
--- /tags/2.4.3/src/bp-notifications/bp-notifications-adminbar.php	(revision 10410)
+++ /tags/2.4.3/src/bp-notifications/bp-notifications-adminbar.php	(revision 10410)
@@ -0,0 +1,62 @@
+<?php
+/**
+ * BuddyPress Notifications Admin Bar functions.
+ *
+ * Admin Bar functions for the Notifications component.
+ *
+ * @package BuddyPress
+ * @subpackage NotificationsToolbar
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Build the "Notifications" dropdown.
+ *
+ * @since 1.9.0
+ *
+ * @return bool
+ */
+function bp_notifications_toolbar_menu() {
+	global $wp_admin_bar;
+
+	if ( ! is_user_logged_in() ) {
+		return false;
+	}
+
+	$notifications = bp_notifications_get_notifications_for_user( bp_loggedin_user_id(), 'object' );
+	$count         = ! empty( $notifications ) ? count( $notifications ) : 0;
+	$alert_class   = (int) $count > 0 ? 'pending-count alert' : 'count no-alert';
+	$menu_title    = '<span id="ab-pending-notifications" class="' . $alert_class . '">' . number_format_i18n( $count ) . '</span>';
+	$menu_link     = trailingslashit( bp_loggedin_user_domain() . bp_get_notifications_slug() );
+
+	// Add the top-level Notifications button.
+	$wp_admin_bar->add_menu( array(
+		'parent'    => 'top-secondary',
+		'id'        => 'bp-notifications',
+		'title'     => $menu_title,
+		'href'      => $menu_link,
+	) );
+
+	if ( ! empty( $notifications ) ) {
+		foreach ( (array) $notifications as $notification ) {
+			$wp_admin_bar->add_menu( array(
+				'parent' => 'bp-notifications',
+				'id'     => 'notification-' . $notification->id,
+				'title'  => $notification->content,
+				'href'   => $notification->href,
+			) );
+		}
+	} else {
+		$wp_admin_bar->add_menu( array(
+			'parent' => 'bp-notifications',
+			'id'     => 'no-notifications',
+			'title'  => __( 'No new notifications', 'buddypress' ),
+			'href'   => $menu_link,
+		) );
+	}
+
+	return;
+}
+add_action( 'admin_bar_menu', 'bp_members_admin_bar_notifications_menu', 90 );
Index: /tags/2.4.3/src/bp-notifications/bp-notifications-cache.php
===================================================================
--- /tags/2.4.3/src/bp-notifications/bp-notifications-cache.php	(revision 10410)
+++ /tags/2.4.3/src/bp-notifications/bp-notifications-cache.php	(revision 10410)
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Functions related to notifications caching.
+ *
+ * @package BuddyPress
+ * @subpackage NotificationsCache
+ * @since 2.0.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Slurp up metadata for a set of notifications.
+ *
+ * It grabs all notification meta associated with all of the notifications
+ * passed in $notification_ids and adds it to WP cache. This improves efficiency
+ * when using notification meta within a loop context.
+ *
+ * @since 2.3.0
+ *
+ * @param int|string|array|bool $notification_ids Accepts a single notification_id, or a
+ *                                                comma-separated list or array of
+ *                                                notification ids.
+ */
+function bp_notifications_update_meta_cache( $notification_ids = false ) {
+	bp_update_meta_cache( array(
+		'object_ids' 	   => $notification_ids,
+		'object_type' 	   => buddypress()->notifications->id,
+		'cache_group'      => 'notification_meta',
+		'object_column'    => 'notification_id',
+		'meta_table' 	   => buddypress()->notifications->table_name_meta,
+		'cache_key_prefix' => 'bp_notifications_meta'
+	) );
+}
+
+/**
+ * Clear all notifications cache for a given user ID.
+ *
+ * @since 2.3.0
+ *
+ * @param int $user_id The user ID's cache to clear.
+ */
+function bp_notifications_clear_all_for_user_cache( $user_id = 0 ) {
+	wp_cache_delete( 'all_for_user_' . $user_id, 'bp_notifications' );
+}
+
+/**
+ * Invalidate 'all_for_user_' cache when saving.
+ *
+ * @since 2.0.0
+ *
+ * @param BP_Notifications_Notification $n Notification object.
+ */
+function bp_notifications_clear_all_for_user_cache_after_save( BP_Notifications_Notification $n ) {
+	bp_notifications_clear_all_for_user_cache( $n->user_id );
+}
+add_action( 'bp_notification_after_save', 'bp_notifications_clear_all_for_user_cache_after_save' );
+
+/**
+ * Invalidate the 'all_for_user_' cache when deleting.
+ *
+ * @since 2.0.0
+ *
+ * @param int $args Notification deletion arguments.
+ */
+function bp_notifications_clear_all_for_user_cache_before_delete( $args ) {
+
+	// Pull up a list of items matching the args (those about te be deleted).
+	$ns = BP_Notifications_Notification::get( $args );
+
+	$user_ids = array();
+	foreach ( $ns as $n ) {
+		$user_ids[] = $n->user_id;
+	}
+
+	$user_ids = array_unique( $user_ids );
+	foreach ( $user_ids as $user_id ) {
+		bp_notifications_clear_all_for_user_cache( $user_id );
+	}
+}
+add_action( 'bp_notification_before_delete', 'bp_notifications_clear_all_for_user_cache_before_delete' );
+
+/**
+ * Invalidates 'all_for_user_' cache when updating.
+ *
+ * @since 2.3.0
+ *
+ * @param array $update_args See BP_Notifications_Notification::update() for description.
+ * @param array $where_args  See BP_Notifications_Notification::update() for description.
+ */
+function bp_notifications_clear_all_for_user_cache_before_update( $update_args, $where_args ) {
+
+	// User ID is passed in where arugments.
+	if ( ! empty( $where_args['user_id'] ) ) {
+		bp_notifications_clear_all_for_user_cache( $where_args['user_id'] );
+
+	// Get user ID from Notification ID.
+	} elseif ( ! empty( $where_args['id'] ) ) {
+		$n = bp_notifications_get_notification( $where_args['id'] );
+		bp_notifications_clear_all_for_user_cache( $n->user_id );
+	}
+}
+add_action( 'bp_notification_before_update', 'bp_notifications_clear_all_for_user_cache_before_update', 10, 2 );
Index: /tags/2.4.3/src/bp-notifications/bp-notifications-classes.php
===================================================================
--- /tags/2.4.3/src/bp-notifications/bp-notifications-classes.php	(revision 10410)
+++ /tags/2.4.3/src/bp-notifications/bp-notifications-classes.php	(revision 10410)
@@ -0,0 +1,15 @@
+<?php
+/**
+ * BuddyPress Notifications Classes.
+ *
+ * Classes used for the Notifications component.
+ *
+ * @package BuddyPress
+ * @subpackage NotificationsClasses
+ * @since 1.9.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+require dirname( __FILE__ ) . '/classes/class-bp-notifications-notification.php';
Index: /tags/2.4.3/src/bp-notifications/bp-notifications-functions.php
===================================================================
--- /tags/2.4.3/src/bp-notifications/bp-notifications-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-notifications/bp-notifications-functions.php	(revision 10410)
@@ -0,0 +1,750 @@
+<?php
+/**
+ * BuddyPress Member Notifications Functions.
+ *
+ * Functions and filters used in the Notifications component.
+ *
+ * @package BuddyPress
+ * @subpackage NotificationsFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Add a notification for a specific user, from a specific component.
+ *
+ * @since 1.9.0
+ *
+ * @param array $args {
+ *     Array of arguments describing the notification. All are optional.
+ *     @type int    $user_id           ID of the user to associate the notification with.
+ *     @type int    $item_id           ID of the item to associate the notification with.
+ *     @type int    $secondary_item_id ID of the secondary item to associate the
+ *                                     notification with.
+ *     @type string $component_name    Name of the component to associate the
+ *                                     notification with.
+ *     @type string $component_action  Name of the action to associate the
+ *                                     notification with.
+ *     @type string $date_notified     Timestamp for the notification.
+ * }
+ * @return int|bool ID of the newly created notification on success, false
+ *                  on failure.
+ */
+function bp_notifications_add_notification( $args = array() ) {
+
+	$r = bp_parse_args( $args, array(
+		'user_id'           => 0,
+		'item_id'           => 0,
+		'secondary_item_id' => 0,
+		'component_name'    => '',
+		'component_action'  => '',
+		'date_notified'     => bp_core_current_time(),
+		'is_new'            => 1,
+		'allow_duplicate'   => false,
+	), 'notifications_add_notification' );;
+
+	// Check for existing duplicate notifications.
+	if ( ! $r['allow_duplicate'] ) {
+		// Date_notified, allow_duplicate don't count toward
+		// duplicate status.
+		$existing = BP_Notifications_Notification::get( array(
+			'user_id'           => $r['user_id'],
+			'item_id'           => $r['item_id'],
+			'secondary_item_id' => $r['secondary_item_id'],
+			'component_name'    => $r['component_name'],
+			'component_action'  => $r['component_action'],
+			'is_new'            => $r['is_new'],
+		) );
+
+		if ( ! empty( $existing ) ) {
+			return false;
+		}
+	}
+
+	// Setup the new notification.
+	$notification                    = new BP_Notifications_Notification;
+	$notification->user_id           = $r['user_id'];
+	$notification->item_id           = $r['item_id'];
+	$notification->secondary_item_id = $r['secondary_item_id'];
+	$notification->component_name    = $r['component_name'];
+	$notification->component_action  = $r['component_action'];
+	$notification->date_notified     = $r['date_notified'];
+	$notification->is_new            = $r['is_new'];
+
+	// Save the new notification.
+	return $notification->save();
+}
+
+/**
+ * Get a specific notification by its ID.
+ *
+ * @since 1.9.0
+ *
+ * @param int $id ID of the notification.
+ * @return BP_Notifications_Notification Notification object for ID specified.
+ */
+function bp_notifications_get_notification( $id ) {
+	return new BP_Notifications_Notification( $id );
+}
+
+/**
+ * Delete a specific notification by its ID.
+ *
+ * @since 1.9.0
+ *
+ * @param int $id ID of the notification to delete.
+ * @return bool True on success, false on failure.
+ */
+function bp_notifications_delete_notification( $id ) {
+	if ( ! bp_notifications_check_notification_access( bp_loggedin_user_id(), $id ) ) {
+		return false;
+	}
+
+	return BP_Notifications_Notification::delete( array( 'id' => $id ) );
+}
+
+/**
+ * Mark notification read/unread for a user by ID.
+ *
+ * Used when clearing out notifications for a specific notification item.
+ *
+ * @since 1.9.0
+ *
+ * @param int      $id     ID of the notification.
+ * @param int|bool $is_new 0 for read, 1 for unread.
+ * @return bool True on success, false on failure.
+ */
+function bp_notifications_mark_notification( $id, $is_new = false ) {
+	if ( ! bp_notifications_check_notification_access( bp_loggedin_user_id(), $id ) ) {
+		return false;
+	}
+
+	return BP_Notifications_Notification::update(
+		array( 'is_new' => $is_new ),
+		array( 'id'     => $id     )
+	);
+}
+
+/**
+ * Get all notifications for a user and cache them.
+ *
+ * @since 2.1.0
+ *
+ * @param int $user_id ID of the user whose notifications are being fetched.
+ * @return array $notifications Array of notifications for user.
+ */
+function bp_notifications_get_all_notifications_for_user( $user_id = 0 ) {
+
+	// Default to displayed user if no ID is passed.
+	if ( empty( $user_id ) ) {
+		$user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+	}
+
+	// Get notifications out of the cache, or query if necessary.
+	$notifications = wp_cache_get( 'all_for_user_' . $user_id, 'bp_notifications' );
+	if ( false === $notifications ) {
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $user_id
+		) );
+		wp_cache_set( 'all_for_user_' . $user_id, $notifications, 'bp_notifications' );
+	}
+
+	/**
+	 * Filters all notifications for a user.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param array $notifications Array of notifications for user.
+	 * @param int   $user_id       ID of the user being fetched.
+	 */
+	return apply_filters( 'bp_notifications_get_all_notifications_for_user', $notifications, $user_id );
+}
+
+/**
+ * Get notifications for a specific user.
+ *
+ * @since 1.9.0
+ *
+ * @param int    $user_id ID of the user whose notifications are being fetched.
+ * @param string $format  Format of the returned values. 'string' returns HTML,
+ *                        while 'object' returns a structured object for parsing.
+ * @return mixed Object or array on success, false on failure.
+ */
+function bp_notifications_get_notifications_for_user( $user_id, $format = 'string' ) {
+
+	// Setup local variables.
+	$bp = buddypress();
+
+	// Get notifications (out of the cache, or query if necessary).
+	$notifications         = bp_notifications_get_all_notifications_for_user( $user_id );
+	$grouped_notifications = array(); // Notification groups.
+	$renderable            = array(); // Renderable notifications.
+
+	// Group notifications by component and component_action and provide totals.
+	for ( $i = 0, $count = count( $notifications ); $i < $count; ++$i ) {
+		$notification = $notifications[$i];
+		$grouped_notifications[$notification->component_name][$notification->component_action][] = $notification;
+	}
+
+	// Bail if no notification groups.
+	if ( empty( $grouped_notifications ) ) {
+		return false;
+	}
+
+	// Calculate a renderable output for each notification type.
+	foreach ( $grouped_notifications as $component_name => $action_arrays ) {
+
+		// We prefer that extended profile component-related notifications use
+		// the component_name of 'xprofile'. However, the extended profile child
+		// object in the $bp object is keyed as 'profile', which is where we need
+		// to look for the registered notification callback.
+		if ( 'xprofile' == $component_name ) {
+			$component_name = 'profile';
+		}
+
+		// Skip if group is empty.
+		if ( empty( $action_arrays ) ) {
+			continue;
+		}
+
+		// Loop through each actionable item and try to map it to a component.
+		foreach ( (array) $action_arrays as $component_action_name => $component_action_items ) {
+
+			// Get the number of actionable items.
+			$action_item_count = count( $component_action_items );
+
+			// Skip if the count is less than 1.
+			if ( $action_item_count < 1 ) {
+				continue;
+			}
+
+			// Callback function exists.
+			if ( isset( $bp->{$component_name}->notification_callback ) && is_callable( $bp->{$component_name}->notification_callback ) ) {
+
+				// Function should return an object.
+				if ( 'object' === $format ) {
+
+					// Retrieve the content of the notification using the callback.
+					$content = call_user_func(
+						$bp->{$component_name}->notification_callback,
+						$component_action_name,
+						$component_action_items[0]->item_id,
+						$component_action_items[0]->secondary_item_id,
+						$action_item_count,
+						'array'
+					);
+
+					// Create the object to be returned.
+					$notification_object = $component_action_items[0];
+
+					// Minimal backpat with non-compatible notification
+					// callback functions.
+					if ( is_string( $content ) ) {
+						$notification_object->content = $content;
+						$notification_object->href    = bp_loggedin_user_domain();
+					} else {
+						$notification_object->content = $content['text'];
+						$notification_object->href    = $content['link'];
+					}
+
+					$renderable[] = $notification_object;
+
+				// Return an array of content strings.
+				} else {
+					$content      = call_user_func( $bp->{$component_name}->notification_callback, $component_action_name, $component_action_items[0]->item_id, $component_action_items[0]->secondary_item_id, $action_item_count );
+					$renderable[] = $content;
+				}
+
+			// @deprecated format_notification_function - 1.5
+			} elseif ( isset( $bp->{$component_name}->format_notification_function ) && function_exists( $bp->{$component_name}->format_notification_function ) ) {
+				$renderable[] = call_user_func( $bp->{$component_name}->format_notification_function, $component_action_name, $component_action_items[0]->item_id, $component_action_items[0]->secondary_item_id, $action_item_count );
+
+			// Allow non BuddyPress components to hook in.
+			} else {
+
+				// The array to reference with apply_filters_ref_array().
+				$ref_array = array(
+					$component_action_name,
+					$component_action_items[0]->item_id,
+					$component_action_items[0]->secondary_item_id,
+					$action_item_count,
+					$format
+				);
+
+				// Function should return an object.
+				if ( 'object' === $format ) {
+
+					/**
+					 * Filters the notifications for a user.
+					 *
+					 * @since 1.9.0
+					 *
+					 * @param array $ref_array Array of properties for the current notification being rendered.
+					 */
+					$content = apply_filters_ref_array( 'bp_notifications_get_notifications_for_user', $ref_array );
+
+					// Create the object to be returned.
+					$notification_object = $component_action_items[0];
+
+					// Minimal backpat with non-compatible notification
+					// callback functions.
+					if ( is_string( $content ) ) {
+						$notification_object->content = $content;
+						$notification_object->href    = bp_loggedin_user_domain();
+					} else {
+						$notification_object->content = $content['text'];
+						$notification_object->href    = $content['link'];
+					}
+
+					$renderable[] = $notification_object;
+
+				// Return an array of content strings.
+				} else {
+
+					/** This filters is documented in bp-notifications/bp-notifications-functions.php */
+					$renderable[] = apply_filters_ref_array( 'bp_notifications_get_notifications_for_user', $ref_array );
+				}
+			}
+		}
+	}
+
+	// If renderable is empty array, set to false.
+	if ( empty( $renderable ) ) {
+		$renderable = false;
+	}
+
+	/**
+	 * Filters the final array of notifications to be displayed for a user.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array|bool $renderable Array of notifications to render or false if no notifications.
+	 * @param int        $user_id    ID of the user whose notifications are being displayed.
+	 * @param string     $format     Display format requested for the notifications.
+	 */
+	return apply_filters( 'bp_core_get_notifications_for_user', $renderable, $user_id, $format );
+}
+
+/** Delete ********************************************************************/
+
+/**
+ * Delete notifications for a user by type.
+ *
+ * Used when clearing out notifications for a specific component when the user
+ * has visited that component.
+ *
+ * @since 1.9.0
+ *
+ * @param int    $user_id          ID of the user whose notifications are being deleted.
+ * @param string $component_name   Name of the associated component.
+ * @param string $component_action Name of the associated action.
+ * @return bool True on success, false on failure.
+ */
+function bp_notifications_delete_notifications_by_type( $user_id, $component_name, $component_action ) {
+	return BP_Notifications_Notification::delete( array(
+		'user_id'          => $user_id,
+		'component_name'   => $component_name,
+		'component_action' => $component_action,
+	) );
+}
+
+/**
+ * Delete notifications for an item ID.
+ *
+ * Used when clearing out notifications for a specific component when the user
+ * has visited that component.
+ *
+ * @since 1.9.0
+ *
+ * @param int      $user_id           ID of the user whose notifications are being deleted.
+ * @param int      $item_id           ID of the associated item.
+ * @param string   $component_name    Name of the associated component.
+ * @param string   $component_action  Name of the associated action.
+ * @param int|bool $secondary_item_id ID of the secondary associated item.
+ * @return bool True on success, false on failure.
+ */
+function bp_notifications_delete_notifications_by_item_id( $user_id, $item_id, $component_name, $component_action, $secondary_item_id = false ) {
+	return BP_Notifications_Notification::delete( array(
+		'user_id'           => $user_id,
+		'item_id'           => $item_id,
+		'secondary_item_id' => $secondary_item_id,
+		'component_name'    => $component_name,
+		'component_action'  => $component_action,
+	) );
+}
+
+/**
+ * Delete all notifications by type.
+ *
+ * Used when clearing out notifications for an entire component.
+ *
+ * @since 1.9.0
+ *
+ * @param int         $item_id           ID of the user whose notifications are being deleted.
+ * @param string      $component_name    Name of the associated component.
+ * @param string|bool $component_action  Optional. Name of the associated action.
+ * @param int|bool    $secondary_item_id Optional. ID of the secondary associated item.
+ * @return bool True on success, false on failure.
+ */
+function bp_notifications_delete_all_notifications_by_type( $item_id, $component_name, $component_action = false, $secondary_item_id = false ) {
+	return BP_Notifications_Notification::delete( array(
+		'item_id'           => $item_id,
+		'secondary_item_id' => $secondary_item_id,
+		'component_name'    => $component_name,
+		'component_action'  => $component_action,
+	) );
+}
+
+/**
+ * Delete all notifications from a user.
+ *
+ * Used when clearing out all notifications for a user, when deleted or spammed.
+ *
+ * @todo This function assumes that items with the user_id in the item_id slot
+ *       are associated with that user. However, this will only be true with
+ *       certain components (such as Friends). Use with caution!
+ *
+ * @since 1.9.0
+ *
+ * @param int    $user_id          ID of the user whose associated items are being deleted.
+ * @param string $component_name   Name of the associated component.
+ * @param string $component_action Name of the associated action.
+ * @return bool True on success, false on failure.
+ */
+function bp_notifications_delete_notifications_from_user( $user_id, $component_name, $component_action ) {
+	return BP_Notifications_Notification::delete( array(
+		'item_id'           => $user_id,
+		'component_name'    => $component_name,
+		'component_action'  => $component_action,
+	) );
+}
+
+/** Mark **********************************************************************/
+
+/**
+ * Mark notifications read/unread for a user by type.
+ *
+ * Used when clearing out notifications for a specific component when the user
+ * has visited that component.
+ *
+ * @since 1.9.0
+ *
+ * @param int      $user_id          ID of the user whose notifications are being deleted.
+ * @param string   $component_name   Name of the associated component.
+ * @param string   $component_action Name of the associated action.
+ * @param int|bool $is_new           0 for read, 1 for unread.
+ * @return bool True on success, false on failure.
+ */
+function bp_notifications_mark_notifications_by_type( $user_id, $component_name, $component_action, $is_new = false ) {
+	return BP_Notifications_Notification::update(
+		array(
+			'is_new' => $is_new
+		),
+		array(
+			'user_id'          => $user_id,
+			'component_name'   => $component_name,
+			'component_action' => $component_action
+		)
+	);
+}
+
+/**
+ * Mark notifications read/unread for an item ID.
+ *
+ * Used when clearing out notifications for a specific component when the user
+ * has visited that component.
+ *
+ * @since 1.9.0
+ *
+ * @param int      $user_id           ID of the user whose notifications are being deleted.
+ * @param int      $item_id           ID of the associated item.
+ * @param string   $component_name    Name of the associated component.
+ * @param string   $component_action  Name of the associated action.
+ * @param int|bool $secondary_item_id ID of the secondary associated item.
+ * @param int|bool $is_new            0 for read, 1 for unread.
+ * @return bool True on success, false on failure.
+ */
+function bp_notifications_mark_notifications_by_item_id( $user_id, $item_id, $component_name, $component_action, $secondary_item_id = false, $is_new = false ) {
+	return BP_Notifications_Notification::update(
+		array(
+			'is_new' => $is_new
+		),
+		array(
+			'user_id'           => $user_id,
+			'item_id'           => $item_id,
+			'secondary_item_id' => $secondary_item_id,
+			'component_name'    => $component_name,
+			'component_action'  => $component_action
+		)
+	);
+}
+
+/**
+ * Mark all notifications read/unread by type.
+ *
+ * Used when clearing out notifications for an entire component.
+ *
+ * @since 1.9.0
+ *
+ * @param int         $item_id           ID of the user whose notifications are being deleted.
+ * @param string      $component_name    Name of the associated component.
+ * @param string|bool $component_action  Optional. Name of the associated action.
+ * @param int|bool    $secondary_item_id Optional. ID of the secondary associated item.
+ * @param int|bool    $is_new            0 for read, 1 for unread.
+ * @return bool True on success, false on failure.
+ */
+function bp_notifications_mark_all_notifications_by_type( $item_id, $component_name, $component_action = false, $secondary_item_id = false, $is_new = false ) {
+	return BP_Notifications_Notification::update(
+		array(
+			'is_new' => $is_new
+		),
+		array(
+			'item_id'           => $item_id,
+			'secondary_item_id' => $secondary_item_id,
+			'component_name'    => $component_name,
+			'component_action'  => $component_action
+		)
+	);
+}
+
+/**
+ * Mark all notifications read/unread from a user.
+ *
+ * Used when clearing out all notifications for a user, when deleted or spammed.
+ *
+ * @todo This function assumes that items with the user_id in the item_id slot
+ *       are associated with that user. However, this will only be true with
+ *       certain components (such as Friends). Use with caution!
+ *
+ * @since 1.9.0
+ *
+ * @param int      $user_id          ID of the user whose associated items are being deleted.
+ * @param string   $component_name   Name of the associated component.
+ * @param string   $component_action Name of the associated action.
+ * @param int|bool $is_new           0 for read, 1 for unread.
+ * @return bool True on success, false on failure.
+ */
+function bp_notifications_mark_notifications_from_user( $user_id, $component_name, $component_action, $is_new = false ) {
+	return BP_Notifications_Notification::update(
+		array(
+			'is_new' => $is_new
+		),
+		array(
+			'item_id'          => $user_id,
+			'component_name'   => $component_name,
+			'component_action' => $component_action
+		)
+	);
+}
+
+/** Helpers *******************************************************************/
+
+/**
+ * Check if a user has access to a specific notification.
+ *
+ * Used before deleting a notification for a user.
+ *
+ * @since 1.9.0
+ *
+ * @param int $user_id         ID of the user being checked.
+ * @param int $notification_id ID of the notification being checked.
+ * @return bool True if the notification belongs to the user, otherwise false.
+ */
+function bp_notifications_check_notification_access( $user_id, $notification_id ) {
+	return (bool) BP_Notifications_Notification::check_access( $user_id, $notification_id );
+}
+
+/**
+ * Get a count of unread notification items for a user.
+ *
+ * @since 1.9.0
+ *
+ * @param int $user_id ID of the user whose unread notifications are being
+ *                     counted.
+ * @return int Unread notification count.
+ */
+function bp_notifications_get_unread_notification_count( $user_id = 0 ) {
+	$notifications = bp_notifications_get_all_notifications_for_user( $user_id );
+	$count         = ! empty( $notifications ) ? count( $notifications ) : 0;
+
+	/**
+	 * Filters the count of unread notification items for a user.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param int $count Count of unread notification items for a user.
+	 */
+	return apply_filters( 'bp_notifications_get_total_notification_count', (int) $count );
+}
+
+/**
+ * Return an array of component names that are currently active and have
+ * registered Notifications callbacks.
+ *
+ * @since 1.9.1
+ *
+ * @see http://buddypress.trac.wordpress.org/ticket/5300
+ *
+ * @return array $component_names Array of registered components.
+ */
+function bp_notifications_get_registered_components() {
+
+	// Load BuddyPress.
+	$bp = buddypress();
+
+	// Setup return value.
+	$component_names = array();
+
+	// Get the active components.
+	$active_components = array_keys( $bp->active_components );
+
+	// Loop through components, look for callbacks, add to return value.
+	foreach ( $active_components as $component ) {
+		if ( !empty( $bp->$component->notification_callback ) ) {
+			$component_names[] = $component;
+		}
+		// The extended profile component is identified in the active_components array as 'xprofile'.
+		// However, the extended profile child object has the key 'profile' in the $bp object.
+		if ( 'xprofile' == $component && ! empty( $bp->profile->notification_callback ) ) {
+			$component_names[] = $component;
+		}
+	}
+
+	/**
+	 * Filters active components with registered notifications callbacks.
+	 *
+	 * @since 1.9.1
+	 *
+	 * @param array $component_names   Array of registered component names.
+	 * @param array $active_components Array of active components.
+	 */
+	return apply_filters( 'bp_notifications_get_registered_components', $component_names, $active_components );
+}
+
+/** Meta **********************************************************************/
+
+/**
+ * Delete a meta entry from the DB for a notification item.
+ *
+ * @since 2.3.0
+ *
+ * @global object $wpdb WordPress database access object.
+ *
+ * @param int    $notification_id ID of the notification item whose metadata is being deleted.
+ * @param string $meta_key        Optional. The key of the metadata being deleted. If
+ *                                omitted, all metadata associated with the notification
+ *                                item will be deleted.
+ * @param string $meta_value      Optional. If present, the metadata will only be
+ *                                deleted if the meta_value matches this parameter.
+ * @param bool   $delete_all      Optional. If true, delete matching metadata entries
+ *                                for all objects, ignoring the specified object_id. Otherwise,
+ *                                only delete matching metadata entries for the specified
+ *                                notification item. Default: false.
+ * @return bool                   True on success, false on failure.
+ */
+function bp_notifications_delete_meta( $notification_id, $meta_key = '', $meta_value = '', $delete_all = false ) {
+
+	// Legacy - if no meta_key is passed, delete all for the item.
+	if ( empty( $meta_key ) ) {
+		$all_meta = bp_notifications_get_meta( $notification_id );
+		$keys     = ! empty( $all_meta )
+			? array_keys( $all_meta )
+			: array();
+
+		// With no meta_key, ignore $delete_all.
+		$delete_all = false;
+	} else {
+		$keys = array( $meta_key );
+	}
+
+	$retval = true;
+
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	foreach ( $keys as $key ) {
+		$retval = delete_metadata( 'notifications', $notification_id, $key, $meta_value, $delete_all );
+	}
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Get metadata for a given notification item.
+ *
+ * @since 2.3.0
+ *
+ * @uses apply_filters() To call the 'bp_notifications_get_meta' hook.
+ *
+ * @param int    $notification_id ID of the notification item whose metadata is being requested.
+ * @param string $meta_key        Optional. If present, only the metadata matching
+ *                                that meta key will be returned. Otherwise, all metadata for the
+ *                                notification item will be fetched.
+ * @param bool   $single          Optional. If true, return only the first value of the
+ *                                specified meta_key. This parameter has no effect if meta_key is not
+ *                                specified. Default: true.
+ * @return mixed                  The meta value(s) being requested.
+ */
+function bp_notifications_get_meta( $notification_id = 0, $meta_key = '', $single = true ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = get_metadata( 'notifications', $notification_id, $meta_key, $single );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	/**
+	 * Filters the metadata for a specified notification item.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param mixed  $retval          The meta values for the notification item.
+	 * @param int    $notification_id ID of the notification item.
+	 * @param string $meta_key        Meta key for the value being requested.
+	 * @param bool   $single          Whether to return one matched meta key row or all.
+	 */
+	return apply_filters( 'bp_notifications_get_meta', $retval, $notification_id, $meta_key, $single );
+}
+
+/**
+ * Update a piece of notification meta.
+ *
+ * @since 1.2.0
+ *
+ * @param  int    $notification_id ID of the notification item whose metadata is being
+ *                                 updated.
+ * @param  string $meta_key        Key of the metadata being updated.
+ * @param  mixed  $meta_value      Value to be set.
+ * @param  mixed  $prev_value      Optional. If specified, only update existing
+ *                                 metadata entries with the specified value.
+ *                                 Otherwise, update all entries.
+ * @return bool|int                Returns false on failure. On successful
+ *                                 update of existing metadata, returns true. On
+ *                                 successful creation of new metadata,  returns
+ *                                 the integer ID of the new metadata row.
+ */
+function bp_notifications_update_meta( $notification_id, $meta_key, $meta_value, $prev_value = '' ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = update_metadata( 'notifications', $notification_id, $meta_key, $meta_value, $prev_value );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Add a piece of notification metadata.
+ *
+ * @since 2.3.0
+ *
+ * @param int    $notification_id ID of the notification item.
+ * @param string $meta_key        Metadata key.
+ * @param mixed  $meta_value      Metadata value.
+ * @param bool   $unique          Optional. Whether to enforce a single metadata value
+ *                                for the given key. If true, and the object already has a value for
+ *                                the key, no change will be made. Default: false.
+ * @return int|bool               The meta ID on successful update, false on failure.
+ */
+function bp_notifications_add_meta( $notification_id, $meta_key, $meta_value, $unique = false ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	$retval = add_metadata( 'notifications', $notification_id, $meta_key, $meta_value, $unique );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
Index: /tags/2.4.3/src/bp-notifications/bp-notifications-loader.php
===================================================================
--- /tags/2.4.3/src/bp-notifications/bp-notifications-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-notifications/bp-notifications-loader.php	(revision 10410)
@@ -0,0 +1,274 @@
+<?php
+/**
+ * BuddyPress Member Notifications Loader.
+ *
+ * Initializes the Notifications component.
+ *
+ * @package BuddyPress
+ * @subpackage NotificationsLoader
+ * @since 1.9.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Extends the component class to set up the Notifications component.
+ */
+class BP_Notifications_Component extends BP_Component {
+
+	/**
+	 * Start the notifications component creation process.
+	 *
+	 * @since 1.9.0
+	 */
+	public function __construct() {
+		parent::start(
+			'notifications',
+			_x( 'Notifications', 'Page <title>', 'buddypress' ),
+			buddypress()->plugin_dir,
+			array(
+				'adminbar_myaccount_order' => 30
+			)
+		);
+	}
+
+	/**
+	 * Include notifications component files.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see BP_Component::includes() for a description of arguments.
+	 *
+	 * @param array $includes See BP_Component::includes() for a description.
+	 */
+	public function includes( $includes = array() ) {
+		$includes = array(
+			'actions',
+			'classes',
+			'screens',
+			'adminbar',
+			'template',
+			'functions',
+			'cache',
+		);
+
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Set up component global data.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see BP_Component::setup_globals() for a description of arguments.
+	 *
+	 * @param array $args See BP_Component::setup_globals() for a description.
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		// Define a slug, if necessary.
+		if ( ! defined( 'BP_NOTIFICATIONS_SLUG' ) ) {
+			define( 'BP_NOTIFICATIONS_SLUG', $this->id );
+		}
+
+		// Global tables for the notifications component.
+		$global_tables = array(
+			'table_name'      => $bp->table_prefix . 'bp_notifications',
+			'table_name_meta' => $bp->table_prefix . 'bp_notifications_meta',
+		);
+
+		// All globals for the notifications component.
+		// Note that global_tables is included in this array.
+		$args = array(
+			'slug'          => BP_NOTIFICATIONS_SLUG,
+			'has_directory' => false,
+			'search_string' => __( 'Search Notifications...', 'buddypress' ),
+			'global_tables' => $global_tables,
+		);
+
+		parent::setup_globals( $args );
+	}
+
+	/**
+	 * Set up component navigation.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see BP_Component::setup_nav() for a description of arguments.
+	 *
+	 * @param array $main_nav Optional. See BP_Component::setup_nav() for
+	 *                        description.
+	 * @param array $sub_nav  Optional. See BP_Component::setup_nav() for
+	 *                        description.
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		// Determine user to use.
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		$access             = bp_core_can_edit_settings();
+		$slug               = bp_get_notifications_slug();
+		$notifications_link = trailingslashit( $user_domain . $slug );
+
+		// Only grab count if we're on a user page and current user has access.
+		if ( bp_is_user() && bp_user_has_access() ) {
+			$count    = bp_notifications_get_unread_notification_count( bp_displayed_user_id() );
+			$class    = ( 0 === $count ) ? 'no-count' : 'count';
+			$nav_name = sprintf( _x( 'Notifications <span class="%s">%s</span>', 'Profile screen nav', 'buddypress' ), esc_attr( $class ), bp_core_number_format( $count ) );
+		} else {
+			$nav_name = _x( 'Notifications', 'Profile screen nav', 'buddypress' );
+		}
+
+		// Add 'Notifications' to the main navigation.
+		$main_nav = array(
+			'name'                    => $nav_name,
+			'slug'                    => $slug,
+			'position'                => 30,
+			'show_for_displayed_user' => $access,
+			'screen_function'         => 'bp_notifications_screen_unread',
+			'default_subnav_slug'     => 'unread',
+			'item_css_id'             => $this->id,
+		);
+
+		// Add the subnav items to the notifications nav item.
+		$sub_nav[] = array(
+			'name'            => _x( 'Unread', 'Notification screen nav', 'buddypress' ),
+			'slug'            => 'unread',
+			'parent_url'      => $notifications_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'bp_notifications_screen_unread',
+			'position'        => 10,
+			'item_css_id'     => 'notifications-my-notifications',
+			'user_has_access' => $access,
+		);
+
+		$sub_nav[] = array(
+			'name'            => _x( 'Read', 'Notification screen nav', 'buddypress' ),
+			'slug'            => 'read',
+			'parent_url'      => $notifications_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'bp_notifications_screen_read',
+			'position'        => 20,
+			'user_has_access' => $access,
+		);
+
+		parent::setup_nav( $main_nav, $sub_nav );
+	}
+
+	/**
+	 * Set up the component entries in the WordPress Admin Bar.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see BP_Component::setup_nav() for a description of the $wp_admin_nav
+	 *      parameter array.
+	 *
+	 * @param array $wp_admin_nav See BP_Component::setup_admin_bar() for a
+	 *                            description.
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		// Menus for logged in user.
+		if ( is_user_logged_in() ) {
+
+			// Setup the logged in user variables.
+			$notifications_link = trailingslashit( bp_loggedin_user_domain() . bp_get_notifications_slug() );
+
+			// Pending notification requests.
+			$count = bp_notifications_get_unread_notification_count( bp_loggedin_user_id() );
+			if ( ! empty( $count ) ) {
+				$title  = sprintf( _x( 'Notifications <span class="count">%s</span>', 'My Account Notification pending', 'buddypress' ), bp_core_number_format( $count ) );
+				$unread = sprintf( _x( 'Unread <span class="count">%s</span>',        'My Account Notification pending', 'buddypress' ), bp_core_number_format( $count ) );
+			} else {
+				$title  = _x( 'Notifications', 'My Account Notification',         'buddypress' );
+				$unread = _x( 'Unread',        'My Account Notification sub nav', 'buddypress' );
+			}
+
+			// Add the "My Account" sub menus.
+			$wp_admin_nav[] = array(
+				'parent' => buddypress()->my_account_menu_id,
+				'id'     => 'my-account-' . $this->id,
+				'title'  => $title,
+				'href'   => $notifications_link
+			);
+
+			// Unread.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-unread',
+				'title'  => $unread,
+				'href'   => $notifications_link
+			);
+
+			// Read.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-read',
+				'title'  => _x( 'Read', 'My Account Notification sub nav', 'buddypress' ),
+				'href'   => trailingslashit( $notifications_link . 'read' ),
+			);
+		}
+
+		parent::setup_admin_bar( $wp_admin_nav );
+	}
+
+	/**
+	 * Set up the title for pages and <title>.
+	 *
+	 * @since 1.9.0
+	 */
+	public function setup_title() {
+
+		// Adjust title.
+		if ( bp_is_notifications_component() ) {
+			$bp = buddypress();
+
+			if ( bp_is_my_profile() ) {
+				$bp->bp_options_title = __( 'Notifications', 'buddypress' );
+			} else {
+				$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+					'item_id' => bp_displayed_user_id(),
+					'type'    => 'thumb',
+					'alt'     => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_displayed_user_fullname() )
+				) );
+				$bp->bp_options_title = bp_get_displayed_user_fullname();
+			}
+		}
+
+		parent::setup_title();
+	}
+
+	/**
+	 * Setup cache groups.
+	 *
+	 * @since 2.2.0
+	 */
+	public function setup_cache_groups() {
+
+		// Global groups.
+		wp_cache_add_global_groups( array(
+			'bp_notifications',
+			'notification_meta'
+		) );
+
+		parent::setup_cache_groups();
+	}
+}
+
+/**
+ * Bootstrap the Notifications component.
+ *
+ * @since 1.9.0
+ */
+function bp_setup_notifications() {
+	buddypress()->notifications = new BP_Notifications_Component();
+}
+add_action( 'bp_setup_components', 'bp_setup_notifications', 6 );
Index: /tags/2.4.3/src/bp-notifications/bp-notifications-screens.php
===================================================================
--- /tags/2.4.3/src/bp-notifications/bp-notifications-screens.php	(revision 10410)
+++ /tags/2.4.3/src/bp-notifications/bp-notifications-screens.php	(revision 10410)
@@ -0,0 +1,71 @@
+<?php
+/**
+ * BuddyPress Notifications Screen Functions.
+ *
+ * Screen functions are the controllers of BuddyPress. They will execute when
+ * their specific URL is caught. They will first save or manipulate data using
+ * business functions, then pass on the user to a template file.
+ *
+ * @package BuddyPress
+ * @subpackage NotificationsScreens
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Catch and route the 'unread' notifications screen.
+ *
+ * @since 1.9.0
+ */
+function bp_notifications_screen_unread() {
+
+	/**
+	 * Fires right before the loading of the notifications unread screen template file.
+	 *
+	 * @since 1.9.0
+	 */
+	do_action( 'bp_notifications_screen_unread' );
+
+	/**
+	 * Filters the template to load for the notifications unread screen.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $template Path to the notifications unread template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_notifications_template_unread', 'members/single/home' ) );
+}
+
+/**
+ * Catch and route the 'read' notifications screen.
+ *
+ * @since 1.9.0
+ */
+function bp_notifications_screen_read() {
+
+	/**
+	 * Fires right before the loading of the notifications read screen template file.
+	 *
+	 * @since 1.9.0
+	 */
+	do_action( 'bp_notifications_screen_read' );
+
+	/**
+	 * Filters the template to load for the notifications read screen.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $template Path to the notifications read template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_notifications_template_read', 'members/single/home' ) );
+}
+
+/**
+ * Catch and route the 'settings' notifications screen.
+ *
+ * @since 1.9.0
+ */
+function bp_notifications_screen_settings() {
+
+}
Index: /tags/2.4.3/src/bp-notifications/bp-notifications-template.php
===================================================================
--- /tags/2.4.3/src/bp-notifications/bp-notifications-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-notifications/bp-notifications-template.php	(revision 10410)
@@ -0,0 +1,1272 @@
+<?php
+/**
+ * BuddyPress Notifications Template Functions.
+ *
+ * @package BuddyPress
+ * @subpackage TonificationsTemplate
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the notifications component slug.
+ *
+ * @since 1.9.0
+ */
+function bp_notifications_slug() {
+	echo bp_get_notifications_slug();
+}
+	/**
+	 * Return the notifications component slug.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return string Slug of the Notifications component.
+	 */
+	function bp_get_notifications_slug() {
+
+		/**
+		 * Filters the notifications component slug.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $slug Notifications component slug.
+		 */
+		return apply_filters( 'bp_get_notifications_slug', buddypress()->notifications->slug );
+	}
+
+/**
+ * Output the notifications permalink.
+ *
+ * @since 1.9.0
+ */
+function bp_notifications_permalink() {
+	echo bp_get_notifications_permalink();
+}
+	/**
+	 * Return the notifications permalink.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return string Notifications permalink.
+	 */
+	function bp_get_notifications_permalink() {
+		$retval = trailingslashit( bp_loggedin_user_domain() . bp_get_notifications_slug() );
+
+		/**
+		 * Filters the notifications permalink.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $retval Permalink for the notifications.
+		 */
+		return apply_filters( 'bp_get_notifications_permalink', $retval );
+	}
+
+/**
+ * Output the unread notifications permalink.
+ *
+ * @since 1.9.0
+ */
+function bp_notifications_unread_permalink() {
+	echo bp_get_notifications_unread_permalink();
+}
+	/**
+	 * Return the unread notifications permalink.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return string Unread notifications permalink.
+	 */
+	function bp_get_notifications_unread_permalink() {
+		$retval = trailingslashit( bp_loggedin_user_domain() . bp_get_notifications_slug() . '/unread' );
+
+		/**
+		 * Filters the unread notifications permalink.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $retval Permalink for the unread notifications.
+		 */
+		return apply_filters( 'bp_get_notifications_unread_permalink', $retval );
+	}
+
+/**
+ * Output the read notifications permalink.
+ *
+ * @since 1.9.0
+ */
+function bp_notifications_read_permalink() {
+	echo bp_get_notifications_read_permalink();
+}
+	/**
+	 * Return the read notifications permalink.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return string Read notifications permalink.
+	 */
+	function bp_get_notifications_read_permalink() {
+		$retval = trailingslashit( bp_loggedin_user_domain() . bp_get_notifications_slug() . '/read' );
+
+		/**
+		 * Filters the read notifications permalink.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $retval Permalink for the read notifications.
+		 */
+		return apply_filters( 'bp_get_notifications_unread_permalink', $retval );
+	}
+
+/** Main Loop *****************************************************************/
+
+/**
+ * The main notifications template loop class.
+ *
+ * Responsible for loading a group of notifications into a loop for display.
+ *
+ * @since 1.9.0
+ */
+class BP_Notifications_Template {
+
+	/**
+	 * The loop iterator.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $current_notification = -1;
+
+	/**
+	 * The number of notifications returned by the paged query.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $current_notification_count;
+
+	/**
+	 * Total number of notifications matching the query.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $total_notification_count;
+
+	/**
+	 * Array of notifications located by the query.
+	 *
+	 * @since 1.9.0
+	 * @var array
+	 */
+	public $notifications;
+
+	/**
+	 * The notification object currently being iterated on.
+	 *
+	 * @since 1.9.0
+	 * @var object
+	 */
+	public $notification;
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @since 1.9.0
+	 * @var bool
+	 */
+	public $in_the_loop;
+
+	/**
+	 * The ID of the user to whom the displayed notifications belong.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $user_id;
+
+	/**
+	 * The page number being requested.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $pag_page;
+
+	/**
+	 * The $_GET argument used in URLs for determining pagination.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $pag_arg;
+
+	/**
+	 * The number of items to display per page of results.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $pag_num;
+
+	/**
+	 * An HTML string containing pagination links.
+	 *
+	 * @since 1.9.0
+	 * @var string
+	 */
+	public $pag_links;
+
+	/**
+	 * A string to match against.
+	 *
+	 * @since 1.9.0
+	 * @var string
+	 */
+	public $search_terms;
+
+	/**
+	 * A database column to order the results by.
+	 *
+	 * @since 1.9.0
+	 * @var string
+	 */
+	public $order_by;
+
+	/**
+	 * The direction to sort the results (ASC or DESC).
+	 *
+	 * @since 1.9.0
+	 * @var string
+	 */
+	public $sort_order;
+
+	/**
+	 * Array of variables used in this notification query.
+	 *
+	 * @since 2.2.2
+	 * @var array
+	 */
+	public $query_vars;
+
+	/**
+	 * Constructor method.
+	 *
+	 * @see bp_has_notifications() For information on the array format.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param array $args {
+	 *     An array of arguments. See {@link bp_has_notifications()}
+	 *     for more details.
+	 * }
+	 */
+	public function __construct( $args = array() ) {
+
+		// Parse arguments.
+		$r = wp_parse_args( $args, array(
+			'id'                => false,
+			'user_id'           => 0,
+			'item_id'           => false,
+			'secondary_item_id' => false,
+			'component_name'    => bp_notifications_get_registered_components(),
+			'component_action'  => false,
+			'is_new'            => true,
+			'search_terms'      => '',
+			'order_by'          => 'date_notified',
+			'sort_order'        => 'DESC',
+			'page_arg'          => 'npage',
+			'page'              => 1,
+			'per_page'          => 25,
+			'max'               => null,
+			'meta_query'        => false,
+			'date_query'        => false
+		) );
+
+		// Sort order direction.
+		$orders = array( 'ASC', 'DESC' );
+		if ( ! empty( $_GET['sort_order'] ) && in_array( $_GET['sort_order'], $orders ) ) {
+			$r['sort_order'] = $_GET['sort_order'];
+		} else {
+			$r['sort_order'] = in_array( $r['sort_order'], $orders ) ? $r['sort_order'] : 'DESC';
+		}
+
+		// Setup variables.
+		$this->pag_arg      = sanitize_key( $r['page_arg'] );
+		$this->pag_page     = bp_sanitize_pagination_arg( $this->pag_arg, $r['page']     );
+		$this->pag_num      = bp_sanitize_pagination_arg( 'num',          $r['per_page'] );
+		$this->user_id      = $r['user_id'];
+		$this->is_new       = $r['is_new'];
+		$this->search_terms = $r['search_terms'];
+		$this->order_by     = $r['order_by'];
+		$this->sort_order   = $r['sort_order'];
+		$this->query_vars   = array(
+			'id'                => $r['id'],
+			'user_id'           => $this->user_id,
+			'item_id'           => $r['item_id'],
+			'secondary_item_id' => $r['secondary_item_id'],
+			'component_name'    => $r['component_name'],
+			'component_action'  => $r['component_action'],
+			'meta_query'        => $r['meta_query'],
+			'date_query'        => $r['date_query'],
+			'is_new'            => $this->is_new,
+			'search_terms'      => $this->search_terms,
+			'order_by'          => $this->order_by,
+			'sort_order'        => $this->sort_order,
+			'page'              => $this->pag_page,
+			'per_page'          => $this->pag_num,
+		);
+
+		// Setup the notifications to loop through.
+		$this->notifications            = BP_Notifications_Notification::get( $this->query_vars );
+		$this->total_notification_count = BP_Notifications_Notification::get_total_count( $this->query_vars );
+
+		if ( empty( $this->notifications ) ) {
+			$this->notification_count       = 0;
+			$this->total_notification_count = 0;
+
+		} else {
+			if ( ! empty( $r['max'] ) ) {
+				if ( $r['max'] >= count( $this->notifications ) ) {
+					$this->notification_count = count( $this->notifications );
+				} else {
+					$this->notification_count = (int) $r['max'];
+				}
+			} else {
+				$this->notification_count = count( $this->notifications );
+			}
+		}
+
+		if ( (int) $this->total_notification_count && (int) $this->pag_num ) {
+			$add_args = array(
+				'sort_order' => $this->sort_order,
+			);
+
+			$this->pag_links = paginate_links( array(
+				'base'      => add_query_arg( $this->pag_arg, '%#%' ),
+				'format'    => '',
+				'total'     => ceil( (int) $this->total_notification_count / (int) $this->pag_num ),
+				'current'   => $this->pag_page,
+				'prev_text' => _x( '&larr;', 'Notifications pagination previous text', 'buddypress' ),
+				'next_text' => _x( '&rarr;', 'Notifications pagination next text',     'buddypress' ),
+				'mid_size'  => 1,
+				'add_args'  => $add_args,
+			) );
+		}
+	}
+
+	/**
+	 * Whether there are notifications available in the loop.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see bp_has_notifications()
+	 *
+	 * @return bool True if there are items in the loop, otherwise false.
+	 */
+	public function has_notifications() {
+		if ( $this->notification_count ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Set up the next notification and iterate index.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return object The next notification to iterate over.
+	 */
+	public function next_notification() {
+
+		$this->current_notification++;
+
+		$this->notification = $this->notifications[ $this->current_notification ];
+
+		return $this->notification;
+	}
+
+	/**
+	 * Rewind the blogs and reset blog index.
+	 *
+	 * @since 1.9.0
+	 */
+	public function rewind_notifications() {
+
+		$this->current_notification = -1;
+
+		if ( $this->notification_count > 0 ) {
+			$this->notification = $this->notifications[0];
+		}
+	}
+
+	/**
+	 * Whether there are notifications left in the loop to iterate over.
+	 *
+	 * This method is used by {@link bp_notifications()} as part of the
+	 * while loop that controls iteration inside the notifications loop, eg:
+	 *     while ( bp_notifications() ) { ...
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see bp_notifications()
+	 *
+	 * @return bool True if there are more notifications to show,
+	 *              otherwise false.
+	 */
+	public function notifications() {
+
+		if ( $this->current_notification + 1 < $this->notification_count ) {
+			return true;
+
+		} elseif ( $this->current_notification + 1 == $this->notification_count ) {
+
+			/**
+			 * Fires right before the rewinding of notification posts.
+			 *
+			 * @since 1.9.0
+			 */
+			do_action( 'notifications_loop_end');
+
+			$this->rewind_notifications();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	/**
+	 * Set up the current notification inside the loop.
+	 *
+	 * Used by {@link bp_the_notification()} to set up the current
+	 * notification data while looping, so that template tags used during
+	 * that iteration make reference to the current notification.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see bp_the_notification()
+	 */
+	public function the_notification() {
+		$this->in_the_loop  = true;
+		$this->notification = $this->next_notification();
+
+		// Loop has just started.
+		if ( 0 === $this->current_notification ) {
+
+			/**
+			 * Fires if the current notification item is the first in the notification loop.
+			 *
+			 * @since 1.9.0
+			 */
+			do_action( 'notifications_loop_start' );
+		}
+	}
+}
+
+/** The Loop ******************************************************************/
+
+/**
+ * Initialize the notifications loop.
+ *
+ * Based on the $args passed, bp_has_notifications() populates
+ * buddypress()->notifications->query_loop global, enabling the use of BP
+ * templates and template functions to display a list of notifications.
+ *
+ * @since 1.9.0
+ *
+ * @param array|string $args {
+ *     Arguments for limiting the contents of the notifications loop. Can be
+ *     passed as an associative array, or as a URL query string.
+ *
+ *     See {@link BP_Notifications_Notification::get()} for detailed
+ *     information on the arguments.  In addition, also supports:
+ *
+ *     @type int    $max      Optional. Max items to display. Default: false.
+ *     @type string $page_arg URL argument to use for pagination.
+ *                            Default: 'npage'.
+ * }
+ * @return bool
+ */
+function bp_has_notifications( $args = '' ) {
+
+	// Get the default is_new argument.
+	if ( bp_is_current_action( 'unread' ) ) {
+		$is_new = 1;
+	} elseif ( bp_is_current_action( 'read' ) ) {
+		$is_new = 0;
+
+	// Not on a notifications page? default to fetch new notifications.
+	} else {
+		$is_new = 1;
+	}
+
+	// Get the user ID.
+	if ( bp_displayed_user_id() ) {
+		$user_id = bp_displayed_user_id();
+	} else {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	// Parse the args.
+	$r = bp_parse_args( $args, array(
+		'id'                => false,
+		'user_id'           => $user_id,
+		'secondary_item_id' => false,
+		'component_name'    => bp_notifications_get_registered_components(),
+		'component_action'  => false,
+		'is_new'            => $is_new,
+		'search_terms'      => isset( $_REQUEST['s'] ) ? stripslashes( $_REQUEST['s'] ) : '',
+		'order_by'          => 'date_notified',
+		'sort_order'        => 'DESC',
+		'meta_query'        => false,
+		'date_query'        => false,
+		'page'              => 1,
+		'per_page'          => 25,
+
+		// These are additional arguments that are not available in
+		// BP_Notifications_Notification::get().
+		'max'               => false,
+		'page_arg'          => 'npage',
+	), 'has_notifications' );
+
+	// Get the notifications.
+	$query_loop = new BP_Notifications_Template( $r );
+
+	// Setup the global query loop.
+	buddypress()->notifications->query_loop = $query_loop;
+
+	/**
+	 * Filters whether or not the user has notifications to display.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param bool                      $value      Whether or not there are notifications to display.
+	 * @param BP_Notifications_Template $query_loop BP_Notifications_Template object instance.
+	 */
+	return apply_filters( 'bp_has_notifications', $query_loop->has_notifications(), $query_loop );
+}
+
+/**
+ * Get the notifications returned by the template loop.
+ *
+ * @since 1.9.0
+ *
+ * @return array List of notifications.
+ */
+function bp_the_notifications() {
+	return buddypress()->notifications->query_loop->notifications();
+}
+
+/**
+ * Get the current notification object in the loop.
+ *
+ * @since 1.9.0
+ *
+ * @return object The current notification within the loop.
+ */
+function bp_the_notification() {
+	return buddypress()->notifications->query_loop->the_notification();
+}
+
+/** Loop Output ***************************************************************/
+
+/**
+ * Output the ID of the notification currently being iterated on.
+ *
+ * @since 1.9.0
+ */
+function bp_the_notification_id() {
+	echo bp_get_the_notification_id();
+}
+	/**
+	 * Return the ID of the notification currently being iterated on.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return int ID of the current notification.
+	 */
+	function bp_get_the_notification_id() {
+
+		/**
+		 * Filters the ID of the notification currently being iterated on.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param int $id ID of the notification being iterated on.
+		 */
+		return apply_filters( 'bp_get_the_notification_id', buddypress()->notifications->query_loop->notification->id );
+	}
+
+/**
+ * Output the associated item ID of the notification currently being iterated on.
+ *
+ * @since 1.9.0
+ */
+function bp_the_notification_item_id() {
+	echo bp_get_the_notification_item_id();
+}
+	/**
+	 * Return the associated item ID of the notification currently being iterated on.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return int ID of the item associated with the current notification.
+	 */
+	function bp_get_the_notification_item_id() {
+
+		/**
+		 * Filters the associated item ID of the notification currently being iterated on.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param int $item_id ID of the associated item.
+		 */
+		return apply_filters( 'bp_get_the_notification_item_id', buddypress()->notifications->query_loop->notification->item_id );
+	}
+
+/**
+ * Output the secondary associated item ID of the notification currently being iterated on.
+ *
+ * @since 1.9.0
+ */
+function bp_the_notification_secondary_item_id() {
+	echo bp_get_the_notification_secondary_item_id();
+}
+	/**
+	 * Return the secondary associated item ID of the notification currently being iterated on.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return int ID of the secondary item associated with the current notification.
+	 */
+	function bp_get_the_notification_secondary_item_id() {
+
+		/**
+		 * Filters the secondary associated item ID of the notification currently being iterated on.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param int $secondary_item_id ID of the secondary associated item.
+		 */
+		return apply_filters( 'bp_get_the_notification_secondary_item_id', buddypress()->notifications->query_loop->notification->secondary_item_id );
+	}
+
+/**
+ * Output the name of the component associated with the notification currently being iterated on.
+ *
+ * @since 1.9.0
+ */
+function bp_the_notification_component_name() {
+	echo bp_get_the_notification_component_name();
+}
+	/**
+	 * Return the name of the component associated with the notification currently being iterated on.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return int Name of the component associated with the current notification.
+	 */
+	function bp_get_the_notification_component_name() {
+
+		/**
+		 * Filters the name of the component associated with the notification currently being iterated on.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param int $component_name Name of the component associated with the current notification.
+		 */
+		return apply_filters( 'bp_get_the_notification_component_name', buddypress()->notifications->query_loop->notification->component_name );
+	}
+
+/**
+ * Output the name of the action associated with the notification currently being iterated on.
+ *
+ * @since 1.9.0
+ */
+function bp_the_notification_component_action() {
+	echo bp_get_the_notification_component_action();
+}
+	/**
+	 * Return the name of the action associated with the notification currently being iterated on.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return int Name of the action associated with the current notification.
+	 */
+	function bp_get_the_notification_component_action() {
+
+		/**
+		 * Filters the name of the action associated with the notification currently being iterated on.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param int $component_action Name of the action associated with the current notification.
+		 */
+		return apply_filters( 'bp_get_the_notification_component_action', buddypress()->notifications->query_loop->notification->component_action );
+	}
+
+/**
+ * Output the timestamp of the current notification.
+ *
+ * @since 1.9.0
+ */
+function bp_the_notification_date_notified() {
+	echo bp_get_the_notification_date_notified();
+}
+	/**
+	 * Return the timestamp of the current notification.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return string Timestamp of the current notification.
+	 */
+	function bp_get_the_notification_date_notified() {
+
+		/**
+		 * Filters the timestamp of the current notification.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $date_notified Timestamp of the current notification.
+		 */
+		return apply_filters( 'bp_get_the_notification_date_notified', buddypress()->notifications->query_loop->notification->date_notified );
+	}
+
+/**
+ * Output the timestamp of the current notification.
+ *
+ * @since 1.9.0
+ */
+function bp_the_notification_time_since() {
+	echo bp_get_the_notification_time_since();
+}
+	/**
+	 * Return the timestamp of the current notification.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return string Timestamp of the current notification.
+	 */
+	function bp_get_the_notification_time_since() {
+
+		// Get the notified date.
+		$date_notified = bp_get_the_notification_date_notified();
+
+		// Notified date has legitimate data.
+		if ( '0000-00-00 00:00:00' !== $date_notified ) {
+			$retval = bp_core_time_since( $date_notified );
+
+		// Notified date is empty, so return a fun string.
+		} else {
+			$retval = __( 'Date not found', 'buddypress' );
+		}
+
+		/**
+		 * Filters the time since value of the current notification.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $retval Time since value for current notification.
+		 */
+		return apply_filters( 'bp_get_the_notification_time_since', $retval );
+	}
+
+/**
+ * Output full-text description for a specific notification.
+ *
+ * @since 1.9.0
+ */
+function bp_the_notification_description() {
+	echo bp_get_the_notification_description();
+}
+
+	/**
+	 * Get full-text description for a specific notification.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_notification_description() {
+		$bp           = buddypress();
+		$notification = $bp->notifications->query_loop->notification;
+
+		// Callback function exists.
+		if ( isset( $bp->{ $notification->component_name }->notification_callback ) && is_callable( $bp->{ $notification->component_name }->notification_callback ) ) {
+			$description = call_user_func( $bp->{ $notification->component_name }->notification_callback, $notification->component_action, $notification->item_id, $notification->secondary_item_id, 1 );
+
+		// @deprecated format_notification_function - 1.5
+		} elseif ( isset( $bp->{ $notification->component_name }->format_notification_function ) && function_exists( $bp->{ $notification->component_name }->format_notification_function ) ) {
+			$description = call_user_func( $bp->{ $notification->component_name }->format_notification_function, $notification->component_action, $notification->item_id, $notification->secondary_item_id, 1 );
+
+		// Allow non BuddyPress components to hook in.
+		} else {
+
+			/** This filter is documented in bp-notifications/bp-notifications-functions.php */
+			$description = apply_filters_ref_array( 'bp_notifications_get_notifications_for_user', array( $notification->component_action, $notification->item_id, $notification->secondary_item_id, 1 ) );
+		}
+
+		/**
+		 * Filters the full-text description for a specific notification.
+		 *
+		 * @since 1.9.0
+		 * @since 2.3.0 Added the `$notification` parameter.
+		 *
+		 * @param string $description  Full-text description for a specific notification.
+		 * @param object $notification Notification object.
+		 */
+		return apply_filters( 'bp_get_the_notification_description', $description, $notification );
+	}
+
+/**
+ * Output the mark read link for the current notification.
+ *
+ * @since 1.9.0
+ *
+ * @uses bp_get_the_notification_mark_read_link()
+ */
+function bp_the_notification_mark_read_link() {
+	echo bp_get_the_notification_mark_read_link();
+}
+	/**
+	 * Return the mark read link for the current notification.
+	 *
+	 * @since 1.9.0
+	 */
+	function bp_get_the_notification_mark_read_link() {
+
+		// Start the output buffer.
+		ob_start(); ?>
+
+		<a href="<?php bp_the_notification_mark_read_url(); ?>" class="mark-read primary"><?php _e( 'Read', 'buddypress' ); ?></a>
+
+		<?php $retval = ob_get_clean();
+
+		/**
+		 * Filters the mark read link for the current notification.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $retval HTML for the mark read link for the current notification.
+		 */
+		return apply_filters( 'bp_get_the_notification_mark_read_link', $retval );
+	}
+
+/**
+ * Output the URL used for marking a single notification as read.
+ *
+ * Since this function directly outputs a URL, it is escaped.
+ *
+ * @since 2.1.0
+ *
+ * @uses bp_get_the_notification_mark_read_url()
+ */
+function bp_the_notification_mark_read_url() {
+	echo esc_url( bp_get_the_notification_mark_read_url() );
+}
+	/**
+	 * Return the URL used for marking a single notification as read.
+	 *
+	 * @since 2.1.0
+	 */
+	function bp_get_the_notification_mark_read_url() {
+
+		// Get the notification ID.
+		$id   = bp_get_the_notification_id();
+
+		// Get the args to add to the URL.
+		$args = array(
+			'action'          => 'read',
+			'notification_id' => $id
+		);
+
+		// Add the args to the URL.
+		$url = add_query_arg( $args, bp_get_notifications_unread_permalink() );
+
+		// Add the nonce.
+		$url = wp_nonce_url( $url, 'bp_notification_mark_read_' . $id );
+
+		/**
+		 * Filters the URL used for marking a single notification as read.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $url URL to use for marking the single notification as read.
+		 */
+		return apply_filters( 'bp_get_the_notification_mark_read_url', $url );
+	}
+
+/**
+ * Output the mark unread link for the current notification.
+ *
+ * @since 1.9.0
+ *
+ * @uses bp_get_the_notification_mark_unread_link()
+ */
+function bp_the_notification_mark_unread_link() {
+	echo bp_get_the_notification_mark_unread_link();
+}
+	/**
+	 * Return the mark unread link for the current notification.
+	 *
+	 * @since 1.9.0
+	 */
+	function bp_get_the_notification_mark_unread_link() {
+
+		// Start the output buffer.
+		ob_start(); ?>
+
+		<a href="<?php bp_the_notification_mark_unread_url(); ?>" class="mark-unread primary"><?php _ex( 'Unread',  'Notification screen action', 'buddypress' ); ?></a>
+
+		<?php $retval = ob_get_clean();
+
+		/**
+		 * Filters the link used for marking a single notification as unread.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $retval HTML for the mark unread link for the current notification.
+		 */
+		return apply_filters( 'bp_get_the_notification_mark_unread_link', $retval );
+	}
+
+/**
+ * Output the URL used for marking a single notification as unread.
+ *
+ * Since this function directly outputs a URL, it is escaped.
+ *
+ * @since 2.1.0
+ *
+ * @uses bp_get_the_notification_mark_unread_url()
+ */
+function bp_the_notification_mark_unread_url() {
+	echo esc_url( bp_get_the_notification_mark_unread_url() );
+}
+	/**
+	 * Return the URL used for marking a single notification as unread.
+	 *
+	 * @since 2.1.0
+	 */
+	function bp_get_the_notification_mark_unread_url() {
+
+		// Get the notification ID.
+		$id   = bp_get_the_notification_id();
+
+		// Get the args to add to the URL.
+		$args = array(
+			'action'          => 'unread',
+			'notification_id' => $id
+		);
+
+		// Add the args to the URL.
+		$url = add_query_arg( $args, bp_get_notifications_read_permalink() );
+
+		// Add the nonce.
+		$url = wp_nonce_url( $url, 'bp_notification_mark_unread_' . $id );
+
+		/**
+		 * Filters the URL used for marking a single notification as unread.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $url URL to use for marking the single notification as unread.
+		 */
+		return apply_filters( 'bp_get_the_notification_mark_unread_url', $url );
+	}
+
+/**
+ * Output the mark link for the current notification.
+ *
+ * @since 1.9.0
+ *
+ * @uses bp_get_the_notification_mark_unread_link()
+ */
+function bp_the_notification_mark_link() {
+	echo bp_get_the_notification_mark_link();
+}
+	/**
+	 * Return the mark link for the current notification.
+	 *
+	 * @since 1.9.0
+	 */
+	function bp_get_the_notification_mark_link() {
+
+		if ( bp_is_current_action( 'read' ) ) {
+			$retval = bp_get_the_notification_mark_unread_link();
+		} else {
+			$retval = bp_get_the_notification_mark_read_link();
+		}
+
+		/**
+		 * Filters the mark link for the current notification.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $retval The mark link for the current notification.
+		 */
+		return apply_filters( 'bp_get_the_notification_mark_link', $retval );
+	}
+
+/**
+ * Output the delete link for the current notification.
+ *
+ * @since 1.9.0
+ *
+ * @uses bp_get_the_notification_delete_link()
+ */
+function bp_the_notification_delete_link() {
+	echo bp_get_the_notification_delete_link();
+}
+	/**
+	 * Return the delete link for the current notification.
+	 *
+	 * @since 1.9.0
+	 */
+	function bp_get_the_notification_delete_link() {
+
+		// Start the output buffer.
+		ob_start(); ?>
+
+		<a href="<?php bp_the_notification_delete_url(); ?>" class="delete secondary confirm"><?php _e( 'Delete', 'buddypress' ); ?></a>
+
+		<?php $retval = ob_get_clean();
+
+		/**
+		 * Filters the delete link for the current notification.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $retval HTML for the delete link for the current notification.
+		 */
+		return apply_filters( 'bp_get_the_notification_delete_link', $retval );
+	}
+
+/**
+ * Output the URL used for deleting a single notification.
+ *
+ * Since this function directly outputs a URL, it is escaped.
+ *
+ * @since 2.1.0
+ *
+ * @uses esc_url()
+ * @uses bp_get_the_notification_delete_url()
+ */
+function bp_the_notification_delete_url() {
+	echo esc_url( bp_get_the_notification_delete_url() );
+}
+	/**
+	 * Return the URL used for deleting a single notification.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_notification_delete_url() {
+
+		// URL to add nonce to.
+		if ( bp_is_current_action( 'unread' ) ) {
+			$link = bp_get_notifications_unread_permalink();
+		} elseif ( bp_is_current_action( 'read' ) ) {
+			$link = bp_get_notifications_read_permalink();
+		}
+
+		// Get the ID.
+		$id = bp_get_the_notification_id();
+
+		// Get the args to add to the URL.
+		$args = array(
+			'action'          => 'delete',
+			'notification_id' => $id
+		);
+
+		// Add the args.
+		$url = add_query_arg( $args, $link );
+
+		// Add the nonce.
+		$url = wp_nonce_url( $url, 'bp_notification_delete_' . $id );
+
+		/**
+		 * Filters the URL used for deleting a single notification.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $url URL used for deleting a single notification.
+		 */
+		return apply_filters( 'bp_get_the_notification_delete_url', $url );
+	}
+
+/**
+ * Output the action links for the current notification.
+ *
+ * @since 1.9.0
+ *
+ * @param array|string $args Array of arguments.
+ */
+function bp_the_notification_action_links( $args = '' ) {
+	echo bp_get_the_notification_action_links( $args );
+}
+	/**
+	 * Return the action links for the current notification.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param array|string $args {
+	 *     @type string $before HTML before the links.
+	 *     @type string $after  HTML after the links.
+	 *     @type string $sep    HTML between the links.
+	 *     @type array  $links  Array of links to implode by 'sep'.
+	 * }
+	 * @return string HTML links for actions to take on single notifications.
+	 */
+	function bp_get_the_notification_action_links( $args = '' ) {
+
+		// Parse.
+		$r = wp_parse_args( $args, array(
+			'before' => '',
+			'after'  => '',
+			'sep'    => ' | ',
+			'links'  => array(
+				bp_get_the_notification_mark_link(),
+				bp_get_the_notification_delete_link()
+			)
+		) );
+
+		// Build the links.
+		$retval = $r['before'] . implode( $r['links'], $r['sep'] ) . $r['after'];
+
+		/**
+		 * Filters the action links for the current notification.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $retval HTML links for actions to take on single notifications.
+		 */
+		return apply_filters( 'bp_get_the_notification_action_links', $retval );
+	}
+
+/**
+ * Output the pagination count for the current notification loop.
+ *
+ * @since 1.9.0
+ */
+function bp_notifications_pagination_count() {
+	echo bp_get_notifications_pagination_count();
+}
+	/**
+	 * Return the pagination count for the current notification loop.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return string HTML for the pagination count.
+	 */
+	function bp_get_notifications_pagination_count() {
+		$query_loop = buddypress()->notifications->query_loop;
+		$start_num  = intval( ( $query_loop->pag_page - 1 ) * $query_loop->pag_num ) + 1;
+		$from_num   = bp_core_number_format( $start_num );
+		$to_num     = bp_core_number_format( ( $start_num + ( $query_loop->pag_num - 1 ) > $query_loop->total_notification_count ) ? $query_loop->total_notification_count : $start_num + ( $query_loop->pag_num - 1 ) );
+		$total      = bp_core_number_format( $query_loop->total_notification_count );
+
+		if ( 1 == $query_loop->total_notification_count ) {
+			$pag = __( 'Viewing 1 notification', 'buddypress' );
+		} else {
+			$pag = sprintf( _n( 'Viewing %1$s - %2$s of %3$s notification', 'Viewing %1$s - %2$s of %3$s notifications', $query_loop->total_notification_count, 'buddypress' ), $from_num, $to_num, $total );
+		}
+
+		/**
+		 * Filters the pagination count for the current notification loop.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $pag HTML for the pagination count.
+		 */
+		return apply_filters( 'bp_notifications_pagination_count', $pag );
+	}
+
+/**
+ * Output the pagination links for the current notification loop.
+ *
+ * @since 1.9.0
+ */
+function bp_notifications_pagination_links() {
+	echo bp_get_notifications_pagination_links();
+}
+	/**
+	 * Return the pagination links for the current notification loop.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @return string HTML for the pagination links.
+	 */
+	function bp_get_notifications_pagination_links() {
+
+		/**
+		 * Filters the pagination links for the current notification loop.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param string $pag_links HTML for the pagination links.
+		 */
+		return apply_filters( 'bp_get_notifications_pagination_links', buddypress()->notifications->query_loop->pag_links );
+	}
+
+/** Form Helpers **************************************************************/
+
+/**
+ * Output the form for changing the sort order of notifications.
+ *
+ * @since 1.9.0
+ */
+function bp_notifications_sort_order_form() {
+
+	// Setup local variables.
+	$orders   = array( 'DESC', 'ASC' );
+	$selected = 'DESC';
+
+	// Check for a custom sort_order.
+	if ( !empty( $_REQUEST['sort_order'] ) ) {
+		if ( in_array( $_REQUEST['sort_order'], $orders ) ) {
+			$selected = $_REQUEST['sort_order'];
+		}
+	} ?>
+
+	<form action="" method="get" id="notifications-sort-order">
+		<label for="notifications-sort-order-list"><?php esc_html_e( 'Order By:', 'buddypress' ); ?></label>
+
+		<select id="notifications-sort-order-list" name="sort_order" onchange="this.form.submit();">
+			<option value="DESC" <?php selected( $selected, 'DESC' ); ?>><?php _e( 'Newest First', 'buddypress' ); ?></option>
+			<option value="ASC"  <?php selected( $selected, 'ASC'  ); ?>><?php _e( 'Oldest First', 'buddypress' ); ?></option>
+		</select>
+
+		<noscript>
+			<input id="submit" type="submit" name="form-submit" class="submit" value="<?php esc_attr_e( 'Go', 'buddypress' ); ?>" />
+		</noscript>
+	</form>
+
+<?php
+}
+
+/**
+ * Output the dropdown for bulk management of notifications.
+ *
+ * @since 2.2.0
+ */
+function bp_notifications_bulk_management_dropdown() {
+	?>
+	<label class="bp-screen-reader-text" for="notification-select"><?php _e( 'Select Bulk Action', 'buddypress' ); ?></label>
+	<select name="notification_bulk_action" id="notification-select">
+		<option value="" selected="selected"><?php _e( 'Bulk Actions', 'buddypress' ); ?></option>
+
+		<?php if ( bp_is_current_action( 'unread' ) ) : ?>
+			<option value="read"><?php _e( 'Mark read', 'buddypress' ); ?></option>
+		<?php elseif ( bp_is_current_action( 'read' ) ) : ?>
+			<option value="unread"><?php _e( 'Mark unread', 'buddypress' ); ?></option>
+		<?php endif; ?>
+		<option value="delete"><?php _e( 'Delete', 'buddypress' ); ?></option>
+	</select>
+	<input type="submit" id="notification-bulk-manage" class="button action" value="<?php esc_attr_e( 'Apply', 'buddypress' ); ?>">
+	<?php
+}
Index: /tags/2.4.3/src/bp-notifications/classes/class-bp-notifications-notification.php
===================================================================
--- /tags/2.4.3/src/bp-notifications/classes/class-bp-notifications-notification.php	(revision 10410)
+++ /tags/2.4.3/src/bp-notifications/classes/class-bp-notifications-notification.php	(revision 10410)
@@ -0,0 +1,1117 @@
+<?php
+/**
+ * BuddyPress Notifications Classes.
+ *
+ * Classes used for the Notifications component.
+ *
+ * @package BuddyPress
+ * @subpackage NotificationsClasses
+ * @since 1.9.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * BuddyPress Notification items.
+ *
+ * Use this class to create, access, edit, or delete BuddyPress Notifications.
+ *
+ * @since 1.9.0
+ */
+class BP_Notifications_Notification {
+
+	/**
+	 * The notification ID.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $id;
+
+	/**
+	 * The ID of the item associated with the notification.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $item_id;
+
+	/**
+	 * The ID of the secondary item associated with the notification.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $secondary_item_id = null;
+
+	/**
+	 * The ID of the user the notification is associated with.
+	 *
+	 * @since 1.9.0
+	 * @var int
+	 */
+	public $user_id;
+
+	/**
+	 * The name of the component that the notification is for.
+	 *
+	 * @since 1.9.0
+	 * @var string
+	 */
+	public $component_name;
+
+	/**
+	 * The component action which the notification is related to.
+	 *
+	 * @since 1.9.0
+	 * @var string
+	 */
+	public $component_action;
+
+	/**
+	 * The date the notification was created.
+	 *
+	 * @since 1.9.0
+	 * @var string
+	 */
+	public $date_notified;
+
+	/**
+	 * Is the notification new, or has it already been read.
+	 *
+	 * @since 1.9.0
+	 * @var bool
+	 */
+	public $is_new;
+
+	/** Public Methods ********************************************************/
+
+	/**
+	 * Constructor method.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param int $id Optional. Provide an ID to access an existing
+	 *                notification item.
+	 */
+	public function __construct( $id = 0 ) {
+		if ( ! empty( $id ) ) {
+			$this->id = $id;
+			$this->populate();
+		}
+	}
+
+	/**
+	 * Update or insert notification details into the database.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @global wpdb $wpdb WordPress database object.
+	 *
+	 * @return bool True on success, false on failure.
+	 */
+	public function save() {
+
+		// Return value.
+		$retval = false;
+
+		// Default data and format.
+		$data = array(
+			'user_id'           => $this->user_id,
+			'item_id'           => $this->item_id,
+			'secondary_item_id' => $this->secondary_item_id,
+			'component_name'    => $this->component_name,
+			'component_action'  => $this->component_action,
+			'date_notified'     => $this->date_notified,
+			'is_new'            => $this->is_new,
+		);
+		$data_format = array( '%d', '%d', '%d', '%s', '%s', '%s', '%d' );
+
+		/**
+		 * Fires before the current notification item gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_Notifications_Notification $value Current instance of the notification item being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'bp_notification_before_save', array( &$this ) );
+
+		// Update.
+		if ( ! empty( $this->id ) ) {
+			$result = self::_update( $data, array( 'ID' => $this->id ), $data_format, array( '%d' ) );
+
+		// Insert.
+		} else {
+			$result = self::_insert( $data, $data_format );
+		}
+
+		// Set the notification ID if successful.
+		if ( ! empty( $result ) && ! is_wp_error( $result ) ) {
+			global $wpdb;
+
+			$this->id = $wpdb->insert_id;
+			$retval   = $wpdb->insert_id;
+		}
+
+		/**
+		 * Fires after the current notification item gets saved.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_Notifications_Notification $value Current instance of the notification item being saved.
+		 *                                             Passed by reference.
+		 */
+		do_action_ref_array( 'bp_notification_after_save', array( &$this ) );
+
+		// Return the result.
+		return $retval;
+	}
+
+	/**
+	 * Fetch data for an existing notification from the database.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @global BuddyPress $bp The one true BuddyPress instance.
+	 * @global wpdb $wpdb WordPress database object.
+	 */
+	public function populate() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		// Look for a notification.
+		$notification = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->notifications->table_name} WHERE id = %d", $this->id ) );
+
+		// Setup the notification data.
+		if ( ! empty( $notification ) && ! is_wp_error( $notification ) ) {
+			$this->item_id           = $notification->item_id;
+			$this->secondary_item_id = $notification->secondary_item_id;
+			$this->user_id           = $notification->user_id;
+			$this->component_name    = $notification->component_name;
+			$this->component_action  = $notification->component_action;
+			$this->date_notified     = $notification->date_notified;
+			$this->is_new            = $notification->is_new;
+		}
+	}
+
+	/** Protected Static Methods **********************************************/
+
+	/**
+	 * Create a notification entry.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param array $data {
+	 *     Array of notification data, passed to {@link wpdb::insert()}.
+	 *     @type int    $user_id           ID of the associated user.
+	 *     @type int    $item_id           ID of the associated item.
+	 *     @type int    $secondary_item_id ID of the secondary associated item.
+	 *     @type string $component_name    Name of the associated component.
+	 *     @type string $component_action  Name of the associated component
+	 *                                     action.
+	 *     @type string $date_notified     Timestamp of the notification.
+	 *     @type bool   $is_new            True if the notification is unread, otherwise false.
+	 * }
+	 * @param array $data_format See {@link wpdb::insert()}.
+	 * @return int|false The number of rows inserted, or false on error.
+	 */
+	protected static function _insert( $data = array(), $data_format = array() ) {
+		global $wpdb;
+		return $wpdb->insert( buddypress()->notifications->table_name, $data, $data_format );
+	}
+
+	/**
+	 * Update notifications.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see wpdb::update() for further description of paramater formats.
+	 *
+	 * @param array $data         Array of notification data to update, passed to
+	 *                            {@link wpdb::update()}. Accepts any property of a
+	 *                            BP_Notification_Notification object.
+	 * @param array $where        The WHERE params as passed to wpdb::update().
+	 *                            Typically consists of array( 'ID' => $id ) to specify the ID
+	 *                            of the item being updated. See {@link wpdb::update()}.
+	 * @param array $data_format  See {@link wpdb::insert()}.
+	 * @param array $where_format See {@link wpdb::insert()}.
+	 * @return int|false The number of rows updated, or false on error.
+	 */
+	protected static function _update( $data = array(), $where = array(), $data_format = array(), $where_format = array() ) {
+		global $wpdb;
+		return $wpdb->update( buddypress()->notifications->table_name, $data, $where, $data_format, $where_format );
+	}
+
+	/**
+	 * Delete notifications.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see wpdb::update() for further description of paramater formats.
+	 *
+	 * @param array $where        Array of WHERE clauses to filter by, passed to
+	 *                            {@link wpdb::delete()}. Accepts any property of a
+	 *                            BP_Notification_Notification object.
+	 * @param array $where_format See {@link wpdb::insert()}.
+	 * @return int|false The number of rows updated, or false on error.
+	 */
+	protected static function _delete( $where = array(), $where_format = array() ) {
+		global $wpdb;
+		return $wpdb->delete( buddypress()->notifications->table_name, $where, $where_format );
+	}
+
+	/**
+	 * Assemble the WHERE clause of a get() SQL statement.
+	 *
+	 * Used by BP_Notifications_Notification::get() to create its WHERE
+	 * clause.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param array  $args           See {@link BP_Notifications_Notification::get()}
+	 *                               for more details.
+	 * @param string $select_sql     SQL SELECT fragment.
+	 * @param string $from_sql       SQL FROM fragment.
+	 * @param string $join_sql       SQL JOIN fragment.
+	 * @param string $meta_query_sql SQL meta query fragment.
+	 * @return string WHERE clause.
+	 */
+	protected static function get_where_sql( $args = array(), $select_sql = '', $from_sql = '', $join_sql = '', $meta_query_sql = '' ) {
+		global $wpdb;
+
+		$where_conditions = array();
+		$where            = '';
+
+		// The id.
+		if ( ! empty( $args['id'] ) ) {
+			$id_in = implode( ',', wp_parse_id_list( $args['id'] ) );
+			$where_conditions['id'] = "id IN ({$id_in})";
+		}
+
+		// The user_id.
+		if ( ! empty( $args['user_id'] ) ) {
+			$user_id_in = implode( ',', wp_parse_id_list( $args['user_id'] ) );
+			$where_conditions['user_id'] = "user_id IN ({$user_id_in})";
+		}
+
+		// The item_id.
+		if ( ! empty( $args['item_id'] ) ) {
+			$item_id_in = implode( ',', wp_parse_id_list( $args['item_id'] ) );
+			$where_conditions['item_id'] = "item_id IN ({$item_id_in})";
+		}
+
+		// The secondary_item_id.
+		if ( ! empty( $args['secondary_item_id'] ) ) {
+			$secondary_item_id_in = implode( ',', wp_parse_id_list( $args['secondary_item_id'] ) );
+			$where_conditions['secondary_item_id'] = "secondary_item_id IN ({$secondary_item_id_in})";
+		}
+
+		// The component_name.
+		if ( ! empty( $args['component_name'] ) ) {
+			if ( ! is_array( $args['component_name'] ) ) {
+				$component_names = explode( ',', $args['component_name'] );
+			} else {
+				$component_names = $args['component_name'];
+			}
+
+			$cn_clean = array();
+			foreach ( $component_names as $cn ) {
+				$cn_clean[] = $wpdb->prepare( '%s', $cn );
+			}
+
+			$cn_in = implode( ',', $cn_clean );
+			$where_conditions['component_name'] = "component_name IN ({$cn_in})";
+		}
+
+		// The component_action.
+		if ( ! empty( $args['component_action'] ) ) {
+			if ( ! is_array( $args['component_action'] ) ) {
+				$component_actions = explode( ',', $args['component_action'] );
+			} else {
+				$component_actions = $args['component_action'];
+			}
+
+			$ca_clean = array();
+			foreach ( $component_actions as $ca ) {
+				$ca_clean[] = $wpdb->prepare( '%s', $ca );
+			}
+
+			$ca_in = implode( ',', $ca_clean );
+			$where_conditions['component_action'] = "component_action IN ({$ca_in})";
+		}
+
+		// If is_new.
+		if ( ! empty( $args['is_new'] ) && 'both' !== $args['is_new'] ) {
+			$where_conditions['is_new'] = "is_new = 1";
+		} elseif ( isset( $args['is_new'] ) && ( 0 === $args['is_new'] || false === $args['is_new'] ) ) {
+			$where_conditions['is_new'] = "is_new = 0";
+		}
+
+		// The search_terms.
+		if ( ! empty( $args['search_terms'] ) ) {
+			$search_terms_like = '%' . bp_esc_like( $args['search_terms'] ) . '%';
+			$where_conditions['search_terms'] = $wpdb->prepare( "( component_name LIKE %s OR component_action LIKE %s )", $search_terms_like, $search_terms_like );
+		}
+
+		// The date query.
+		if ( ! empty( $args['date_query'] ) ) {
+			$where_conditions['date_query'] = self::get_date_query_sql( $args['date_query'] );
+		}
+
+		// The meta query.
+		if ( ! empty( $meta_query_sql['where'] ) ) {
+			$where_conditions['meta_query'] = $meta_query_sql['where'];
+		}
+
+		/**
+		 * Filters the MySQL WHERE conditions for the Notifications items get method.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array  $where_conditions Current conditions for MySQL WHERE statement.
+		 * @param array  $args             Parsed arguments passed into method.
+		 * @param string $select_sql       Current SELECT MySQL statement at point of execution.
+		 * @param string $from_sql         Current FROM MySQL statement at point of execution.
+		 * @param string $join_sql         Current INNER JOIN MySQL statement at point of execution.
+		 * @param string $meta_query_sql   Current meta query WHERE statement at point of execution.
+		 */
+		$where_conditions = apply_filters( 'bp_notifications_get_where_conditions', $where_conditions, $args, $select_sql, $from_sql, $join_sql, $meta_query_sql );
+
+		// Custom WHERE.
+		if ( ! empty( $where_conditions ) ) {
+			$where = 'WHERE ' . implode( ' AND ', $where_conditions );
+		}
+
+		return $where;
+	}
+
+	/**
+	 * Assemble the ORDER BY clause of a get() SQL statement.
+	 *
+	 * Used by BP_Notifications_Notification::get() to create its ORDER BY
+	 * clause.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param array $args See {@link BP_Notifications_Notification::get()}
+	 *                    for more details.
+	 * @return string ORDER BY clause.
+	 */
+	protected static function get_order_by_sql( $args = array() ) {
+
+		// Setup local variable.
+		$conditions = array();
+		$retval     = '';
+
+		// Order by.
+		if ( ! empty( $args['order_by'] ) ) {
+			$order_by               = implode( ', ', (array) $args['order_by'] );
+			$conditions['order_by'] = "{$order_by}";
+		}
+
+		// Sort order direction.
+		if ( ! empty( $args['sort_order'] ) && in_array( $args['sort_order'], array( 'ASC', 'DESC' ) ) ) {
+			$sort_order               = $args['sort_order'];
+			$conditions['sort_order'] = "{$sort_order}";
+		}
+
+		// Custom ORDER BY.
+		if ( ! empty( $conditions ) ) {
+			$retval = 'ORDER BY ' . implode( ' ', $conditions );
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * Assemble the LIMIT clause of a get() SQL statement.
+	 *
+	 * Used by BP_Notifications_Notification::get() to create its LIMIT clause.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param array $args See {@link BP_Notifications_Notification::get()}
+	 *                    for more details.
+	 * @return string $retval LIMIT clause.
+	 */
+	protected static function get_paged_sql( $args = array() ) {
+		global $wpdb;
+
+		// Setup local variable.
+		$retval = '';
+
+		// Custom LIMIT.
+		if ( ! empty( $args['page'] ) && ! empty( $args['per_page'] ) ) {
+			$page     = absint( $args['page']     );
+			$per_page = absint( $args['per_page'] );
+			$offset   = $per_page * ( $page - 1 );
+			$retval   = $wpdb->prepare( "LIMIT %d, %d", $offset, $per_page );
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * Assemble query clauses, based on arguments, to pass to $wpdb methods.
+	 *
+	 * The insert(), update(), and delete() methods of {@link wpdb} expect
+	 * arguments of the following forms:
+	 *
+	 * - associative arrays whose key/value pairs are column => value, to
+	 *   be used in WHERE, SET, or VALUES clauses.
+	 * - arrays of "formats", which tell $wpdb->prepare() which type of
+	 *   value to expect when sanitizing (eg, array( '%s', '%d' ))
+	 *
+	 * This utility method can be used to assemble both kinds of params,
+	 * out of a single set of associative array arguments, such as:
+	 *
+	 *     $args = array(
+	 *         'user_id' => 4,
+	 *         'component_name' => 'groups',
+	 *     );
+	 *
+	 * This will be converted to:
+	 *
+	 *     array(
+	 *         'data' => array(
+	 *             'user_id' => 4,
+	 *             'component_name' => 'groups',
+	 *         ),
+	 *         'format' => array(
+	 *             '%d',
+	 *             '%s',
+	 *         ),
+	 *     )
+	 *
+	 * which can easily be passed as arguments to the $wpdb methods.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param array $args Associative array of filter arguments.
+	 *                    See {@BP_Notifications_Notification::get()}
+	 *                    for a breakdown.
+	 * @return array Associative array of 'data' and 'format' args.
+	 */
+	protected static function get_query_clauses( $args = array() ) {
+		$where_clauses = array(
+			'data'   => array(),
+			'format' => array(),
+		);
+
+		// The id.
+		if ( ! empty( $args['id'] ) ) {
+			$where_clauses['data']['id'] = absint( $args['id'] );
+			$where_clauses['format'][] = '%d';
+		}
+
+		// The user_id.
+		if ( ! empty( $args['user_id'] ) ) {
+			$where_clauses['data']['user_id'] = absint( $args['user_id'] );
+			$where_clauses['format'][] = '%d';
+		}
+
+		// The item_id.
+		if ( ! empty( $args['item_id'] ) ) {
+			$where_clauses['data']['item_id'] = absint( $args['item_id'] );
+			$where_clauses['format'][] = '%d';
+		}
+
+		// The secondary_item_id.
+		if ( ! empty( $args['secondary_item_id'] ) ) {
+			$where_clauses['data']['secondary_item_id'] = absint( $args['secondary_item_id'] );
+			$where_clauses['format'][] = '%d';
+		}
+
+		// The component_name.
+		if ( ! empty( $args['component_name'] ) ) {
+			$where_clauses['data']['component_name'] = $args['component_name'];
+			$where_clauses['format'][] = '%s';
+		}
+
+		// The component_action.
+		if ( ! empty( $args['component_action'] ) ) {
+			$where_clauses['data']['component_action'] = $args['component_action'];
+			$where_clauses['format'][] = '%s';
+		}
+
+		// If is_new.
+		if ( isset( $args['is_new'] ) ) {
+			$where_clauses['data']['is_new'] = ! empty( $args['is_new'] ) ? 1 : 0;
+			$where_clauses['format'][] = '%d';
+		}
+
+		return $where_clauses;
+	}
+
+	/** Public Static Methods *************************************************/
+
+	/**
+	 * Check that a specific notification is for a specific user.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param int $user_id         ID of the user being checked.
+	 * @param int $notification_id ID of the notification being checked.
+	 * @return bool True if the notification belongs to the user, otherwise
+	 *              false.
+	 */
+	public static function check_access( $user_id, $notification_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(id) FROM {$bp->core->table_name_notifications} WHERE id = %d AND user_id = %d", $notification_id, $user_id ) );
+	}
+
+	/**
+	 * Parse notifications query arguments.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param mixed $args Args to parse.
+	 * @return array
+	 */
+	public static function parse_args( $args = '' ) {
+		return wp_parse_args( $args, array(
+			'id'                => false,
+			'user_id'           => false,
+			'item_id'           => false,
+			'secondary_item_id' => false,
+			'component_name'    => bp_notifications_get_registered_components(),
+			'component_action'  => false,
+			'is_new'            => true,
+			'search_terms'      => '',
+			'order_by'          => false,
+			'sort_order'        => false,
+			'page'              => false,
+			'per_page'          => false,
+			'meta_query'        => false,
+			'date_query'        => false,
+			'update_meta_cache' => true
+		) );
+	}
+
+	/**
+	 * Get notifications, based on provided filter parameters.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param array $args {
+	 *     Associative array of arguments. All arguments but $page and
+	 *     $per_page can be treated as filter values for get_where_sql()
+	 *     and get_query_clauses(). All items are optional.
+	 *     @type int|array    $id                ID of notification being updated. Can be an
+	 *                                           array of IDs.
+	 *     @type int|array    $user_id           ID of user being queried. Can be an
+	 *                                           array of user IDs.
+	 *     @type int|array    $item_id           ID of associated item. Can be an array
+	 *                                           of multiple item IDs.
+	 *     @type int|array    $secondary_item_id ID of secondary associated
+	 *                                           item. Can be an array of multiple IDs.
+	 *     @type string|array $component_name    Name of the component to
+	 *                                           filter by. Can be an array of component names.
+	 *     @type string|array $component_action  Name of the action to
+	 *                                           filter by. Can be an array of actions.
+	 *     @type bool         $is_new            Whether to limit to new notifications. True
+	 *                                           returns only new notifications, false returns only non-new
+	 *                                           notifications. 'both' returns all. Default: true.
+	 *     @type string       $search_terms      Term to match against component_name
+	 *                                           or component_action fields.
+	 *     @type string       $order_by          Database column to order notifications by.
+	 *     @type string       $sort_order        Either 'ASC' or 'DESC'.
+	 *     @type string       $order_by          Field to order results by.
+	 *     @type string       $sort_order        ASC or DESC.
+	 *     @type int          $page              Number of the current page of results. Default:
+	 *                                           false (no pagination - all items).
+	 *     @type int          $per_page          Number of items to show per page. Default:
+	 *                                           false (no pagination - all items).
+	 * }
+	 * @return array Located notifications.
+	 */
+	public static function get( $args = array() ) {
+		global $wpdb;
+
+		// Parse the arguments.
+		$r = self::parse_args( $args );
+
+		// Get BuddyPress.
+		$bp = buddypress();
+
+		// METADATA.
+		$meta_query_sql = self::get_meta_query_sql( $r['meta_query'] );
+
+		// SELECT.
+		$select_sql = "SELECT *";
+
+		// FROM.
+		$from_sql   = "FROM {$bp->notifications->table_name} n ";
+
+		// JOIN.
+		$join_sql   = $meta_query_sql['join'];
+
+		// WHERE.
+		$where_sql  = self::get_where_sql( array(
+			'id'                => $r['id'],
+			'user_id'           => $r['user_id'],
+			'item_id'           => $r['item_id'],
+			'secondary_item_id' => $r['secondary_item_id'],
+			'component_name'    => $r['component_name'],
+			'component_action'  => $r['component_action'],
+			'is_new'            => $r['is_new'],
+			'search_terms'      => $r['search_terms'],
+			'date_query'        => $r['date_query']
+		), $select_sql, $from_sql, $join_sql, $meta_query_sql );
+
+		// ORDER BY.
+		$order_sql  = self::get_order_by_sql( array(
+			'order_by'   => $r['order_by'],
+			'sort_order' => $r['sort_order']
+		) );
+
+		// LIMIT %d, %d.
+		$pag_sql    = self::get_paged_sql( array(
+			'page'     => $r['page'],
+			'per_page' => $r['per_page']
+		) );
+
+		// Concatenate query parts.
+		$sql = "{$select_sql} {$from_sql} {$join_sql} {$where_sql} {$order_sql} {$pag_sql}";
+
+		return $wpdb->get_results( $sql );
+	}
+
+	/**
+	 * Get a count of total notifications matching a set of arguments.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see BP_Notifications_Notification::get() for a description of arguments.
+	 *
+	 * @param array $args See {@link BP_Notifications_Notification::get()}.
+	 * @return int Count of located items.
+	 */
+	public static function get_total_count( $args ) {
+		global $wpdb;
+
+		// Parse the arguments.
+		$r = self::parse_args( $args );
+
+		// Load BuddyPress.
+		$bp = buddypress();
+
+		// METADATA.
+		$meta_query_sql = self::get_meta_query_sql( $r['meta_query'] );
+
+		// SELECT.
+		$select_sql = "SELECT COUNT(*)";
+
+		// FROM.
+		$from_sql   = "FROM {$bp->notifications->table_name} n ";
+
+		// JOIN.
+		$join_sql   = $meta_query_sql['join'];
+
+		// WHERE.
+		$where_sql  = self::get_where_sql( array(
+			'id'                => $r['id'],
+			'user_id'           => $r['user_id'],
+			'item_id'           => $r['item_id'],
+			'secondary_item_id' => $r['secondary_item_id'],
+			'component_name'    => $r['component_name'],
+			'component_action'  => $r['component_action'],
+			'is_new'            => $r['is_new'],
+			'search_terms'      => $r['search_terms'],
+			'date_query'        => $r['date_query']
+		), $select_sql, $from_sql, $join_sql, $meta_query_sql );
+
+		// Concatenate query parts.
+		$sql = "{$select_sql} {$from_sql} {$join_sql} {$where_sql}";
+
+		// Return the queried results.
+		return $wpdb->get_var( $sql );
+	}
+
+	/**
+	 * Get the SQL for the 'meta_query' param in BP_Notifications_Notification::get().
+	 *
+	 * We use WP_Meta_Query to do the heavy lifting of parsing the
+	 * meta_query array and creating the necessary SQL clauses. However,
+	 * since BP_Notifications_Notification::get() builds its SQL differently than
+	 * WP_Query, we have to alter the return value (stripping the leading
+	 * AND keyword from the 'where' clause).
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param  array $meta_query An array of meta_query filters. See the
+	 *                           documentation for WP_Meta_Query for details.
+	 * @return array $sql_array 'join' and 'where' clauses.
+	 */
+	public static function get_meta_query_sql( $meta_query = array() ) {
+
+		// Default array keys & empty values.
+		$sql_array = array(
+			'join'  => '',
+			'where' => '',
+		);
+
+		// Bail if no meta query.
+		if ( empty( $meta_query ) ) {
+			return $sql_array;
+		}
+
+		// WP_Meta_Query expects the table name at $wpdb->notificationmeta.
+		$GLOBALS['wpdb']->notificationmeta = buddypress()->notifications->table_name_meta;
+
+		$n_meta_query = new WP_Meta_Query( $meta_query );
+		$meta_sql     = $n_meta_query->get_sql( 'notification', 'n', 'id' );
+
+		// Strip the leading AND - it's handled in get().
+		$sql_array['where'] = preg_replace( '/^\sAND/', '', $meta_sql['where'] );
+		$sql_array['join']  = $meta_sql['join'];
+
+		return $sql_array;
+	}
+
+	/**
+	 * Get the SQL for the 'date_query' param in BP_Notifications_Notification::get().
+	 *
+	 * We use BP_Date_Query, which extends WP_Date_Query, to do the heavy lifting
+	 * of parsing the date_query array and creating the necessary SQL clauses.
+	 * However, since BP_Notifications_Notification::get() builds its SQL
+	 * differently than WP_Query, we have to alter the return value (stripping
+	 * the leading AND keyword from the query).
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param array $date_query An array of date_query parameters. See the
+	 *                          documentation for the first parameter of WP_Date_Query.
+	 * @return string
+	 */
+	public static function get_date_query_sql( $date_query = array() ) {
+
+		// Bail if not a proper date query format.
+		if ( empty( $date_query ) || ! is_array( $date_query ) ) {
+			return '';
+		}
+
+		// Date query.
+		$date_query = new BP_Date_Query( $date_query, 'date_recorded' );
+
+		// Strip the leading AND - it's handled in get().
+		return preg_replace( '/^\sAND/', '', $date_query->get_sql() );
+	}
+
+	/**
+	 * Update notifications.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see BP_Notifications_Notification::get() for a description of
+	 *      accepted update/where arguments.
+	 *
+	 * @param array $update_args Associative array of fields to update,
+	 *                           and the values to update them to. Of the format
+	 *                           array( 'user_id' => 4, 'component_name' => 'groups', ).
+	 * @param array $where_args  Associative array of columns/values, to
+	 *                           determine which rows should be updated. Of the format
+	 *                           array( 'item_id' => 7, 'component_action' => 'members', ).
+	 * @return int|bool Number of rows updated on success, false on failure.
+	 */
+	public static function update( $update_args = array(), $where_args = array() ) {
+		$update = self::get_query_clauses( $update_args );
+		$where  = self::get_query_clauses( $where_args  );
+
+		/**
+		 * Fires before the update of a notification item.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array $update_args See BP_Notifications_Notification::update().
+		 * @param array $where_args  See BP_Notifications_Notification::update().
+		 */
+		do_action( 'bp_notification_before_update', $update_args, $where_args );
+
+		return self::_update(
+			$update['data'],
+			$where['data'],
+			$update['format'],
+			$where['format']
+		);
+	}
+
+	/**
+	 * Delete notifications.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see BP_Notifications_Notification::get() for a description of
+	 *      accepted update/where arguments.
+	 *
+	 * @param array $args Associative array of columns/values, to determine
+	 *                    which rows should be deleted.  Of the format
+	 *                    array( 'item_id' => 7, 'component_action' => 'members', ).
+	 * @return int|bool Number of rows deleted on success, false on failure.
+	 */
+	public static function delete( $args = array() ) {
+		$where = self::get_query_clauses( $args );
+
+		/**
+		 * Fires before the deletion of a notification item.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $args Associative array of columns/values, to determine
+		 *                    which rows should be deleted. Of the format
+		 *                    array( 'item_id' => 7, 'component_action' => 'members' ).
+		 */
+		do_action( 'bp_notification_before_delete', $args );
+
+		return self::_delete( $where['data'], $where['format'] );
+	}
+
+	/** Convenience methods ***************************************************/
+
+	/**
+	 * Delete a single notification by ID.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @see BP_Notifications_Notification::delete() for explanation of
+	 *      return value.
+	 *
+	 * @param int $id ID of the notification item to be deleted.
+	 * @return bool True on success, false on failure.
+	 */
+	public static function delete_by_id( $id ) {
+		return self::delete( array(
+			'id' => $id,
+		) );
+	}
+
+	/**
+	 * Fetch all the notifications in the database for a specific user.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param int    $user_id ID of the user whose notifications are being
+	 *                        fetched.
+	 * @param string $status  Optional. Status of notifications to fetch.
+	 *                        'is_new' to get only unread items, 'all' to get all.
+	 * @return array Associative array of notification items.
+	 */
+	public static function get_all_for_user( $user_id, $status = 'is_new' ) {
+		return self::get( array(
+			'user_id' => $user_id,
+			'is_new'  => 'is_new' === $status,
+		) );
+	}
+
+	/**
+	 * Fetch all the unread notifications in the database for a specific user.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param int $user_id ID of the user whose notifications are being
+	 *                     fetched.
+	 * @return array Associative array of unread notification items.
+	 */
+	public static function get_unread_for_user( $user_id = 0 ) {
+		return self::get( array(
+			'user_id' => $user_id,
+			'is_new'  => true,
+		) );
+	}
+
+	/**
+	 * Fetch all the read notifications in the database for a specific user.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param int $user_id ID of the user whose notifications are being
+	 *                     fetched.
+	 * @return array Associative array of unread notification items.
+	 */
+	public static function get_read_for_user( $user_id = 0 ) {
+		return self::get( array(
+			'user_id' => $user_id,
+			'is_new'  => false,
+		) );
+	}
+
+	/**
+	 * Get unread notifications for a user, in a pagination-friendly format.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param array $args {
+	 *     Array of arguments.
+	 *     @type int    $user_id      ID of the user for whom the notifications are
+	 *                                being fetched. Default: logged-in user ID.
+	 *     @type bool   $is_new       Whether to limit the query to unread
+	 *                                notifications. Default: true.
+	 *     @type int    $page         Number of the page to return. Default: 1.
+	 *     @type int    $per_page     Number of results to display per page.
+	 *                                Default: 10.
+	 *     @type string $search_terms Optional. A term to search against in
+	 *                                the 'component_name' and 'component_action' columns.
+	 * }
+	 * @return array {
+	 *     @type array $notifications Array of notification results.
+	 *     @type int   $total         Count of all located notifications matching
+	 *                                the query params.
+	 * }
+	 */
+	public static function get_current_notifications_for_user( $args = array() ) {
+		$r = wp_parse_args( $args, array(
+			'user_id'      => bp_loggedin_user_id(),
+			'is_new'       => true,
+			'page'         => 1,
+			'per_page'     => 25,
+			'search_terms' => '',
+		) );
+
+		$notifications = self::get( $r );
+
+		// Bail if no notifications.
+		if ( empty( $notifications ) ) {
+			return false;
+		}
+
+		$total_count = self::get_total_count( $r );
+
+		return array( 'notifications' => &$notifications, 'total' => $total_count );
+	}
+
+	/** Mark ******************************************************************/
+
+	/**
+	 * Mark all user notifications as read.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param int    $user_id           The ID of the user who the notifications are for.
+	 * @param int    $is_new            Mark as read (1) or unread (0).
+	 * @param int    $item_id           Item ID being acted on.
+	 * @param string $component_name    Name of component the notifications are for.
+	 * @param string $component_action  Name of the component action.
+	 * @param int    $secondary_item_id The ID of the secondary item.
+	 * @return bool|int False on failure to update. ID on success.
+	 */
+	public static function mark_all_for_user( $user_id, $is_new = 0, $item_id = 0, $component_name = '', $component_action = '', $secondary_item_id = 0 ) {
+
+		// Values to be updated.
+		$update_args = array(
+			'is_new' => $is_new,
+		);
+
+		// WHERE clauses.
+		$where_args = array(
+			'user_id' => $user_id,
+		);
+
+		if ( ! empty( $item_id ) ) {
+			$where_args['item_id'] = $item_id;
+		}
+
+		if ( ! empty( $component_name ) ) {
+			$where_args['component_name'] = $component_name;
+		}
+
+		if ( ! empty( $component_action ) ) {
+			$where_args['component_action'] = $component_action;
+		}
+
+		if ( ! empty( $secondary_item_id ) ) {
+			$where_args['secondary_item_id'] = $secondary_item_id;
+		}
+
+		return self::update( $update_args, $where_args );
+	}
+
+	/**
+	 * Mark all notifications from a user as read.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param int    $user_id           The ID of the user who the notifications are from.
+	 * @param int    $is_new            Mark as read (1) or unread (0).
+	 * @param string $component_name    Name of component the notifications are for.
+	 * @param string $component_action  Name of the component action.
+	 * @param int    $secondary_item_id The ID of the secondary item.
+	 * @return bool|int
+	 */
+	public static function mark_all_from_user( $user_id, $is_new = 0, $component_name = '', $component_action = '', $secondary_item_id = 0 ) {
+
+		// Values to be updated.
+		$update_args = array(
+			'is_new' => $is_new,
+		);
+
+		// WHERE clauses.
+		$where_args = array(
+			'item_id' => $user_id,
+		);
+
+		if ( ! empty( $component_name ) ) {
+			$where_args['component_name'] = $component_name;
+		}
+
+		if ( ! empty( $component_action ) ) {
+			$where_args['component_action'] = $component_action;
+		}
+
+		if ( ! empty( $secondary_item_id ) ) {
+			$where_args['secondary_item_id'] = $secondary_item_id;
+		}
+
+		return self::update( $update_args, $where_args );
+	}
+
+	/**
+	 * Mark all notifications for all users as read by item id, and optional
+	 * secondary item id, and component name and action.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param int    $item_id           The ID of the item associated with the
+	 *                                  notifications.
+	 * @param int    $is_new            Mark as read (1) or unread (0).
+	 * @param string $component_name    The component that the notifications
+	 *                                  are associated with.
+	 * @param string $component_action  The action that the notifications
+	 *                                  are associated with.
+	 * @param int    $secondary_item_id Optional. ID of the secondary
+	 *                                  associated item.
+	 * @return bool|int
+	 */
+	public static function mark_all_by_type( $item_id, $is_new = 0, $component_name = '', $component_action = '', $secondary_item_id = 0 ) {
+
+		// Values to be updated.
+		$update_args = array(
+			'is_new' => $is_new,
+		);
+
+		// WHERE clauses.
+		$where_args = array(
+			'item_id' => $item_id,
+		);
+
+		if ( ! empty( $component_name ) ) {
+			$where_args['component_name'] = $component_name;
+		}
+
+		if ( ! empty( $component_action ) ) {
+			$where_args['component_action'] = $component_action;
+		}
+
+		if ( ! empty( $secondary_item_id ) ) {
+			$where_args['secondary_item_id'] = $secondary_item_id;
+		}
+
+		return self::update( $update_args, $where_args );
+	}
+}
Index: /tags/2.4.3/src/bp-settings/bp-settings-actions.php
===================================================================
--- /tags/2.4.3/src/bp-settings/bp-settings-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-settings/bp-settings-actions.php	(revision 10410)
@@ -0,0 +1,512 @@
+<?php
+/**
+ * BuddyPress Settings Actions
+ *
+ * @todo split actions into separate screen functions
+ * @package BuddyPress
+ * @subpackage SettingsActions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Handles the changing and saving of user email addresses and passwords.
+ *
+ * We do quite a bit of logic and error handling here to make sure that users
+ * do not accidentally lock themselves out of their accounts. We also try to
+ * provide as accurate of feedback as possible without exposing anyone else's
+ * information to them.
+ *
+ * Special considerations are made for super admins that are able to edit any
+ * users accounts already, without knowing their existing password.
+ *
+ * @global BuddyPress $bp
+ */
+function bp_settings_action_general() {
+
+	// Bail if not a POST action
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Bail if no submit action
+	if ( ! isset( $_POST['submit'] ) )
+		return;
+
+	// Bail if not in settings
+	if ( ! bp_is_settings_component() || ! bp_is_current_action( 'general' ) )
+		return;
+
+	// 404 if there are any additional action variables attached
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	// Define local defaults
+	$bp            = buddypress(); // The instance
+	$email_error   = false;        // invalid|blocked|taken|empty|nochange
+	$pass_error    = false;        // invalid|mismatch|empty|nochange
+	$pass_changed  = false;        // true if the user changes their password
+	$email_changed = false;        // true if the user changes their email
+	$feedback_type = 'error';      // success|error
+	$feedback      = array();      // array of strings for feedback
+
+	// Nonce check
+	check_admin_referer('bp_settings_general');
+
+	// Validate the user again for the current password when making a big change
+	if ( ( is_super_admin() ) || ( !empty( $_POST['pwd'] ) && wp_check_password( $_POST['pwd'], $bp->displayed_user->userdata->user_pass, bp_displayed_user_id() ) ) ) {
+
+		$update_user = get_userdata( bp_displayed_user_id() );
+
+		/** Email Change Attempt ******************************************/
+
+		if ( !empty( $_POST['email'] ) ) {
+
+			// What is missing from the profile page vs signup -
+			// let's double check the goodies
+			$user_email     = sanitize_email( esc_html( trim( $_POST['email'] ) ) );
+			$old_user_email = $bp->displayed_user->userdata->user_email;
+
+			// User is changing email address
+			if ( $old_user_email != $user_email ) {
+
+				// Run some tests on the email address
+				$email_checks = bp_core_validate_email_address( $user_email );
+
+				if ( true !== $email_checks ) {
+					if ( isset( $email_checks['invalid'] ) ) {
+						$email_error = 'invalid';
+					}
+
+					if ( isset( $email_checks['domain_banned'] ) || isset( $email_checks['domain_not_allowed'] ) ) {
+						$email_error = 'blocked';
+					}
+
+					if ( isset( $email_checks['in_use'] ) ) {
+						$email_error = 'taken';
+					}
+				}
+
+				// Store a hash to enable email validation
+				if ( false === $email_error ) {
+					$hash = wp_hash( $_POST['email'] );
+
+					$pending_email = array(
+						'hash'     => $hash,
+						'newemail' => $user_email,
+					);
+
+					bp_update_user_meta( bp_displayed_user_id(), 'pending_email_change', $pending_email );
+
+					$email_text = sprintf(
+						__( 'Dear %1$s,
+
+You recently changed the email address associated with your account on %2$s.
+If this is correct, please click on the following link to complete the change:
+%3$s
+
+You can safely ignore and delete this email if you do not want to take this action or if you have received this email in error.
+
+This email has been sent to %4$s.
+
+Regards,
+%5$s
+%6$s', 'buddypress' ),
+						bp_core_get_user_displayname( bp_displayed_user_id() ),
+						bp_get_site_name(),
+						esc_url( bp_displayed_user_domain() . bp_get_settings_slug() . '/?verify_email_change=' . $hash ),
+						$user_email,
+						bp_get_site_name(),
+						bp_get_root_domain()
+					);
+
+					/**
+					 * Filter the email text sent when a user changes emails.
+					 *
+					 * @since 2.1.0
+					 *
+					 * @param string  $email_text     Text of the email.
+					 * @param string  $new_user_email New user email that the
+					 *                                current user has changed to.
+					 * @param string  $old_user_email Existing email address
+					 *                                for the current user.
+					 * @param WP_User $update_user    Userdata object for the current user.
+					 */
+					$content = apply_filters( 'bp_new_user_email_content', $email_text, $user_email, $old_user_email, $update_user );
+
+					// Send the verification email
+					wp_mail( $user_email, sprintf( __( '[%s] Verify your new email address', 'buddypress' ), wp_specialchars_decode( bp_get_site_name() ) ), $content );
+
+					// We mark that the change has taken place so as to ensure a
+					// success message, even though verification is still required
+					$_POST['email'] = $update_user->user_email;
+					$email_changed = true;
+				}
+
+			// No change
+			} else {
+				$email_error = false;
+			}
+
+		// Email address cannot be empty
+		} else {
+			$email_error = 'empty';
+		}
+
+		/** Password Change Attempt ***************************************/
+
+		if ( !empty( $_POST['pass1'] ) && !empty( $_POST['pass2'] ) ) {
+
+			if ( ( $_POST['pass1'] == $_POST['pass2'] ) && !strpos( " " . $_POST['pass1'], "\\" ) ) {
+
+				// Password change attempt is successful
+				if ( ( ! empty( $_POST['pwd'] ) && $_POST['pwd'] != $_POST['pass1'] ) || is_super_admin() )  {
+					$update_user->user_pass = $_POST['pass1'];
+					$pass_changed = true;
+
+				// The new password is the same as the current password
+				} else {
+					$pass_error = 'same';
+				}
+
+			// Password change attempt was unsuccessful
+			} else {
+				$pass_error = 'mismatch';
+			}
+
+		// Both password fields were empty
+		} elseif ( empty( $_POST['pass1'] ) && empty( $_POST['pass2'] ) ) {
+			$pass_error = false;
+
+		// One of the password boxes was left empty
+		} elseif ( ( empty( $_POST['pass1'] ) && !empty( $_POST['pass2'] ) ) || ( !empty( $_POST['pass1'] ) && empty( $_POST['pass2'] ) ) ) {
+			$pass_error = 'empty';
+		}
+
+		// The structure of the $update_user object changed in WP 3.3, but
+		// wp_update_user() still expects the old format
+		if ( isset( $update_user->data ) && is_object( $update_user->data ) ) {
+			$update_user = $update_user->data;
+			$update_user = get_object_vars( $update_user );
+
+			// Unset the password field to prevent it from emptying out the
+			// user's user_pass field in the database.
+			// @see wp_update_user()
+			if ( false === $pass_changed ) {
+				unset( $update_user['user_pass'] );
+			}
+		}
+
+		// Clear cached data, so that the changed settings take effect
+		// on the current page load
+		if ( ( false === $email_error ) && ( false === $pass_error ) && ( wp_update_user( $update_user ) ) ) {
+			wp_cache_delete( 'bp_core_userdata_' . bp_displayed_user_id(), 'bp' );
+			$bp->displayed_user->userdata = bp_core_get_core_userdata( bp_displayed_user_id() );
+		}
+
+	// Password Error
+	} else {
+		$pass_error = 'invalid';
+	}
+
+	// Email feedback
+	switch ( $email_error ) {
+		case 'invalid' :
+			$feedback['email_invalid']  = __( 'That email address is invalid. Check the formatting and try again.', 'buddypress' );
+			break;
+		case 'blocked' :
+			$feedback['email_blocked']  = __( 'That email address is currently unavailable for use.', 'buddypress' );
+			break;
+		case 'taken' :
+			$feedback['email_taken']    = __( 'That email address is already taken.', 'buddypress' );
+			break;
+		case 'empty' :
+			$feedback['email_empty']    = __( 'Email address cannot be empty.', 'buddypress' );
+			break;
+		case false :
+			// No change
+			break;
+	}
+
+	// Password feedback
+	switch ( $pass_error ) {
+		case 'invalid' :
+			$feedback['pass_error']    = __( 'Your current password is invalid.', 'buddypress' );
+			break;
+		case 'mismatch' :
+			$feedback['pass_mismatch'] = __( 'The new password fields did not match.', 'buddypress' );
+			break;
+		case 'empty' :
+			$feedback['pass_empty']    = __( 'One of the password fields was empty.', 'buddypress' );
+			break;
+		case 'same' :
+			$feedback['pass_same'] 	   = __( 'The new password must be different from the current password.', 'buddypress' );
+			break;
+		case false :
+			// No change
+			break;
+	}
+
+	// No errors so show a simple success message
+	if ( ( ( false === $email_error ) || ( false == $pass_error ) ) && ( ( true === $pass_changed ) || ( true === $email_changed ) ) ) {
+		$feedback[]    = __( 'Your settings have been saved.', 'buddypress' );
+		$feedback_type = 'success';
+
+	// Some kind of errors occurred
+	} elseif ( ( ( false === $email_error ) || ( false === $pass_error ) ) && ( ( false === $pass_changed ) || ( false === $email_changed ) ) ) {
+		if ( bp_is_my_profile() ) {
+			$feedback['nochange'] = __( 'No changes were made to your account.', 'buddypress' );
+		} else {
+			$feedback['nochange'] = __( 'No changes were made to this account.', 'buddypress' );
+		}
+	}
+
+	// Set the feedback
+	bp_core_add_message( implode( "\n", $feedback ), $feedback_type );
+
+	/**
+	 * Fires after the general settings have been saved, and before redirect.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_core_general_settings_after_save' );
+
+	// Redirect to prevent issues with browser back button
+	bp_core_redirect( trailingslashit( bp_displayed_user_domain() . bp_get_settings_slug() . '/general' ) );
+}
+add_action( 'bp_actions', 'bp_settings_action_general' );
+
+/**
+ * Handles the changing and saving of user notification settings.
+ */
+function bp_settings_action_notifications() {
+
+	// Bail if not a POST action
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Bail if no submit action
+	if ( ! isset( $_POST['submit'] ) )
+		return;
+
+	// Bail if not in settings
+	if ( ! bp_is_settings_component() || ! bp_is_current_action( 'notifications' ) )
+		return false;
+
+	// 404 if there are any additional action variables attached
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	check_admin_referer( 'bp_settings_notifications' );
+
+	bp_settings_update_notification_settings( bp_displayed_user_id(), (array) $_POST['notifications'] );
+
+	// Switch feedback for super admins
+	if ( bp_is_my_profile() ) {
+		bp_core_add_message( __( 'Your notification settings have been saved.',        'buddypress' ), 'success' );
+	} else {
+		bp_core_add_message( __( "This user's notification settings have been saved.", 'buddypress' ), 'success' );
+	}
+
+	/**
+	 * Fires after the notification settings have been saved, and before redirect.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_core_notification_settings_after_save' );
+
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_settings_slug() . '/notifications/' );
+}
+add_action( 'bp_actions', 'bp_settings_action_notifications' );
+
+/**
+ * Handles the setting of user capabilities, spamming, hamming, role, etc...
+ */
+function bp_settings_action_capabilities() {
+
+	// Bail if not a POST action
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Bail if no submit action
+	if ( ! isset( $_POST['capabilities-submit'] ) )
+		return;
+
+	// Bail if not in settings
+	if ( ! bp_is_settings_component() || ! bp_is_current_action( 'capabilities' ) )
+		return false;
+
+	// 404 if there are any additional action variables attached
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	// Only super admins can currently spam users (but they can't spam
+	// themselves)
+	if ( ! is_super_admin() || bp_is_my_profile() ) {
+		return;
+	}
+
+	// Nonce check
+	check_admin_referer( 'capabilities' );
+
+	/**
+	 * Fires before the capabilities settings have been saved.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_settings_capabilities_before_save' );
+
+	/** Spam **************************************************************/
+
+	$is_spammer = !empty( $_POST['user-spammer'] ) ? true : false;
+
+	if ( bp_is_user_spammer( bp_displayed_user_id() ) != $is_spammer ) {
+		$status = ( true == $is_spammer ) ? 'spam' : 'ham';
+		bp_core_process_spammer_status( bp_displayed_user_id(), $status );
+
+		/**
+		 * Fires after processing a user as a spammer.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int    $value  ID of the currently displayed user.
+		 * @param string $status Determined status of "spam" or "ham" for the displayed user.
+		 */
+		do_action( 'bp_core_action_set_spammer_status', bp_displayed_user_id(), $status );
+	}
+
+	/** Other *************************************************************/
+
+	/**
+	 * Fires after the capabilities settings have been saved and before redirect.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_settings_capabilities_after_save' );
+
+	// Redirect to the root domain
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_settings_slug() . '/capabilities/' );
+}
+add_action( 'bp_actions', 'bp_settings_action_capabilities' );
+
+/**
+ * Handles the deleting of a user.
+ */
+function bp_settings_action_delete_account() {
+
+	// Bail if not a POST action
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Bail if no submit action
+	if ( ! isset( $_POST['delete-account-understand'] ) )
+		return;
+
+	// Bail if not in settings
+	if ( ! bp_is_settings_component() || ! bp_is_current_action( 'delete-account' ) )
+		return false;
+
+	// 404 if there are any additional action variables attached
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	// Bail if account deletion is disabled
+	if ( bp_disable_account_deletion() && ! bp_current_user_can( 'delete_users' ) ) {
+		return false;
+	}
+
+	// Nonce check
+	check_admin_referer( 'delete-account' );
+
+	// Get username now because it might be gone soon!
+	$username = bp_get_displayed_user_fullname();
+
+	// delete the users account
+	if ( bp_core_delete_account( bp_displayed_user_id() ) ) {
+
+		// Add feedback after deleting a user
+		bp_core_add_message( sprintf( __( '%s was successfully deleted.', 'buddypress' ), $username ), 'success' );
+
+		// Redirect to the root domain
+		bp_core_redirect( bp_get_root_domain() );
+	}
+}
+add_action( 'bp_actions', 'bp_settings_action_delete_account' );
+
+/**
+ * Process email change verification or cancel requests.
+ *
+ * @since 2.1.0
+ */
+function bp_settings_verify_email_change(){
+	if ( ! bp_is_settings_component() ) {
+		return;
+	}
+
+	if ( ! bp_is_my_profile() ) {
+		return;
+	}
+
+	$redirect_to = trailingslashit( bp_displayed_user_domain() . bp_get_settings_slug() );
+
+	// Email change is being verified
+	if ( isset( $_GET['verify_email_change'] ) ) {
+		$pending_email = bp_get_user_meta( bp_displayed_user_id(), 'pending_email_change', true );
+
+		// Bail if the hash provided doesn't match the one saved in the database
+		if ( urldecode( $_GET['verify_email_change'] ) !== $pending_email['hash'] ) {
+			return;
+		}
+
+		$email_changed = wp_update_user( array(
+			'ID'         => bp_displayed_user_id(),
+			'user_email' => trim( $pending_email['newemail'] ),
+		) );
+
+		if ( $email_changed ) {
+			// Delete object cache for displayed user
+			wp_cache_delete( 'bp_core_userdata_' . bp_displayed_user_id(), 'bp' );
+
+			// Delete the pending email change key
+			bp_delete_user_meta( bp_displayed_user_id(), 'pending_email_change' );
+
+			// Post a success message and redirect
+			bp_core_add_message( __( 'You have successfully verified your new email address.', 'buddypress' ) );
+		} else {
+			// Unknown error
+			bp_core_add_message( __( 'There was a problem verifying your new email address. Please try again.', 'buddypress' ), 'error' );
+		}
+
+		bp_core_redirect( $redirect_to );
+		die();
+
+	// Email change is being dismissed
+	} elseif ( ! empty( $_GET['dismiss_email_change'] ) ) {
+	        bp_delete_user_meta( bp_displayed_user_id(), 'pending_email_change' );
+		bp_core_add_message( __( 'You have successfully dismissed your pending email change.', 'buddypress' ) );
+
+		bp_core_redirect( $redirect_to );
+		die();
+	}
+}
+add_action( 'bp_actions', 'bp_settings_verify_email_change' );
+
+/**
+ * Removes 'Email' sub nav, if no component has registered options there.
+ *
+ * @since 2.2.0
+ */
+function bp_settings_remove_email_subnav() {
+	if ( ! has_action( 'bp_notification_settings' ) ) {
+		bp_core_remove_subnav_item( BP_SETTINGS_SLUG, 'notifications' );
+	}
+}
+add_action( 'bp_actions', 'bp_settings_remove_email_subnav' );
Index: /tags/2.4.3/src/bp-settings/bp-settings-functions.php
===================================================================
--- /tags/2.4.3/src/bp-settings/bp-settings-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-settings/bp-settings-functions.php	(revision 10410)
@@ -0,0 +1,104 @@
+<?php
+/**
+ * BuddyPress Settings Functions
+ *
+ * @package BuddyPress
+ * @subpackage SettingsFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Update email notification settings for a specific user.
+ *
+ * @since 2.3.5
+ *
+ * @param int   $user_id  ID of the user whose settings are being updated.
+ * @param array $settings Settings array.
+ */
+function bp_settings_update_notification_settings( $user_id, $settings ) {
+	$user_id = (int) $user_id;
+
+	$settings = bp_settings_sanitize_notification_settings( $settings );
+	foreach ( $settings as $setting_key => $setting_value ) {
+		bp_update_user_meta( $user_id, $setting_key, $setting_value );
+	}
+}
+
+/**
+ * Sanitize email notification settings as submitted by a user.
+ *
+ * @since 2.3.5
+ *
+ * @param array $settings Array of settings.
+ * @return array Sanitized settings.
+ */
+function bp_settings_sanitize_notification_settings( $settings = array() ) {
+	$sanitized_settings = array();
+
+	if ( empty( $settings ) ) {
+		return $sanitized_settings;
+	}
+
+	// Get registered notification keys.
+	$registered_notification_settings = bp_settings_get_registered_notification_keys();
+
+	/*
+	 * We sanitize values for core notification keys.
+	 *
+	 * @todo use register_meta()
+	 */
+	$core_notification_settings = array(
+		'notification_messages_new_message',
+		'notification_activity_new_mention',
+		'notification_activity_new_reply',
+		'notification_groups_invite',
+		'notification_groups_group_updated',
+		'notification_groups_admin_promotion',
+		'notification_groups_membership_request',
+		'notification_membership_request_completed',
+		'notification_friends_friendship_request',
+		'notification_friends_friendship_accepted',
+	);
+
+	foreach ( (array) $settings as $key => $value ) {
+		// Skip if not a registered setting.
+		if ( ! in_array( $key, $registered_notification_settings, true ) ) {
+			continue;
+		}
+
+		// Force core keys to 'yes' or 'no' values.
+		if ( in_array( $key, $core_notification_settings, true ) ) {
+			$value = 'yes' === $value ? 'yes' : 'no';
+		}
+
+		$sanitized_settings[ $key ] = $value;
+	}
+
+	return $sanitized_settings;
+}
+
+/**
+ * Build a dynamic whitelist of notification keys, based on what's hooked to 'bp_notification_settings'.
+ *
+ * @since 2.3.5
+ *
+ * @return array
+ */
+function bp_settings_get_registered_notification_keys() {
+
+	ob_start();
+	do_action( 'bp_notification_settings' );
+	$screen = ob_get_clean();
+
+	$matched = preg_match_all( '/<input[^>]+name="notifications\[([^\]]+)\]/', $screen, $matches );
+
+	if ( $matched && isset( $matches[1] ) ) {
+		$key_whitelist = $matches[1];
+	} else {
+		$key_whitelist = array();
+	}
+
+	return $key_whitelist;
+}
Index: /tags/2.4.3/src/bp-settings/bp-settings-loader.php
===================================================================
--- /tags/2.4.3/src/bp-settings/bp-settings-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-settings/bp-settings-loader.php	(revision 10410)
@@ -0,0 +1,208 @@
+<?php
+/**
+ * BuddyPress Settings Loader.
+ *
+ * @package BuddyPress
+ * @subpackage SettingsLoader
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+class BP_Settings_Component extends BP_Component {
+
+	/**
+	 * Start the settings component creation process.
+	 *
+	 * @since 1.5.0
+	 */
+	public function __construct() {
+		parent::start(
+			'settings',
+			__( 'Settings', 'buddypress' ),
+			buddypress()->plugin_dir,
+			array(
+				'adminbar_myaccount_order' => 100
+			)
+		);
+	}
+
+	/**
+	 * Include files.
+	 *
+	 * @param array $includes Array of values to include. Not used.
+	 */
+	public function includes( $includes = array() ) {
+		parent::includes( array(
+			'actions',
+			'screens',
+			'template',
+			'functions',
+		) );
+	}
+
+	/**
+	 * Setup globals.
+	 *
+	 * The BP_SETTINGS_SLUG constant is deprecated, and only used here for
+	 * backwards compatibility.
+	 *
+	 * @param array $args Array of arguments.
+	 *
+	 * @since 1.5.0
+	 */
+	public function setup_globals( $args = array() ) {
+
+		// Define a slug, if necessary
+		if ( ! defined( 'BP_SETTINGS_SLUG' ) ) {
+			define( 'BP_SETTINGS_SLUG', $this->id );
+		}
+
+		// All globals for settings component.
+		parent::setup_globals( array(
+			'slug'          => BP_SETTINGS_SLUG,
+			'has_directory' => false,
+		) );
+	}
+
+	/**
+	 * Set up navigation.
+	 *
+	 * @param array $main_nav Array of main nav items.
+	 * @param array $sub_nav  Array of sub nav items.
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		// Determine user to use
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		$access        = bp_core_can_edit_settings();
+		$slug          = bp_get_settings_slug();
+		$settings_link = trailingslashit( $user_domain . $slug );
+
+		// Add the settings navigation item
+		$main_nav = array(
+			'name'                    => __( 'Settings', 'buddypress' ),
+			'slug'                    => $slug,
+			'position'                => 100,
+			'show_for_displayed_user' => $access,
+			'screen_function'         => 'bp_settings_screen_general',
+			'default_subnav_slug'     => 'general'
+		);
+
+		// Add General Settings nav item
+		$sub_nav[] = array(
+			'name'            => __( 'General', 'buddypress' ),
+			'slug'            => 'general',
+			'parent_url'      => $settings_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'bp_settings_screen_general',
+			'position'        => 10,
+			'user_has_access' => $access
+		);
+
+		// Add Email nav item. Formerly called 'Notifications', we
+		// retain the old slug and function names for backward compat
+		$sub_nav[] = array(
+			'name'            => __( 'Email', 'buddypress' ),
+			'slug'            => 'notifications',
+			'parent_url'      => $settings_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'bp_settings_screen_notification',
+			'position'        => 20,
+			'user_has_access' => $access
+		);
+
+		// Add Spam Account nav item
+		if ( bp_current_user_can( 'bp_moderate' ) ) {
+			$sub_nav[] = array(
+				'name'            => __( 'Capabilities', 'buddypress' ),
+				'slug'            => 'capabilities',
+				'parent_url'      => $settings_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'bp_settings_screen_capabilities',
+				'position'        => 80,
+				'user_has_access' => ! bp_is_my_profile()
+			);
+		}
+
+		// Add Delete Account nav item
+		if ( ( ! bp_disable_account_deletion() && bp_is_my_profile() ) || bp_current_user_can( 'delete_users' ) ) {
+			$sub_nav[] = array(
+				'name'            => __( 'Delete Account', 'buddypress' ),
+				'slug'            => 'delete-account',
+				'parent_url'      => $settings_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'bp_settings_screen_delete_account',
+				'position'        => 90,
+				'user_has_access' => ! is_super_admin( bp_displayed_user_id() )
+			);
+		}
+
+		parent::setup_nav( $main_nav, $sub_nav );
+	}
+
+	/**
+	 * Set up the Toolbar.
+	 *
+	 * @param array $wp_admin_nav Array of Admin Bar items.
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		// Menus for logged in user
+		if ( is_user_logged_in() ) {
+
+			// Setup the logged in user variables
+			$settings_link = trailingslashit( bp_loggedin_user_domain() . bp_get_settings_slug() );
+
+			// Add main Settings menu
+			$wp_admin_nav[] = array(
+				'parent' => buddypress()->my_account_menu_id,
+				'id'     => 'my-account-' . $this->id,
+				'title'  => __( 'Settings', 'buddypress' ),
+				'href'   => $settings_link
+			);
+
+			// General Account
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-general',
+				'title'  => __( 'General', 'buddypress' ),
+				'href'   => $settings_link
+			);
+
+			// Notifications - only add the tab when there is something to display there.
+			if ( has_action( 'bp_notification_settings' ) ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-notifications',
+					'title'  => __( 'Email', 'buddypress' ),
+					'href'   => trailingslashit( $settings_link . 'notifications' )
+				);
+			}
+
+			// Delete Account
+			if ( !bp_current_user_can( 'bp_moderate' ) && ! bp_core_get_root_option( 'bp-disable-account-deletion' ) ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-delete-account',
+					'title'  => __( 'Delete Account', 'buddypress' ),
+					'href'   => trailingslashit( $settings_link . 'delete-account' )
+				);
+			}
+		}
+
+		parent::setup_admin_bar( $wp_admin_nav );
+	}
+}
+
+function bp_setup_settings() {
+	buddypress()->settings = new BP_Settings_Component();
+}
+add_action( 'bp_setup_components', 'bp_setup_settings', 6 );
Index: /tags/2.4.3/src/bp-settings/bp-settings-screens.php
===================================================================
--- /tags/2.4.3/src/bp-settings/bp-settings-screens.php	(revision 10410)
+++ /tags/2.4.3/src/bp-settings/bp-settings-screens.php	(revision 10410)
@@ -0,0 +1,98 @@
+<?php
+/**
+ * BuddyPress Settings Screens.
+ *
+ * @package BuddyPress
+ * @subpackage SettingsScreens
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Show the general settings template.
+ *
+ * @since 1.5.0
+ */
+function bp_settings_screen_general() {
+
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	/**
+	 * Filters the template file path to use for the general settings screen.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $value Directory path to look in for the template file.
+	 */
+	bp_core_load_template( apply_filters( 'bp_settings_screen_general_settings', 'members/single/settings/general' ) );
+}
+
+/**
+ * Show the notifications settings template.
+ *
+ * @since 1.5.0
+ */
+function bp_settings_screen_notification() {
+
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	/**
+	 * Filters the template file path to use for the notification settings screen.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $value Directory path to look in for the template file.
+	 */
+	bp_core_load_template( apply_filters( 'bp_settings_screen_notification_settings', 'members/single/settings/notifications' ) );
+}
+
+/**
+ * Show the delete-account settings template.
+ *
+ * @since 1.5.0
+ */
+function bp_settings_screen_delete_account() {
+
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	/**
+	 * Filters the template file path to use for the delete-account settings screen.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $value Directory path to look in for the template file.
+	 */
+	bp_core_load_template( apply_filters( 'bp_settings_screen_delete_account', 'members/single/settings/delete-account' ) );
+}
+
+/**
+ * Show the capabilities settings template.
+ *
+ * @since 1.6.0
+ */
+function bp_settings_screen_capabilities() {
+
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	/**
+	 * Filters the template file path to use for the capabilities settings screen.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param string $value Directory path to look in for the template file.
+	 */
+	bp_core_load_template( apply_filters( 'bp_settings_screen_capabilities', 'members/single/settings/capabilities' ) );
+}
Index: /tags/2.4.3/src/bp-settings/bp-settings-template.php
===================================================================
--- /tags/2.4.3/src/bp-settings/bp-settings-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-settings/bp-settings-template.php	(revision 10410)
@@ -0,0 +1,104 @@
+<?php
+/**
+ * BuddyPress Settings Template Functions.
+ *
+ * @package BuddyPress
+ * @subpackage SettingsTemplate
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Output the settings component slug.
+ *
+ * @package BuddyPress
+ * @subpackage SettingsTemplate
+ * @since 1.5.0
+ *
+ * @uses bp_get_settings_slug()
+ */
+function bp_settings_slug() {
+	echo bp_get_settings_slug();
+}
+	/**
+	 * Return the settings component slug.
+	 *
+	 * @package BuddyPress
+	 * @subpackage SettingsTemplate
+	 * @since 1.5.0
+	 */
+	function bp_get_settings_slug() {
+
+		/**
+		 * Filters the Settings component slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $slug Settings component slug.
+		 */
+		return apply_filters( 'bp_get_settings_slug', buddypress()->settings->slug );
+	}
+
+/**
+ * Output the settings component root slug.
+ *
+ * @package BuddyPress
+ * @subpackage SettingsTemplate
+ * @since 1.5.0
+ *
+ * @uses bp_get_settings_root_slug()
+ */
+function bp_settings_root_slug() {
+	echo bp_get_settings_root_slug();
+}
+	/**
+	 * Return the settings component root slug.
+	 *
+	 * @package BuddyPress
+	 * @subpackage SettingsTemplate
+	 * @since 1.5.0
+	 */
+	function bp_get_settings_root_slug() {
+
+		/**
+		 * Filters the Settings component root slug.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string $root_slug Settings component root slug.
+		 */
+		return apply_filters( 'bp_get_settings_root_slug', buddypress()->settings->root_slug );
+	}
+
+/**
+ * Add the 'pending email change' message to the settings page.
+ *
+ * @since 2.1.0
+ */
+function bp_settings_pending_email_notice() {
+	$pending_email = bp_get_user_meta( bp_displayed_user_id(), 'pending_email_change', true );
+
+	if ( empty( $pending_email['newemail'] ) ) {
+		return;
+	}
+
+	if ( bp_get_displayed_user_email() == $pending_email['newemail'] ) {
+		return;
+	}
+
+	?>
+
+	<div id="message" class="bp-template-notice error">
+		<p><?php printf(
+			__( 'There is a pending change of your email address to %1$s.<br />Check your email (%2$s) for the verification link. <a href="%3$s">Cancel</a>', 'buddypress' ),
+			'<code>' . $pending_email['newemail'] . '</code>',
+			'<code>' . $pending_email['newemail'] . '</code>',
+			esc_url( bp_displayed_user_domain() . bp_get_settings_slug() . '/?dismiss_email_change=1' )
+		);
+		?></p>
+	</div>
+
+	<?php
+}
+add_action( 'bp_before_member_settings_template', 'bp_settings_pending_email_notice' );
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress-functions.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress-functions.php	(revision 10410)
@@ -0,0 +1,1928 @@
+<?php
+/**
+ * Functions of BuddyPress's Legacy theme.
+ *
+ * @since 1.7.0
+ *
+ * @package BuddyPress
+ * @subpackage BP_Theme_Compat
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/** Theme Setup ***************************************************************/
+
+if ( !class_exists( 'BP_Legacy' ) ) :
+
+/**
+ * Loads BuddyPress Legacy 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 BuddyPress templates in it.
+ *
+ * To make your custom theme BuddyPress compatible and customize the templates, you
+ * can copy these files into your theme without needing to merge anything
+ * together; BuddyPress should safely handle the rest.
+ *
+ * See @link BP_Theme_Compat() for more.
+ *
+ * @since 1.7.0
+ *
+ * @package BuddyPress
+ * @subpackage BP_Theme_Compat
+ */
+class BP_Legacy extends BP_Theme_Compat {
+
+	/** Functions *************************************************************/
+
+	/**
+	 * The main BuddyPress (Legacy) Loader.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @uses BP_Legacy::setup_globals()
+	 * @uses BP_Legacy::setup_actions()
+	 */
+	public function __construct() {
+		parent::start();
+	}
+
+	/**
+	 * Component global variables.
+	 *
+	 * You'll want to customize the values in here, so they match whatever your
+	 * needs are.
+	 *
+	 * @since 1.7.0
+	 */
+	protected function setup_globals() {
+		$bp            = buddypress();
+		$this->id      = 'legacy';
+		$this->name    = __( 'BuddyPress Legacy', 'buddypress' );
+		$this->version = bp_get_version();
+		$this->dir     = trailingslashit( $bp->themes_dir . '/bp-legacy' );
+		$this->url     = trailingslashit( $bp->themes_url . '/bp-legacy' );
+	}
+
+	/**
+	 * Setup the theme hooks.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @uses add_filter() To add various filters
+	 * @uses add_action() To add various actions
+	 */
+	protected function setup_actions() {
+
+		// Template Output.
+		add_filter( 'bp_get_activity_action_pre_meta', array( $this, 'secondary_avatars' ), 10, 2 );
+
+		// Filter BuddyPress template hierarchy and look for page templates.
+		add_filter( 'bp_get_buddypress_template', array( $this, 'theme_compat_page_templates' ), 10, 1 );
+
+		/** Scripts ***********************************************************/
+
+		add_action( 'bp_enqueue_scripts', array( $this, 'enqueue_styles'   ) ); // Enqueue theme CSS
+		add_action( 'bp_enqueue_scripts', array( $this, 'enqueue_scripts'  ) ); // Enqueue theme JS
+		add_filter( 'bp_enqueue_scripts', array( $this, 'localize_scripts' ) ); // Enqueue theme script localization
+		add_action( 'bp_head',            array( $this, 'head_scripts'     ) ); // Output some extra JS in the <head>.
+
+		/** Body no-js Class **************************************************/
+
+		add_filter( 'body_class', array( $this, 'add_nojs_body_class' ), 20, 1 );
+
+		/** Buttons ***********************************************************/
+
+		if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
+			// Register buttons for the relevant component templates
+			// Friends button.
+			if ( bp_is_active( 'friends' ) )
+				add_action( 'bp_member_header_actions',    'bp_add_friend_button',           5 );
+
+			// Activity button.
+			if ( bp_is_active( 'activity' ) && bp_activity_do_mentions() )
+				add_action( 'bp_member_header_actions',    'bp_send_public_message_button',  20 );
+
+			// Messages button.
+			if ( bp_is_active( 'messages' ) )
+				add_action( 'bp_member_header_actions',    'bp_send_private_message_button', 20 );
+
+			// Group buttons.
+			if ( bp_is_active( 'groups' ) ) {
+				add_action( 'bp_group_header_actions',          'bp_group_join_button',               5 );
+				add_action( 'bp_group_header_actions',          'bp_group_new_topic_button',         20 );
+				add_action( 'bp_directory_groups_actions',      'bp_group_join_button'                  );
+				add_action( 'bp_groups_directory_group_filter', 'bp_legacy_theme_group_create_nav', 999 );
+			}
+
+			// Blog button.
+			if ( bp_is_active( 'blogs' ) ) {
+				add_action( 'bp_directory_blogs_actions',    'bp_blogs_visit_blog_button'           );
+				add_action( 'bp_blogs_directory_blog_types', 'bp_legacy_theme_blog_create_nav', 999 );
+			}
+		}
+
+		/** Notices ***********************************************************/
+
+		// Only hook the 'sitewide_notices' overlay if the Sitewide
+		// Notices widget is not in use (to avoid duplicate content).
+		if ( bp_is_active( 'messages' ) && ! is_active_widget( false, false, 'bp_messages_sitewide_notices_widget', true ) ) {
+			add_action( 'wp_footer', array( $this, 'sitewide_notices' ), 9999 );
+		}
+
+		/** Ajax **************************************************************/
+
+		$actions = array(
+
+			// Directory filters.
+			'blogs_filter'    => 'bp_legacy_theme_object_template_loader',
+			'forums_filter'   => 'bp_legacy_theme_object_template_loader',
+			'groups_filter'   => 'bp_legacy_theme_object_template_loader',
+			'members_filter'  => 'bp_legacy_theme_object_template_loader',
+			'messages_filter' => 'bp_legacy_theme_messages_template_loader',
+			'invite_filter'   => 'bp_legacy_theme_invite_template_loader',
+			'requests_filter' => 'bp_legacy_theme_requests_template_loader',
+
+			// Friends.
+			'accept_friendship' => 'bp_legacy_theme_ajax_accept_friendship',
+			'addremove_friend'  => 'bp_legacy_theme_ajax_addremove_friend',
+			'reject_friendship' => 'bp_legacy_theme_ajax_reject_friendship',
+
+			// Activity.
+			'activity_get_older_updates'  => 'bp_legacy_theme_activity_template_loader',
+			'activity_mark_fav'           => 'bp_legacy_theme_mark_activity_favorite',
+			'activity_mark_unfav'         => 'bp_legacy_theme_unmark_activity_favorite',
+			'activity_widget_filter'      => 'bp_legacy_theme_activity_template_loader',
+			'delete_activity'             => 'bp_legacy_theme_delete_activity',
+			'delete_activity_comment'     => 'bp_legacy_theme_delete_activity_comment',
+			'get_single_activity_content' => 'bp_legacy_theme_get_single_activity_content',
+			'new_activity_comment'        => 'bp_legacy_theme_new_activity_comment',
+			'post_update'                 => 'bp_legacy_theme_post_update',
+			'bp_spam_activity'            => 'bp_legacy_theme_spam_activity',
+			'bp_spam_activity_comment'    => 'bp_legacy_theme_spam_activity',
+
+			// Groups.
+			'groups_invite_user' => 'bp_legacy_theme_ajax_invite_user',
+			'joinleave_group'    => 'bp_legacy_theme_ajax_joinleave_group',
+
+			// Messages.
+			'messages_autocomplete_results' => 'bp_legacy_theme_ajax_messages_autocomplete_results',
+			'messages_close_notice'         => 'bp_legacy_theme_ajax_close_notice',
+			'messages_delete'               => 'bp_legacy_theme_ajax_messages_delete',
+			'messages_markread'             => 'bp_legacy_theme_ajax_message_markread',
+			'messages_markunread'           => 'bp_legacy_theme_ajax_message_markunread',
+			'messages_send_reply'           => 'bp_legacy_theme_ajax_messages_send_reply',
+		);
+
+		// Conditional actions.
+		if ( bp_is_active( 'messages', 'star' ) ) {
+			$actions['messages_star'] = 'bp_legacy_theme_ajax_messages_star_handler';
+		}
+
+		/**
+		 * Register all of these AJAX handlers.
+		 *
+		 * The "wp_ajax_" action is used for logged in users, and "wp_ajax_nopriv_"
+		 * executes for users that aren't logged in. This is for backpat with BP <1.6.
+		 */
+		foreach( $actions as $name => $function ) {
+			add_action( 'wp_ajax_'        . $name, $function );
+			add_action( 'wp_ajax_nopriv_' . $name, $function );
+		}
+
+		add_filter( 'bp_ajax_querystring', 'bp_legacy_theme_ajax_querystring', 10, 2 );
+
+		/** Override **********************************************************/
+
+		/**
+		 * Fires after all of the BuddyPress theme compat actions have been added.
+		 *
+		 * @since 1.7.0
+		 *
+		 * @param BP_Legacy $this Current BP_Legacy instance.
+		 */
+		do_action_ref_array( 'bp_theme_compat_actions', array( &$this ) );
+	}
+
+	/**
+	 * Load the theme CSS
+	 *
+	 * @since 1.7.0
+	 * @since 2.3.0 Support custom CSS file named after the current theme or parent theme.
+	 *
+	 * @uses wp_enqueue_style() To enqueue the styles
+	 */
+	public function enqueue_styles() {
+		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+
+		// Locate the BP stylesheet.
+		$ltr = $this->locate_asset_in_stack( "buddypress{$min}.css",     'css' );
+
+		// LTR.
+		if ( ! is_rtl() && isset( $ltr['location'], $ltr['handle'] ) ) {
+			wp_enqueue_style( $ltr['handle'], $ltr['location'], array(), $this->version, 'screen' );
+
+			if ( $min ) {
+				wp_style_add_data( $ltr['handle'], 'suffix', $min );
+			}
+		}
+
+		// RTL.
+		if ( is_rtl() ) {
+			$rtl = $this->locate_asset_in_stack( "buddypress-rtl{$min}.css", 'css' );
+
+			if ( isset( $rtl['location'], $rtl['handle'] ) ) {
+				$rtl['handle'] = str_replace( '-css', '-css-rtl', $rtl['handle'] );  // Backwards compatibility.
+				wp_enqueue_style( $rtl['handle'], $rtl['location'], array(), $this->version, 'screen' );
+
+				if ( $min ) {
+					wp_style_add_data( $rtl['handle'], 'suffix', $min );
+				}
+			}
+		}
+
+		// Compatibility stylesheets for specific themes.
+		$theme = $this->locate_asset_in_stack( get_template() . "{$min}.css", 'css' );
+		if ( ! is_rtl() && isset( $theme['location'] ) ) {
+			// Use a unique handle.
+			$theme['handle'] = 'bp-' . get_template();
+			wp_enqueue_style( $theme['handle'], $theme['location'], array(), $this->version, 'screen' );
+
+			if ( $min ) {
+				wp_style_add_data( $theme['handle'], 'suffix', $min );
+			}
+		}
+
+		// Compatibility stylesheet for specific themes, RTL-version.
+		if ( is_rtl() ) {
+			$theme_rtl = $this->locate_asset_in_stack( get_template() . "-rtl{$min}.css", 'css' );
+
+			if ( isset( $theme_rtl['location'] ) ) {
+				$theme_rtl['handle'] = $theme['handle'] . '-rtl';
+				wp_enqueue_style( $theme_rtl['handle'], $theme_rtl['location'], array(), $this->version, 'screen' );
+
+				if ( $min ) {
+					wp_style_add_data( $theme_rtl['handle'], 'suffix', $min );
+				}
+			}
+		}
+	}
+
+	/**
+	 * Enqueue the required JavaScript files
+	 *
+	 * @since 1.7.0
+	 */
+	public function enqueue_scripts() {
+		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+
+		// Locate the BP JS file.
+		$asset = $this->locate_asset_in_stack( "buddypress{$min}.js", 'js' );
+
+		// Enqueue the global JS, if found - AJAX will not work
+		// without it.
+		if ( isset( $asset['location'], $asset['handle'] ) ) {
+			wp_enqueue_script( $asset['handle'], $asset['location'], bp_core_get_js_dependencies(), $this->version );
+		}
+
+		/**
+		 * Filters core JavaScript strings for internationalization before AJAX usage.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array $value Array of key/value pairs for AJAX usage.
+		 */
+		$params = apply_filters( 'bp_core_get_js_strings', array(
+			'accepted'            => __( 'Accepted', 'buddypress' ),
+			'close'               => __( 'Close', 'buddypress' ),
+			'comments'            => __( 'comments', 'buddypress' ),
+			'leave_group_confirm' => __( 'Are you sure you want to leave this group?', 'buddypress' ),
+			'mark_as_fav'	      => __( 'Favorite', 'buddypress' ),
+			'my_favs'             => __( 'My Favorites', 'buddypress' ),
+			'rejected'            => __( 'Rejected', 'buddypress' ),
+			'remove_fav'	      => __( 'Remove Favorite', 'buddypress' ),
+			'show_all'            => __( 'Show all', 'buddypress' ),
+			'show_all_comments'   => __( 'Show all comments for this thread', 'buddypress' ),
+			'show_x_comments'     => __( 'Show all %d comments', 'buddypress' ),
+			'unsaved_changes'     => __( 'Your profile has unsaved changes. If you leave the page, the changes will be lost.', 'buddypress' ),
+			'view'                => __( 'View', 'buddypress' ),
+		) );
+		wp_localize_script( $asset['handle'], 'BP_DTheme', $params );
+
+		// Maybe enqueue comment reply JS.
+		if ( is_singular() && bp_is_blog_page() && get_option( 'thread_comments' ) ) {
+			wp_enqueue_script( 'comment-reply' );
+		}
+
+		// Maybe enqueue password verify JS (register page or user settings page).
+		if ( bp_is_register_page() || ( function_exists( 'bp_is_user_settings_general' ) && bp_is_user_settings_general() ) ) {
+
+			// Locate the Register Page JS file.
+			$asset = $this->locate_asset_in_stack( "password-verify{$min}.js", 'js', 'bp-legacy-password-verify' );
+
+			$dependencies = array_merge( bp_core_get_js_dependencies(), array(
+				'password-strength-meter',
+			) );
+
+			// Enqueue script.
+			wp_enqueue_script( $asset['handle'] . '-password-verify', $asset['location'], $dependencies, $this->version);
+		}
+
+		// Star private messages.
+		if ( bp_is_active( 'messages', 'star' ) && bp_is_user_messages() ) {
+			wp_localize_script( $asset['handle'], 'BP_PM_Star', array(
+				'strings' => array(
+					'text_unstar'  => __( 'Unstar', 'buddypress' ),
+					'text_star'    => __( 'Star', 'buddypress' ),
+					'title_unstar' => __( 'Starred', 'buddypress' ),
+					'title_star'   => __( 'Not starred', 'buddypress' ),
+					'title_unstar_thread' => __( 'Remove all starred messages in this thread', 'buddypress' ),
+					'title_star_thread'   => __( 'Star the first message in this thread', 'buddypress' ),
+				),
+				'is_single_thread' => (int) bp_is_messages_conversation(),
+				'star_counter'     => 0,
+				'unstar_counter'   => 0
+			) );
+		}
+	}
+
+	/**
+	 * Get the URL and handle of a web-accessible CSS or JS asset
+	 *
+	 * We provide two levels of customizability with respect to where CSS
+	 * and JS files can be stored: (1) the child theme/parent theme/theme
+	 * compat hierarchy, and (2) the "template stack" of /buddypress/css/,
+	 * /community/css/, and /css/. In this way, CSS and JS assets can be
+	 * overloaded, and default versions provided, in exactly the same way
+	 * as corresponding PHP templates.
+	 *
+	 * We are duplicating some of the logic that is currently found in
+	 * bp_locate_template() and the _template_stack() functions. Those
+	 * functions were built with PHP templates in mind, and will require
+	 * refactoring in order to provide "stack" functionality for assets
+	 * that must be accessible both using file_exists() (the file path)
+	 * and at a public URI.
+	 *
+	 * This method is marked private, with the understanding that the
+	 * implementation is subject to change or removal in an upcoming
+	 * release, in favor of a unified _template_stack() system. Plugin
+	 * and theme authors should not attempt to use what follows.
+	 *
+	 * @since 1.8.0
+	 * @param string $file A filename like buddypress.css.
+	 * @param string $type Optional. Either "js" or "css" (the default).
+	 * @param string $script_handle Optional. If set, used as the script name in `wp_enqueue_script`.
+	 * @return array An array of data for the wp_enqueue_* function:
+	 *   'handle' (eg 'bp-child-css') and a 'location' (the URI of the
+	 *   asset)
+	 */
+	private function locate_asset_in_stack( $file, $type = 'css', $script_handle = '' ) {
+		$locations = array();
+
+		// Ensure the assets can be located when running from /src/.
+		if ( defined( 'BP_SOURCE_SUBDIRECTORY' ) && BP_SOURCE_SUBDIRECTORY === 'src' ) {
+			$file = str_replace( '.min', '', $file );
+		}
+
+		// No need to check child if template == stylesheet.
+		if ( is_child_theme() ) {
+			$locations['bp-child'] = array(
+				'dir'  => get_stylesheet_directory(),
+				'uri'  => get_stylesheet_directory_uri(),
+				'file' => str_replace( '.min', '', $file ),
+			);
+		}
+
+		$locations['bp-parent'] = array(
+			'dir'  => get_template_directory(),
+			'uri'  => get_template_directory_uri(),
+			'file' => str_replace( '.min', '', $file ),
+		);
+
+		$locations['bp-legacy'] = array(
+			'dir'  => bp_get_theme_compat_dir(),
+			'uri'  => bp_get_theme_compat_url(),
+			'file' => $file,
+		);
+
+		// Subdirectories within the top-level $locations directories.
+		$subdirs = array(
+			'buddypress/' . $type,
+			'community/' . $type,
+			$type,
+		);
+
+		$retval = array();
+
+		foreach ( $locations as $location_type => $location ) {
+			foreach ( $subdirs as $subdir ) {
+				if ( file_exists( trailingslashit( $location['dir'] ) . trailingslashit( $subdir ) . $location['file'] ) ) {
+					$retval['location'] = trailingslashit( $location['uri'] ) . trailingslashit( $subdir ) . $location['file'];
+					$retval['handle']   = ( $script_handle ) ? $script_handle : "{$location_type}-{$type}";
+
+					break 2;
+				}
+			}
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * Put some scripts in the header, like AJAX url for wp-lists.
+	 *
+	 * @since 1.7.0
+	 */
+	public function head_scripts() {
+	?>
+
+		<script type="text/javascript">
+			/* <![CDATA[ */
+			var ajaxurl = '<?php echo bp_core_ajax_url(); ?>';
+			/* ]]> */
+		</script>
+
+	<?php
+	}
+
+	/**
+	 * Adds the no-js class to the body tag.
+	 *
+	 * This function ensures that the <body> element will have the 'no-js' class by default. If you're
+	 * using JavaScript for some visual functionality in your theme, and you want to provide noscript
+	 * support, apply those styles to body.no-js.
+	 *
+	 * The no-js class is removed by the JavaScript created in buddypress.js.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param array $classes Array of classes to append to body tag.
+	 * @return array $classes
+	 */
+	public function add_nojs_body_class( $classes ) {
+		if ( ! in_array( 'no-js', $classes ) )
+			$classes[] = 'no-js';
+
+		return array_unique( $classes );
+	}
+
+	/**
+	 * Load localizations for topic script.
+	 *
+	 * These localizations require information that may not be loaded even by init.
+	 *
+	 * @since 1.7.0
+	 */
+	public function localize_scripts() {
+	}
+
+	/**
+	 * Outputs sitewide notices markup in the footer.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @see https://buddypress.trac.wordpress.org/ticket/4802
+	 */
+	public function sitewide_notices() {
+		// Do not show notices if user is not logged in.
+		if ( ! is_user_logged_in() )
+			return;
+
+		// Add a class to determine if the admin bar is on or not.
+		$class = did_action( 'admin_bar_menu' ) ? 'admin-bar-on' : 'admin-bar-off';
+
+		echo '<div id="sitewide-notice" class="' . $class . '">';
+		bp_message_get_notices();
+		echo '</div>';
+	}
+
+	/**
+	 * Add secondary avatar image to this activity stream's record, if supported.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string               $action   The text of this activity.
+	 * @param BP_Activity_Activity $activity Activity object.
+	 * @return string
+	 */
+	function secondary_avatars( $action, $activity ) {
+		switch ( $activity->component ) {
+			case 'groups' :
+			case 'friends' :
+				// Only insert avatar if one exists.
+				if ( $secondary_avatar = bp_get_activity_secondary_avatar() ) {
+					$reverse_content = strrev( $action );
+					$position        = strpos( $reverse_content, 'a<' );
+					$action          = substr_replace( $action, $secondary_avatar, -$position - 2, 0 );
+				}
+				break;
+		}
+
+		return $action;
+	}
+
+	/**
+	 * Filter the default theme compatibility root template hierarchy, and prepend
+	 * a page template to the front if it's set.
+	 *
+	 * @see https://buddypress.trac.wordpress.org/ticket/6065
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param  array $templates Array of templates.
+	 * @uses   apply_filters() call 'bp_legacy_theme_compat_page_templates_directory_only' and return false
+	 *                         to use the defined page template for component's directory and its single items
+	 * @return array
+	 */
+	public function theme_compat_page_templates( $templates = array() ) {
+
+		/**
+		 * Filters whether or not we are looking at a directory to determine if to return early.
+		 *
+		 * @since 2.2.0
+		 *
+		 * @param bool $value Whether or not we are viewing a directory.
+		 */
+		if ( true === (bool) apply_filters( 'bp_legacy_theme_compat_page_templates_directory_only', ! bp_is_directory() ) ) {
+			return $templates;
+		}
+
+		// No page ID yet.
+		$page_id = 0;
+
+		// Get the WordPress Page ID for the current view.
+		foreach ( (array) buddypress()->pages as $component => $bp_page ) {
+
+			// Handles the majority of components.
+			if ( bp_is_current_component( $component ) ) {
+				$page_id = (int) $bp_page->id;
+			}
+
+			// Stop if not on a user page.
+			if ( ! bp_is_user() && ! empty( $page_id ) ) {
+				break;
+			}
+
+			// The Members component requires an explicit check due to overlapping components.
+			if ( bp_is_user() && ( 'members' === $component ) ) {
+				$page_id = (int) $bp_page->id;
+				break;
+			}
+		}
+
+		// Bail if no directory page set.
+		if ( 0 === $page_id ) {
+			return $templates;
+		}
+
+		// Check for page template.
+		$page_template = get_page_template_slug( $page_id );
+
+		// Add it to the beginning of the templates array so it takes precedence
+		// over the default hierarchy.
+		if ( ! empty( $page_template ) ) {
+
+			/**
+			 * Check for existence of template before adding it to template
+			 * stack to avoid accidentally including an unintended file.
+			 *
+			 * @see: https://buddypress.trac.wordpress.org/ticket/6190
+			 */
+			if ( '' !== locate_template( $page_template ) ) {
+				array_unshift( $templates, $page_template );
+			}
+		}
+
+		return $templates;
+	}
+}
+new BP_Legacy();
+endif;
+
+/**
+ * Add the Create a Group button to the Groups directory title.
+ *
+ * The bp-legacy puts the Create a Group button into the page title, to mimic
+ * the behavior of bp-default.
+ *
+ * @since 2.0.0
+ * @todo Deprecate
+ *
+ * @param string $title Groups directory title.
+ * @return string
+ */
+function bp_legacy_theme_group_create_button( $title ) {
+	return $title . ' ' . bp_get_group_create_button();
+}
+
+/**
+ * Add the Create a Group nav to the Groups directory navigation.
+ *
+ * The bp-legacy puts the Create a Group nav at the last position of
+ * the Groups directory navigation.
+ *
+ * @since 2.2.0
+ *
+ * @uses   bp_group_create_nav_item() to output the create a Group nav item.
+ */
+function bp_legacy_theme_group_create_nav() {
+	bp_group_create_nav_item();
+}
+
+/**
+ * Add the Create a Site button to the Sites directory title.
+ *
+ * The bp-legacy puts the Create a Site button into the page title, to mimic
+ * the behavior of bp-default.
+ *
+ * @since 2.0.0
+ * @todo Deprecate
+ *
+ * @param string $title Sites directory title.
+ * @return string
+ */
+function bp_legacy_theme_blog_create_button( $title ) {
+	return $title . ' ' . bp_get_blog_create_button();
+}
+
+/**
+ * Add the Create a Site nav to the Sites directory navigation.
+ *
+ * The bp-legacy puts the Create a Site nav at the last position of
+ * the Sites directory navigation.
+ *
+ * @since 2.2.0
+ *
+ * @uses   bp_blog_create_nav_item() to output the Create a Site nav item
+ */
+function bp_legacy_theme_blog_create_nav() {
+	bp_blog_create_nav_item();
+}
+
+/**
+ * This function looks scarier than it actually is. :)
+ * Each object loop (activity/members/groups/blogs/forums) contains default
+ * parameters to show specific information based on the page we are currently
+ * looking at.
+ *
+ * The following function will take into account any cookies set in the JS and
+ * allow us to override the parameters sent. That way we can change the results
+ * returned without reloading the page.
+ *
+ * By using cookies we can also make sure that user settings are retained
+ * across page loads.
+ *
+ * @param string $query_string Query string for the current request.
+ * @param string $object       Object for cookie.
+ * @return string Query string for the component loops
+ * @since 1.2.0
+ */
+function bp_legacy_theme_ajax_querystring( $query_string, $object ) {
+	if ( empty( $object ) )
+		return '';
+
+	// Set up the cookies passed on this AJAX request. Store a local var to avoid conflicts.
+	if ( ! empty( $_POST['cookie'] ) ) {
+		$_BP_COOKIE = wp_parse_args( str_replace( '; ', '&', urldecode( $_POST['cookie'] ) ) );
+	} else {
+		$_BP_COOKIE = &$_COOKIE;
+	}
+
+	$qs = array();
+
+	/**
+	 * Check if any cookie values are set. If there are then override the
+	 * default params passed to the template loop.
+	 */
+
+	// Activity stream filtering on action.
+	if ( ! empty( $_BP_COOKIE['bp-' . $object . '-filter'] ) && '-1' != $_BP_COOKIE['bp-' . $object . '-filter'] ) {
+		$qs[] = 'type='   . $_BP_COOKIE['bp-' . $object . '-filter'];
+		$qs[] = 'action=' . $_BP_COOKIE['bp-' . $object . '-filter'];
+	}
+
+	if ( ! empty( $_BP_COOKIE['bp-' . $object . '-scope'] ) ) {
+		if ( 'personal' == $_BP_COOKIE['bp-' . $object . '-scope'] ) {
+			$user_id = ( bp_displayed_user_id() ) ? bp_displayed_user_id() : bp_loggedin_user_id();
+			$qs[] = 'user_id=' . $user_id;
+		}
+
+		// Activity stream scope only on activity directory.
+		if ( 'all' != $_BP_COOKIE['bp-' . $object . '-scope'] && ! bp_displayed_user_id() && ! bp_is_single_item() )
+			$qs[] = 'scope=' . $_BP_COOKIE['bp-' . $object . '-scope'];
+	}
+
+	// If page and search_terms have been passed via the AJAX post request, use those.
+	if ( ! empty( $_POST['page'] ) && '-1' != $_POST['page'] )
+		$qs[] = 'page=' . absint( $_POST['page'] );
+
+	// Excludes activity just posted and avoids duplicate ids.
+	if ( ! empty( $_POST['exclude_just_posted'] ) ) {
+		$just_posted = wp_parse_id_list( $_POST['exclude_just_posted'] );
+		$qs[] = 'exclude=' . implode( ',', $just_posted );
+	}
+
+	// To get newest activities.
+	if ( ! empty( $_POST['offset'] ) ) {
+		$qs[] = 'offset=' . intval( $_POST['offset'] );
+	}
+
+	$object_search_text = bp_get_search_default_text( $object );
+ 	if ( ! empty( $_POST['search_terms'] ) && $object_search_text != $_POST['search_terms'] && 'false' != $_POST['search_terms'] && 'undefined' != $_POST['search_terms'] )
+		$qs[] = 'search_terms=' . urlencode( $_POST['search_terms'] );
+
+	// Now pass the querystring to override default values.
+	$query_string = empty( $qs ) ? '' : join( '&', (array) $qs );
+
+	$object_filter = '';
+	if ( isset( $_BP_COOKIE['bp-' . $object . '-filter'] ) )
+		$object_filter = $_BP_COOKIE['bp-' . $object . '-filter'];
+
+	$object_scope = '';
+	if ( isset( $_BP_COOKIE['bp-' . $object . '-scope'] ) )
+		$object_scope = $_BP_COOKIE['bp-' . $object . '-scope'];
+
+	$object_page = '';
+	if ( isset( $_BP_COOKIE['bp-' . $object . '-page'] ) )
+		$object_page = $_BP_COOKIE['bp-' . $object . '-page'];
+
+	$object_search_terms = '';
+	if ( isset( $_BP_COOKIE['bp-' . $object . '-search-terms'] ) )
+		$object_search_terms = $_BP_COOKIE['bp-' . $object . '-search-terms'];
+
+	$object_extras = '';
+	if ( isset( $_BP_COOKIE['bp-' . $object . '-extras'] ) )
+		$object_extras = $_BP_COOKIE['bp-' . $object . '-extras'];
+
+	/**
+	 * Filters the AJAX query string for the component loops.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $query_string        The query string we are working with.
+	 * @param string $object              The type of page we are on.
+	 * @param string $object_filter       The current object filter.
+	 * @param string $object_scope        The current object scope.
+	 * @param string $object_page         The current object page.
+	 * @param string $object_search_terms The current object search terms.
+	 * @param string $object_extras       The current object extras.
+	 */
+	return apply_filters( 'bp_legacy_theme_ajax_querystring', $query_string, $object, $object_filter, $object_scope, $object_page, $object_search_terms, $object_extras );
+}
+
+/**
+ * Load the template loop for the current object.
+ *
+ * @return string Prints template loop for the specified object
+ * @since 1.2.0
+ */
+function bp_legacy_theme_object_template_loader() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Bail if no object passed.
+	if ( empty( $_POST['object'] ) )
+		return;
+
+	// Sanitize the object.
+	$object = sanitize_title( $_POST['object'] );
+
+	// Bail if object is not an active component to prevent arbitrary file inclusion.
+	if ( ! bp_is_active( $object ) )
+		return;
+
+ 	/**
+	 * AJAX requests happen too early to be seen by bp_update_is_directory()
+	 * so we do it manually here to ensure templates load with the correct
+	 * context. Without this check, templates will load the 'single' version
+	 * of themselves rather than the directory version.
+	 */
+	if ( ! bp_current_action() )
+		bp_update_is_directory( true, bp_current_component() );
+
+	$template_part = $object . '/' . $object . '-loop';
+
+	// The template part can be overridden by the calling JS function.
+	if ( ! empty( $_POST['template'] ) ) {
+		$template_part = sanitize_option( 'upload_path', $_POST['template'] );
+	}
+
+	// Locate the object template.
+	bp_get_template_part( $template_part );
+	exit();
+}
+
+/**
+ * Load messages template loop when searched on the private message page
+ *
+ * @since 1.6.0
+ *
+ * @return string Prints template loop for the Messages component.
+ */
+function bp_legacy_theme_messages_template_loader() {
+	bp_get_template_part( 'members/single/messages/messages-loop' );
+	exit();
+}
+
+/**
+ * Load group invitations loop to handle pagination requests sent via AJAX.
+ *
+ * @since 2.0.0
+ */
+function bp_legacy_theme_invite_template_loader() {
+	bp_get_template_part( 'groups/single/invites-loop' );
+	exit();
+}
+
+/**
+ * Load group membership requests loop to handle pagination requests sent via AJAX.
+ *
+ * @since 2.0.0
+ */
+function bp_legacy_theme_requests_template_loader() {
+	bp_get_template_part( 'groups/single/requests-loop' );
+	exit();
+}
+
+/**
+ * Load the activity loop template when activity is requested via AJAX.
+ *
+ * @return string JSON object containing 'contents' (output of the template loop
+ * for the Activity component) and 'feed_url' (URL to the relevant RSS feed).
+ *
+ * @since 1.2.0
+ */
+function bp_legacy_theme_activity_template_loader() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	$scope = '';
+	if ( ! empty( $_POST['scope'] ) )
+		$scope = $_POST['scope'];
+
+	// We need to calculate and return the feed URL for each scope.
+	switch ( $scope ) {
+		case 'friends':
+			$feed_url = bp_loggedin_user_domain() . bp_get_activity_slug() . '/friends/feed/';
+			break;
+		case 'groups':
+			$feed_url = bp_loggedin_user_domain() . bp_get_activity_slug() . '/groups/feed/';
+			break;
+		case 'favorites':
+			$feed_url = bp_loggedin_user_domain() . bp_get_activity_slug() . '/favorites/feed/';
+			break;
+		case 'mentions':
+			$feed_url = bp_loggedin_user_domain() . bp_get_activity_slug() . '/mentions/feed/';
+			bp_activity_clear_new_mentions( bp_loggedin_user_id() );
+			break;
+		default:
+			$feed_url = home_url( bp_get_activity_root_slug() . '/feed/' );
+			break;
+	}
+
+	// Buffer the loop in the template to a var for JS to spit out.
+	ob_start();
+	bp_get_template_part( 'activity/activity-loop' );
+	$result['contents'] = ob_get_contents();
+
+	/**
+	 * Filters the feed URL for when activity is requested via AJAX.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $feed_url URL for the feed to be used.
+	 * @param string $scope    Scope for the activity request.
+	 */
+	$result['feed_url'] = apply_filters( 'bp_legacy_theme_activity_feed_url', $feed_url, $scope );
+	ob_end_clean();
+
+	exit( json_encode( $result ) );
+}
+
+/**
+ * Processes Activity updates received via a POST request.
+ *
+ * @return string HTML
+ * @since 1.2.0
+ */
+function bp_legacy_theme_post_update() {
+	$bp = buddypress();
+
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Check the nonce.
+	check_admin_referer( 'post_update', '_wpnonce_post_update' );
+
+	if ( ! is_user_logged_in() )
+		exit( '-1' );
+
+	if ( empty( $_POST['content'] ) )
+		exit( '-1<div id="message" class="error bp-ajax-message"><p>' . __( 'Please enter some content to post.', 'buddypress' ) . '</p></div>' );
+
+	$activity_id = 0;
+	$item_id     = 0;
+	$object      = '';
+
+
+	// Try to get the item id from posted variables.
+	if ( ! empty( $_POST['item_id'] ) ) {
+		$item_id = (int) $_POST['item_id'];
+	}
+
+	// Try to get the object from posted variables.
+	if ( ! empty( $_POST['object'] ) ) {
+		$object  = sanitize_key( $_POST['object'] );
+
+	// If the object is not set and we're in a group, set the item id and the object
+	} elseif ( bp_is_group() ) {
+		$item_id = bp_get_current_group_id();
+		$object = 'groups';
+	}
+
+	if ( ! $object && bp_is_active( 'activity' ) ) {
+		$activity_id = bp_activity_post_update( array( 'content' => $_POST['content'] ) );
+
+	} elseif ( 'groups' === $object ) {
+		if ( $item_id && bp_is_active( 'groups' ) )
+			$activity_id = groups_post_update( array( 'content' => $_POST['content'], 'group_id' => $item_id ) );
+
+	} else {
+
+		/** This filter is documented in bp-activity/bp-activity-actions.php */
+		$activity_id = apply_filters( 'bp_activity_custom_update', false, $object, $item_id, $_POST['content'] );
+	}
+
+	if ( empty( $activity_id ) )
+		exit( '-1<div id="message" class="error bp-ajax-message"><p>' . __( 'There was a problem posting your update. Please try again.', 'buddypress' ) . '</p></div>' );
+
+	$last_recorded = ! empty( $_POST['since'] ) ? date( 'Y-m-d H:i:s', intval( $_POST['since'] ) ) : 0;
+	if ( $last_recorded ) {
+		$activity_args = array( 'since' => $last_recorded );
+		$bp->activity->last_recorded = $last_recorded;
+		add_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10, 1 );
+	} else {
+		$activity_args = array( 'include' => $activity_id );
+	}
+
+	if ( bp_has_activities ( $activity_args ) ) {
+		while ( bp_activities() ) {
+			bp_the_activity();
+			bp_get_template_part( 'activity/entry' );
+		}
+	}
+
+	if ( ! empty( $last_recorded ) ) {
+		remove_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10, 1 );
+	}
+
+	exit;
+}
+
+/**
+ * Posts new Activity comments received via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @global BP_Activity_Template $activities_template
+ *
+ * @return string HTML
+ */
+function bp_legacy_theme_new_activity_comment() {
+	global $activities_template;
+
+	$bp = buddypress();
+
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
+		return;
+	}
+
+	// Check the nonce.
+	check_admin_referer( 'new_activity_comment', '_wpnonce_new_activity_comment' );
+
+	if ( ! is_user_logged_in() ) {
+		exit( '-1' );
+	}
+
+	$feedback = __( 'There was an error posting your reply. Please try again.', 'buddypress' );
+
+	if ( empty( $_POST['content'] ) ) {
+		exit( '-1<div id="message" class="error bp-ajax-message"><p>' . esc_html__( 'Please do not leave the comment area blank.', 'buddypress' ) . '</p></div>' );
+	}
+
+	if ( empty( $_POST['form_id'] ) || empty( $_POST['comment_id'] ) || ! is_numeric( $_POST['form_id'] ) || ! is_numeric( $_POST['comment_id'] ) ) {
+		exit( '-1<div id="message" class="error bp-ajax-message"><p>' . esc_html( $feedback ) . '</p></div>' );
+	}
+
+	$comment_id = bp_activity_new_comment( array(
+		'activity_id' => $_POST['form_id'],
+		'content'     => $_POST['content'],
+		'parent_id'   => $_POST['comment_id'],
+	) );
+
+	if ( ! $comment_id ) {
+		if ( ! empty( $bp->activity->errors['new_comment'] ) && is_wp_error( $bp->activity->errors['new_comment'] ) ) {
+			$feedback = $bp->activity->errors['new_comment']->get_error_message();
+			unset( $bp->activity->errors['new_comment'] );
+		}
+
+		exit( '-1<div id="message" class="error bp-ajax-message"><p>' . esc_html( $feedback ) . '</p></div>' );
+	}
+
+	// Load the new activity item into the $activities_template global.
+	bp_has_activities( 'display_comments=stream&hide_spam=false&show_hidden=true&include=' . $comment_id );
+
+	// Swap the current comment with the activity item we just loaded.
+	if ( isset( $activities_template->activities[0] ) ) {
+		$activities_template->activity = new stdClass();
+		$activities_template->activity->id              = $activities_template->activities[0]->item_id;
+		$activities_template->activity->current_comment = $activities_template->activities[0];
+
+		// Because the whole tree has not been loaded, we manually
+		// determine depth.
+		$depth = 1;
+		$parent_id = (int) $activities_template->activities[0]->secondary_item_id;
+		while ( $parent_id !== (int) $activities_template->activities[0]->item_id ) {
+			$depth++;
+			$p_obj = new BP_Activity_Activity( $parent_id );
+			$parent_id = (int) $p_obj->secondary_item_id;
+		}
+		$activities_template->activity->current_comment->depth = $depth;
+	}
+
+	// Get activity comment template part.
+	bp_get_template_part( 'activity/comment' );
+
+	unset( $activities_template );
+	exit;
+}
+
+/**
+ * Deletes an Activity item received via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return mixed String on error, void on success.
+ */
+function bp_legacy_theme_delete_activity() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Check the nonce.
+	check_admin_referer( 'bp_activity_delete_link' );
+
+	if ( ! is_user_logged_in() )
+		exit( '-1' );
+
+	if ( empty( $_POST['id'] ) || ! is_numeric( $_POST['id'] ) )
+		exit( '-1' );
+
+	$activity = new BP_Activity_Activity( (int) $_POST['id'] );
+
+	// Check access.
+	if ( ! bp_activity_user_can_delete( $activity ) )
+		exit( '-1' );
+
+	/** This action is documented in bp-activity/bp-activity-actions.php */
+	do_action( 'bp_activity_before_action_delete_activity', $activity->id, $activity->user_id );
+
+	if ( ! bp_activity_delete( array( 'id' => $activity->id, 'user_id' => $activity->user_id ) ) )
+		exit( '-1<div id="message" class="error bp-ajax-message"><p>' . __( 'There was a problem when deleting. Please try again.', 'buddypress' ) . '</p></div>' );
+
+	/** This action is documented in bp-activity/bp-activity-actions.php */
+	do_action( 'bp_activity_action_delete_activity', $activity->id, $activity->user_id );
+	exit;
+}
+
+/**
+ * Deletes an Activity comment received via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return mixed String on error, void on success.
+ */
+function bp_legacy_theme_delete_activity_comment() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Check the nonce.
+	check_admin_referer( 'bp_activity_delete_link' );
+
+	if ( ! is_user_logged_in() )
+		exit( '-1' );
+
+	$comment = new BP_Activity_Activity( $_POST['id'] );
+
+	// Check access.
+	if ( ! bp_current_user_can( 'bp_moderate' ) && $comment->user_id != bp_loggedin_user_id() )
+		exit( '-1' );
+
+	if ( empty( $_POST['id'] ) || ! is_numeric( $_POST['id'] ) )
+		exit( '-1' );
+
+	/** This action is documented in bp-activity/bp-activity-actions.php */
+	do_action( 'bp_activity_before_action_delete_activity', $_POST['id'], $comment->user_id );
+
+	if ( ! bp_activity_delete_comment( $comment->item_id, $comment->id ) )
+		exit( '-1<div id="message" class="error bp-ajax-message"><p>' . __( 'There was a problem when deleting. Please try again.', 'buddypress' ) . '</p></div>' );
+
+	/** This action is documented in bp-activity/bp-activity-actions.php */
+	do_action( 'bp_activity_action_delete_activity', $_POST['id'], $comment->user_id );
+	exit;
+}
+
+/**
+ * AJAX spam an activity item or comment.
+ *
+ * @since 1.6.0
+ *
+ * @return mixed String on error, void on success.
+ */
+function bp_legacy_theme_spam_activity() {
+	$bp = buddypress();
+
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Check that user is logged in, Activity Streams are enabled, and Akismet is present.
+	if ( ! is_user_logged_in() || ! bp_is_active( 'activity' ) || empty( $bp->activity->akismet ) )
+		exit( '-1' );
+
+	// Check an item ID was passed.
+	if ( empty( $_POST['id'] ) || ! is_numeric( $_POST['id'] ) )
+		exit( '-1' );
+
+	// Is the current user allowed to spam items?
+	if ( ! bp_activity_user_can_mark_spam() )
+		exit( '-1' );
+
+	// Load up the activity item.
+	$activity = new BP_Activity_Activity( (int) $_POST['id'] );
+	if ( empty( $activity->component ) )
+		exit( '-1' );
+
+	// Check nonce.
+	check_admin_referer( 'bp_activity_akismet_spam_' . $activity->id );
+
+	/** This action is documented in bp-activity/bp-activity-actions.php */
+	do_action( 'bp_activity_before_action_spam_activity', $activity->id, $activity );
+
+	// Mark as spam.
+	bp_activity_mark_as_spam( $activity );
+	$activity->save();
+
+	/** This action is documented in bp-activity/bp-activity-actions.php */
+	do_action( 'bp_activity_action_spam_activity', $activity->id, $activity->user_id );
+	exit;
+}
+
+/**
+ * Mark an activity as a favourite via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return string HTML
+ */
+function bp_legacy_theme_mark_activity_favorite() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	if ( bp_activity_add_user_favorite( $_POST['id'] ) )
+		_e( 'Remove Favorite', 'buddypress' );
+	else
+		_e( 'Favorite', 'buddypress' );
+
+	exit;
+}
+
+/**
+ * Un-favourite an activity via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return string HTML
+ */
+function bp_legacy_theme_unmark_activity_favorite() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	if ( bp_activity_remove_user_favorite( $_POST['id'] ) )
+		_e( 'Favorite', 'buddypress' );
+	else
+		_e( 'Remove Favorite', 'buddypress' );
+
+	exit;
+}
+
+/**
+ * Fetches an activity's full, non-excerpted content via a POST request.
+ * Used for the 'Read More' link on long activity items.
+ *
+ * @since 1.5.0
+ *
+ * @return string HTML
+ */
+function bp_legacy_theme_get_single_activity_content() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	$activity_array = bp_activity_get_specific( array(
+		'activity_ids'     => $_POST['activity_id'],
+		'display_comments' => 'stream'
+	) );
+
+	$activity = ! empty( $activity_array['activities'][0] ) ? $activity_array['activities'][0] : false;
+
+	if ( empty( $activity ) )
+		exit; // @todo: error?
+
+	/**
+	 * Fires before the return of an activity's full, non-excerpted content via a POST request.
+	 *
+	 * @since 1.7.0
+	 *
+	 * @param string $activity Activity content. Passed by reference.
+	 */
+	do_action_ref_array( 'bp_legacy_theme_get_single_activity_content', array( &$activity ) );
+
+	// Activity content retrieved through AJAX should run through normal filters, but not be truncated.
+	remove_filter( 'bp_get_activity_content_body', 'bp_activity_truncate_entry', 5 );
+
+	/** This filter is documented in bp-activity/bp-activity-template.php */
+	$content = apply_filters( 'bp_get_activity_content_body', $activity->content );
+
+	exit( $content );
+}
+
+/**
+ * Invites a friend to join a group via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @todo Audit return types
+ */
+function bp_legacy_theme_ajax_invite_user() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	check_ajax_referer( 'groups_invite_uninvite_user' );
+
+	if ( ! $_POST['friend_id'] || ! $_POST['friend_action'] || ! $_POST['group_id'] )
+		return;
+
+	if ( ! bp_groups_user_can_send_invites( $_POST['group_id'] ) )
+		return;
+
+	if ( ! friends_check_friendship( bp_loggedin_user_id(), $_POST['friend_id'] ) )
+		return;
+
+	$group_id = (int) $_POST['group_id'];
+	$friend_id = (int) $_POST['friend_id'];
+
+	if ( 'invite' == $_POST['friend_action'] ) {
+		$group = groups_get_group( $group_id );
+
+		// Users who have previously requested membership do not need
+		// another invitation created for them.
+		if ( BP_Groups_Member::check_for_membership_request( $friend_id, $group_id ) ) {
+			$user_status = 'is_pending';
+
+		// Create the user invitation.
+		} elseif ( groups_invite_user( array( 'user_id' => $friend_id, 'group_id' => $group_id ) ) ) {
+			$user_status = 'is_invited';
+
+		// Miscellaneous failure.
+		} else {
+			return;
+		}
+
+		$user = new BP_Core_User( $friend_id );
+
+		$uninvite_url = bp_is_current_action( 'create' )
+			? bp_get_groups_directory_permalink() . 'create/step/group-invites/?user_id=' . $friend_id
+			: bp_get_group_permalink( $group )    . 'send-invites/remove/' . $friend_id;
+
+		echo '<li id="uid-' . esc_attr( $user->id ) . '">';
+		echo $user->avatar_thumb;
+		echo '<h4>' . $user->user_link . '</h4>';
+		echo '<span class="activity">' . esc_attr( $user->last_active ) . '</span>';
+		echo '<div class="action">
+				<a class="button remove" href="' . wp_nonce_url( $uninvite_url, 'groups_invite_uninvite_user' ) . '" id="uid-' . esc_attr( $user->id ) . '">' . __( 'Remove Invite', 'buddypress' ) . '</a>
+			  </div>';
+
+		if ( 'is_pending' == $user_status ) {
+			echo '<p class="description">' . sprintf( __( '%s has previously requested to join this group. Sending an invitation will automatically add the member to the group.', 'buddypress' ), $user->user_link ) . '</p>';
+		}
+
+		echo '</li>';
+		exit;
+
+	} elseif ( 'uninvite' == $_POST['friend_action'] ) {
+		// Users who have previously requested membership should not
+		// have their requests deleted on the "uninvite" action.
+		if ( BP_Groups_Member::check_for_membership_request( $friend_id, $group_id ) ) {
+			return;
+		}
+
+		// Remove the unsent invitation.
+		if ( ! groups_uninvite_user( $friend_id, $group_id ) ) {
+			return;
+		}
+
+		exit;
+
+	} else {
+		return;
+	}
+}
+
+/**
+ * Friend/un-friend a user via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return string HTML
+ */
+function bp_legacy_theme_ajax_addremove_friend() {
+
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Cast fid as an integer.
+	$friend_id = (int) $_POST['fid'];
+
+	// Trying to cancel friendship.
+	if ( 'is_friend' == BP_Friends_Friendship::check_is_friend( bp_loggedin_user_id(), $friend_id ) ) {
+		check_ajax_referer( 'friends_remove_friend' );
+
+		if ( ! friends_remove_friend( bp_loggedin_user_id(), $friend_id ) ) {
+			echo __( 'Friendship could not be canceled.', 'buddypress' );
+		} else {
+			echo '<a id="friend-' . esc_attr( $friend_id ) . '" class="add" rel="add" title="' . __( 'Add Friend', 'buddypress' ) . '" href="' . wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/add-friend/' . $friend_id, 'friends_add_friend' ) . '">' . __( 'Add Friend', 'buddypress' ) . '</a>';
+		}
+
+	// Trying to request friendship.
+	} elseif ( 'not_friends' == BP_Friends_Friendship::check_is_friend( bp_loggedin_user_id(), $friend_id ) ) {
+		check_ajax_referer( 'friends_add_friend' );
+
+		if ( ! friends_add_friend( bp_loggedin_user_id(), $friend_id ) ) {
+			echo __(' Friendship could not be requested.', 'buddypress' );
+		} else {
+			echo '<a id="friend-' . esc_attr( $friend_id ) . '" class="remove" rel="remove" title="' . __( 'Cancel Friendship Request', 'buddypress' ) . '" href="' . wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/requests/cancel/' . $friend_id . '/', 'friends_withdraw_friendship' ) . '" class="requested">' . __( 'Cancel Friendship Request', 'buddypress' ) . '</a>';
+		}
+
+	// Trying to cancel pending request.
+	} elseif ( 'pending' == BP_Friends_Friendship::check_is_friend( bp_loggedin_user_id(), $friend_id ) ) {
+		check_ajax_referer( 'friends_withdraw_friendship' );
+
+		if ( friends_withdraw_friendship( bp_loggedin_user_id(), $friend_id ) ) {
+			echo '<a id="friend-' . esc_attr( $friend_id ) . '" class="add" rel="add" title="' . __( 'Add Friend', 'buddypress' ) . '" href="' . wp_nonce_url( bp_loggedin_user_domain() . bp_get_friends_slug() . '/add-friend/' . $friend_id, 'friends_add_friend' ) . '">' . __( 'Add Friend', 'buddypress' ) . '</a>';
+		} else {
+			echo __("Friendship request could not be cancelled.", 'buddypress');
+		}
+
+	// Request already pending.
+	} else {
+		echo __( 'Request Pending', 'buddypress' );
+	}
+
+	exit;
+}
+
+/**
+ * Accept a user friendship request via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return mixed String on error, void on success.
+ */
+function bp_legacy_theme_ajax_accept_friendship() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	check_admin_referer( 'friends_accept_friendship' );
+
+	if ( ! friends_accept_friendship( (int) $_POST['id'] ) )
+		echo "-1<div id='message' class='error'><p>" . __( 'There was a problem accepting that request. Please try again.', 'buddypress' ) . '</p></div>';
+
+	exit;
+}
+
+/**
+ * Reject a user friendship request via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return mixed String on error, void on success.
+ */
+function bp_legacy_theme_ajax_reject_friendship() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	check_admin_referer( 'friends_reject_friendship' );
+
+	if ( ! friends_reject_friendship( (int) $_POST['id'] ) )
+		echo "-1<div id='message' class='error'><p>" . __( 'There was a problem rejecting that request. Please try again.', 'buddypress' ) . '</p></div>';
+
+	exit;
+}
+
+/**
+ * Join or leave a group when clicking the "join/leave" button via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return string HTML
+ */
+function bp_legacy_theme_ajax_joinleave_group() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	// Cast gid as integer.
+	$group_id = (int) $_POST['gid'];
+
+	if ( groups_is_user_banned( bp_loggedin_user_id(), $group_id ) )
+		return;
+
+	if ( ! $group = groups_get_group( array( 'group_id' => $group_id ) ) )
+		return;
+
+	if ( ! groups_is_user_member( bp_loggedin_user_id(), $group->id ) ) {
+		if ( 'public' == $group->status ) {
+			check_ajax_referer( 'groups_join_group' );
+
+			if ( ! groups_join_group( $group->id ) ) {
+				_e( 'Error joining group', 'buddypress' );
+			} else {
+				echo '<a id="group-' . esc_attr( $group->id ) . '" class="leave-group" rel="leave" title="' . __( 'Leave Group', 'buddypress' ) . '" href="' . wp_nonce_url( bp_get_group_permalink( $group ) . 'leave-group', 'groups_leave_group' ) . '">' . __( 'Leave Group', 'buddypress' ) . '</a>';
+			}
+
+		} elseif ( 'private' == $group->status ) {
+
+			// If the user has already been invited, then this is
+			// an Accept Invitation button.
+			if ( groups_check_user_has_invite( bp_loggedin_user_id(), $group->id ) ) {
+				check_ajax_referer( 'groups_accept_invite' );
+
+				if ( ! groups_accept_invite( bp_loggedin_user_id(), $group->id ) ) {
+					_e( 'Error requesting membership', 'buddypress' );
+				} else {
+					echo '<a id="group-' . esc_attr( $group->id ) . '" class="leave-group" rel="leave" title="' . __( 'Leave Group', 'buddypress' ) . '" href="' . wp_nonce_url( bp_get_group_permalink( $group ) . 'leave-group', 'groups_leave_group' ) . '">' . __( 'Leave Group', 'buddypress' ) . '</a>';
+				}
+
+			// Otherwise, it's a Request Membership button.
+			} else {
+				check_ajax_referer( 'groups_request_membership' );
+
+				if ( ! groups_send_membership_request( bp_loggedin_user_id(), $group->id ) ) {
+					_e( 'Error requesting membership', 'buddypress' );
+				} else {
+					echo '<a id="group-' . esc_attr( $group->id ) . '" class="group-button disabled pending membership-requested" rel="membership-requested" title="' . __( 'Request Sent', 'buddypress' ) . '" href="' . bp_get_group_permalink( $group ) . '">' . __( 'Request Sent', 'buddypress' ) . '</a>';
+				}
+			}
+		}
+
+	} else {
+		check_ajax_referer( 'groups_leave_group' );
+
+		if ( ! groups_leave_group( $group->id ) ) {
+			_e( 'Error leaving group', 'buddypress' );
+		} elseif ( 'public' == $group->status ) {
+			echo '<a id="group-' . esc_attr( $group->id ) . '" class="join-group" rel="join" title="' . __( 'Join Group', 'buddypress' ) . '" href="' . wp_nonce_url( bp_get_group_permalink( $group ) . 'join', 'groups_join_group' ) . '">' . __( 'Join Group', 'buddypress' ) . '</a>';
+		} elseif ( 'private' == $group->status ) {
+			echo '<a id="group-' . esc_attr( $group->id ) . '" class="request-membership" rel="join" title="' . __( 'Request Membership', 'buddypress' ) . '" href="' . wp_nonce_url( bp_get_group_permalink( $group ) . 'request-membership', 'groups_request_membership' ) . '">' . __( 'Request Membership', 'buddypress' ) . '</a>';
+		}
+	}
+
+	exit;
+}
+
+/**
+ * Close and keep closed site wide notices from an admin in the sidebar, via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return mixed String on error, void on success.
+ */
+function bp_legacy_theme_ajax_close_notice() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	if ( ! isset( $_POST['notice_id'] ) ) {
+		echo "-1<div id='message' class='error'><p>" . __( 'There was a problem closing the notice.', 'buddypress' ) . '</p></div>';
+
+	} else {
+		$user_id      = get_current_user_id();
+		$notice_ids   = bp_get_user_meta( $user_id, 'closed_notices', true );
+		$notice_ids[] = (int) $_POST['notice_id'];
+
+		bp_update_user_meta( $user_id, 'closed_notices', $notice_ids );
+	}
+
+	exit;
+}
+
+/**
+ * Send a private message reply to a thread via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return string HTML
+ */
+function bp_legacy_theme_ajax_messages_send_reply() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	check_ajax_referer( 'messages_send_message' );
+
+	$result = messages_new_message( array( 'thread_id' => (int) $_REQUEST['thread_id'], 'content' => $_REQUEST['content'] ) );
+
+	if ( !empty( $result ) ) {
+
+		// Pretend we're in the message loop.
+		global $thread_template;
+
+		bp_thread_has_messages( array( 'thread_id' => (int) $_REQUEST['thread_id'] ) );
+
+		// Set the current message to the 2nd last.
+		$thread_template->message = end( $thread_template->thread->messages );
+		$thread_template->message = prev( $thread_template->thread->messages );
+
+		// Set current message to current key.
+		$thread_template->current_message = key( $thread_template->thread->messages );
+
+		// Now manually iterate message like we're in the loop.
+		bp_thread_the_message();
+
+		// Manually call oEmbed
+		// this is needed because we're not at the beginning of the loop.
+		bp_messages_embed();
+
+		// Add new-message css class.
+		add_filter( 'bp_get_the_thread_message_css_class', create_function( '$retval', '
+			$retval[] = "new-message";
+			return $retval;
+		' ) );
+
+		// Output single message template part.
+		bp_get_template_part( 'members/single/messages/message' );
+
+		// Clean up the loop.
+		bp_thread_messages();
+
+	} else {
+		echo "-1<div id='message' class='error'><p>" . __( 'There was a problem sending that reply. Please try again.', 'buddypress' ) . '</p></div>';
+	}
+
+	exit;
+}
+
+/**
+ * Mark a private message as unread in your inbox via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return mixed String on error, void on success.
+ */
+function bp_legacy_theme_ajax_message_markunread() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	if ( ! isset($_POST['thread_ids']) ) {
+		echo "-1<div id='message' class='error'><p>" . __( 'There was a problem marking messages as unread.', 'buddypress' ) . '</p></div>';
+
+	} else {
+		$thread_ids = explode( ',', $_POST['thread_ids'] );
+
+		for ( $i = 0, $count = count( $thread_ids ); $i < $count; ++$i ) {
+			BP_Messages_Thread::mark_as_unread( (int) $thread_ids[$i] );
+		}
+	}
+
+	exit;
+}
+
+/**
+ * Mark a private message as read in your inbox via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return mixed String on error, void on success.
+ */
+function bp_legacy_theme_ajax_message_markread() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	if ( ! isset($_POST['thread_ids']) ) {
+		echo "-1<div id='message' class='error'><p>" . __('There was a problem marking messages as read.', 'buddypress' ) . '</p></div>';
+
+	} else {
+		$thread_ids = explode( ',', $_POST['thread_ids'] );
+
+		for ( $i = 0, $count = count( $thread_ids ); $i < $count; ++$i ) {
+			BP_Messages_Thread::mark_as_read( (int) $thread_ids[$i] );
+		}
+	}
+
+	exit;
+}
+
+/**
+ * Delete a private message(s) in your inbox via a POST request.
+ *
+ * @since 1.2.0
+ *
+ * @return string HTML
+ */
+function bp_legacy_theme_ajax_messages_delete() {
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
+		return;
+
+	if ( ! isset($_POST['thread_ids']) ) {
+		echo "-1<div id='message' class='error'><p>" . __( 'There was a problem deleting messages.', 'buddypress' ) . '</p></div>';
+
+	} else {
+		$thread_ids = wp_parse_id_list( $_POST['thread_ids'] );
+		messages_delete_thread( $thread_ids );
+
+		_e( 'Messages deleted.', 'buddypress' );
+	}
+
+	exit;
+}
+
+/**
+ * AJAX handler for autocomplete.
+ *
+ * Displays friends only, unless BP_MESSAGES_AUTOCOMPLETE_ALL is defined.
+ *
+ * @since 1.2.0
+ */
+function bp_legacy_theme_ajax_messages_autocomplete_results() {
+
+	/**
+	 * Filters the max results default value for ajax messages autocomplete results.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $value Max results for autocomplete. Default 10.
+	 */
+	$limit = isset( $_GET['limit'] ) ? absint( $_GET['limit'] )          : (int) apply_filters( 'bp_autocomplete_max_results', 10 );
+	$term  = isset( $_GET['q'] )     ? sanitize_text_field( $_GET['q'] ) : '';
+
+	// Include everyone in the autocomplete, or just friends?
+	if ( bp_is_current_component( bp_get_messages_slug() ) ) {
+		$only_friends = ( buddypress()->messages->autocomplete_all === false );
+	} else {
+		$only_friends = true;
+	}
+
+	$suggestions = bp_core_get_suggestions( array(
+		'limit'        => $limit,
+		'only_friends' => $only_friends,
+		'term'         => $term,
+		'type'         => 'members',
+	) );
+
+	if ( $suggestions && ! is_wp_error( $suggestions ) ) {
+		foreach ( $suggestions as $user ) {
+
+			// Note that the final line break acts as a delimiter for the
+			// autocomplete JavaScript and thus should not be removed.
+			printf( '<span id="%s" href="#"></span><img src="%s" style="width: 15px"> &nbsp; %s (%s)' . "\n",
+				esc_attr( 'link-' . $user->ID ),
+				esc_url( $user->image ),
+				esc_html( $user->name ),
+				esc_html( $user->ID )
+			);
+		}
+	}
+
+	exit;
+}
+
+/**
+ * AJAX callback to set a message's star status.
+ *
+ * @since 2.3.0
+ */
+function bp_legacy_theme_ajax_messages_star_handler() {
+	if ( false === bp_is_active( 'messages', 'star' ) || empty( $_POST['message_id'] ) ) {
+		return;
+	}
+
+	// Check nonce.
+	check_ajax_referer( 'bp-messages-star-' . (int) $_POST['message_id'], 'nonce' );
+
+	// Check capability.
+	if ( ! is_user_logged_in() || ! bp_core_can_edit_settings() ) {
+		return;
+	}
+
+	if ( true === bp_messages_star_set_action( array(
+		'action'     => $_POST['star_status'],
+		'message_id' => (int) $_POST['message_id'],
+		'bulk'       => ! empty( $_POST['bulk'] ) ? true : false
+	 ) ) ) {
+		echo '1';
+		die();
+	}
+
+	echo '-1';
+	die();
+}
+
+/**
+ * BP Legacy's callback for the cover image feature.
+ *
+ * @since  2.4.0
+ *
+ * @param  array $params the current component's feature parameters.
+ * @return array          an array to inform about the css handle to attach the css rules to
+ */
+function bp_legacy_theme_cover_image( $params = array() ) {
+	if ( empty( $params ) ) {
+		return;
+	}
+
+	// Avatar height - padding - 1/2 avatar height.
+	$avatar_offset = $params['height'] - 5 - round( (int) bp_core_avatar_full_height() / 2 );
+
+	// Header content offset + spacing.
+	$top_offset  = bp_core_avatar_full_height() - 10;
+	$left_offset = bp_core_avatar_full_width() + 20;
+
+	$cover_image = isset( $params['cover_image'] ) ? 'background-image: url(' . $params['cover_image'] . ');' : '';
+
+	$hide_avatar_style = '';
+
+	// Adjust the cover image header, in case avatars are completely disabled.
+	if ( ! buddypress()->avatar->show_avatars ) {
+		$hide_avatar_style = '
+			#buddypress #item-header-cover-image #item-header-avatar {
+				display:  none;
+			}
+		';
+
+		if ( bp_is_user() ) {
+			$hide_avatar_style = '
+				#buddypress #item-header-cover-image #item-header-avatar a {
+					display: block;
+					height: ' . $top_offset . 'px;
+					margin: 0 15px 19px 0;
+				}
+
+				#buddypress div#item-header #item-header-cover-image #item-header-content {
+					margin-left:auto;
+				}
+			';
+		}
+	}
+
+	return '
+		/* Cover image */
+		#buddypress #header-cover-image {
+			height: ' . $params["height"] . 'px;
+			' . $cover_image . '
+		}
+
+		#buddypress #create-group-form #header-cover-image {
+			position: relative;
+			margin: 1em 0;
+		}
+
+		.bp-user #buddypress #item-header {
+			padding-top: 0;
+		}
+
+		#buddypress #item-header-cover-image #item-header-avatar {
+			margin-top: '. $avatar_offset .'px;
+			float: left;
+			overflow: visible;
+			width:auto;
+		}
+
+		#buddypress div#item-header #item-header-cover-image #item-header-content {
+			clear: both;
+			float: left;
+			margin-left: ' . $left_offset . 'px;
+			margin-top: -' . $top_offset . 'px;
+			width:auto;
+		}
+
+		body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-header-content,
+		body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
+			margin-top: ' . $params["height"] . 'px;
+			margin-left: 0;
+			clear: none;
+			max-width: 50%;
+		}
+
+		body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
+			padding-top: 20px;
+			max-width: 20%;
+		}
+
+		' . $hide_avatar_style . '
+
+		#buddypress div#item-header-cover-image h2 a,
+		#buddypress div#item-header-cover-image h2 {
+			color: #FFF;
+			text-rendering: optimizelegibility;
+			text-shadow: 0px 0px 3px rgba( 0, 0, 0, 0.8 );
+			margin: 0 0 .6em;
+			font-size:200%;
+		}
+
+		#buddypress #item-header-cover-image #item-header-avatar img.avatar {
+			border: solid 2px #FFF;
+			background: rgba( 255, 255, 255, 0.8 );
+		}
+
+		#buddypress #item-header-cover-image #item-header-avatar a {
+			border: none;
+			text-decoration: none;
+		}
+
+		#buddypress #item-header-cover-image #item-buttons {
+			margin: 0 0 10px;
+			padding: 0 0 5px;
+		}
+
+		#buddypress #item-header-cover-image #item-buttons:after {
+			clear: both;
+			content: "";
+			display: table;
+		}
+
+		@media screen and (max-width: 782px) {
+			#buddypress #item-header-cover-image #item-header-avatar,
+			.bp-user #buddypress #item-header #item-header-cover-image #item-header-avatar,
+			#buddypress div#item-header #item-header-cover-image #item-header-content {
+				width:100%;
+				text-align:center;
+			}
+
+			#buddypress #item-header-cover-image #item-header-avatar a {
+				display:inline-block;
+			}
+
+			#buddypress #item-header-cover-image #item-header-avatar img {
+				margin:0;
+			}
+
+			#buddypress div#item-header #item-header-cover-image #item-header-content,
+			body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-header-content,
+			body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
+				margin:0;
+			}
+
+			body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-header-content,
+			body.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
+				max-width: 100%;
+			}
+
+			#buddypress div#item-header-cover-image h2 a,
+			#buddypress div#item-header-cover-image h2 {
+				color: inherit;
+				text-shadow: none;
+				margin:25px 0 0;
+				font-size:200%;
+			}
+
+			#buddypress #item-header-cover-image #item-buttons div {
+				float:none;
+				display:inline-block;
+			}
+
+			#buddypress #item-header-cover-image #item-buttons:before {
+				content:"";
+			}
+
+			#buddypress #item-header-cover-image #item-buttons {
+				margin: 5px 0;
+			}
+		}
+	';
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/activity-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/activity-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/activity-loop.php	(revision 10410)
@@ -0,0 +1,69 @@
+<?php
+/**
+ * BuddyPress - Activity Loop
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the start of the activity loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_activity_loop' ); ?>
+
+<?php if ( bp_has_activities( bp_ajax_querystring( 'activity' ) ) ) : ?>
+
+	<?php if ( empty( $_POST['page'] ) ) : ?>
+
+		<ul id="activity-stream" class="activity-list item-list">
+
+	<?php endif; ?>
+
+	<?php while ( bp_activities() ) : bp_the_activity(); ?>
+
+		<?php bp_get_template_part( 'activity/entry' ); ?>
+
+	<?php endwhile; ?>
+
+	<?php if ( bp_activity_has_more_items() ) : ?>
+
+		<li class="load-more">
+			<a href="<?php bp_activity_load_more_link() ?>"><?php _e( 'Load More', 'buddypress' ); ?></a>
+		</li>
+
+	<?php endif; ?>
+
+	<?php if ( empty( $_POST['page'] ) ) : ?>
+
+		</ul>
+
+	<?php endif; ?>
+
+<?php else : ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'Sorry, there was no activity found. Please try a different filter.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the finish of the activity loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_activity_loop' ); ?>
+
+<?php if ( empty( $_POST['page'] ) ) : ?>
+
+	<form action="" name="activity-loop-form" id="activity-loop-form" method="post">
+
+		<?php wp_nonce_field( 'activity_filter', '_wpnonce_activity_filter' ); ?>
+
+	</form>
+
+<?php endif; ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/comment.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/comment.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/comment.php	(revision 10410)
@@ -0,0 +1,70 @@
+<?php
+/**
+ * BuddyPress - Activity Stream Comment
+ *
+ * This template is used by bp_activity_comments() functions to show
+ * each activity.
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of an activity comment.
+ *
+ * @since 1.5.0
+ */
+do_action( 'bp_before_activity_comment' ); ?>
+
+<li id="acomment-<?php bp_activity_comment_id(); ?>">
+	<div class="acomment-avatar">
+		<a href="<?php bp_activity_comment_user_link(); ?>">
+			<?php bp_activity_avatar( 'type=thumb&user_id=' . bp_get_activity_comment_user_id() ); ?>
+		</a>
+	</div>
+
+	<div class="acomment-meta">
+		<?php
+		/* translators: 1: user profile link, 2: user name, 3: activity permalink, 4: activity timestamp */
+		printf( __( '<a href="%1$s">%2$s</a> replied <a href="%3$s" class="activity-time-since"><span class="time-since">%4$s</span></a>', 'buddypress' ), bp_get_activity_comment_user_link(), bp_get_activity_comment_name(), bp_get_activity_comment_permalink(), bp_get_activity_comment_date_recorded() );
+		?>
+	</div>
+
+	<div class="acomment-content"><?php bp_activity_comment_content(); ?></div>
+
+	<div class="acomment-options">
+
+		<?php if ( is_user_logged_in() && bp_activity_can_comment_reply( bp_activity_current_comment() ) ) : ?>
+
+			<a href="#acomment-<?php bp_activity_comment_id(); ?>" class="acomment-reply bp-primary-action" id="acomment-reply-<?php bp_activity_id(); ?>-from-<?php bp_activity_comment_id(); ?>"><?php _e( 'Reply', 'buddypress' ); ?></a>
+
+		<?php endif; ?>
+
+		<?php if ( bp_activity_user_can_delete() ) : ?>
+
+			<a href="<?php bp_activity_comment_delete_link(); ?>" class="delete acomment-delete confirm bp-secondary-action" rel="nofollow"><?php _e( 'Delete', 'buddypress' ); ?></a>
+
+		<?php endif; ?>
+
+		<?php
+
+		/**
+		 * Fires after the defualt comment action options display.
+		 *
+		 * @since 1.6.0
+		 */
+		do_action( 'bp_activity_comment_options' ); ?>
+
+	</div>
+
+	<?php bp_activity_recurse_comments( bp_activity_current_comment() ); ?>
+</li>
+
+<?php
+
+/**
+ * Fires after the display of an activity comment.
+ *
+ * @since 1.5.0
+ */
+do_action( 'bp_after_activity_comment' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/entry.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/entry.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/entry.php	(revision 10410)
@@ -0,0 +1,167 @@
+<?php
+/**
+ * BuddyPress - Activity Stream (Single Item)
+ *
+ * This template is used by activity-loop.php and AJAX functions to show
+ * each activity.
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of an activity entry.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_activity_entry' ); ?>
+
+<li class="<?php bp_activity_css_class(); ?>" id="activity-<?php bp_activity_id(); ?>">
+	<div class="activity-avatar">
+		<a href="<?php bp_activity_user_link(); ?>">
+
+			<?php bp_activity_avatar(); ?>
+
+		</a>
+	</div>
+
+	<div class="activity-content">
+
+		<div class="activity-header">
+
+			<?php bp_activity_action(); ?>
+
+		</div>
+
+		<?php if ( bp_activity_has_content() ) : ?>
+
+			<div class="activity-inner">
+
+				<?php bp_activity_content_body(); ?>
+
+			</div>
+
+		<?php endif; ?>
+
+		<?php
+
+		/**
+		 * Fires after the display of an activity entry content.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_activity_entry_content' ); ?>
+
+		<div class="activity-meta">
+
+			<?php if ( bp_get_activity_type() == 'activity_comment' ) : ?>
+
+				<a href="<?php bp_activity_thread_permalink(); ?>" class="button view bp-secondary-action" title="<?php esc_attr_e( 'View Conversation', 'buddypress' ); ?>"><?php _e( 'View Conversation', 'buddypress' ); ?></a>
+
+			<?php endif; ?>
+
+			<?php if ( is_user_logged_in() ) : ?>
+
+				<?php if ( bp_activity_can_comment() ) : ?>
+
+					<a href="<?php bp_activity_comment_link(); ?>" class="button acomment-reply bp-primary-action" id="acomment-comment-<?php bp_activity_id(); ?>"><?php printf( __( 'Comment %s', 'buddypress' ), '<span>' . bp_activity_get_comment_count() . '</span>' ); ?></a>
+
+				<?php endif; ?>
+
+				<?php if ( bp_activity_can_favorite() ) : ?>
+
+					<?php if ( !bp_get_activity_is_favorite() ) : ?>
+
+						<a href="<?php bp_activity_favorite_link(); ?>" class="button fav bp-secondary-action" title="<?php esc_attr_e( 'Mark as Favorite', 'buddypress' ); ?>"><?php _e( 'Favorite', 'buddypress' ); ?></a>
+
+					<?php else : ?>
+
+						<a href="<?php bp_activity_unfavorite_link(); ?>" class="button unfav bp-secondary-action" title="<?php esc_attr_e( 'Remove Favorite', 'buddypress' ); ?>"><?php _e( 'Remove Favorite', 'buddypress' ); ?></a>
+
+					<?php endif; ?>
+
+				<?php endif; ?>
+
+				<?php if ( bp_activity_user_can_delete() ) bp_activity_delete_link(); ?>
+
+				<?php
+
+				/**
+				 * Fires at the end of the activity entry meta data area.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_activity_entry_meta' ); ?>
+
+			<?php endif; ?>
+
+		</div>
+
+	</div>
+
+	<?php
+
+	/**
+	 * Fires before the display of the activity entry comments.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_before_activity_entry_comments' ); ?>
+
+	<?php if ( ( bp_activity_get_comment_count() || bp_activity_can_comment() ) || bp_is_single_activity() ) : ?>
+
+		<div class="activity-comments">
+
+			<?php bp_activity_comments(); ?>
+
+			<?php if ( is_user_logged_in() && bp_activity_can_comment() ) : ?>
+
+				<form action="<?php bp_activity_comment_form_action(); ?>" method="post" id="ac-form-<?php bp_activity_id(); ?>" class="ac-form"<?php bp_activity_comment_form_nojs_display(); ?>>
+					<div class="ac-reply-avatar"><?php bp_loggedin_user_avatar( 'width=' . BP_AVATAR_THUMB_WIDTH . '&height=' . BP_AVATAR_THUMB_HEIGHT ); ?></div>
+					<div class="ac-reply-content">
+						<div class="ac-textarea">
+							<label for="ac-input-<?php bp_activity_id(); ?>" class="bp-screen-reader-text"><?php _e( 'Comment', 'buddypress' ); ?></label>
+							<textarea id="ac-input-<?php bp_activity_id(); ?>" class="ac-input bp-suggestions" name="ac_input_<?php bp_activity_id(); ?>"></textarea>
+						</div>
+						<input type="submit" name="ac_form_submit" value="<?php esc_attr_e( 'Post', 'buddypress' ); ?>" /> &nbsp; <a href="#" class="ac-reply-cancel"><?php _e( 'Cancel', 'buddypress' ); ?></a>
+						<input type="hidden" name="comment_form_id" value="<?php bp_activity_id(); ?>" />
+					</div>
+
+					<?php
+
+					/**
+					 * Fires after the activity entry comment form.
+					 *
+					 * @since 1.5.0
+					 */
+					do_action( 'bp_activity_entry_comments' ); ?>
+
+					<?php wp_nonce_field( 'new_activity_comment', '_wpnonce_new_activity_comment' ); ?>
+
+				</form>
+
+			<?php endif; ?>
+
+		</div>
+
+	<?php endif; ?>
+
+	<?php
+
+	/**
+	 * Fires after the display of the activity entry comments.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_after_activity_entry_comments' ); ?>
+
+</li>
+
+<?php
+
+/**
+ * Fires after the display of an activity entry.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_activity_entry' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/index.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/index.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/index.php	(revision 10410)
@@ -0,0 +1,223 @@
+<?php
+/**
+ * BuddyPress Activity templates
+ *
+ * @since 2.3.0
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the activity directory listing.
+ *
+ * @since 1.5.0
+ */
+do_action( 'bp_before_directory_activity' ); ?>
+
+<div id="buddypress">
+
+	<?php
+
+	/**
+	 * Fires before the activity directory display content.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_before_directory_activity_content' ); ?>
+
+	<?php if ( is_user_logged_in() ) : ?>
+
+		<?php bp_get_template_part( 'activity/post-form' ); ?>
+
+	<?php endif; ?>
+
+	<?php
+
+	/**
+	 * Fires towards the top of template pages for notice display.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'template_notices' ); ?>
+
+	<div class="item-list-tabs activity-type-tabs" role="navigation">
+		<ul>
+			<?php
+
+			/**
+			 * Fires before the listing of activity type tabs.
+			 *
+			 * @since 1.2.0
+			 */
+			do_action( 'bp_before_activity_type_tab_all' ); ?>
+
+			<li class="selected" id="activity-all"><a href="<?php bp_activity_directory_permalink(); ?>" title="<?php esc_attr_e( 'The public activity for everyone on this site.', 'buddypress' ); ?>"><?php printf( __( 'All Members %s', 'buddypress' ), '<span>' . bp_get_total_member_count() . '</span>' ); ?></a></li>
+
+			<?php if ( is_user_logged_in() ) : ?>
+
+				<?php
+
+				/**
+				 * Fires before the listing of friends activity type tab.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_before_activity_type_tab_friends' ); ?>
+
+				<?php if ( bp_is_active( 'friends' ) ) : ?>
+
+					<?php if ( bp_get_total_friend_count( bp_loggedin_user_id() ) ) : ?>
+
+						<li id="activity-friends"><a href="<?php echo bp_loggedin_user_domain() . bp_get_activity_slug() . '/' . bp_get_friends_slug() . '/'; ?>" title="<?php esc_attr_e( 'The activity of my friends only.', 'buddypress' ); ?>"><?php printf( __( 'My Friends %s', 'buddypress' ), '<span>' . bp_get_total_friend_count( bp_loggedin_user_id() ) . '</span>' ); ?></a></li>
+
+					<?php endif; ?>
+
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires before the listing of groups activity type tab.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_before_activity_type_tab_groups' ); ?>
+
+				<?php if ( bp_is_active( 'groups' ) ) : ?>
+
+					<?php if ( bp_get_total_group_count_for_user( bp_loggedin_user_id() ) ) : ?>
+
+						<li id="activity-groups"><a href="<?php echo bp_loggedin_user_domain() . bp_get_activity_slug() . '/' . bp_get_groups_slug() . '/'; ?>" title="<?php esc_attr_e( 'The activity of groups I am a member of.', 'buddypress' ); ?>"><?php printf( __( 'My Groups %s', 'buddypress' ), '<span>' . bp_get_total_group_count_for_user( bp_loggedin_user_id() ) . '</span>' ); ?></a></li>
+
+					<?php endif; ?>
+
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires before the listing of favorites activity type tab.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_before_activity_type_tab_favorites' ); ?>
+
+				<?php if ( bp_get_total_favorite_count_for_user( bp_loggedin_user_id() ) ) : ?>
+
+					<li id="activity-favorites"><a href="<?php echo bp_loggedin_user_domain() . bp_get_activity_slug() . '/favorites/'; ?>" title="<?php esc_attr_e( "The activity I've marked as a favorite.", 'buddypress' ); ?>"><?php printf( __( 'My Favorites %s', 'buddypress' ), '<span>' . bp_get_total_favorite_count_for_user( bp_loggedin_user_id() ) . '</span>' ); ?></a></li>
+
+				<?php endif; ?>
+
+				<?php if ( bp_activity_do_mentions() ) : ?>
+
+					<?php
+
+					/**
+					 * Fires before the listing of mentions activity type tab.
+					 *
+					 * @since 1.2.0
+					 */
+					do_action( 'bp_before_activity_type_tab_mentions' ); ?>
+
+					<li id="activity-mentions"><a href="<?php echo bp_loggedin_user_domain() . bp_get_activity_slug() . '/mentions/'; ?>" title="<?php esc_attr_e( 'Activity that I have been mentioned in.', 'buddypress' ); ?>"><?php _e( 'Mentions', 'buddypress' ); ?><?php if ( bp_get_total_mention_count_for_user( bp_loggedin_user_id() ) ) : ?> <strong><span><?php printf( _nx( '%s new', '%s new', bp_get_total_mention_count_for_user( bp_loggedin_user_id() ), 'Number of new activity mentions', 'buddypress' ), bp_get_total_mention_count_for_user( bp_loggedin_user_id() ) ); ?></span></strong><?php endif; ?></a></li>
+
+				<?php endif; ?>
+
+			<?php endif; ?>
+
+			<?php
+
+			/**
+			 * Fires after the listing of activity type tabs.
+			 *
+			 * @since 1.2.0
+			 */
+			do_action( 'bp_activity_type_tabs' ); ?>
+		</ul>
+	</div><!-- .item-list-tabs -->
+
+	<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+		<ul>
+			<li class="feed"><a href="<?php bp_sitewide_activity_feed_link(); ?>" title="<?php esc_attr_e( 'RSS Feed', 'buddypress' ); ?>"><?php _e( 'RSS', 'buddypress' ); ?></a></li>
+
+			<?php
+
+			/**
+			 * Fires before the display of the activity syndication options.
+			 *
+			 * @since 1.2.0
+			 */
+			do_action( 'bp_activity_syndication_options' ); ?>
+
+			<li id="activity-filter-select" class="last">
+				<label for="activity-filter-by"><?php _e( 'Show:', 'buddypress' ); ?></label>
+				<select id="activity-filter-by">
+					<option value="-1"><?php _e( '&mdash; Everything &mdash;', 'buddypress' ); ?></option>
+
+					<?php bp_activity_show_filters(); ?>
+
+					<?php
+
+					/**
+					 * Fires inside the select input for activity filter by options.
+					 *
+					 * @since 1.2.0
+					 */
+					do_action( 'bp_activity_filter_options' ); ?>
+
+				</select>
+			</li>
+		</ul>
+	</div><!-- .item-list-tabs -->
+
+	<?php
+
+	/**
+	 * Fires before the display of the activity list.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_before_directory_activity_list' ); ?>
+
+	<div class="activity">
+
+		<?php bp_get_template_part( 'activity/activity-loop' ); ?>
+
+	</div><!-- .activity -->
+
+	<?php
+
+	/**
+	 * Fires after the display of the activity list.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_after_directory_activity_list' ); ?>
+
+	<?php
+
+	/**
+	 * Fires inside and displays the activity directory display content.
+	 */
+	do_action( 'bp_directory_activity_content' ); ?>
+
+	<?php
+
+	/**
+	 * Fires after the activity directory display content.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_after_directory_activity_content' ); ?>
+
+	<?php
+
+	/**
+	 * Fires after the activity directory listing.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_after_directory_activity' ); ?>
+
+</div>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/post-form.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/post-form.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/post-form.php	(revision 10410)
@@ -0,0 +1,98 @@
+<?php
+/**
+ * BuddyPress - Activity Post Form
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<form action="<?php bp_activity_post_form_action(); ?>" method="post" id="whats-new-form" name="whats-new-form" role="complementary">
+
+	<?php
+
+	/**
+	 * Fires before the activity post form.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_before_activity_post_form' ); ?>
+
+	<div id="whats-new-avatar">
+		<a href="<?php echo bp_loggedin_user_domain(); ?>">
+			<?php bp_loggedin_user_avatar( 'width=' . bp_core_avatar_thumb_width() . '&height=' . bp_core_avatar_thumb_height() ); ?>
+		</a>
+	</div>
+
+	<p class="activity-greeting"><?php if ( bp_is_group() )
+		printf( __( "What's new in %s, %s?", 'buddypress' ), bp_get_group_name(), bp_get_user_firstname( bp_get_loggedin_user_fullname() ) );
+	else
+		printf( __( "What's new, %s?", 'buddypress' ), bp_get_user_firstname( bp_get_loggedin_user_fullname() ) );
+	?></p>
+
+	<div id="whats-new-content">
+		<div id="whats-new-textarea">
+			<label for="whats-new" class="bp-screen-reader-text"><?php _e( 'Post what\'s new', 'buddypress' ); ?></label>
+			<textarea class="bp-suggestions" name="whats-new" id="whats-new" cols="50" rows="10"
+				<?php if ( bp_is_group() ) : ?>data-suggestions-group-id="<?php echo esc_attr( (int) bp_get_current_group_id() ); ?>" <?php endif; ?>
+			><?php if ( isset( $_GET['r'] ) ) : ?>@<?php echo esc_textarea( $_GET['r'] ); ?> <?php endif; ?></textarea>
+		</div>
+
+		<div id="whats-new-options">
+			<div id="whats-new-submit">
+				<input type="submit" name="aw-whats-new-submit" id="aw-whats-new-submit" value="<?php esc_attr_e( 'Post Update', 'buddypress' ); ?>" />
+			</div>
+
+			<?php if ( bp_is_active( 'groups' ) && !bp_is_my_profile() && !bp_is_group() ) : ?>
+
+				<div id="whats-new-post-in-box">
+
+					<?php _e( 'Post in', 'buddypress' ); ?>:
+
+					<label for="whats-new-post-in" class="bp-screen-reader-text"><?php _e( 'Post in', 'buddypress' ); ?></label>
+					<select id="whats-new-post-in" name="whats-new-post-in">
+						<option selected="selected" value="0"><?php _e( 'My Profile', 'buddypress' ); ?></option>
+
+						<?php if ( bp_has_groups( 'user_id=' . bp_loggedin_user_id() . '&type=alphabetical&max=100&per_page=100&populate_extras=0&update_meta_cache=0' ) ) :
+							while ( bp_groups() ) : bp_the_group(); ?>
+
+								<option value="<?php bp_group_id(); ?>"><?php bp_group_name(); ?></option>
+
+							<?php endwhile;
+						endif; ?>
+
+					</select>
+				</div>
+				<input type="hidden" id="whats-new-post-object" name="whats-new-post-object" value="groups" />
+
+			<?php elseif ( bp_is_group_activity() ) : ?>
+
+				<input type="hidden" id="whats-new-post-object" name="whats-new-post-object" value="groups" />
+				<input type="hidden" id="whats-new-post-in" name="whats-new-post-in" value="<?php bp_group_id(); ?>" />
+
+			<?php endif; ?>
+
+			<?php
+
+			/**
+			 * Fires at the end of the activity post form markup.
+			 *
+			 * @since 1.2.0
+			 */
+			do_action( 'bp_activity_post_form_options' ); ?>
+
+		</div><!-- #whats-new-options -->
+	</div><!-- #whats-new-content -->
+
+	<?php wp_nonce_field( 'post_update', '_wpnonce_post_update' ); ?>
+	<?php
+
+	/**
+	 * Fires after the activity post form.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_after_activity_post_form' ); ?>
+
+</form><!-- #whats-new-form -->
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/single/home.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/single/home.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/activity/single/home.php	(revision 10410)
@@ -0,0 +1,29 @@
+<?php
+/**
+ * BuddyPress - Home
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+<div id="buddypress">
+	<?php
+
+	/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+	do_action( 'template_notices' ); ?>
+
+	<div class="activity no-ajax">
+		<?php if ( bp_has_activities( 'display_comments=threaded&show_hidden=true&include=' . bp_current_action() ) ) : ?>
+
+			<ul id="activity-stream" class="activity-list item-list">
+			<?php while ( bp_activities() ) : bp_the_activity(); ?>
+
+				<?php bp_get_template_part( 'activity/entry' ); ?>
+
+			<?php endwhile; ?>
+			</ul>
+
+		<?php endif; ?>
+	</div>
+</div>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/avatars/camera.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/avatars/camera.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/avatars/camera.php	(revision 10410)
@@ -0,0 +1,29 @@
+<?php
+/**
+ * BuddyPress Avatars camera template.
+ *
+ * This template is used to create the camera Backbone views.
+ *
+ * @since 2.3.0
+ *
+ * @package BuddyPress
+ * @subpackage bp-attachments
+ */
+
+?>
+<script id="tmpl-bp-avatar-webcam" type="text/html">
+	<# if ( ! data.user_media ) { #>
+		<div id="bp-webcam-message">
+			<p class="warning"><?php esc_html_e( 'Your browser does not support this feature.', 'buddypress' );?></p>
+		</div>
+	<# } else { #>
+		<div id="avatar-to-crop"></div>
+		<div class="avatar-crop-management">
+			<div id="avatar-crop-pane" class="avatar" style="width:{{data.w}}px; height:{{data.h}}px"></div>
+			<div id="avatar-crop-actions">
+				<a class="button avatar-webcam-capture" href="#"><?php esc_html_e( 'Capture', 'buddypress' );?></a>
+				<a class="button avatar-webcam-save" href="#"><?php esc_html_e( 'Save', 'buddypress' );?></a>
+			</div>
+		</div>
+	<# } #>
+</script>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/avatars/crop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/avatars/crop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/avatars/crop.php	(revision 10410)
@@ -0,0 +1,26 @@
+<?php
+/**
+ * BuddyPress Avatars crop template.
+ *
+ * This template is used to create the crop Backbone views.
+ *
+ * @since 2.3.0
+ *
+ * @package BuddyPress
+ * @subpackage bp-attachments
+ */
+
+?>
+<script id="tmpl-bp-avatar-item" type="text/html">
+	<div id="avatar-to-crop">
+		<img src="{{data.url}}"/>
+	</div>
+	<div class="avatar-crop-management">
+		<div id="avatar-crop-pane" class="avatar" style="width:{{data.full_w}}px; height:{{data.full_h}}px">
+			<img src="{{data.url}}" id="avatar-crop-preview"/>
+		</div>
+		<div id="avatar-crop-actions">
+			<a class="button avatar-crop-submit" href="#"><?php esc_html_e( 'Crop Image', 'buddypress' ); ?></a>
+		</div>
+	</div>
+</script>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/avatars/index.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/avatars/index.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/avatars/index.php	(revision 10410)
@@ -0,0 +1,47 @@
+<?php
+/**
+ * BuddyPress Avatars main template.
+ *
+ * This template is used to inject the BuddyPress Backbone views
+ * dealing with avatars.
+ *
+ * It's also used to create the common Backbone views.
+ *
+ * @since 2.3.0
+ *
+ * @package BuddyPress
+ * @subpackage bp-attachments
+ */
+
+/**
+ * This action is for internal use, please do not use it
+ */
+do_action( 'bp_attachments_avatar_check_template' );
+?>
+<div class="bp-avatar-nav"></div>
+<div class="bp-avatar"></div>
+<div class="bp-avatar-status"></div>
+
+<script type="text/html" id="tmpl-bp-avatar-nav">
+	<a href="{{data.href}}" class="bp-avatar-nav-item" data-nav="{{data.id}}">{{data.name}}</a>
+</script>
+
+<?php bp_attachments_get_template_part( 'uploader' ); ?>
+
+<?php bp_attachments_get_template_part( 'avatars/crop' ); ?>
+
+<?php bp_attachments_get_template_part( 'avatars/camera' ); ?>
+
+<script id="tmpl-bp-avatar-delete" type="text/html">
+	<# if ( 'user' === data.object ) { #>
+		<p><?php _e( "If you'd like to delete your current profile photo but not upload a new one, please use the delete profile photo button.", 'buddypress' ); ?></p>
+		<p><a class="button edit" id="bp-delete-avatar" href="#" title="<?php esc_attr_e( 'Delete Profile Photo', 'buddypress' ); ?>"><?php esc_html_e( 'Delete My Profile Photo', 'buddypress' ); ?></a></p>
+	<# } else if ( 'group' === data.object ) { #>
+		<p><?php _e( "If you'd like to remove the existing group profile photo but not upload a new one, please use the delete group profile photo button.", 'buddypress' ); ?></p>
+		<p><a class="button edit" id="bp-delete-avatar" href="#" title="<?php esc_attr_e( 'Delete Group Profile Photo', 'buddypress' ); ?>"><?php esc_html_e( 'Delete Group Profile Photo', 'buddypress' ); ?></a></p>
+	<# } else { #>
+		<?php do_action( 'bp_attachments_avatar_delete_template' ); ?>
+	<# } #>
+</script>
+
+<?php do_action( 'bp_attachments_avatar_main_template' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/cover-images/index.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/cover-images/index.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/cover-images/index.php	(revision 10410)
@@ -0,0 +1,36 @@
+<?php
+/**
+ * BuddyPress Cover Images main template.
+ *
+ * This template is used to inject the BuddyPress Backbone views
+ * dealing with cover images.
+ *
+ * It's also used to create the common Backbone views.
+ *
+ * @since 2.4.0
+ *
+ * @package BuddyPress
+ * @subpackage bp-attachments
+ */
+
+?>
+
+<div class="bp-cover-image"></div>
+<div class="bp-cover-image-status"></div>
+<div class="bp-cover-image-manage"></div>
+
+<?php bp_attachments_get_template_part( 'uploader' ); ?>
+
+<script id="tmpl-bp-cover-image-delete" type="text/html">
+	<# if ( 'user' === data.object ) { #>
+		<p><?php _e( "If you'd like to delete your current cover image but not upload a new one, please use the delete Cover Image button.", 'buddypress' ); ?></p>
+		<p><a class="button edit" id="bp-delete-cover-image" href="#" title="<?php esc_attr_e( 'Delete Cover Image', 'buddypress' ); ?>"><?php esc_html_e( 'Delete My Cover Image', 'buddypress' ); ?></a></p>
+	<# } else if ( 'group' === data.object ) { #>
+		<p><?php _e( "If you'd like to remove the existing group cover image but not upload a new one, please use the delete group cover image button.", 'buddypress' ); ?></p>
+		<p><a class="button edit" id="bp-delete-cover-image" href="#" title="<?php esc_attr_e( 'Delete Cover Image', 'buddypress' ); ?>"><?php esc_html_e( 'Delete Group Cover Image', 'buddypress' ); ?></a></p>
+	<# } else { #>
+		<?php do_action( 'bp_attachments_cover_image_delete_template' ); ?>
+	<# } #>
+</script>
+
+<?php do_action( 'bp_attachments_cover_image_main_template' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/uploader.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/uploader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/assets/_attachments/uploader.php	(revision 10410)
@@ -0,0 +1,39 @@
+<?php
+/**
+ * BuddyPress Uploader templates.
+ *
+ * This template is used to create the BuddyPress Uploader Backbone views.
+ *
+ * @since 2.3.0
+ *
+ * @package BuddyPress
+ * @subpackage bp-attachments
+ */
+
+?>
+<script type="text/html" id="tmpl-upload-window">
+	<?php if ( ! _device_can_upload() ) : ?>
+		<h3 class="upload-instructions"><?php esc_html_e( 'The web browser on your device cannot be used to upload files.', 'buddypress' ); ?></h3>
+	<?php elseif ( is_multisite() && ! is_upload_space_available() ) : ?>
+		<h3 class="upload-instructions"><?php esc_html_e( 'Upload Limit Exceeded', 'buddypress' ); ?></h3>
+	<?php else : ?>
+		<div id="{{data.container}}">
+			<div id="{{data.drop_element}}">
+				<div class="drag-drop-inside">
+					<p class="drag-drop-info"><?php esc_html_e( 'Drop your file here', 'buddypress' ); ?></p>
+					<p><?php _ex( 'or', 'Uploader: Drop your file here - or - Select your File', 'buddypress' ); ?></p>
+					<p class="drag-drop-buttons"><label for="{{data.browse_button}}" class="<?php echo is_admin() ? 'screen-reader-text' : 'bp-screen-reader-text' ;?>"><?php esc_html_e( 'Select your File', 'buddypress' ); ?></label><input id="{{data.browse_button}}" type="button" value="<?php esc_attr_e( 'Select your File', 'buddypress' ); ?>" class="button" /></p>
+				</div>
+			</div>
+		</div>
+	<?php endif; ?>
+</script>
+
+<script type="text/html" id="tmpl-progress-window">
+	<div id="{{data.id}}">
+		<div class="bp-progress">
+			<div class="bp-bar"></div>
+		</div>
+		<div class="filename">{{data.filename}}</div>
+	</div>
+</script>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/blogs/blogs-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/blogs/blogs-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/blogs/blogs-loop.php	(revision 10410)
@@ -0,0 +1,132 @@
+<?php
+/**
+ * BuddyPress - Blogs Loop
+ *
+ * Querystring is set via AJAX in _inc/ajax.php - bp_legacy_theme_object_filter().
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the start of the blogs loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_blogs_loop' ); ?>
+
+<?php if ( bp_has_blogs( bp_ajax_querystring( 'blogs' ) ) ) : ?>
+
+	<div id="pag-top" class="pagination">
+
+		<div class="pag-count" id="blog-dir-count-top">
+			<?php bp_blogs_pagination_count(); ?>
+		</div>
+
+		<div class="pagination-links" id="blog-dir-pag-top">
+			<?php bp_blogs_pagination_links(); ?>
+		</div>
+
+	</div>
+
+	<?php
+
+	/**
+	 * Fires before the blogs directory list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_directory_blogs_list' ); ?>
+
+	<ul id="blogs-list" class="item-list">
+
+	<?php while ( bp_blogs() ) : bp_the_blog(); ?>
+
+		<li <?php bp_blog_class() ?>>
+			<div class="item-avatar">
+				<a href="<?php bp_blog_permalink(); ?>"><?php bp_blog_avatar( 'type=thumb' ); ?></a>
+			</div>
+
+			<div class="item">
+				<div class="item-title"><a href="<?php bp_blog_permalink(); ?>"><?php bp_blog_name(); ?></a></div>
+				<div class="item-meta"><span class="activity"><?php bp_blog_last_active(); ?></span></div>
+
+				<?php
+
+				/**
+				 * Fires after the listing of a blog item in the blogs loop.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_directory_blogs_item' ); ?>
+			</div>
+
+			<div class="action">
+
+				<?php
+
+				/**
+				 * Fires inside the blogs action listing area.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_directory_blogs_actions' ); ?>
+
+				<div class="meta">
+
+					<?php bp_blog_latest_post(); ?>
+
+				</div>
+
+			</div>
+
+			<div class="clear"></div>
+		</li>
+
+	<?php endwhile; ?>
+
+	</ul>
+
+	<?php
+
+	/**
+	 * Fires after the blogs directory list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_directory_blogs_list' ); ?>
+
+	<?php bp_blog_hidden_fields(); ?>
+
+	<div id="pag-bottom" class="pagination">
+
+		<div class="pag-count" id="blog-dir-count-bottom">
+
+			<?php bp_blogs_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="blog-dir-pag-bottom">
+
+			<?php bp_blogs_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'Sorry, there were no sites found.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the display of the blogs loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_blogs_loop' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/blogs/create.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/blogs/create.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/blogs/create.php	(revision 10410)
@@ -0,0 +1,58 @@
+<?php
+/**
+ * BuddyPress - Blogs Create
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires at the top of the blog creation template file.
+ *
+ * @since 1.6.0
+ */
+do_action( 'bp_before_create_blog_content_template' ); ?>
+
+<?php
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+do_action( 'template_notices' ); ?>
+
+<?php
+
+/**
+ * Fires before the display of the blog creation form.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_before_create_blog_content' ); ?>
+
+<?php if ( bp_blog_signup_enabled() ) : ?>
+
+	<?php bp_show_blog_signup_form(); ?>
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'Site registration is currently disabled', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the display of the blog creation form.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_after_create_blog_content' ); ?>
+
+<?php
+
+/**
+ * Fires at the bottom of the blog creation template file.
+ *
+ * @since 1.6.0
+ */
+do_action( 'bp_after_create_blog_content_template' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/blogs/index.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/blogs/index.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/blogs/index.php	(revision 10410)
@@ -0,0 +1,153 @@
+<?php
+/**
+ * BuddyPress - Blogs
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires at the top of the blogs directory template file.
+ *
+ * @since 2.3.0
+ */
+do_action( 'bp_before_directory_blogs_page' ); ?>
+
+<div id="buddypress">
+
+	<?php
+
+	/**
+	 * Fires before the display of the blogs.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_before_directory_blogs' ); ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of the blogs listing content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_directory_blogs_content' ); ?>
+
+	<div id="blog-dir-search" class="dir-search" role="search">
+		<?php bp_directory_blogs_search_form(); ?>
+	</div><!-- #blog-dir-search -->
+
+	<?php
+
+	/**
+	 * Fires before the display of the blogs list tabs.
+	 *
+	 * @since 2.3.0
+	 */
+	do_action( 'bp_before_directory_blogs_tabs' ); ?>
+
+	<form action="" method="post" id="blogs-directory-form" class="dir-form">
+
+		<div class="item-list-tabs" role="navigation">
+			<ul>
+				<li class="selected" id="blogs-all"><a href="<?php bp_root_domain(); ?>/<?php bp_blogs_root_slug(); ?>"><?php printf( __( 'All Sites %s', 'buddypress' ), '<span>' . bp_get_total_blog_count() . '</span>' ); ?></a></li>
+
+				<?php if ( is_user_logged_in() && bp_get_total_blog_count_for_user( bp_loggedin_user_id() ) ) : ?>
+
+					<li id="blogs-personal"><a href="<?php echo bp_loggedin_user_domain() . bp_get_blogs_slug(); ?>"><?php printf( __( 'My Sites %s', 'buddypress' ), '<span>' . bp_get_total_blog_count_for_user( bp_loggedin_user_id() ) . '</span>' ); ?></a></li>
+
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires inside the unordered list displaying blog types.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_blogs_directory_blog_types' ); ?>
+
+			</ul>
+		</div><!-- .item-list-tabs -->
+
+		<div class="item-list-tabs" id="subnav" role="navigation">
+			<ul>
+
+				<?php
+
+				/**
+				 * Fires inside the unordered list displaying blog sub-types.
+				 *
+				 * @since 1.5.0
+				 */
+				do_action( 'bp_blogs_directory_blog_sub_types' ); ?>
+
+				<li id="blogs-order-select" class="last filter">
+
+					<label for="blogs-order-by"><?php _e( 'Order By:', 'buddypress' ); ?></label>
+					<select id="blogs-order-by">
+						<option value="active"><?php _e( 'Last Active', 'buddypress' ); ?></option>
+						<option value="newest"><?php _e( 'Newest', 'buddypress' ); ?></option>
+						<option value="alphabetical"><?php _e( 'Alphabetical', 'buddypress' ); ?></option>
+
+						<?php
+
+						/**
+						 * Fires inside the select input listing blogs orderby options.
+						 *
+						 * @since 1.2.0
+						 */
+						do_action( 'bp_blogs_directory_order_options' ); ?>
+
+					</select>
+				</li>
+			</ul>
+		</div>
+
+		<div id="blogs-dir-list" class="blogs dir-list">
+
+			<?php bp_get_template_part( 'blogs/blogs-loop' ); ?>
+
+		</div><!-- #blogs-dir-list -->
+
+		<?php
+
+		/**
+		 * Fires inside and displays the blogs content.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_directory_blogs_content' ); ?>
+
+		<?php wp_nonce_field( 'directory_blogs', '_wpnonce-blogs-filter' ); ?>
+
+		<?php
+
+		/**
+		 * Fires after the display of the blogs listing content.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_after_directory_blogs_content' ); ?>
+
+	</form><!-- #blogs-directory-form -->
+
+	<?php
+
+	/**
+	 * Fires at the bottom of the blogs directory template file.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_after_directory_blogs' ); ?>
+
+</div>
+
+<?php
+
+/**
+ * Fires at the bottom of the blogs directory template file.
+ *
+ * @since 2.3.0
+ */
+do_action( 'bp_after_directory_blogs_page' );
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/forums/forums-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/forums/forums-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/forums/forums-loop.php	(revision 10410)
@@ -0,0 +1,175 @@
+<?php
+/**
+ * BuddyPress - Forums Loop
+ *
+ * Querystring is set via AJAX in _inc/ajax.php - bp_legacy_theme_object_filter().
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<?php
+
+/**
+ * Fires at the start of the forums loop.
+ *
+ * @since 1.2.6
+ */
+do_action( 'bp_before_forums_loop' ); ?>
+
+<?php if ( bp_has_forum_topics( bp_ajax_querystring( 'forums' ) ) ) : ?>
+
+	<div id="pag-top" class="pagination">
+
+		<div class="pag-count" id="topic-count-top">
+
+			<?php bp_forum_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="topic-pag-top">
+
+			<?php bp_forum_pagination(); ?>
+
+		</div>
+
+	</div>
+
+	<?php
+
+	/**
+	 * Fires before the display of the forums list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_directory_forums_list' ); ?>
+
+	<table class="forum">
+		<thead>
+			<tr>
+				<th id="th-title"><?php _e( 'Topic', 'buddypress' ); ?></th>
+				<th id="th-postcount"><?php _e( 'Posts', 'buddypress' ); ?></th>
+				<th id="th-freshness"><?php _e( 'Freshness', 'buddypress' ); ?></th>
+
+				<?php
+
+				/**
+				 * Fires at the end of <tr> row holding the <th> tags.
+				 *
+				 * @since 1.2.4
+				 */
+				do_action( 'bp_directory_forums_extra_cell_head' ); ?>
+
+			</tr>
+		</thead>
+
+		<tbody>
+
+			<?php while ( bp_forum_topics() ) : bp_the_forum_topic(); ?>
+
+			<tr class="<?php bp_the_topic_css_class(); ?>">
+				<td class="td-title">
+					<a class="topic-title" href="<?php bp_the_topic_permalink(); ?>" title="<?php esc_attr_e( 'Permanent link to this post', 'buddypress' ); ?>">
+
+						<?php bp_the_topic_title(); ?>
+
+					</a>
+
+					<p class="topic-meta">
+						<span class="topic-by"><?php /* translators: "started by [poster] in [forum]" */ printf( __( 'Started by %1$s', 'buddypress' ), bp_get_the_topic_poster_avatar( 'height=20&width=20') . bp_get_the_topic_poster_name() ); ?></span>
+
+						<?php if ( !bp_is_group_forum() ) : ?>
+
+							<span class="topic-in">
+
+								<?php
+									$topic_in = '<a href="' . bp_get_the_topic_object_permalink() . '">' . bp_get_the_topic_object_avatar( 'type=thumb&width=20&height=20' ) . '</a>' .
+													'<a href="' . bp_get_the_topic_object_permalink() . '" title="' . bp_get_the_topic_object_name() . '">' . bp_get_the_topic_object_name() .'</a>';
+
+									/* translators: "started by [poster] in [forum]" */
+									printf( __( 'in %1$s', 'buddypress' ), $topic_in );
+								?>
+
+							</span>
+
+						<?php endif; ?>
+
+					</p>
+				</td>
+				<td class="td-postcount">
+					<?php bp_the_topic_total_posts(); ?>
+				</td>
+				<td class="td-freshness">
+					<span class="time-since"><?php bp_the_topic_time_since_last_post(); ?></span>
+					<p class="topic-meta">
+						<span class="freshness-author">
+							<a href="<?php bp_the_topic_permalink(); ?>"><?php bp_the_topic_last_poster_avatar( 'type=thumb&width=20&height=20' ); ?></a>
+							<?php bp_the_topic_last_poster_name(); ?>
+						</span>
+					</p>
+				</td>
+
+				<?php
+
+				/**
+				 * Fires at the end of <tr> row holding the <td> tags.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_directory_forums_extra_cell' ); ?>
+
+			</tr>
+
+			<?php
+
+				/**
+				 * Fires after the <tr> for a forum listing display.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_directory_forums_extra_row' ); ?>
+
+			<?php endwhile; ?>
+
+		</tbody>
+	</table>
+
+	<?php
+
+	/**
+	 * Fires after the display of the forums list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_directory_forums_list' ); ?>
+
+	<div id="pag-bottom" class="pagination">
+
+		<div class="pag-count" id="topic-count-bottom">
+			<?php bp_forum_pagination_count(); ?>
+		</div>
+
+		<div class="pagination-links" id="topic-pag-bottom">
+			<?php bp_forum_pagination(); ?>
+		</div>
+
+	</div>
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'Sorry, there were no forum topics found.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires at the end of the forums loop.
+ *
+ * @since 1.2.6
+ */
+do_action( 'bp_after_forums_loop' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/forums/index.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/forums/index.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/forums/index.php	(revision 10410)
@@ -0,0 +1,243 @@
+<?php
+/**
+ * BuddyPress - Forums
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+<div id="buddypress">
+
+	<?php
+
+	/**
+	 * Fires at the start of the forums template.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_before_directory_forums' ); ?>
+
+	<form action="" method="post" id="forums-search-form" class="dir-form">
+
+		<?php
+
+		/**
+		 * Fires before the display of the forums content.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_before_directory_forums_content' ); ?>
+
+		<div id="forums-dir-search" class="dir-search" role="search">
+
+			<?php bp_directory_forums_search_form(); ?>
+
+		</div>
+	</form>
+
+	<?php
+
+	/**
+	 * Fires before the display of the forum topics.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_before_topics' ); ?>
+
+	<form action="" method="post" id="forums-directory-form" class="dir-form">
+
+		<div class="item-list-tabs" role="navigation">
+			<ul>
+				<li class="selected" id="forums-all"><a href="<?php echo trailingslashit( bp_get_root_domain() . '/' . bp_get_forums_root_slug() ); ?>"><?php printf( __( 'All Topics %s', 'buddypress' ), '<span>' . bp_get_forum_topic_count() . '</span>' ); ?></a></li>
+
+				<?php if ( is_user_logged_in() && bp_get_forum_topic_count_for_user( bp_loggedin_user_id() ) ) : ?>
+
+					<li id="forums-personal"><a href="<?php echo trailingslashit( bp_loggedin_user_domain() . bp_get_forums_slug() . '/topics' ); ?>"><?php printf( __( 'My Topics %s', 'buddypress' ), '<span>' . bp_get_forum_topic_count_for_user( bp_loggedin_user_id() ) . '</span>' ); ?></a></li>
+
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires inside the forum group types list.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_forums_directory_group_types' ); ?>
+
+			</ul>
+		</div>
+
+		<div class="item-list-tabs" id="subnav" role="navigation">
+			<ul>
+
+				<?php
+
+				/**
+				 * Fires inside the forum group sub-types list.
+				 *
+				 * @since 1.5.0
+				 */
+				do_action( 'bp_forums_directory_group_sub_types' ); ?>
+
+				<li id="forums-order-select" class="last filter">
+
+					<label for="forums-order-by"><?php _e( 'Order By:', 'buddypress' ); ?></label>
+					<select id="forums-order-by">
+						<option value="active"><?php _e( 'Last Active', 'buddypress' ); ?></option>
+						<option value="popular"><?php _e( 'Most Posts', 'buddypress' ); ?></option>
+						<option value="unreplied"><?php _e( 'Unreplied', 'buddypress' ); ?></option>
+
+						<?php
+
+						/**
+						 * Fires inside the select input for forums order options.
+						 *
+						 * @since 1.2.0
+						 */
+						do_action( 'bp_forums_directory_order_options' ); ?>
+
+					</select>
+				</li>
+			</ul>
+		</div>
+
+		<div id="forums-dir-list" class="forums dir-list">
+
+			<?php bp_get_template_part( 'forums/forums-loop' ); ?>
+
+		</div>
+
+		<?php
+
+		/**
+		 * Fires and displays the forums content.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_directory_forums_content' ); ?>
+
+		<?php wp_nonce_field( 'directory_forums', '_wpnonce-forums-filter' ); ?>
+
+	</form>
+
+	<?php
+
+	/**
+	 * Fires after the display of the forums.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_after_directory_forums' ); ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of the new topic form.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_before_new_topic_form' ); ?>
+
+	<div id="new-topic-post">
+
+		<?php if ( is_user_logged_in() ) : ?>
+
+			<?php if ( bp_is_active( 'groups' ) && bp_has_groups( 'user_id=' . bp_loggedin_user_id() . '&type=alphabetical&max=100&per_page=100' ) ) : ?>
+
+				<form action="" method="post" id="forum-topic-form" class="standard-form">
+
+					<?php
+
+					/**
+					 * Fires inside the new topic form tag and before input display.
+					 *
+					 * @since 1.0.0
+					 */
+					do_action( 'groups_forum_new_topic_before' ); ?>
+
+					<a name="post-new"></a>
+					<h5><?php _e( 'Create New Topic:', 'buddypress' ); ?></h5>
+
+					<?php
+
+					/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+					do_action( 'template_notices' ); ?>
+
+					<label for="topic_title"><?php _e( 'Title:', 'buddypress' ); ?></label>
+					<input type="text" name="topic_title" id="topic_title" value="" maxlength="100" />
+
+					<label for="topic_text"><?php _e( 'Content:', 'buddypress' ); ?></label>
+					<textarea name="topic_text" id="topic_text"></textarea>
+
+					<label for="topic_tags"><?php _e( 'Tags (comma separated):', 'buddypress' ); ?></label>
+					<input type="text" name="topic_tags" id="topic_tags" value="" />
+
+					<label for="topic_group_id"><?php _e( 'Post In Group Forum:', 'buddypress' ); ?></label>
+					<select id="topic_group_id" name="topic_group_id">
+
+						<option value=""><?php /* translators: no option picked in select box */ _e( '----', 'buddypress' ); ?></option>
+
+						<?php while ( bp_groups() ) : bp_the_group(); ?>
+
+							<?php if ( bp_group_is_forum_enabled() && ( bp_current_user_can( 'bp_moderate' ) || 'public' == bp_get_group_status() || bp_group_is_member() ) ) : ?>
+
+								<option value="<?php bp_group_id(); ?>"><?php bp_group_name(); ?></option>
+
+							<?php endif; ?>
+
+						<?php endwhile; ?>
+
+					</select><!-- #topic_group_id -->
+
+					<?php
+
+					/**
+					 * Fires before the new topic form submit actions.
+					 *
+					 * @since 1.0.0
+					 */
+					do_action( 'groups_forum_new_topic_after' ); ?>
+
+					<div class="submit">
+						<input type="submit" name="submit_topic" id="submit" value="<?php esc_attr_e( 'Post Topic', 'buddypress' ); ?>" />
+						<input type="button" name="submit_topic_cancel" id="submit_topic_cancel" value="<?php esc_attr_e( 'Cancel', 'buddypress' ); ?>" />
+					</div>
+
+					<?php wp_nonce_field( 'bp_forums_new_topic' ); ?>
+
+				</form><!-- #forum-topic-form -->
+
+			<?php elseif ( bp_is_active( 'groups' ) ) : ?>
+
+				<div id="message" class="info">
+
+					<p><?php printf( __( "You are not a member of any groups so you don't have any group forums you can post in. To start posting, first find a group that matches the topic subject you'd like to start. If this group does not exist, why not <a href='%s'>create a new group</a>? Once you have joined or created the group you can post your topic in that group's forum.", 'buddypress' ), trailingslashit( bp_get_groups_directory_permalink() . 'create' ) ); ?></p>
+
+				</div>
+
+			<?php endif; ?>
+
+		<?php endif; ?>
+	</div><!-- #new-topic-post -->
+
+	<?php
+
+	/**
+	 * Fires after the display of the new topic form.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_after_new_topic_form' ); ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of the forums content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_directory_forums_content' ); ?>
+
+</div>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/create.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/create.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/create.php	(revision 10410)
@@ -0,0 +1,476 @@
+<?php
+/**
+ * BuddyPress - Groups Create
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires at the top of the groups creation template file.
+ *
+ * @since 1.7.0
+ */
+do_action( 'bp_before_create_group_page' ); ?>
+
+<div id="buddypress">
+
+	<?php
+
+	/**
+	 * Fires before the display of group creation content.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_before_create_group_content_template' ); ?>
+
+	<form action="<?php bp_group_creation_form_action(); ?>" method="post" id="create-group-form" class="standard-form" enctype="multipart/form-data">
+
+		<?php
+
+		/**
+		 * Fires before the display of group creation.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_before_create_group' ); ?>
+
+		<div class="item-list-tabs no-ajax" id="group-create-tabs" role="navigation">
+			<ul>
+
+				<?php bp_group_creation_tabs(); ?>
+
+			</ul>
+		</div>
+
+		<?php
+
+		/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+		do_action( 'template_notices' ); ?>
+
+		<div class="item-body" id="group-create-body">
+
+			<?php /* Group creation step 1: Basic group details */ ?>
+			<?php if ( bp_is_group_creation_step( 'group-details' ) ) : ?>
+
+				<?php
+
+				/**
+				 * Fires before the display of the group details creation step.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_before_group_details_creation_step' ); ?>
+
+				<div>
+					<label for="group-name"><?php _e( 'Group Name (required)', 'buddypress' ); ?></label>
+					<input type="text" name="group-name" id="group-name" aria-required="true" value="<?php bp_new_group_name(); ?>" />
+				</div>
+
+				<div>
+					<label for="group-desc"><?php _e( 'Group Description (required)', 'buddypress' ); ?></label>
+					<textarea name="group-desc" id="group-desc" aria-required="true"><?php bp_new_group_description(); ?></textarea>
+				</div>
+
+				<?php
+
+				/**
+				 * Fires after the display of the group details creation step.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_after_group_details_creation_step' );
+				do_action( 'groups_custom_group_fields_editable' ); // @Deprecated
+
+				wp_nonce_field( 'groups_create_save_group-details' ); ?>
+
+			<?php endif; ?>
+
+			<?php /* Group creation step 2: Group settings */ ?>
+			<?php if ( bp_is_group_creation_step( 'group-settings' ) ) : ?>
+
+				<?php
+
+				/**
+				 * Fires before the display of the group settings creation step.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_before_group_settings_creation_step' ); ?>
+
+				<h4><?php _e( 'Privacy Options', 'buddypress' ); ?></h4>
+
+				<div class="radio">
+
+					<label for="group-status-public"><input type="radio" name="group-status" id="group-status-public" value="public"<?php if ( 'public' == bp_get_new_group_status() || !bp_get_new_group_status() ) { ?> checked="checked"<?php } ?> aria-describedby="public-group-description" /> <?php _e( 'This is a public group', 'buddypress' ); ?></label>
+
+					<ul id="public-group-description">
+						<li><?php _e( 'Any site member can join this group.', 'buddypress' ); ?></li>
+						<li><?php _e( 'This group will be listed in the groups directory and in search results.', 'buddypress' ); ?></li>
+						<li><?php _e( 'Group content and activity will be visible to any site member.', 'buddypress' ); ?></li>
+					</ul>
+
+
+					<label for="group-status-private"><input type="radio" name="group-status" id="group-status-private" value="private"<?php if ( 'private' == bp_get_new_group_status() ) { ?> checked="checked"<?php } ?> aria-describedby="private-group-description" /> <?php _e( 'This is a private group', 'buddypress' ); ?></label>
+
+					<ul id="private-group-description">
+						<li><?php _e( 'Only users who request membership and are accepted can join the group.', 'buddypress' ); ?></li>
+						<li><?php _e( 'This group will be listed in the groups directory and in search results.', 'buddypress' ); ?></li>
+						<li><?php _e( 'Group content and activity will only be visible to members of the group.', 'buddypress' ); ?></li>
+					</ul>
+
+
+					<label for="group-status-hidden"><input type="radio" name="group-status" id="group-status-hidden" value="hidden"<?php if ( 'hidden' == bp_get_new_group_status() ) { ?> checked="checked"<?php } ?> aria-describedby="hidden-group-description" /> <?php _e('This is a hidden group', 'buddypress' ); ?></label>
+
+					<ul id="hidden-group-description">
+						<li><?php _e( 'Only users who are invited can join the group.', 'buddypress' ); ?></li>
+						<li><?php _e( 'This group will not be listed in the groups directory or search results.', 'buddypress' ); ?></li>
+						<li><?php _e( 'Group content and activity will only be visible to members of the group.', 'buddypress' ); ?></li>
+					</ul>
+
+				</div>
+
+				<h4><?php _e( 'Group Invitations', 'buddypress' ); ?></h4>
+
+				<p><?php _e( 'Which members of this group are allowed to invite others?', 'buddypress' ); ?></p>
+
+				<div class="radio">
+
+					<label for="group-invite-status-members"><input type="radio" name="group-invite-status" id="group-invite-status-members" value="members"<?php bp_group_show_invite_status_setting( 'members' ); ?> /> <?php _e( 'All group members', 'buddypress' ); ?></label>
+
+					<label for="group-invite-status-mods"><input type="radio" name="group-invite-status" id="group-invite-status-mods" value="mods"<?php bp_group_show_invite_status_setting( 'mods' ); ?> /> <?php _e( 'Group admins and mods only', 'buddypress' ); ?></label>
+
+					<label for="group-invite-status-admins"><input type="radio" name="group-invite-status" id="group-invite-status-admins" value="admins"<?php bp_group_show_invite_status_setting( 'admins' ); ?> /> <?php _e( 'Group admins only', 'buddypress' ); ?></label>
+
+				</div>
+
+				<?php if ( bp_is_active( 'forums' ) ) : ?>
+
+					<h4><?php _e( 'Group Forums', 'buddypress' ); ?></h4>
+
+					<?php if ( bp_forums_is_installed_correctly() ) : ?>
+
+						<p><?php _e( 'Should this group have a forum?', 'buddypress' ); ?></p>
+
+						<div class="checkbox">
+							<label for="group-show-forum"><input type="checkbox" name="group-show-forum" id="group-show-forum" value="1"<?php checked( bp_get_new_group_enable_forum(), true, true ); ?> /> <?php _e( 'Enable discussion forum', 'buddypress' ); ?></label>
+						</div>
+					<?php elseif ( is_super_admin() ) : ?>
+
+						<p><?php printf( __( '<strong>Attention Site Admin:</strong> Group forums require the <a href="%s">correct setup and configuration</a> of a bbPress installation.', 'buddypress' ), bp_core_do_network_admin() ? network_admin_url( 'settings.php?page=bb-forums-setup' ) :  admin_url( 'admin.php?page=bb-forums-setup' ) ); ?></p>
+
+					<?php endif; ?>
+
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires after the display of the group settings creation step.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_after_group_settings_creation_step' ); ?>
+
+				<?php wp_nonce_field( 'groups_create_save_group-settings' ); ?>
+
+			<?php endif; ?>
+
+			<?php /* Group creation step 3: Avatar Uploads */ ?>
+			<?php if ( bp_is_group_creation_step( 'group-avatar' ) ) : ?>
+
+				<?php
+
+				/**
+				 * Fires before the display of the group avatar creation step.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_before_group_avatar_creation_step' ); ?>
+
+				<?php if ( 'upload-image' == bp_get_avatar_admin_step() ) : ?>
+
+					<div class="left-menu">
+
+						<?php bp_new_group_avatar(); ?>
+
+					</div><!-- .left-menu -->
+
+					<div class="main-column">
+						<p><?php _e( "Upload an image to use as a profile photo for this group. The image will be shown on the main group page, and in search results.", 'buddypress' ); ?></p>
+
+						<p>
+							<label for="file" class="bp-screen-reader-text"><?php _e( 'Select an image', 'buddypress' ); ?></label>
+							<input type="file" name="file" id="file" />
+							<input type="submit" name="upload" id="upload" value="<?php esc_attr_e( 'Upload Image', 'buddypress' ); ?>" />
+							<input type="hidden" name="action" id="action" value="bp_avatar_upload" />
+						</p>
+
+						<p><?php _e( 'To skip the group profile photo upload process, hit the "Next Step" button.', 'buddypress' ); ?></p>
+					</div><!-- .main-column -->
+
+					<?php
+					/**
+					 * Load the Avatar UI templates
+					 *
+					 * @since 2.3.0
+					 */
+					bp_avatar_get_templates(); ?>
+
+				<?php endif; ?>
+
+				<?php if ( 'crop-image' == bp_get_avatar_admin_step() ) : ?>
+
+					<h4><?php _e( 'Crop Group Profile Photo', 'buddypress' ); ?></h4>
+
+					<img src="<?php bp_avatar_to_crop(); ?>" id="avatar-to-crop" class="avatar" alt="<?php esc_attr_e( 'Profile photo to crop', 'buddypress' ); ?>" />
+
+					<div id="avatar-crop-pane">
+						<img src="<?php bp_avatar_to_crop(); ?>" id="avatar-crop-preview" class="avatar" alt="<?php esc_attr_e( 'Profile photo preview', 'buddypress' ); ?>" />
+					</div>
+
+					<input type="submit" name="avatar-crop-submit" id="avatar-crop-submit" value="<?php esc_attr_e( 'Crop Image', 'buddypress' ); ?>" />
+
+					<input type="hidden" name="image_src" id="image_src" value="<?php bp_avatar_to_crop_src(); ?>" />
+					<input type="hidden" name="upload" id="upload" />
+					<input type="hidden" id="x" name="x" />
+					<input type="hidden" id="y" name="y" />
+					<input type="hidden" id="w" name="w" />
+					<input type="hidden" id="h" name="h" />
+
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires after the display of the group avatar creation step.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_after_group_avatar_creation_step' ); ?>
+
+				<?php wp_nonce_field( 'groups_create_save_group-avatar' ); ?>
+
+			<?php endif; ?>
+
+			<?php /* Group creation step 4: Cover image */ ?>
+			<?php if ( bp_is_group_creation_step( 'group-cover-image' ) ) : ?>
+
+				<?php
+
+				/**
+				 * Fires before the display of the group cover image creation step.
+				 *
+				 * @since 2.4.0
+				 */
+				do_action( 'bp_before_group_cover_image_creation_step' ); ?>
+
+				<div id="header-cover-image"></div>
+
+				<p><?php _e( 'The Cover Image will be used to customize the header of your group.', 'buddypress' ); ?></p>
+
+				<?php bp_attachments_get_template_part( 'cover-images/index' ); ?>
+
+				<?php
+
+				/**
+				 * Fires after the display of the group cover image creation step.
+				 *
+				 * @since 2.4.0
+				 */
+				do_action( 'bp_after_group_cover_image_creation_step' ); ?>
+
+				<?php wp_nonce_field( 'groups_create_save_group-cover-image' ); ?>
+
+			<?php endif; ?>
+
+			<?php /* Group creation step 5: Invite friends to group */ ?>
+			<?php if ( bp_is_group_creation_step( 'group-invites' ) ) : ?>
+
+				<?php
+
+				/**
+				 * Fires before the display of the group invites creation step.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_before_group_invites_creation_step' ); ?>
+
+				<?php if ( bp_is_active( 'friends' ) && bp_get_total_friend_count( bp_loggedin_user_id() ) ) : ?>
+
+					<div class="left-menu">
+
+						<div id="invite-list">
+							<ul>
+								<?php bp_new_group_invite_friend_list(); ?>
+							</ul>
+
+							<?php wp_nonce_field( 'groups_invite_uninvite_user', '_wpnonce_invite_uninvite_user' ); ?>
+						</div>
+
+					</div><!-- .left-menu -->
+
+					<div class="main-column">
+
+						<div id="message" class="info">
+							<p><?php _e('Select people to invite from your friends list.', 'buddypress' ); ?></p>
+						</div>
+
+						<?php /* The ID 'friend-list' is important for AJAX support. */ ?>
+						<ul id="friend-list" class="item-list">
+
+						<?php if ( bp_group_has_invites() ) : ?>
+
+							<?php while ( bp_group_invites() ) : bp_group_the_invite(); ?>
+
+								<li id="<?php bp_group_invite_item_id(); ?>">
+
+									<?php bp_group_invite_user_avatar(); ?>
+
+									<h4><?php bp_group_invite_user_link(); ?></h4>
+									<span class="activity"><?php bp_group_invite_user_last_active(); ?></span>
+
+									<div class="action">
+										<a class="remove" href="<?php bp_group_invite_user_remove_invite_url(); ?>" id="<?php bp_group_invite_item_id(); ?>"><?php _e( 'Remove Invite', 'buddypress' ); ?></a>
+									</div>
+								</li>
+
+							<?php endwhile; ?>
+
+							<?php wp_nonce_field( 'groups_send_invites', '_wpnonce_send_invites' ); ?>
+
+						<?php endif; ?>
+
+						</ul>
+
+					</div><!-- .main-column -->
+
+				<?php else : ?>
+
+					<div id="message" class="info">
+						<p><?php _e( 'Once you have built up friend connections you will be able to invite others to your group.', 'buddypress' ); ?></p>
+					</div>
+
+				<?php endif; ?>
+
+				<?php wp_nonce_field( 'groups_create_save_group-invites' ); ?>
+
+				<?php
+
+				/**
+				 * Fires after the display of the group invites creation step.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_after_group_invites_creation_step' ); ?>
+
+			<?php endif; ?>
+
+			<?php
+
+			/**
+			 * Fires inside the group admin template.
+			 *
+			 * Allows plugins to add custom group creation steps.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'groups_custom_create_steps' ); ?>
+
+			<?php
+
+			/**
+			 * Fires before the display of the group creation step buttons.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'bp_before_group_creation_step_buttons' ); ?>
+
+			<?php if ( 'crop-image' != bp_get_avatar_admin_step() ) : ?>
+
+				<div class="submit" id="previous-next">
+
+					<?php /* Previous Button */ ?>
+					<?php if ( !bp_is_first_group_creation_step() ) : ?>
+
+						<input type="button" value="<?php esc_attr_e( 'Back to Previous Step', 'buddypress' ); ?>" id="group-creation-previous" name="previous" onclick="location.href='<?php bp_group_creation_previous_link(); ?>'" />
+
+					<?php endif; ?>
+
+					<?php /* Next Button */ ?>
+					<?php if ( !bp_is_last_group_creation_step() && !bp_is_first_group_creation_step() ) : ?>
+
+						<input type="submit" value="<?php esc_attr_e( 'Next Step', 'buddypress' ); ?>" id="group-creation-next" name="save" />
+
+					<?php endif;?>
+
+					<?php /* Create Button */ ?>
+					<?php if ( bp_is_first_group_creation_step() ) : ?>
+
+						<input type="submit" value="<?php esc_attr_e( 'Create Group and Continue', 'buddypress' ); ?>" id="group-creation-create" name="save" />
+
+					<?php endif; ?>
+
+					<?php /* Finish Button */ ?>
+					<?php if ( bp_is_last_group_creation_step() ) : ?>
+
+						<input type="submit" value="<?php esc_attr_e( 'Finish', 'buddypress' ); ?>" id="group-creation-finish" name="save" />
+
+					<?php endif; ?>
+				</div>
+
+			<?php endif;?>
+
+			<?php
+
+			/**
+			 * Fires after the display of the group creation step buttons.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'bp_after_group_creation_step_buttons' ); ?>
+
+			<?php /* Don't leave out this hidden field */ ?>
+			<input type="hidden" name="group_id" id="group_id" value="<?php bp_new_group_id(); ?>" />
+
+			<?php
+
+			/**
+			 * Fires and displays the groups directory content.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'bp_directory_groups_content' ); ?>
+
+		</div><!-- .item-body -->
+
+		<?php
+
+		/**
+		 * Fires after the display of group creation.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_after_create_group' ); ?>
+
+	</form>
+
+	<?php
+
+	/**
+	 * Fires after the display of group creation content.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_after_create_group_content_template' ); ?>
+
+</div>
+
+<?php
+
+/**
+ * Fires at the bottom of the groups creation template file.
+ *
+ * @since 1.7.0
+ */
+do_action( 'bp_after_create_group_page' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/groups-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/groups-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/groups-loop.php	(revision 10410)
@@ -0,0 +1,143 @@
+<?php
+/**
+ * BuddyPress - Groups Loop
+ *
+ * Querystring is set via AJAX in _inc/ajax.php - bp_legacy_theme_object_filter().
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<?php
+
+/**
+ * Fires before the display of groups from the groups loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_groups_loop' ); ?>
+
+<?php if ( bp_has_groups( bp_ajax_querystring( 'groups' ) ) ) : ?>
+
+	<div id="pag-top" class="pagination">
+
+		<div class="pag-count" id="group-dir-count-top">
+
+			<?php bp_groups_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="group-dir-pag-top">
+
+			<?php bp_groups_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+	<?php
+
+	/**
+	 * Fires before the listing of the groups list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_directory_groups_list' ); ?>
+
+	<ul id="groups-list" class="item-list">
+
+	<?php while ( bp_groups() ) : bp_the_group(); ?>
+
+		<li <?php bp_group_class(); ?>>
+			<?php if ( ! bp_disable_group_avatar_uploads() ) : ?>
+				<div class="item-avatar">
+					<a href="<?php bp_group_permalink(); ?>"><?php bp_group_avatar( 'type=thumb&width=50&height=50' ); ?></a>
+				</div>
+			<?php endif; ?>
+
+			<div class="item">
+				<div class="item-title"><a href="<?php bp_group_permalink(); ?>"><?php bp_group_name(); ?></a></div>
+				<div class="item-meta"><span class="activity"><?php printf( __( 'active %s', 'buddypress' ), bp_get_group_last_active() ); ?></span></div>
+
+				<div class="item-desc"><?php bp_group_description_excerpt(); ?></div>
+
+				<?php
+
+				/**
+				 * Fires inside the listing of an individual group listing item.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_directory_groups_item' ); ?>
+
+			</div>
+
+			<div class="action">
+
+				<?php
+
+				/**
+				 * Fires inside the action section of an individual group listing item.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_directory_groups_actions' ); ?>
+
+				<div class="meta">
+
+					<?php bp_group_type(); ?> / <?php bp_group_member_count(); ?>
+
+				</div>
+
+			</div>
+
+			<div class="clear"></div>
+		</li>
+
+	<?php endwhile; ?>
+
+	</ul>
+
+	<?php
+
+	/**
+	 * Fires after the listing of the groups list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_directory_groups_list' ); ?>
+
+	<div id="pag-bottom" class="pagination">
+
+		<div class="pag-count" id="group-dir-count-bottom">
+
+			<?php bp_groups_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="group-dir-pag-bottom">
+
+			<?php bp_groups_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'There were no groups found.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the display of groups from the groups loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_groups_loop' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/index.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/index.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/index.php	(revision 10410)
@@ -0,0 +1,145 @@
+<?php
+/**
+ * BuddyPress - Groups
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires at the top of the groups directory template file.
+ *
+ * @since 1.5.0
+ */
+do_action( 'bp_before_directory_groups_page' ); ?>
+
+<div id="buddypress">
+
+	<?php
+
+	/**
+	 * Fires before the display of the groups.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_directory_groups' ); ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of the groups content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_directory_groups_content' ); ?>
+
+	<div id="group-dir-search" class="dir-search" role="search">
+		<?php bp_directory_groups_search_form(); ?>
+	</div><!-- #group-dir-search -->
+
+	<form action="" method="post" id="groups-directory-form" class="dir-form">
+
+		<?php
+
+		/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+		do_action( 'template_notices' ); ?>
+
+		<div class="item-list-tabs" role="navigation">
+			<ul>
+				<li class="selected" id="groups-all"><a href="<?php bp_groups_directory_permalink(); ?>"><?php printf( __( 'All Groups %s', 'buddypress' ), '<span>' . bp_get_total_group_count() . '</span>' ); ?></a></li>
+
+				<?php if ( is_user_logged_in() && bp_get_total_group_count_for_user( bp_loggedin_user_id() ) ) : ?>
+					<li id="groups-personal"><a href="<?php echo bp_loggedin_user_domain() . bp_get_groups_slug() . '/my-groups/'; ?>"><?php printf( __( 'My Groups %s', 'buddypress' ), '<span>' . bp_get_total_group_count_for_user( bp_loggedin_user_id() ) . '</span>' ); ?></a></li>
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires inside the groups directory group filter input.
+				 *
+				 * @since 1.5.0
+				 */
+				do_action( 'bp_groups_directory_group_filter' ); ?>
+
+			</ul>
+		</div><!-- .item-list-tabs -->
+
+		<div class="item-list-tabs" id="subnav" role="navigation">
+			<ul>
+				<?php
+
+				/**
+				 * Fires inside the groups directory group types.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_groups_directory_group_types' ); ?>
+
+				<li id="groups-order-select" class="last filter">
+
+					<label for="groups-order-by"><?php _e( 'Order By:', 'buddypress' ); ?></label>
+
+					<select id="groups-order-by">
+						<option value="active"><?php _e( 'Last Active', 'buddypress' ); ?></option>
+						<option value="popular"><?php _e( 'Most Members', 'buddypress' ); ?></option>
+						<option value="newest"><?php _e( 'Newly Created', 'buddypress' ); ?></option>
+						<option value="alphabetical"><?php _e( 'Alphabetical', 'buddypress' ); ?></option>
+
+						<?php
+
+						/**
+						 * Fires inside the groups directory group order options.
+						 *
+						 * @since 1.2.0
+						 */
+						do_action( 'bp_groups_directory_order_options' ); ?>
+					</select>
+				</li>
+			</ul>
+		</div>
+
+		<div id="groups-dir-list" class="groups dir-list">
+			<?php bp_get_template_part( 'groups/groups-loop' ); ?>
+		</div><!-- #groups-dir-list -->
+
+		<?php
+
+		/**
+ 		 * Fires and displays the group content.
+ 		 *
+ 		 * @since 1.1.0
+ 		 */
+		do_action( 'bp_directory_groups_content' ); ?>
+
+		<?php wp_nonce_field( 'directory_groups', '_wpnonce-groups-filter' ); ?>
+
+		<?php
+
+		/**
+ 		 * Fires after the display of the groups content.
+ 		 *
+ 		 * @since 1.1.0
+ 		 */
+		do_action( 'bp_after_directory_groups_content' ); ?>
+
+	</form><!-- #groups-directory-form -->
+
+	<?php
+
+	/**
+ 	 * Fires after the display of the groups.
+ 	 *
+ 	 * @since 1.1.0
+ 	 */
+	do_action( 'bp_after_directory_groups' ); ?>
+
+</div><!-- #buddypress -->
+
+<?php
+
+/**
+ * Fires at the bottom of the groups directory template file.
+ *
+ * @since 1.5.0
+ */
+do_action( 'bp_after_directory_groups_page' );
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/activity.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/activity.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/activity.php	(revision 10410)
@@ -0,0 +1,88 @@
+<?php
+/**
+ * BuddyPress - Groups Activity
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+	<ul>
+		<li class="feed"><a href="<?php bp_group_activity_feed_link(); ?>" title="<?php esc_attr_e( 'RSS Feed', 'buddypress' ); ?>"><?php _e( 'RSS', 'buddypress' ); ?></a></li>
+
+		<?php
+
+		/**
+		 * Fires inside the syndication options list, after the RSS option.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_group_activity_syndication_options' ); ?>
+
+		<li id="activity-filter-select" class="last">
+			<label for="activity-filter-by"><?php _e( 'Show:', 'buddypress' ); ?></label>
+			<select id="activity-filter-by">
+				<option value="-1"><?php _e( '&mdash; Everything &mdash;', 'buddypress' ); ?></option>
+
+				<?php bp_activity_show_filters( 'group' ); ?>
+
+				<?php
+
+				/**
+				 * Fires inside the select input for group activity filter options.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_group_activity_filter_options' ); ?>
+			</select>
+		</li>
+	</ul>
+</div><!-- .item-list-tabs -->
+
+<?php
+
+/**
+ * Fires before the display of the group activity post form.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_group_activity_post_form' ); ?>
+
+<?php if ( is_user_logged_in() && bp_group_is_member() ) : ?>
+
+	<?php bp_get_template_part( 'activity/post-form' ); ?>
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the display of the group activity post form.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_group_activity_post_form' ); ?>
+<?php
+
+/**
+ * Fires before the display of the group activities list.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_group_activity_content' ); ?>
+
+<div class="activity single-group">
+
+	<?php bp_get_template_part( 'activity/activity-loop' ); ?>
+
+</div><!-- .activity.single-group -->
+
+<?php
+
+/**
+ * Fires after the display of the group activities list.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_group_activity_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/admin.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/admin.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/admin.php	(revision 10410)
@@ -0,0 +1,488 @@
+<?php
+/**
+ * BuddyPress - Groups Admin
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+	<ul>
+		<?php bp_group_admin_tabs(); ?>
+	</ul>
+</div><!-- .item-list-tabs -->
+
+<form action="<?php bp_group_admin_form_action(); ?>" name="group-settings-form" id="group-settings-form" class="standard-form" method="post" enctype="multipart/form-data">
+
+<?php
+
+/**
+ * Fires inside the group admin form and before the content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_before_group_admin_content' ); ?>
+
+<?php /* Edit Group Details */ ?>
+<?php if ( bp_is_group_admin_screen( 'edit-details' ) ) : ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of group admin details.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_group_details_admin' ); ?>
+
+	<label for="group-name"><?php _e( 'Group Name (required)', 'buddypress' ); ?></label>
+	<input type="text" name="group-name" id="group-name" value="<?php bp_group_name(); ?>" aria-required="true" />
+
+	<label for="group-desc"><?php _e( 'Group Description (required)', 'buddypress' ); ?></label>
+	<textarea name="group-desc" id="group-desc" aria-required="true"><?php bp_group_description_editable(); ?></textarea>
+
+	<?php
+
+	/**
+	 * Fires after the group description admin details.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'groups_custom_group_fields_editable' ); ?>
+
+	<p>
+		<label for="group-notify-members">
+			<input type="checkbox" name="group-notify-members" id="group-notify-members" value="1" /> <?php _e( 'Notify group members of these changes via email', 'buddypress' ); ?>
+		</label>
+	</p>
+
+	<?php
+
+	/**
+	 * Fires after the display of group admin details.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_group_details_admin' ); ?>
+
+	<p><input type="submit" value="<?php esc_attr_e( 'Save Changes', 'buddypress' ); ?>" id="save" name="save" /></p>
+	<?php wp_nonce_field( 'groups_edit_group_details' ); ?>
+
+<?php endif; ?>
+
+<?php /* Manage Group Settings */ ?>
+<?php if ( bp_is_group_admin_screen( 'group-settings' ) ) : ?>
+
+	<?php
+
+	/**
+	 * Fires before the group settings admin display.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_group_settings_admin' ); ?>
+
+	<?php if ( bp_is_active( 'forums' ) ) : ?>
+
+		<?php if ( bp_forums_is_installed_correctly() ) : ?>
+
+			<div class="checkbox">
+				<label for="group-show-forum"><input type="checkbox" name="group-show-forum" id="group-show-forum" value="1"<?php bp_group_show_forum_setting(); ?> /> <?php _e( 'Enable discussion forum', 'buddypress' ); ?></label>
+			</div>
+
+			<hr />
+
+		<?php endif; ?>
+
+	<?php endif; ?>
+
+	<h4><?php _e( 'Privacy Options', 'buddypress' ); ?></h4>
+
+	<div class="radio">
+
+		<label for="group-status-public"><input type="radio" name="group-status" id="group-status-public" value="public"<?php if ( 'public' == bp_get_new_group_status() || !bp_get_new_group_status() ) { ?> checked="checked"<?php } ?> aria-describedby="public-group-description" /> <?php _e( 'This is a public group', 'buddypress' ); ?></label>
+
+		<ul id="public-group-description">
+			<li><?php _e( 'Any site member can join this group.', 'buddypress' ); ?></li>
+			<li><?php _e( 'This group will be listed in the groups directory and in search results.', 'buddypress' ); ?></li>
+			<li><?php _e( 'Group content and activity will be visible to any site member.', 'buddypress' ); ?></li>
+		</ul>
+
+		<label for="group-status-private"><input type="radio" name="group-status" id="group-status-private" value="private"<?php if ( 'private' == bp_get_new_group_status() ) { ?> checked="checked"<?php } ?> aria-describedby="private-group-description" /> <?php _e( 'This is a private group', 'buddypress' ); ?></label>
+
+		<ul id="private-group-description">
+			<li><?php _e( 'Only users who request membership and are accepted can join the group.', 'buddypress' ); ?></li>
+			<li><?php _e( 'This group will be listed in the groups directory and in search results.', 'buddypress' ); ?></li>
+			<li><?php _e( 'Group content and activity will only be visible to members of the group.', 'buddypress' ); ?></li>
+		</ul>
+
+		<label for="group-status-hidden"><input type="radio" name="group-status" id="group-status-hidden" value="hidden"<?php if ( 'hidden' == bp_get_new_group_status() ) { ?> checked="checked"<?php } ?> aria-describedby="hidden-group-description" /> <?php _e('This is a hidden group', 'buddypress' ); ?></label>
+
+		<ul id="hidden-group-description">
+			<li><?php _e( 'Only users who are invited can join the group.', 'buddypress' ); ?></li>
+			<li><?php _e( 'This group will not be listed in the groups directory or search results.', 'buddypress' ); ?></li>
+			<li><?php _e( 'Group content and activity will only be visible to members of the group.', 'buddypress' ); ?></li>
+		</ul>
+
+	</div>
+
+	<hr />
+
+	<h4><?php _e( 'Group Invitations', 'buddypress' ); ?></h4>
+
+	<p><?php _e( 'Which members of this group are allowed to invite others?', 'buddypress' ); ?></p>
+
+	<div class="radio">
+
+		<label for="group-invite-status-members"><input type="radio" name="group-invite-status" id="group-invite-status-members" value="members"<?php bp_group_show_invite_status_setting( 'members' ); ?> /> <?php _e( 'All group members', 'buddypress' ); ?></label>
+
+		<label for="group-invite-status-mods"><input type="radio" name="group-invite-status" id="group-invite-status-mods" value="mods"<?php bp_group_show_invite_status_setting( 'mods' ); ?> /> <?php _e( 'Group admins and mods only', 'buddypress' ); ?></label>
+
+		<label for="group-invite-status-admins"><input type="radio" name="group-invite-status" id="group-invite-status-admins" value="admins"<?php bp_group_show_invite_status_setting( 'admins' ); ?> /> <?php _e( 'Group admins only', 'buddypress' ); ?></label>
+
+ 	</div>
+
+	<hr />
+
+	<?php
+
+	/**
+	 * Fires after the group settings admin display.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_group_settings_admin' ); ?>
+
+	<p><input type="submit" value="<?php esc_attr_e( 'Save Changes', 'buddypress' ); ?>" id="save" name="save" /></p>
+	<?php wp_nonce_field( 'groups_edit_group_settings' ); ?>
+
+<?php endif; ?>
+
+<?php /* Group Avatar Settings */ ?>
+<?php if ( bp_is_group_admin_screen( 'group-avatar' ) ) : ?>
+
+	<?php if ( 'upload-image' == bp_get_avatar_admin_step() ) : ?>
+
+			<p><?php _e("Upload an image to use as a profile photo for this group. The image will be shown on the main group page, and in search results.", 'buddypress' ); ?></p>
+
+			<p>
+				<label for="file" class="bp-screen-reader-text"><?php _e( 'Select an image', 'buddypress' ); ?></label>
+				<input type="file" name="file" id="file" />
+				<input type="submit" name="upload" id="upload" value="<?php esc_attr_e( 'Upload Image', 'buddypress' ); ?>" />
+				<input type="hidden" name="action" id="action" value="bp_avatar_upload" />
+			</p>
+
+			<?php if ( bp_get_group_has_avatar() ) : ?>
+
+				<p><?php _e( "If you'd like to remove the existing group profile photo but not upload a new one, please use the delete group profile photo button.", 'buddypress' ); ?></p>
+
+				<?php bp_button( array( 'id' => 'delete_group_avatar', 'component' => 'groups', 'wrapper_id' => 'delete-group-avatar-button', 'link_class' => 'edit', 'link_href' => bp_get_group_avatar_delete_link(), 'link_title' => __( 'Delete Group Profile Photo', 'buddypress' ), 'link_text' => __( 'Delete Group Profile Photo', 'buddypress' ) ) ); ?>
+
+			<?php endif; ?>
+
+			<?php
+			/**
+			 * Load the Avatar UI templates
+			 *
+			 * @since  2.3.0
+			 */
+			bp_avatar_get_templates(); ?>
+
+			<?php wp_nonce_field( 'bp_avatar_upload' ); ?>
+
+	<?php endif; ?>
+
+	<?php if ( 'crop-image' == bp_get_avatar_admin_step() ) : ?>
+
+		<h4><?php _e( 'Crop Profile Photo', 'buddypress' ); ?></h4>
+
+		<img src="<?php bp_avatar_to_crop(); ?>" id="avatar-to-crop" class="avatar" alt="<?php esc_attr_e( 'Profile photo to crop', 'buddypress' ); ?>" />
+
+		<div id="avatar-crop-pane">
+			<img src="<?php bp_avatar_to_crop(); ?>" id="avatar-crop-preview" class="avatar" alt="<?php esc_attr_e( 'Profile photo preview', 'buddypress' ); ?>" />
+		</div>
+
+		<input type="submit" name="avatar-crop-submit" id="avatar-crop-submit" value="<?php esc_attr_e( 'Crop Image', 'buddypress' ); ?>" />
+
+		<input type="hidden" name="image_src" id="image_src" value="<?php bp_avatar_to_crop_src(); ?>" />
+		<input type="hidden" id="x" name="x" />
+		<input type="hidden" id="y" name="y" />
+		<input type="hidden" id="w" name="w" />
+		<input type="hidden" id="h" name="h" />
+
+		<?php wp_nonce_field( 'bp_avatar_cropstore' ); ?>
+
+	<?php endif; ?>
+
+<?php endif; ?>
+
+<?php /* Group Cover image Settings */ ?>
+<?php if ( bp_is_group_admin_screen( 'group-cover-image' ) ) : ?>
+
+	<h4><?php _e( 'Change Cover Image', 'buddypress' ); ?></h4>
+
+	<?php
+
+	/**
+	 * Fires before the display of profile cover image upload content.
+	 *
+	 * @since 2.4.0
+	 */
+	do_action( 'bp_before_group_settings_cover_image' ); ?>
+
+	<p><?php _e( 'The Cover Image will be used to customize the header of your group.', 'buddypress' ); ?></p>
+
+	<?php bp_attachments_get_template_part( 'cover-images/index' ); ?>
+
+	<?php
+
+	/**
+	 * Fires after the display of group cover image upload content.
+	 *
+	 * @since 2.4.0
+	 */
+	do_action( 'bp_after_group_settings_cover_image' ); ?>
+
+<?php endif; ?>
+
+<?php /* Manage Group Members */ ?>
+<?php if ( bp_is_group_admin_screen( 'manage-members' ) ) : ?>
+
+	<?php
+
+	/**
+	 * Fires before the group manage members admin display.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_group_manage_members_admin' ); ?>
+
+	<div class="bp-widget">
+		<h4><?php _e( 'Administrators', 'buddypress' ); ?></h4>
+
+		<?php if ( bp_has_members( '&include='. bp_group_admin_ids() ) ) : ?>
+
+		<ul id="admins-list" class="item-list single-line">
+
+			<?php while ( bp_members() ) : bp_the_member(); ?>
+			<li>
+				<?php echo bp_core_fetch_avatar( array( 'item_id' => bp_get_member_user_id(), 'type' => 'thumb', 'width' => 30, 'height' => 30, 'alt' => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_member_name() ) ) ); ?>
+				<h5>
+					<a href="<?php bp_member_permalink(); ?>"> <?php bp_member_name(); ?></a>
+					<?php if ( count( bp_group_admin_ids( false, 'array' ) ) > 1 ) : ?>
+					<span class="small">
+						<a class="button confirm admin-demote-to-member" href="<?php bp_group_member_demote_link( bp_get_member_user_id() ); ?>"><?php _e( 'Demote to Member', 'buddypress' ); ?></a>
+					</span>
+					<?php endif; ?>
+				</h5>
+			</li>
+			<?php endwhile; ?>
+
+		</ul>
+
+		<?php endif; ?>
+
+	</div>
+
+	<?php if ( bp_group_has_moderators() ) : ?>
+		<div class="bp-widget">
+			<h4><?php _e( 'Moderators', 'buddypress' ); ?></h4>
+
+			<?php if ( bp_has_members( '&include=' . bp_group_mod_ids() ) ) : ?>
+				<ul id="mods-list" class="item-list single-line">
+
+					<?php while ( bp_members() ) : bp_the_member(); ?>
+					<li>
+						<?php echo bp_core_fetch_avatar( array( 'item_id' => bp_get_member_user_id(), 'type' => 'thumb', 'width' => 30, 'height' => 30, 'alt' => sprintf( __( 'Profile picture of %s', 'buddypress' ), bp_get_member_name() ) ) ); ?>
+						<h5>
+							<a href="<?php bp_member_permalink(); ?>"> <?php bp_member_name(); ?></a>
+							<span class="small">
+								<a href="<?php bp_group_member_promote_admin_link( array( 'user_id' => bp_get_member_user_id() ) ); ?>" class="button confirm mod-promote-to-admin" title="<?php esc_attr_e( 'Promote to Admin', 'buddypress' ); ?>"><?php _e( 'Promote to Admin', 'buddypress' ); ?></a>
+								<a class="button confirm mod-demote-to-member" href="<?php bp_group_member_demote_link( bp_get_member_user_id() ); ?>"><?php _e( 'Demote to Member', 'buddypress' ); ?></a>
+							</span>
+						</h5>
+					</li>
+					<?php endwhile; ?>
+
+				</ul>
+
+			<?php endif; ?>
+		</div>
+	<?php endif ?>
+
+
+	<div class="bp-widget">
+		<h4><?php _e("Members", "buddypress"); ?></h4>
+
+		<?php if ( bp_group_has_members( 'per_page=15&exclude_banned=0' ) ) : ?>
+
+			<?php if ( bp_group_member_needs_pagination() ) : ?>
+
+				<div class="pagination no-ajax">
+
+					<div id="member-count" class="pag-count">
+						<?php bp_group_member_pagination_count(); ?>
+					</div>
+
+					<div id="member-admin-pagination" class="pagination-links">
+						<?php bp_group_member_admin_pagination(); ?>
+					</div>
+
+				</div>
+
+			<?php endif; ?>
+
+			<ul id="members-list" class="item-list single-line">
+				<?php while ( bp_group_members() ) : bp_group_the_member(); ?>
+
+					<li class="<?php bp_group_member_css_class(); ?>">
+						<?php bp_group_member_avatar_mini(); ?>
+
+						<h5>
+							<?php bp_group_member_link(); ?>
+
+							<?php if ( bp_get_group_member_is_banned() ) _e( '(banned)', 'buddypress' ); ?>
+
+							<span class="small">
+
+							<?php if ( bp_get_group_member_is_banned() ) : ?>
+
+								<a href="<?php bp_group_member_unban_link(); ?>" class="button confirm member-unban" title="<?php esc_attr_e( 'Unban this member', 'buddypress' ); ?>"><?php _e( 'Remove Ban', 'buddypress' ); ?></a>
+
+							<?php else : ?>
+
+								<a href="<?php bp_group_member_ban_link(); ?>" class="button confirm member-ban" title="<?php esc_attr_e( 'Kick and ban this member', 'buddypress' ); ?>"><?php _e( 'Kick &amp; Ban', 'buddypress' ); ?></a>
+								<a href="<?php bp_group_member_promote_mod_link(); ?>" class="button confirm member-promote-to-mod" title="<?php esc_attr_e( 'Promote to Mod', 'buddypress' ); ?>"><?php _e( 'Promote to Mod', 'buddypress' ); ?></a>
+								<a href="<?php bp_group_member_promote_admin_link(); ?>" class="button confirm member-promote-to-admin" title="<?php esc_attr_e( 'Promote to Admin', 'buddypress' ); ?>"><?php _e( 'Promote to Admin', 'buddypress' ); ?></a>
+
+							<?php endif; ?>
+
+								<a href="<?php bp_group_member_remove_link(); ?>" class="button confirm" title="<?php esc_attr_e( 'Remove this member', 'buddypress' ); ?>"><?php _e( 'Remove from group', 'buddypress' ); ?></a>
+
+								<?php
+
+								/**
+								 * Fires inside the display of a member admin item in group management area.
+								 *
+								 * @since 1.1.0
+								 */
+								do_action( 'bp_group_manage_members_admin_item' ); ?>
+
+							</span>
+						</h5>
+					</li>
+
+				<?php endwhile; ?>
+			</ul>
+
+		<?php else: ?>
+
+			<div id="message" class="info">
+				<p><?php _e( 'This group has no members.', 'buddypress' ); ?></p>
+			</div>
+
+		<?php endif; ?>
+
+	</div>
+
+	<?php
+
+	/**
+	 * Fires after the group manage members admin display.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_group_manage_members_admin' ); ?>
+
+<?php endif; ?>
+
+<?php /* Manage Membership Requests */ ?>
+<?php if ( bp_is_group_admin_screen( 'membership-requests' ) ) : ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of group membership requests admin.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_group_membership_requests_admin' ); ?>
+
+		<div class="requests">
+
+			<?php bp_get_template_part( 'groups/single/requests-loop' ); ?>
+
+		</div>
+
+	<?php
+
+	/**
+	 * Fires after the display of group membership requests admin.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_group_membership_requests_admin' ); ?>
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires inside the group admin template.
+ *
+ * Allows plugins to add custom group edit screens.
+ *
+ * @since 1.1.0
+ */
+do_action( 'groups_custom_edit_steps' ); ?>
+
+<?php /* Delete Group Option */ ?>
+<?php if ( bp_is_group_admin_screen( 'delete-group' ) ) : ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of group delete admin.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_group_delete_admin' ); ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'WARNING: Deleting this group will completely remove ALL content associated with it. There is no way back, please be careful with this option.', 'buddypress' ); ?></p>
+	</div>
+
+	<label for="delete-group-understand"><input type="checkbox" name="delete-group-understand" id="delete-group-understand" value="1" onclick="if(this.checked) { document.getElementById('delete-group-button').disabled = ''; } else { document.getElementById('delete-group-button').disabled = 'disabled'; }" /> <?php _e( 'I understand the consequences of deleting this group.', 'buddypress' ); ?></label>
+
+	<?php
+
+	/**
+	 * Fires after the display of group delete admin.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_group_delete_admin' ); ?>
+
+	<div class="submit">
+		<input type="submit" disabled="disabled" value="<?php esc_attr_e( 'Delete Group', 'buddypress' ); ?>" id="delete-group-button" name="delete-group-button" />
+	</div>
+
+	<?php wp_nonce_field( 'groups_delete_group' ); ?>
+
+<?php endif; ?>
+
+<?php /* This is important, don't forget it */ ?>
+	<input type="hidden" name="group-id" id="group-id" value="<?php bp_group_id(); ?>" />
+
+<?php
+
+/**
+ * Fires inside the group admin form and after the content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_after_group_admin_content' ); ?>
+
+</form><!-- #group-settings-form -->
+
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/cover-image-header.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/cover-image-header.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/cover-image-header.php	(revision 10410)
@@ -0,0 +1,123 @@
+<?php
+/**
+ * BuddyPress - Groups Cover Image Header.
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of a group's header.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_group_header' ); ?>
+
+<div id="cover-image-container">
+	<a id="header-cover-image" href="<?php bp_group_permalink(); ?>"></a>
+
+	<div id="item-header-cover-image">
+		<?php if ( ! bp_disable_group_avatar_uploads() ) : ?>
+			<div id="item-header-avatar">
+				<a href="<?php bp_group_permalink(); ?>" title="<?php bp_group_name(); ?>">
+
+					<?php bp_group_avatar(); ?>
+
+				</a>
+			</div><!-- #item-header-avatar -->
+		<?php endif; ?>
+
+		<div id="item-header-content">
+
+			<div id="item-buttons"><?php
+
+				/**
+				 * Fires in the group header actions section.
+				 *
+				 * @since 1.2.6
+				 */
+				do_action( 'bp_group_header_actions' ); ?></div><!-- #item-buttons -->
+
+			<?php
+
+			/**
+			 * Fires before the display of the group's header meta.
+			 *
+			 * @since 1.2.0
+			 */
+			do_action( 'bp_before_group_header_meta' ); ?>
+
+			<div id="item-meta">
+
+				<?php
+
+				/**
+				 * Fires after the group header actions section.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_group_header_meta' ); ?>
+
+				<span class="highlight"><?php bp_group_type(); ?></span>
+				<span class="activity"><?php printf( __( 'active %s', 'buddypress' ), bp_get_group_last_active() ); ?></span>
+
+				<?php bp_group_description(); ?>
+
+			</div>
+		</div><!-- #item-header-content -->
+
+		<div id="item-actions">
+
+			<?php if ( bp_group_is_visible() ) : ?>
+
+				<h3><?php _e( 'Group Admins', 'buddypress' ); ?></h3>
+
+				<?php bp_group_list_admins();
+
+				/**
+				 * Fires after the display of the group's administrators.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_after_group_menu_admins' );
+
+				if ( bp_group_has_moderators() ) :
+
+					/**
+					 * Fires before the display of the group's moderators, if there are any.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_before_group_menu_mods' ); ?>
+
+					<h3><?php _e( 'Group Mods' , 'buddypress' ); ?></h3>
+
+					<?php bp_group_list_mods();
+
+					/**
+					 * Fires after the display of the group's moderators, if there are any.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_after_group_menu_mods' );
+
+				endif;
+
+			endif; ?>
+
+		</div><!-- #item-actions -->
+
+	</div><!-- #item-header-cover-image -->
+</div><!-- #cover-image-container -->
+
+<?php
+
+/**
+ * Fires after the display of a group's header.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_group_header' );
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+do_action( 'template_notices' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/forum.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/forum.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/forum.php	(revision 10410)
@@ -0,0 +1,135 @@
+<?php
+/**
+ * BuddyPress - Groups Single Forum
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of a group's forum content.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_group_forum_content' );
+
+if ( bp_is_group_forum_topic_edit() ) :
+	bp_get_template_part( 'groups/single/forum/edit' );
+
+elseif ( bp_is_group_forum_topic() ) :
+	bp_get_template_part( 'groups/single/forum/topic' );
+
+else : ?>
+
+	<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+		<ul>
+
+			<?php if ( is_user_logged_in() ) : ?>
+
+				<li>
+					<a href="#post-new" class="show-hide-new"><?php _e( 'New Topic', 'buddypress' ); ?></a>
+				</li>
+
+			<?php endif; ?>
+
+			<?php if ( bp_forums_has_directory() ) : ?>
+
+				<li>
+					<a href="<?php bp_forums_directory_permalink(); ?>"><?php _e( 'Forum Directory', 'buddypress' ); ?></a>
+				</li>
+
+			<?php endif; ?>
+
+			<?php
+
+			/** This filter is documented in bp-templates/bp-legacy/buddypress/forums/index.php. */
+			do_action( 'bp_forums_directory_group_sub_types' ); ?>
+
+			<li id="forums-order-select" class="last filter">
+
+				<label for="forums-order-by"><?php _e( 'Order By:', 'buddypress' ); ?></label>
+				<select id="forums-order-by">
+					<option value="active"><?php _e( 'Last Active', 'buddypress' ); ?></option>
+					<option value="popular"><?php _e( 'Most Posts', 'buddypress' ); ?></option>
+					<option value="unreplied"><?php _e( 'Unreplied', 'buddypress' ); ?></option>
+
+					<?php
+
+					/** This filter is documented in bp-templates/bp-legacy/buddypress/forums/index.php. */
+					do_action( 'bp_forums_directory_order_options' ); ?>
+
+				</select>
+			</li>
+		</ul>
+	</div>
+
+	<div class="forums single-forum">
+
+		<?php bp_get_template_part( 'forums/forums-loop' ) ?>
+
+	</div><!-- .forums.single-forum -->
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the display of a group's forum content.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_group_forum_content' ); ?>
+
+<?php if ( !bp_is_group_forum_topic_edit() && !bp_is_group_forum_topic() ) : ?>
+
+	<?php if ( !bp_group_is_user_banned() && ( ( is_user_logged_in() && 'public' == bp_get_group_status() ) || bp_group_is_member() ) ) : ?>
+
+		<form action="" method="post" id="forum-topic-form" class="standard-form">
+			<div id="new-topic-post">
+
+				<?php
+
+				/**
+				 * Fires before the display of a group forum new post form.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_before_group_forum_post_new' ); ?>
+
+				<?php if ( bp_groups_auto_join() && !bp_group_is_member() ) : ?>
+					<p><?php _e( 'You will auto join this group when you start a new topic.', 'buddypress' ); ?></p>
+				<?php endif; ?>
+
+				<p id="post-new"></p>
+				<h4><?php _e( 'Post a New Topic:', 'buddypress' ); ?></h4>
+
+				<label for="topic_title"><?php _e( 'Title:', 'buddypress' ); ?></label>
+				<input type="text" name="topic_title" id="topic_title" value="" maxlength="100" />
+
+				<label for="topic_text"><?php _e( 'Content:', 'buddypress' ); ?></label>
+				<textarea name="topic_text" id="topic_text"></textarea>
+
+				<label for="topic_tags"><?php _e( 'Tags (comma separated):', 'buddypress' ); ?></label>
+				<input type="text" name="topic_tags" id="topic_tags" value="" />
+
+				<?php
+
+				/**
+				 * Fires after the display of a group forum new post form.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_after_group_forum_post_new' ); ?>
+
+				<div class="submit">
+					<input type="submit" name="submit_topic" id="submit" value="<?php esc_attr_e( 'Post Topic', 'buddypress' ); ?>" />
+				</div>
+
+				<?php wp_nonce_field( 'bp_forums_new_topic' ); ?>
+			</div><!-- #new-topic-post -->
+		</form><!-- #forum-topic-form -->
+
+	<?php endif; ?>
+
+<?php endif; ?>
+
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/forum/edit.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/forum/edit.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/forum/edit.php	(revision 10410)
@@ -0,0 +1,148 @@
+<?php
+/**
+ * BuddyPress - Groups Single Forum Edit.
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires at the top of the group forum edit form.
+ *
+ * @since 1.2.4
+ */
+do_action( 'bp_before_group_forum_edit_form' ); ?>
+
+<?php if ( bp_has_forum_topic_posts() ) : ?>
+
+	<form action="<?php bp_forum_topic_action(); ?>" method="post" id="forum-topic-form" class="standard-form">
+
+		<div class="item-list-tabs" id="subnav" role="navigation">
+			<ul>
+				<li>
+					<a href="#post-topic-reply"><?php _e( 'Reply', 'buddypress' ); ?></a>
+				</li>
+
+				<?php if ( bp_forums_has_directory() ) : ?>
+
+					<li>
+						<a href="<?php bp_forums_directory_permalink(); ?>"><?php _e( 'Forum Directory', 'buddypress' ); ?></a>
+					</li>
+
+				<?php endif; ?>
+
+			</ul>
+		</div>
+
+		<div id="topic-meta">
+			<h3><?php _e( 'Edit:', 'buddypress' ); ?> <?php bp_the_topic_title(); ?> (<?php bp_the_topic_total_post_count(); ?>)</h3>
+
+			<?php if ( bp_group_is_admin() || bp_group_is_mod() || bp_get_the_topic_is_mine() ) : ?>
+
+				<div class="last admin-links">
+
+					<?php bp_the_topic_admin_links(); ?>
+
+				</div>
+
+			<?php endif; ?>
+
+			<?php
+
+			/**
+			 * Fires at the end of the group forum topic meta section.
+			 *
+			 * @since 1.2.5
+			 */
+			do_action( 'bp_group_forum_topic_meta' ); ?>
+
+		</div>
+
+		<?php if ( bp_is_edit_topic() ) : ?>
+
+			<div id="edit-topic">
+
+				<?php
+
+				/**
+				 * Fires before the group forum topic form fields.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_group_before_edit_forum_topic' ); ?>
+
+				<label for="topic_title"><?php _e( 'Title:', 'buddypress' ); ?></label>
+				<input type="text" name="topic_title" id="topic_title" value="<?php bp_the_topic_title(); ?>" maxlength="100" />
+
+				<label for="topic_text"><?php _e( 'Content:', 'buddypress' ); ?></label>
+				<textarea name="topic_text" id="topic_text"><?php bp_the_topic_text(); ?></textarea>
+
+				<label for="topic_tags"><?php _e( 'Tags (comma separated):', 'buddypress' ); ?></label>
+				<input type="text" name="topic_tags" id="topic_tags" value="<?php bp_forum_topic_tag_list(); ?>" />
+
+				<?php
+
+				/**
+				 * Fires after the group forum topic form fields.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_group_after_edit_forum_topic' ); ?>
+
+				<p class="submit"><input type="submit" name="save_changes" id="save_changes" value="<?php esc_attr_e( 'Save Changes', 'buddypress' ); ?>" /></p>
+
+				<?php wp_nonce_field( 'bp_forums_edit_topic' ); ?>
+
+			</div>
+
+		<?php else : ?>
+
+			<div id="edit-post">
+
+				<?php
+
+				/**
+				 * Fires before the group edit forum textarea.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_group_before_edit_forum_post' ); ?>
+
+				<label for="post_text" class="bp-screen-reader-text"><?php _e( 'Edit text', 'buddypress' ); ?></label>
+				<textarea name="post_text" id="post_text"><?php bp_the_topic_post_edit_text(); ?></textarea>
+
+				<?php
+
+				/**
+				 * Fires after the group edit forum textarea.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_group_after_edit_forum_post' ); ?>
+
+				<p class="submit"><input type="submit" name="save_changes" id="save_changes" value="<?php esc_attr_e( 'Save Changes', 'buddypress' ); ?>" /></p>
+
+				<?php wp_nonce_field( 'bp_forums_edit_post' ); ?>
+
+			</div>
+
+		<?php endif; ?>
+
+	</form><!-- #forum-topic-form -->
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'This topic does not exist.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif;?>
+
+<?php
+
+/**
+ * Fires at the end of the group forum edit form.
+ *
+ * @since 1.2.4
+ */
+do_action( 'bp_after_group_forum_edit_form' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/forum/topic.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/forum/topic.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/forum/topic.php	(revision 10410)
@@ -0,0 +1,229 @@
+<?php
+/**
+ * BuddyPress - Groups Single Forum Topic.
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires at the top of the group forum topic template.
+ *
+ * @since 1.2.4
+ */
+do_action( 'bp_before_group_forum_topic' ); ?>
+
+<form action="<?php bp_forum_topic_action(); ?>" method="post" id="forum-topic-form" class="standard-form">
+	<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+		<ul>
+			<?php if ( is_user_logged_in() ) : ?>
+
+				<li>
+					<a href="<?php bp_forum_topic_new_reply_link(); ?>" class="new-reply-link"><?php _e( 'New Reply', 'buddypress' ); ?></a>
+				</li>
+
+			<?php endif; ?>
+
+			<?php if ( bp_forums_has_directory() ) : ?>
+
+				<li>
+					<a href="<?php bp_forums_directory_permalink(); ?>"><?php _e( 'Forum Directory', 'buddypress' ); ?></a>
+				</li>
+
+			<?php endif; ?>
+
+		</ul>
+	</div>
+
+	<div id="topic-meta">
+		<h3><?php bp_the_topic_title(); ?> (<?php bp_the_topic_total_post_count(); ?>)</h3>
+
+		<?php if ( bp_forum_topic_has_tags() ) : ?>
+
+			<div class="topic-tags">
+
+				<?php _e( 'Topic tags:', 'buddypress' ); ?> <?php bp_forum_topic_tag_list(); ?>
+
+			</div>
+
+		<?php endif; ?>
+
+		<?php if ( bp_group_is_admin() || bp_group_is_mod() || bp_get_the_topic_is_mine() ) : ?>
+
+			<div class="last admin-links">
+
+				<?php bp_the_topic_admin_links(); ?>
+
+			</div>
+
+		<?php endif; ?>
+
+		<?php
+
+		/**
+		 * Fires at the end of the group forum topic meta markup.
+		 *
+		 * @since 1.2.5
+		 */
+		do_action( 'bp_group_forum_topic_meta' ); ?>
+
+	</div>
+
+
+	<?php if ( bp_has_forum_topic_posts() ) : ?>
+
+		<div class="pagination no-ajax">
+
+			<div id="post-count-top" class="pag-count">
+
+				<?php bp_the_topic_pagination_count(); ?>
+
+			</div>
+
+			<div class="pagination-links" id="topic-pag-top">
+
+				<?php bp_the_topic_pagination(); ?>
+
+			</div>
+
+		</div>
+
+		<?php
+
+		/**
+		 * Fires before the listing of the group forum topic posts.
+		 *
+		 * @since 1.2.4
+		 */
+		do_action( 'bp_before_group_forum_topic_posts' ); ?>
+
+		<ul id="topic-post-list" class="item-list">
+			<?php while ( bp_forum_topic_posts() ) : bp_the_forum_topic_post(); ?>
+
+				<li id="post-<?php bp_the_topic_post_id(); ?>" class="<?php bp_the_topic_post_css_class(); ?>">
+					<div class="poster-meta">
+						<a href="<?php bp_the_topic_post_poster_link(); ?>">
+							<?php bp_the_topic_post_poster_avatar( 'width=40&height=40' ); ?>
+						</a>
+						<?php echo sprintf( __( '%1$s said %2$s:', 'buddypress' ), bp_get_the_topic_post_poster_name(), bp_get_the_topic_post_time_since() ); ?>
+					</div>
+
+					<div class="post-content">
+						<?php bp_the_topic_post_content(); ?>
+					</div>
+
+					<div class="admin-links">
+						<?php if ( bp_group_is_admin() || bp_group_is_mod() || bp_get_the_topic_post_is_mine() ) : ?>
+							<?php bp_the_topic_post_admin_links(); ?>
+						<?php endif; ?>
+
+						<?php
+
+						/**
+						 * Fires inside the group forum post meta markup.
+						 *
+						 * @since 1.2.5
+						 */
+						do_action( 'bp_group_forum_post_meta' ); ?>
+
+						<a href="#post-<?php bp_the_topic_post_id(); ?>" title="<?php esc_attr_e( 'Permanent link to this post', 'buddypress' ); ?>">#</a>
+					</div>
+				</li>
+
+			<?php endwhile; ?>
+		</ul><!-- #topic-post-list -->
+
+		<?php
+
+		/**
+		 * Fires before the listing of the group forum topic posts.
+		 *
+		 * @since 1.2.4
+		 */
+		do_action( 'bp_after_group_forum_topic_posts' ); ?>
+
+		<div class="pagination no-ajax">
+
+			<div id="post-count-bottom" class="pag-count">
+				<?php bp_the_topic_pagination_count(); ?>
+			</div>
+
+			<div class="pagination-links" id="topic-pag-bottom">
+				<?php bp_the_topic_pagination(); ?>
+			</div>
+
+		</div>
+
+	<?php else: ?>
+
+		<div id="message" class="info">
+			<p><?php _e( 'There are no posts for this topic.', 'buddypress' ); ?></p>
+		</div>
+
+	<?php endif;?>
+
+	<?php if ( ( is_user_logged_in() && 'public' == bp_get_group_status() ) || bp_group_is_member() ) : ?>
+
+		<?php if ( bp_get_the_topic_is_last_page() ) : ?>
+
+			<?php if ( bp_get_the_topic_is_topic_open() && !bp_group_is_user_banned() ) : ?>
+
+				<div id="post-topic-reply">
+					<p id="post-reply"></p>
+
+					<?php if ( bp_groups_auto_join() && !bp_group_is_member() ) : ?>
+						<p><?php _e( 'You will auto join this group when you reply to this topic.', 'buddypress' ); ?></p>
+					<?php endif; ?>
+
+					<?php
+
+					/**
+					 * Fires before the display of the group forum new reply section.
+					 *
+					 * @since 1.0.0
+					 */
+					do_action( 'groups_forum_new_reply_before' ); ?>
+
+					<h4><?php _e( 'Add a reply:', 'buddypress' ); ?></h4>
+
+					<label for="reply_text" class="bp-screen-reader-text"><?php _e( 'Reply', 'buddypress' ); ?></label>
+					<textarea name="reply_text" id="reply_text"></textarea>
+
+					<div class="submit">
+						<input type="submit" name="submit_reply" id="submit" value="<?php esc_attr_e( 'Post Reply', 'buddypress' ); ?>" />
+					</div>
+
+					<?php
+
+					/**
+					 * Fires after the display of the group forum new reply section.
+					 *
+					 * @since 1.0.0
+					 */
+					do_action( 'groups_forum_new_reply_after' ); ?>
+
+					<?php wp_nonce_field( 'bp_forums_new_reply' ); ?>
+				</div>
+
+			<?php elseif ( !bp_group_is_user_banned() ) : ?>
+
+				<div id="message" class="info">
+					<p><?php _e( 'This topic is closed, replies are no longer accepted.', 'buddypress' ); ?></p>
+				</div>
+
+			<?php endif; ?>
+
+		<?php endif; ?>
+
+	<?php endif; ?>
+
+</form><!-- #forum-topic-form -->
+
+<?php
+
+/**
+ * Fires at the end of the group forum topic template.
+ *
+ * @since 1.2.4
+ */
+do_action( 'bp_after_group_forum_topic' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/group-header.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/group-header.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/group-header.php	(revision 10410)
@@ -0,0 +1,122 @@
+<?php
+/**
+ * BuddyPress - Groups Header
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of a group's header.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_group_header' );
+
+?>
+
+<div id="item-actions">
+
+	<?php if ( bp_group_is_visible() ) : ?>
+
+		<h3><?php _e( 'Group Admins', 'buddypress' ); ?></h3>
+
+		<?php bp_group_list_admins();
+
+		/**
+		 * Fires after the display of the group's administrators.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_after_group_menu_admins' );
+
+		if ( bp_group_has_moderators() ) :
+
+			/**
+			 * Fires before the display of the group's moderators, if there are any.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'bp_before_group_menu_mods' ); ?>
+
+			<h3><?php _e( 'Group Mods' , 'buddypress' ); ?></h3>
+
+			<?php bp_group_list_mods();
+
+			/**
+			 * Fires after the display of the group's moderators, if there are any.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'bp_after_group_menu_mods' );
+
+		endif;
+
+	endif; ?>
+
+</div><!-- #item-actions -->
+
+<?php if ( ! bp_disable_group_avatar_uploads() ) : ?>
+	<div id="item-header-avatar">
+		<a href="<?php bp_group_permalink(); ?>" title="<?php bp_group_name(); ?>">
+
+			<?php bp_group_avatar(); ?>
+
+		</a>
+	</div><!-- #item-header-avatar -->
+<?php endif; ?>
+
+<div id="item-header-content">
+	<span class="highlight"><?php bp_group_type(); ?></span>
+	<span class="activity"><?php printf( __( 'active %s', 'buddypress' ), bp_get_group_last_active() ); ?></span>
+
+	<?php
+
+	/**
+	 * Fires before the display of the group's header meta.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_before_group_header_meta' ); ?>
+
+	<div id="item-meta">
+
+		<?php bp_group_description(); ?>
+
+		<div id="item-buttons">
+
+			<?php
+
+			/**
+			 * Fires in the group header actions section.
+			 *
+			 * @since 1.2.6
+			 */
+			do_action( 'bp_group_header_actions' ); ?>
+
+		</div><!-- #item-buttons -->
+
+		<?php
+
+		/**
+		 * Fires after the group header actions section.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_group_header_meta' ); ?>
+
+	</div>
+</div><!-- #item-header-content -->
+
+<?php
+
+/**
+ * Fires after the display of a group's header.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_group_header' );
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+do_action( 'template_notices' );
+?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/home.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/home.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/home.php	(revision 10410)
@@ -0,0 +1,155 @@
+<?php
+/**
+ * BuddyPress - Groups Home
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+<div id="buddypress">
+
+	<?php if ( bp_has_groups() ) : while ( bp_groups() ) : bp_the_group(); ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of the group home content.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_before_group_home_content' ); ?>
+
+	<div id="item-header" role="complementary">
+
+		<?php
+		/**
+		 * If the cover image feature is enabled, use a specific header
+		 */
+		if ( bp_group_use_cover_image_header() ) :
+			bp_get_template_part( 'groups/single/cover-image-header' );
+		else :
+			bp_get_template_part( 'groups/single/group-header' );
+		endif;
+		?>
+
+	</div><!-- #item-header -->
+
+	<div id="item-nav">
+		<div class="item-list-tabs no-ajax" id="object-nav" role="navigation">
+			<ul>
+
+				<?php bp_get_options_nav(); ?>
+
+				<?php
+
+				/**
+				 * Fires after the display of group options navigation.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_group_options_nav' ); ?>
+
+			</ul>
+		</div>
+	</div><!-- #item-nav -->
+
+	<div id="item-body">
+
+		<?php
+
+		/**
+		 * Fires before the display of the group home body.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_before_group_body' );
+
+		/**
+		 * Does this next bit look familiar? If not, go check out WordPress's
+		 * /wp-includes/template-loader.php file.
+		 *
+		 * @todo A real template hierarchy? Gasp!
+		 */
+
+			// Looking at home location
+			if ( bp_is_group_home() ) :
+
+				if ( bp_group_is_visible() ) {
+
+					// Load appropriate front template
+					bp_groups_front_template_part();
+
+				} else {
+
+					/**
+					 * Fires before the display of the group status message.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_before_group_status_message' ); ?>
+
+					<div id="message" class="info">
+						<p><?php bp_group_status_message(); ?></p>
+					</div>
+
+					<?php
+
+					/**
+					 * Fires after the display of the group status message.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_after_group_status_message' );
+
+				}
+
+			// Not looking at home
+			else :
+
+				// Group Admin
+				if     ( bp_is_group_admin_page() ) : bp_get_template_part( 'groups/single/admin'        );
+
+				// Group Activity
+				elseif ( bp_is_group_activity()   ) : bp_get_template_part( 'groups/single/activity'     );
+
+				// Group Members
+				elseif ( bp_is_group_members()    ) : bp_groups_members_template_part();
+
+				// Group Invitations
+				elseif ( bp_is_group_invites()    ) : bp_get_template_part( 'groups/single/send-invites' );
+
+				// Old group forums
+				elseif ( bp_is_group_forum()      ) : bp_get_template_part( 'groups/single/forum'        );
+
+				// Membership request
+				elseif ( bp_is_group_membership_request() ) : bp_get_template_part( 'groups/single/request-membership' );
+
+				// Anything else (plugins mostly)
+				else                                : bp_get_template_part( 'groups/single/plugins'      );
+
+				endif;
+
+			endif;
+
+		/**
+		 * Fires after the display of the group home body.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_after_group_body' ); ?>
+
+	</div><!-- #item-body -->
+
+	<?php
+
+	/**
+	 * Fires after the display of the group home content.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_after_group_home_content' ); ?>
+
+	<?php endwhile; endif; ?>
+
+</div><!-- #buddypress -->
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/invites-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/invites-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/invites-loop.php	(revision 10410)
@@ -0,0 +1,124 @@
+<?php
+/**
+ * BuddyPress - Group Invites Loop
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+<div class="left-menu">
+
+	<div id="invite-list">
+
+		<ul>
+			<?php bp_new_group_invite_friend_list(); ?>
+		</ul>
+
+		<?php wp_nonce_field( 'groups_invite_uninvite_user', '_wpnonce_invite_uninvite_user' ); ?>
+
+	</div>
+
+</div><!-- .left-menu -->
+
+<div class="main-column">
+
+	<?php
+
+	/**
+	 * Fires before the display of the group send invites list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_group_send_invites_list' ); ?>
+
+	<?php if ( bp_group_has_invites( bp_ajax_querystring( 'invite' ) . '&per_page=10' ) ) : ?>
+
+		<div id="pag-top" class="pagination">
+
+			<div class="pag-count" id="group-invite-count-top">
+
+				<?php bp_group_invite_pagination_count(); ?>
+
+			</div>
+
+			<div class="pagination-links" id="group-invite-pag-top">
+
+				<?php bp_group_invite_pagination_links(); ?>
+
+			</div>
+
+		</div>
+
+		<?php /* The ID 'friend-list' is important for AJAX support. */ ?>
+		<ul id="friend-list" class="item-list">
+
+		<?php while ( bp_group_invites() ) : bp_group_the_invite(); ?>
+
+			<li id="<?php bp_group_invite_item_id(); ?>">
+				<?php bp_group_invite_user_avatar(); ?>
+
+				<h4><?php bp_group_invite_user_link(); ?></h4>
+				<span class="activity"><?php bp_group_invite_user_last_active(); ?></span>
+
+				<?php
+
+				/**
+				 * Fires inside the invite item listing.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_group_send_invites_item' ); ?>
+
+				<div class="action">
+					<a class="button remove" href="<?php bp_group_invite_user_remove_invite_url(); ?>" id="<?php bp_group_invite_item_id(); ?>"><?php _e( 'Remove Invite', 'buddypress' ); ?></a>
+
+					<?php
+
+					/**
+					 * Fires inside the action area for a send invites item.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_group_send_invites_item_action' ); ?>
+				</div>
+			</li>
+
+		<?php endwhile; ?>
+
+		</ul><!-- #friend-list -->
+
+		<div id="pag-bottom" class="pagination">
+
+			<div class="pag-count" id="group-invite-count-bottom">
+
+				<?php bp_group_invite_pagination_count(); ?>
+
+			</div>
+
+			<div class="pagination-links" id="group-invite-pag-bottom">
+
+				<?php bp_group_invite_pagination_links(); ?>
+
+			</div>
+
+		</div>
+
+	<?php else : ?>
+
+		<div id="message" class="info">
+			<p><?php _e( 'Select friends to invite.', 'buddypress' ); ?></p>
+		</div>
+
+	<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the display of the group send invites list.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_after_group_send_invites_list' ); ?>
+
+</div><!-- .main-column -->
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/members.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/members.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/members.php	(revision 10410)
@@ -0,0 +1,134 @@
+<?php
+/**
+ * BuddyPress - Groups Members
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<?php if ( bp_group_has_members( bp_ajax_querystring( 'group_members' ) ) ) : ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of the group members content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_group_members_content' ); ?>
+
+	<div id="pag-top" class="pagination">
+
+		<div class="pag-count" id="member-count-top">
+
+			<?php bp_members_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="member-pag-top">
+
+			<?php bp_members_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+	<?php
+
+	/**
+	 * Fires before the display of the group members list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_group_members_list' ); ?>
+
+	<ul id="member-list" class="item-list">
+
+		<?php while ( bp_group_members() ) : bp_group_the_member(); ?>
+
+			<li>
+				<a href="<?php bp_group_member_domain(); ?>">
+
+					<?php bp_group_member_avatar_thumb(); ?>
+
+				</a>
+
+				<h5><?php bp_group_member_link(); ?></h5>
+				<span class="activity"><?php bp_group_member_joined_since(); ?></span>
+
+				<?php
+
+				/**
+				 * Fires inside the listing of an individual group member listing item.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_group_members_list_item' ); ?>
+
+				<?php if ( bp_is_active( 'friends' ) ) : ?>
+
+					<div class="action">
+
+						<?php bp_add_friend_button( bp_get_group_member_id(), bp_get_group_member_is_friend() ); ?>
+
+						<?php
+
+						/**
+						 * Fires inside the action section of an individual group member listing item.
+						 *
+						 * @since 1.1.0
+						 */
+						do_action( 'bp_group_members_list_item_action' ); ?>
+
+					</div>
+
+				<?php endif; ?>
+			</li>
+
+		<?php endwhile; ?>
+
+	</ul>
+
+	<?php
+
+	/**
+	 * Fires after the display of the group members list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_group_members_list' ); ?>
+
+	<div id="pag-bottom" class="pagination">
+
+		<div class="pag-count" id="member-count-bottom">
+
+			<?php bp_members_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="member-pag-bottom">
+
+			<?php bp_members_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+	<?php
+
+	/**
+	 * Fires after the display of the group members content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_group_members_content' ); ?>
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'No members were found.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif; ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/plugins.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/plugins.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/plugins.php	(revision 10410)
@@ -0,0 +1,32 @@
+<?php
+/**
+ * BuddyPress - Groups plugins
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of content for plugins using the BP_Group_Extension.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_group_plugin_template' ); ?>
+
+<?php
+
+/**
+ * Fires and displays content for plugins using the BP_Group_Extension.
+ *
+ * @since 1.0.0
+ */
+do_action( 'bp_template_content' ); ?>
+
+<?php
+
+/**
+ * Fires after the display of content for plugins using the BP_Group_Extension.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_group_plugin_template' );
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/request-membership.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/request-membership.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/request-membership.php	(revision 10410)
@@ -0,0 +1,45 @@
+<?php
+/**
+ * BuddyPress - Groups Request Membership
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of the group membership request form.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_before_group_request_membership_content' ); ?>
+
+<?php if ( !bp_group_has_requested_membership() ) : ?>
+	<p><?php printf( __( "You are requesting to become a member of the group '%s'.", "buddypress" ), bp_get_group_name( false ) ); ?></p>
+
+	<form action="<?php bp_group_form_action('request-membership' ); ?>" method="post" name="request-membership-form" id="request-membership-form" class="standard-form">
+		<label for="group-request-membership-comments"><?php _e( 'Comments (optional)', 'buddypress' ); ?></label>
+		<textarea name="group-request-membership-comments" id="group-request-membership-comments"></textarea>
+
+		<?php
+
+		/**
+		 * Fires after the textarea for the group membership request form.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_group_request_membership_content' ); ?>
+
+		<p><input type="submit" name="group-request-send" id="group-request-send" value="<?php esc_attr_e( 'Send Request', 'buddypress' ); ?>" />
+
+		<?php wp_nonce_field( 'groups_request_membership' ); ?>
+	</form><!-- #request-membership-form -->
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the display of the group membership request form.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_after_group_request_membership_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/requests-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/requests-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/requests-loop.php	(revision 10410)
@@ -0,0 +1,89 @@
+<?php
+/**
+ * BuddyPress - Groups Requests Loop
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<?php if ( bp_group_has_membership_requests( bp_ajax_querystring( 'membership_requests' ) ) ) : ?>
+
+	<div id="pag-top" class="pagination">
+
+		<div class="pag-count" id="group-mem-requests-count-top">
+
+			<?php bp_group_requests_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="group-mem-requests-pag-top">
+
+			<?php bp_group_requests_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+	<ul id="request-list" class="item-list">
+		<?php while ( bp_group_membership_requests() ) : bp_group_the_membership_request(); ?>
+
+			<li>
+				<?php bp_group_request_user_avatar_thumb(); ?>
+				<h4><?php bp_group_request_user_link(); ?> <span class="comments"><?php bp_group_request_comment(); ?></span></h4>
+				<span class="activity"><?php bp_group_request_time_since_requested(); ?></span>
+
+				<?php
+
+				/**
+				 * Fires inside the groups membership request list loop.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_group_membership_requests_admin_item' ); ?>
+
+				<div class="action">
+
+					<?php bp_button( array( 'id' => 'group_membership_accept', 'component' => 'groups', 'wrapper_class' => 'accept', 'link_href' => bp_get_group_request_accept_link(), 'link_title' => __( 'Accept', 'buddypress' ), 'link_text' => __( 'Accept', 'buddypress' ) ) ); ?>
+
+					<?php bp_button( array( 'id' => 'group_membership_reject', 'component' => 'groups', 'wrapper_class' => 'reject', 'link_href' => bp_get_group_request_reject_link(), 'link_title' => __( 'Reject', 'buddypress' ), 'link_text' => __( 'Reject', 'buddypress' ) ) ); ?>
+
+					<?php
+
+					/**
+					 * Fires inside the list of membership request actions.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_group_membership_requests_admin_item_action' ); ?>
+
+				</div>
+			</li>
+
+		<?php endwhile; ?>
+	</ul>
+
+	<div id="pag-bottom" class="pagination">
+
+		<div class="pag-count" id="group-mem-requests-count-bottom">
+
+			<?php bp_group_requests_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="group-mem-requests-pag-bottom">
+
+			<?php bp_group_requests_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+	<?php else: ?>
+
+		<div id="message" class="info">
+			<p><?php _e( 'There are no pending membership requests.', 'buddypress' ); ?></p>
+		</div>
+
+	<?php endif; ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/send-invites.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/send-invites.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/groups/single/send-invites.php	(revision 10410)
@@ -0,0 +1,64 @@
+<?php
+/**
+ * BuddyPress - Groups Send Invites
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the send invites content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_before_group_send_invites_content' ); ?>
+
+<?php
+/* Does the user have friends that could be invited to the group? */
+if ( bp_get_new_group_invite_friend_list() ) : ?>
+
+	<?php /* 'send-invite-form' is important for AJAX support */ ?>
+	<form action="<?php bp_group_send_invite_form_action(); ?>" method="post" id="send-invite-form" class="standard-form">
+
+		<div class="invite">
+			<?php bp_get_template_part( 'groups/single/invites-loop' ); ?>
+		</div>
+
+		<div class="submit">
+			<input type="submit" name="submit" id="submit" value="<?php esc_attr_e( 'Send Invites', 'buddypress' ); ?>" />
+		</div>
+
+		<?php wp_nonce_field( 'groups_send_invites', '_wpnonce_send_invites' ); ?>
+
+		<?php /* This is important, don't forget it */ ?>
+		<input type="hidden" name="group_id" id="group_id" value="<?php bp_group_id(); ?>" />
+
+	</form><!-- #send-invite-form -->
+
+<?php
+/* No eligible friends? Maybe the user doesn't have any friends yet. */
+elseif ( 0 == bp_get_total_friend_count( bp_loggedin_user_id() ) ) : ?>
+
+	<div id="message" class="info">
+		<p class="notice"><?php _e( 'Group invitations can only be extended to friends.', 'buddypress' ); ?></p>
+		<p class="message-body"><?php _e( "Once you've made some friendships, you'll be able to invite those members to this group.", 'buddypress' ); ?></p>
+	</div>
+
+<?php
+/* The user does have friends, but none are eligible to be invited to this group. */
+else : ?>
+
+	<div id="message" class="info">
+		<p class="notice"><?php _e( 'All of your friends already belong to this group.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the send invites content.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_group_send_invites_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/activate.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/activate.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/activate.php	(revision 10410)
@@ -0,0 +1,83 @@
+<?php
+/**
+ * BuddyPress - Members Activate
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div id="buddypress">
+
+	<?php
+
+	/**
+	 * Fires before the display of the member activation page.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_activation_page' ); ?>
+
+	<div class="page" id="activate-page">
+
+		<?php
+
+		/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+		do_action( 'template_notices' ); ?>
+
+		<?php
+
+		/**
+		 * Fires before the display of the member activation page content.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_before_activate_content' ); ?>
+
+		<?php if ( bp_account_was_activated() ) : ?>
+
+			<?php if ( isset( $_GET['e'] ) ) : ?>
+				<p><?php _e( 'Your account was activated successfully! Your account details have been sent to you in a separate email.', 'buddypress' ); ?></p>
+			<?php else : ?>
+				<p><?php printf( __( 'Your account was activated successfully! You can now <a href="%s">log in</a> with the username and password you provided when you signed up.', 'buddypress' ), wp_login_url( bp_get_root_domain() ) ); ?></p>
+			<?php endif; ?>
+
+		<?php else : ?>
+
+			<p><?php _e( 'Please provide a valid activation key.', 'buddypress' ); ?></p>
+
+			<form action="" method="get" class="standard-form" id="activation-form">
+
+				<label for="key"><?php _e( 'Activation Key:', 'buddypress' ); ?></label>
+				<input type="text" name="key" id="key" value="" />
+
+				<p class="submit">
+					<input type="submit" name="submit" value="<?php esc_attr_e( 'Activate', 'buddypress' ); ?>" />
+				</p>
+
+			</form>
+
+		<?php endif; ?>
+
+		<?php
+
+		/**
+		 * Fires after the display of the member activation page content.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_after_activate_content' ); ?>
+
+	</div><!-- .page -->
+
+	<?php
+
+	/**
+	 * Fires after the display of the member activation page.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_activation_page' ); ?>
+
+</div><!-- #buddypress -->
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/index.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/index.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/index.php	(revision 10410)
@@ -0,0 +1,149 @@
+<?php
+/**
+ * BuddyPress - Members
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires at the top of the members directory template file.
+ *
+ * @since 1.5.0
+ */
+do_action( 'bp_before_directory_members_page' ); ?>
+
+<div id="buddypress">
+
+	<?php
+
+	/**
+	 * Fires before the display of the members.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_directory_members' ); ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of the members content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_directory_members_content' ); ?>
+
+	<div id="members-dir-search" class="dir-search" role="search">
+		<?php bp_directory_members_search_form(); ?>
+	</div><!-- #members-dir-search -->
+
+	<?php
+
+	/**
+	 * Fires before the display of the members list tabs.
+	 *
+	 * @since 1.8.0
+	 */
+	do_action( 'bp_before_directory_members_tabs' ); ?>
+
+	<form action="" method="post" id="members-directory-form" class="dir-form">
+
+		<div class="item-list-tabs" role="navigation">
+			<ul>
+				<li class="selected" id="members-all"><a href="<?php bp_members_directory_permalink(); ?>"><?php printf( __( 'All Members %s', 'buddypress' ), '<span>' . bp_get_total_member_count() . '</span>' ); ?></a></li>
+
+				<?php if ( is_user_logged_in() && bp_is_active( 'friends' ) && bp_get_total_friend_count( bp_loggedin_user_id() ) ) : ?>
+					<li id="members-personal"><a href="<?php echo bp_loggedin_user_domain() . bp_get_friends_slug() . '/my-friends/'; ?>"><?php printf( __( 'My Friends %s', 'buddypress' ), '<span>' . bp_get_total_friend_count( bp_loggedin_user_id() ) . '</span>' ); ?></a></li>
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires inside the members directory member types.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_members_directory_member_types' ); ?>
+
+			</ul>
+		</div><!-- .item-list-tabs -->
+
+		<div class="item-list-tabs" id="subnav" role="navigation">
+			<ul>
+				<?php
+
+				/**
+				 * Fires inside the members directory member sub-types.
+				 *
+				 * @since 1.5.0
+				 */
+				do_action( 'bp_members_directory_member_sub_types' ); ?>
+
+				<li id="members-order-select" class="last filter">
+					<label for="members-order-by"><?php _e( 'Order By:', 'buddypress' ); ?></label>
+					<select id="members-order-by">
+						<option value="active"><?php _e( 'Last Active', 'buddypress' ); ?></option>
+						<option value="newest"><?php _e( 'Newest Registered', 'buddypress' ); ?></option>
+
+						<?php if ( bp_is_active( 'xprofile' ) ) : ?>
+							<option value="alphabetical"><?php _e( 'Alphabetical', 'buddypress' ); ?></option>
+						<?php endif; ?>
+
+						<?php
+
+						/**
+						 * Fires inside the members directory member order options.
+						 *
+						 * @since 1.2.0
+						 */
+						do_action( 'bp_members_directory_order_options' ); ?>
+					</select>
+				</li>
+			</ul>
+		</div>
+
+		<div id="members-dir-list" class="members dir-list">
+			<?php bp_get_template_part( 'members/members-loop' ); ?>
+		</div><!-- #members-dir-list -->
+
+		<?php
+
+		/**
+ 		 * Fires and displays the members content.
+ 		 *
+ 		 * @since 1.1.0
+ 		 */
+		do_action( 'bp_directory_members_content' ); ?>
+
+		<?php wp_nonce_field( 'directory_members', '_wpnonce-member-filter' ); ?>
+
+		<?php
+
+		/**
+		 * Fires after the display of the members content.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_after_directory_members_content' ); ?>
+
+	</form><!-- #members-directory-form -->
+
+	<?php
+
+	/**
+	 * Fires after the display of the members.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_directory_members' ); ?>
+
+</div><!-- #buddypress -->
+
+<?php
+
+/**
+ * Fires at the bottom of the members directory template file.
+ *
+ * @since 1.5.0
+ */
+do_action( 'bp_after_directory_members_page' );
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/members-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/members-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/members-loop.php	(revision 10410)
@@ -0,0 +1,154 @@
+<?php
+/**
+ * BuddyPress - Members Loop
+ *
+ * Querystring is set via AJAX in _inc/ajax.php - bp_legacy_theme_object_filter()
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of the members loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_members_loop' ); ?>
+
+<?php if ( bp_get_current_member_type() ) : ?>
+	<p class="current-member-type"><?php bp_current_member_type_message() ?></p>
+<?php endif; ?>
+
+<?php if ( bp_has_members( bp_ajax_querystring( 'members' ) ) ) : ?>
+
+	<div id="pag-top" class="pagination">
+
+		<div class="pag-count" id="member-dir-count-top">
+
+			<?php bp_members_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="member-dir-pag-top">
+
+			<?php bp_members_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+	<?php
+
+	/**
+	 * Fires before the display of the members list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_directory_members_list' ); ?>
+
+	<ul id="members-list" class="item-list">
+
+	<?php while ( bp_members() ) : bp_the_member(); ?>
+
+		<li <?php bp_member_class(); ?>>
+			<div class="item-avatar">
+				<a href="<?php bp_member_permalink(); ?>"><?php bp_member_avatar(); ?></a>
+			</div>
+
+			<div class="item">
+				<div class="item-title">
+					<a href="<?php bp_member_permalink(); ?>"><?php bp_member_name(); ?></a>
+
+					<?php if ( bp_get_member_latest_update() ) : ?>
+
+						<span class="update"> <?php bp_member_latest_update(); ?></span>
+
+					<?php endif; ?>
+
+				</div>
+
+				<div class="item-meta"><span class="activity"><?php bp_member_last_active(); ?></span></div>
+
+				<?php
+
+				/**
+				 * Fires inside the display of a directory member item.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_directory_members_item' ); ?>
+
+				<?php
+				 /***
+				  * If you want to show specific profile fields here you can,
+				  * but it'll add an extra query for each member in the loop
+				  * (only one regardless of the number of fields you show):
+				  *
+				  * bp_member_profile_data( 'field=the field name' );
+				  */
+				?>
+			</div>
+
+			<div class="action">
+
+				<?php
+
+				/**
+				 * Fires inside the members action HTML markup to display actions.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_directory_members_actions' ); ?>
+
+			</div>
+
+			<div class="clear"></div>
+		</li>
+
+	<?php endwhile; ?>
+
+	</ul>
+
+	<?php
+
+	/**
+	 * Fires after the display of the members list.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_directory_members_list' ); ?>
+
+	<?php bp_member_hidden_fields(); ?>
+
+	<div id="pag-bottom" class="pagination">
+
+		<div class="pag-count" id="member-dir-count-bottom">
+
+			<?php bp_members_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="member-dir-pag-bottom">
+
+			<?php bp_members_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( "Sorry, no members were found.", 'buddypress' ); ?></p>
+	</div>
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the display of the members loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_members_loop' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/register.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/register.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/register.php	(revision 10410)
@@ -0,0 +1,414 @@
+<?php
+/**
+ * BuddyPress - Members Register
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div id="buddypress">
+
+	<?php
+
+	/**
+	 * Fires at the top of the BuddyPress member registration page template.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_register_page' ); ?>
+
+	<div class="page" id="register-page">
+
+		<form action="" name="signup_form" id="signup_form" class="standard-form" method="post" enctype="multipart/form-data">
+
+		<?php if ( 'registration-disabled' == bp_get_current_signup_step() ) : ?>
+			<?php
+
+			/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+			do_action( 'template_notices' ); ?>
+			<?php
+
+			/**
+			 * Fires before the display of the registration disabled message.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'bp_before_registration_disabled' ); ?>
+
+				<p><?php _e( 'User registration is currently not allowed.', 'buddypress' ); ?></p>
+
+			<?php
+
+			/**
+			 * Fires after the display of the registration disabled message.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'bp_after_registration_disabled' ); ?>
+		<?php endif; // registration-disabled signup step ?>
+
+		<?php if ( 'request-details' == bp_get_current_signup_step() ) : ?>
+
+			<?php
+
+			/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+			do_action( 'template_notices' ); ?>
+
+			<p><?php _e( 'Registering for this site is easy. Just fill in the fields below, and we\'ll get a new account set up for you in no time.', 'buddypress' ); ?></p>
+
+			<?php
+
+			/**
+			 * Fires before the display of member registration account details fields.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'bp_before_account_details_fields' ); ?>
+
+			<div class="register-section" id="basic-details-section">
+
+				<?php /***** Basic Account Details ******/ ?>
+
+				<h4><?php _e( 'Account Details', 'buddypress' ); ?></h4>
+
+				<label for="signup_username"><?php _e( 'Username', 'buddypress' ); ?> <?php _e( '(required)', 'buddypress' ); ?></label>
+				<?php
+
+				/**
+				 * Fires and displays any member registration username errors.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_signup_username_errors' ); ?>
+				<input type="text" name="signup_username" id="signup_username" value="<?php bp_signup_username_value(); ?>" <?php bp_form_field_attributes( 'username' ); ?>/>
+
+				<label for="signup_email"><?php _e( 'Email Address', 'buddypress' ); ?> <?php _e( '(required)', 'buddypress' ); ?></label>
+				<?php
+
+				/**
+				 * Fires and displays any member registration email errors.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_signup_email_errors' ); ?>
+				<input type="email" name="signup_email" id="signup_email" value="<?php bp_signup_email_value(); ?>" <?php bp_form_field_attributes( 'email' ); ?>/>
+
+				<label for="signup_password"><?php _e( 'Choose a Password', 'buddypress' ); ?> <?php _e( '(required)', 'buddypress' ); ?></label>
+				<?php
+
+				/**
+				 * Fires and displays any member registration password errors.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_signup_password_errors' ); ?>
+				<input type="password" name="signup_password" id="signup_password" value="" class="password-entry" <?php bp_form_field_attributes( 'password' ); ?>/>
+				<div id="pass-strength-result"></div>
+
+				<label for="signup_password_confirm"><?php _e( 'Confirm Password', 'buddypress' ); ?> <?php _e( '(required)', 'buddypress' ); ?></label>
+				<?php
+
+				/**
+				 * Fires and displays any member registration password confirmation errors.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_signup_password_confirm_errors' ); ?>
+				<input type="password" name="signup_password_confirm" id="signup_password_confirm" value="" class="password-entry-confirm" <?php bp_form_field_attributes( 'password' ); ?>/>
+
+				<?php
+
+				/**
+				 * Fires and displays any extra member registration details fields.
+				 *
+				 * @since 1.9.0
+				 */
+				do_action( 'bp_account_details_fields' ); ?>
+
+			</div><!-- #basic-details-section -->
+
+			<?php
+
+			/**
+			 * Fires after the display of member registration account details fields.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'bp_after_account_details_fields' ); ?>
+
+			<?php /***** Extra Profile Details ******/ ?>
+
+			<?php if ( bp_is_active( 'xprofile' ) ) : ?>
+
+				<?php
+
+				/**
+				 * Fires before the display of member registration xprofile fields.
+				 *
+				 * @since 1.2.4
+				 */
+				do_action( 'bp_before_signup_profile_fields' ); ?>
+
+				<div class="register-section" id="profile-details-section">
+
+					<h4><?php _e( 'Profile Details', 'buddypress' ); ?></h4>
+
+					<?php /* Use the profile field loop to render input fields for the 'base' profile field group */ ?>
+					<?php if ( bp_is_active( 'xprofile' ) ) : if ( bp_has_profile( array( 'profile_group_id' => 1, 'fetch_field_data' => false ) ) ) : while ( bp_profile_groups() ) : bp_the_profile_group(); ?>
+
+					<?php while ( bp_profile_fields() ) : bp_the_profile_field(); ?>
+
+						<div<?php bp_field_css_class( 'editfield' ); ?>>
+
+							<?php
+							$field_type = bp_xprofile_create_field_type( bp_get_the_profile_field_type() );
+							$field_type->edit_field_html();
+
+							/**
+							 * Fires before the display of the visibility options for xprofile fields.
+							 *
+							 * @since 1.7.0
+							 */
+							do_action( 'bp_custom_profile_edit_fields_pre_visibility' );
+
+							if ( bp_current_user_can( 'bp_xprofile_change_field_visibility' ) ) : ?>
+								<p class="field-visibility-settings-toggle" id="field-visibility-settings-toggle-<?php bp_the_profile_field_id() ?>">
+									<?php
+									printf(
+										__( 'This field can be seen by: %s', 'buddypress' ),
+										'<span class="current-visibility-level">' . bp_get_the_profile_field_visibility_level_label() . '</span>'
+									);
+									?>
+									<a href="#" class="visibility-toggle-link"><?php _ex( 'Change', 'Change profile field visibility level', 'buddypress' ); ?></a>
+								</p>
+
+								<div class="field-visibility-settings" id="field-visibility-settings-<?php bp_the_profile_field_id() ?>">
+									<fieldset>
+										<legend><?php _e( 'Who can see this field?', 'buddypress' ) ?></legend>
+
+										<?php bp_profile_visibility_radio_buttons() ?>
+
+									</fieldset>
+									<a class="field-visibility-settings-close" href="#"><?php _e( 'Close', 'buddypress' ) ?></a>
+
+								</div>
+							<?php else : ?>
+								<p class="field-visibility-settings-notoggle" id="field-visibility-settings-toggle-<?php bp_the_profile_field_id() ?>">
+									<?php
+									printf(
+										__( 'This field can be seen by: %s', 'buddypress' ),
+										'<span class="current-visibility-level">' . bp_get_the_profile_field_visibility_level_label() . '</span>'
+									);
+									?>
+								</p>
+							<?php endif ?>
+
+							<?php
+
+							/**
+							 * Fires after the display of the visibility options for xprofile fields.
+							 *
+							 * @since 1.1.0
+							 */
+							do_action( 'bp_custom_profile_edit_fields' ); ?>
+
+							<p class="description"><?php bp_the_profile_field_description(); ?></p>
+
+						</div>
+
+					<?php endwhile; ?>
+
+					<input type="hidden" name="signup_profile_field_ids" id="signup_profile_field_ids" value="<?php bp_the_profile_field_ids(); ?>" />
+
+					<?php endwhile; endif; endif; ?>
+
+					<?php
+
+					/**
+					 * Fires and displays any extra member registration xprofile fields.
+					 *
+					 * @since 1.9.0
+					 */
+					do_action( 'bp_signup_profile_fields' ); ?>
+
+				</div><!-- #profile-details-section -->
+
+				<?php
+
+				/**
+				 * Fires after the display of member registration xprofile fields.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_after_signup_profile_fields' ); ?>
+
+			<?php endif; ?>
+
+			<?php if ( bp_get_blog_signup_allowed() ) : ?>
+
+				<?php
+
+				/**
+				 * Fires before the display of member registration blog details fields.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_before_blog_details_fields' ); ?>
+
+				<?php /***** Blog Creation Details ******/ ?>
+
+				<div class="register-section" id="blog-details-section">
+
+					<h4><?php _e( 'Blog Details', 'buddypress' ); ?></h4>
+
+					<p><label for="signup_with_blog"><input type="checkbox" name="signup_with_blog" id="signup_with_blog" value="1"<?php if ( (int) bp_get_signup_with_blog_value() ) : ?> checked="checked"<?php endif; ?> /> <?php _e( 'Yes, I\'d like to create a new site', 'buddypress' ); ?></label></p>
+
+					<div id="blog-details"<?php if ( (int) bp_get_signup_with_blog_value() ) : ?>class="show"<?php endif; ?>>
+
+						<label for="signup_blog_url"><?php _e( 'Blog URL', 'buddypress' ); ?> <?php _e( '(required)', 'buddypress' ); ?></label>
+						<?php
+
+						/**
+						 * Fires and displays any member registration blog URL errors.
+						 *
+						 * @since 1.1.0
+						 */
+						do_action( 'bp_signup_blog_url_errors' ); ?>
+
+						<?php if ( is_subdomain_install() ) : ?>
+							http:// <input type="text" name="signup_blog_url" id="signup_blog_url" value="<?php bp_signup_blog_url_value(); ?>" /> .<?php bp_signup_subdomain_base(); ?>
+						<?php else : ?>
+							<?php echo home_url( '/' ); ?> <input type="text" name="signup_blog_url" id="signup_blog_url" value="<?php bp_signup_blog_url_value(); ?>" />
+						<?php endif; ?>
+
+						<label for="signup_blog_title"><?php _e( 'Site Title', 'buddypress' ); ?> <?php _e( '(required)', 'buddypress' ); ?></label>
+						<?php
+
+						/**
+						 * Fires and displays any member registration blog title errors.
+						 *
+						 * @since 1.1.0
+						 */
+						do_action( 'bp_signup_blog_title_errors' ); ?>
+						<input type="text" name="signup_blog_title" id="signup_blog_title" value="<?php bp_signup_blog_title_value(); ?>" />
+
+						<span class="label"><?php _e( 'I would like my site to appear in search engines, and in public listings around this network.', 'buddypress' ); ?></span>
+						<?php
+
+						/**
+						 * Fires and displays any member registration blog privacy errors.
+						 *
+						 * @since 1.1.0
+						 */
+						do_action( 'bp_signup_blog_privacy_errors' ); ?>
+
+						<label for="signup_blog_privacy_public"><input type="radio" name="signup_blog_privacy" id="signup_blog_privacy_public" value="public"<?php if ( 'public' == bp_get_signup_blog_privacy_value() || !bp_get_signup_blog_privacy_value() ) : ?> checked="checked"<?php endif; ?> /> <?php _e( 'Yes', 'buddypress' ); ?></label>
+						<label for="signup_blog_privacy_private"><input type="radio" name="signup_blog_privacy" id="signup_blog_privacy_private" value="private"<?php if ( 'private' == bp_get_signup_blog_privacy_value() ) : ?> checked="checked"<?php endif; ?> /> <?php _e( 'No', 'buddypress' ); ?></label>
+
+						<?php
+
+						/**
+						 * Fires and displays any extra member registration blog details fields.
+						 *
+						 * @since 1.9.0
+						 */
+						do_action( 'bp_blog_details_fields' ); ?>
+
+					</div>
+
+				</div><!-- #blog-details-section -->
+
+				<?php
+
+				/**
+				 * Fires after the display of member registration blog details fields.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_after_blog_details_fields' ); ?>
+
+			<?php endif; ?>
+
+			<?php
+
+			/**
+			 * Fires before the display of the registration submit buttons.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'bp_before_registration_submit_buttons' ); ?>
+
+			<div class="submit">
+				<input type="submit" name="signup_submit" id="signup_submit" value="<?php esc_attr_e( 'Complete Sign Up', 'buddypress' ); ?>" />
+			</div>
+
+			<?php
+
+			/**
+			 * Fires after the display of the registration submit buttons.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'bp_after_registration_submit_buttons' ); ?>
+
+			<?php wp_nonce_field( 'bp_new_signup' ); ?>
+
+		<?php endif; // request-details signup step ?>
+
+		<?php if ( 'completed-confirmation' == bp_get_current_signup_step() ) : ?>
+
+			<?php
+
+			/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+			do_action( 'template_notices' ); ?>
+			<?php
+
+			/**
+			 * Fires before the display of the registration confirmed messages.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'bp_before_registration_confirmed' ); ?>
+
+			<?php if ( bp_registration_needs_activation() ) : ?>
+				<p><?php _e( 'You have successfully created your account! To begin using this site you will need to activate your account via the email we have just sent to your address.', 'buddypress' ); ?></p>
+			<?php else : ?>
+				<p><?php _e( 'You have successfully created your account! Please log in using the username and password you have just created.', 'buddypress' ); ?></p>
+			<?php endif; ?>
+
+			<?php
+
+			/**
+			 * Fires after the display of the registration confirmed messages.
+			 *
+			 * @since 1.5.0
+			 */
+			do_action( 'bp_after_registration_confirmed' ); ?>
+
+		<?php endif; // completed-confirmation signup step ?>
+
+		<?php
+
+		/**
+		 * Fires and displays any custom signup steps.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_custom_signup_steps' ); ?>
+
+		</form>
+
+	</div>
+
+	<?php
+
+	/**
+	 * Fires at the bottom of the BuddyPress member registration page template.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_register_page' ); ?>
+
+</div><!-- #buddypress -->
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/activity.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/activity.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/activity.php	(revision 10410)
@@ -0,0 +1,77 @@
+<?php
+/**
+ * BuddyPress - Users Activity
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+	<ul>
+
+		<?php bp_get_options_nav(); ?>
+
+		<li id="activity-filter-select" class="last">
+			<label for="activity-filter-by"><?php _e( 'Show:', 'buddypress' ); ?></label>
+			<select id="activity-filter-by">
+				<option value="-1"><?php _e( '&mdash; Everything &mdash;', 'buddypress' ); ?></option>
+
+				<?php bp_activity_show_filters(); ?>
+
+				<?php
+
+				/**
+				 * Fires inside the select input for member activity filter options.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_member_activity_filter_options' ); ?>
+
+			</select>
+		</li>
+	</ul>
+</div><!-- .item-list-tabs -->
+
+<?php
+
+/**
+ * Fires before the display of the member activity post form.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_member_activity_post_form' ); ?>
+
+<?php
+if ( is_user_logged_in() && bp_is_my_profile() && ( !bp_current_action() || bp_is_current_action( 'just-me' ) ) )
+	bp_get_template_part( 'activity/post-form' );
+
+/**
+ * Fires after the display of the member activity post form.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_member_activity_post_form' );
+
+/**
+ * Fires before the display of the member activities list.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_member_activity_content' ); ?>
+
+<div class="activity">
+
+	<?php bp_get_template_part( 'activity/activity-loop' ) ?>
+
+</div><!-- .activity -->
+
+<?php
+
+/**
+ * Fires after the display of the member activities list.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_member_activity_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/blogs.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/blogs.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/blogs.php	(revision 10410)
@@ -0,0 +1,71 @@
+<?php
+/**
+ * BuddyPress - Users Blogs
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div class="item-list-tabs" id="subnav" role="navigation">
+	<ul>
+
+		<?php bp_get_options_nav(); ?>
+
+		<li id="blogs-order-select" class="last filter">
+
+			<label for="blogs-order-by"><?php _e( 'Order By:', 'buddypress' ); ?></label>
+			<select id="blogs-order-by">
+				<option value="active"><?php _e( 'Last Active', 'buddypress' ); ?></option>
+				<option value="newest"><?php _e( 'Newest', 'buddypress' ); ?></option>
+				<option value="alphabetical"><?php _e( 'Alphabetical', 'buddypress' ); ?></option>
+
+				<?php
+
+				/**
+				 * Fires inside the members blogs order options select input.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_member_blog_order_options' ); ?>
+
+			</select>
+		</li>
+	</ul>
+</div><!-- .item-list-tabs -->
+
+<?php
+switch ( bp_current_action() ) :
+
+	// Home/My Blogs
+	case 'my-sites' :
+
+		/**
+		 * Fires before the display of member blogs content.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_before_member_blogs_content' ); ?>
+
+		<div class="blogs myblogs">
+
+			<?php bp_get_template_part( 'blogs/blogs-loop' ) ?>
+
+		</div><!-- .blogs.myblogs -->
+
+		<?php
+
+		/**
+		 * Fires after the display of member blogs content.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_after_member_blogs_content' );
+		break;
+
+	// Any other
+	default :
+		bp_get_template_part( 'members/single/plugins' );
+		break;
+endswitch;
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/cover-image-header.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/cover-image-header.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/cover-image-header.php	(revision 10410)
@@ -0,0 +1,103 @@
+<?php
+/**
+ * BuddyPress - Users Cover Image Header
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<?php
+
+/**
+ * Fires before the display of a member's header.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_member_header' ); ?>
+
+<div id="cover-image-container">
+	<a id="header-cover-image" href="<?php bp_displayed_user_link(); ?>"></a>
+
+	<div id="item-header-cover-image">
+		<div id="item-header-avatar">
+			<a href="<?php bp_displayed_user_link(); ?>">
+
+				<?php bp_displayed_user_avatar( 'type=full' ); ?>
+
+			</a>
+		</div><!-- #item-header-avatar -->
+
+		<div id="item-header-content">
+
+			<?php if ( bp_is_active( 'activity' ) && bp_activity_do_mentions() ) : ?>
+				<h2 class="user-nicename">@<?php bp_displayed_user_mentionname(); ?></h2>
+			<?php endif; ?>
+
+			<div id="item-buttons"><?php
+
+				/**
+				 * Fires in the member header actions section.
+				 *
+				 * @since 1.2.6
+				 */
+				do_action( 'bp_member_header_actions' ); ?></div><!-- #item-buttons -->
+
+			<span class="activity"><?php bp_last_activity( bp_displayed_user_id() ); ?></span>
+
+			<?php
+
+			/**
+			 * Fires before the display of the member's header meta.
+			 *
+			 * @since 1.2.0
+			 */
+			do_action( 'bp_before_member_header_meta' ); ?>
+
+			<div id="item-meta">
+
+				<?php if ( bp_is_active( 'activity' ) ) : ?>
+
+					<div id="latest-update">
+
+						<?php bp_activity_latest_update( bp_displayed_user_id() ); ?>
+
+					</div>
+
+				<?php endif; ?>
+
+				<?php
+
+				 /**
+				  * Fires after the group header actions section.
+				  *
+				  * If you'd like to show specific profile fields here use:
+				  * bp_member_profile_data( 'field=About Me' ); -- Pass the name of the field
+				  *
+				  * @since 1.2.0
+				  */
+				 do_action( 'bp_profile_header_meta' );
+
+				 ?>
+
+			</div><!-- #item-meta -->
+
+		</div><!-- #item-header-content -->
+
+	</div><!-- #item-header-cover-image -->
+</div><!-- #cover-image-container -->
+
+<?php
+
+/**
+ * Fires after the display of a member's header.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_member_header' ); ?>
+
+<?php
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+do_action( 'template_notices' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/forums.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/forums.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/forums.php	(revision 10410)
@@ -0,0 +1,66 @@
+<?php
+/**
+ * BuddyPress - Users Forums
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+	<ul>
+		<?php bp_get_options_nav(); ?>
+
+		<li id="forums-order-select" class="last filter">
+
+			<label for="forums-order-by"><?php _e( 'Order By:', 'buddypress' ); ?></label>
+			<select id="forums-order-by">
+				<option value="active"><?php _e( 'Last Active', 'buddypress' ); ?></option>
+				<option value="popular"><?php _e( 'Most Posts', 'buddypress' ); ?></option>
+				<option value="unreplied"><?php _e( 'Unreplied', 'buddypress' ); ?></option>
+
+				<?php
+
+				/**
+				 * Fires inside the members forums order options select input.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_forums_directory_order_options' ); ?>
+
+			</select>
+		</li>
+	</ul>
+</div><!-- .item-list-tabs -->
+
+<?php
+
+if ( bp_is_current_action( 'favorites' ) ) :
+	bp_get_template_part( 'members/single/forums/topics' );
+
+else :
+
+	/**
+	 * Fires before the display of member forums content.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_before_member_forums_content' ); ?>
+
+	<div class="forums myforums">
+
+		<?php bp_get_template_part( 'forums/forums-loop' ) ?>
+
+	</div>
+
+	<?php
+
+	/**
+	 * Fires after the display of member forums content.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_after_member_forums_content' ); ?>
+
+<?php endif; ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/forums/topics.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/forums/topics.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/forums/topics.php	(revision 10410)
@@ -0,0 +1,8 @@
+<?php
+/**
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/friends.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/friends.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/friends.php	(revision 10410)
@@ -0,0 +1,79 @@
+<?php
+/**
+ * BuddyPress - Users Friends
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+	<ul>
+		<?php if ( bp_is_my_profile() ) bp_get_options_nav(); ?>
+
+		<?php if ( !bp_is_current_action( 'requests' ) ) : ?>
+
+			<li id="members-order-select" class="last filter">
+
+				<label for="members-friends"><?php _e( 'Order By:', 'buddypress' ); ?></label>
+				<select id="members-friends">
+					<option value="active"><?php _e( 'Last Active', 'buddypress' ); ?></option>
+					<option value="newest"><?php _e( 'Newest Registered', 'buddypress' ); ?></option>
+					<option value="alphabetical"><?php _e( 'Alphabetical', 'buddypress' ); ?></option>
+
+					<?php
+
+					/**
+					 * Fires inside the members friends order options select input.
+					 *
+					 * @since 2.0.0
+					 */
+					do_action( 'bp_member_friends_order_options' ); ?>
+
+				</select>
+			</li>
+
+		<?php endif; ?>
+
+	</ul>
+</div>
+
+<?php
+switch ( bp_current_action() ) :
+
+	// Home/My Friends
+	case 'my-friends' :
+
+		/**
+		 * Fires before the display of member friends content.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_before_member_friends_content' ); ?>
+
+		<div class="members friends">
+
+			<?php bp_get_template_part( 'members/members-loop' ) ?>
+
+		</div><!-- .members.friends -->
+
+		<?php
+
+		/**
+		 * Fires after the display of member friends content.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_after_member_friends_content' );
+		break;
+
+	case 'requests' :
+		bp_get_template_part( 'members/single/friends/requests' );
+		break;
+
+	// Any other
+	default :
+		bp_get_template_part( 'members/single/plugins' );
+		break;
+endswitch;
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/friends/requests.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/friends/requests.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/friends/requests.php	(revision 10410)
@@ -0,0 +1,114 @@
+<?php
+/**
+ * BuddyPress - Members Friends Requests
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of member friend requests content.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_member_friend_requests_content' ); ?>
+
+<?php if ( bp_has_members( 'type=alphabetical&include=' . bp_get_friendship_requests() ) ) : ?>
+
+	<div id="pag-top" class="pagination no-ajax">
+
+		<div class="pag-count" id="member-dir-count-top">
+
+			<?php bp_members_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="member-dir-pag-top">
+
+			<?php bp_members_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+	<ul id="friend-list" class="item-list">
+		<?php while ( bp_members() ) : bp_the_member(); ?>
+
+			<li id="friendship-<?php bp_friend_friendship_id(); ?>">
+				<div class="item-avatar">
+					<a href="<?php bp_member_link(); ?>"><?php bp_member_avatar(); ?></a>
+				</div>
+
+				<div class="item">
+					<div class="item-title"><a href="<?php bp_member_link(); ?>"><?php bp_member_name(); ?></a></div>
+					<div class="item-meta"><span class="activity"><?php bp_member_last_active(); ?></span></div>
+
+					<?php
+					/**
+					 * Fires inside the display of a member friend request item.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_friend_requests_item' );
+					?>
+				</div>
+
+				<div class="action">
+					<a class="button accept" href="<?php bp_friend_accept_request_link(); ?>"><?php _e( 'Accept', 'buddypress' ); ?></a> &nbsp;
+					<a class="button reject" href="<?php bp_friend_reject_request_link(); ?>"><?php _e( 'Reject', 'buddypress' ); ?></a>
+
+					<?php
+
+					/**
+					 * Fires inside the member friend request actions markup.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_friend_requests_item_action' ); ?>
+				</div>
+			</li>
+
+		<?php endwhile; ?>
+	</ul>
+
+	<?php
+
+	/**
+	 * Fires and displays the member friend requests content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_friend_requests_content' ); ?>
+
+	<div id="pag-bottom" class="pagination no-ajax">
+
+		<div class="pag-count" id="member-dir-count-bottom">
+
+			<?php bp_members_pagination_count(); ?>
+
+		</div>
+
+		<div class="pagination-links" id="member-dir-pag-bottom">
+
+			<?php bp_members_pagination_links(); ?>
+
+		</div>
+
+	</div>
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'You have no pending friendship requests.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif;?>
+
+<?php
+
+/**
+ * Fires after the display of member friend requests content.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_member_friend_requests_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/groups.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/groups.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/groups.php	(revision 10410)
@@ -0,0 +1,82 @@
+<?php
+/**
+ * BuddyPress - Users Groups
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+	<ul>
+		<?php if ( bp_is_my_profile() ) bp_get_options_nav(); ?>
+
+		<?php if ( !bp_is_current_action( 'invites' ) ) : ?>
+
+			<li id="groups-order-select" class="last filter">
+
+				<label for="groups-order-by"><?php _e( 'Order By:', 'buddypress' ); ?></label>
+				<select id="groups-order-by">
+					<option value="active"><?php _e( 'Last Active', 'buddypress' ); ?></option>
+					<option value="popular"><?php _e( 'Most Members', 'buddypress' ); ?></option>
+					<option value="newest"><?php _e( 'Newly Created', 'buddypress' ); ?></option>
+					<option value="alphabetical"><?php _e( 'Alphabetical', 'buddypress' ); ?></option>
+
+					<?php
+
+					/**
+					 * Fires inside the members group order options select input.
+					 *
+					 * @since 1.2.0
+					 */
+					do_action( 'bp_member_group_order_options' ); ?>
+
+				</select>
+			</li>
+
+		<?php endif; ?>
+
+	</ul>
+</div><!-- .item-list-tabs -->
+
+<?php
+
+switch ( bp_current_action() ) :
+
+	// Home/My Groups
+	case 'my-groups' :
+
+		/**
+		 * Fires before the display of member groups content.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_before_member_groups_content' ); ?>
+
+		<div class="groups mygroups">
+
+			<?php bp_get_template_part( 'groups/groups-loop' ); ?>
+
+		</div>
+
+		<?php
+
+		/**
+		 * Fires after the display of member groups content.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_after_member_groups_content' );
+		break;
+
+	// Group Invitations
+	case 'invites' :
+		bp_get_template_part( 'members/single/groups/invites' );
+		break;
+
+	// Any other
+	default :
+		bp_get_template_part( 'members/single/plugins' );
+		break;
+endswitch;
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/groups/invites.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/groups/invites.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/groups/invites.php	(revision 10410)
@@ -0,0 +1,78 @@
+<?php
+/**
+ * BuddyPress - Members Single Group Invites
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of member group invites content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_before_group_invites_content' ); ?>
+
+<?php if ( bp_has_groups( 'type=invites&user_id=' . bp_loggedin_user_id() ) ) : ?>
+
+	<ul id="group-list" class="invites item-list">
+
+		<?php while ( bp_groups() ) : bp_the_group(); ?>
+
+			<li>
+				<?php if ( ! bp_disable_group_avatar_uploads() ) : ?>
+					<div class="item-avatar">
+						<a href="<?php bp_group_permalink(); ?>"><?php bp_group_avatar( 'type=thumb&width=50&height=50' ); ?></a>
+					</div>
+				<?php endif; ?>
+
+				<h4><a href="<?php bp_group_permalink(); ?>"><?php bp_group_name(); ?></a><span class="small"> - <?php printf( _nx( '%d member', '%d members', bp_get_group_total_members( false ),'Group member count', 'buddypress' ), bp_get_group_total_members( false )  ); ?></span></h4>
+
+				<p class="desc">
+					<?php bp_group_description_excerpt(); ?>
+				</p>
+
+				<?php
+
+				/**
+				 * Fires inside the display of a member group invite item.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_group_invites_item' ); ?>
+
+				<div class="action">
+					<a class="button accept" href="<?php bp_group_accept_invite_link(); ?>"><?php _e( 'Accept', 'buddypress' ); ?></a> &nbsp;
+					<a class="button reject confirm" href="<?php bp_group_reject_invite_link(); ?>"><?php _e( 'Reject', 'buddypress' ); ?></a>
+
+					<?php
+
+					/**
+					 * Fires inside the member group item action markup.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_group_invites_item_action' ); ?>
+
+				</div>
+			</li>
+
+		<?php endwhile; ?>
+	</ul>
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'You have no outstanding group invites.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif;?>
+
+<?php
+
+/**
+ * Fires after the display of member group invites content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_after_group_invites_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/home.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/home.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/home.php	(revision 10410)
@@ -0,0 +1,118 @@
+<?php
+/**
+ * BuddyPress - Members Home
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div id="buddypress">
+
+	<?php
+
+	/**
+	 * Fires before the display of member home content.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_before_member_home_content' ); ?>
+
+	<div id="item-header" role="complementary">
+
+		<?php
+		/**
+		 * If the cover image feature is enabled, use a specific header
+		 */
+		if ( bp_displayed_user_use_cover_image_header() ) :
+			bp_get_template_part( 'members/single/cover-image-header' );
+		else :
+			bp_get_template_part( 'members/single/member-header' );
+		endif;
+		?>
+
+	</div><!-- #item-header -->
+
+	<div id="item-nav">
+		<div class="item-list-tabs no-ajax" id="object-nav" role="navigation">
+			<ul>
+
+				<?php bp_get_displayed_user_nav(); ?>
+
+				<?php
+
+				/**
+				 * Fires after the display of member options navigation.
+				 *
+				 * @since 1.2.4
+				 */
+				do_action( 'bp_member_options_nav' ); ?>
+
+			</ul>
+		</div>
+	</div><!-- #item-nav -->
+
+	<div id="item-body">
+
+		<?php
+
+		/**
+		 * Fires before the display of member body content.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_before_member_body' );
+
+		if ( bp_is_user_activity() || !bp_current_component() ) :
+			bp_get_template_part( 'members/single/activity' );
+
+		elseif ( bp_is_user_blogs() ) :
+			bp_get_template_part( 'members/single/blogs'    );
+
+		elseif ( bp_is_user_friends() ) :
+			bp_get_template_part( 'members/single/friends'  );
+
+		elseif ( bp_is_user_groups() ) :
+			bp_get_template_part( 'members/single/groups'   );
+
+		elseif ( bp_is_user_messages() ) :
+			bp_get_template_part( 'members/single/messages' );
+
+		elseif ( bp_is_user_profile() ) :
+			bp_get_template_part( 'members/single/profile'  );
+
+		elseif ( bp_is_user_forums() ) :
+			bp_get_template_part( 'members/single/forums'   );
+
+		elseif ( bp_is_user_notifications() ) :
+			bp_get_template_part( 'members/single/notifications' );
+
+		elseif ( bp_is_user_settings() ) :
+			bp_get_template_part( 'members/single/settings' );
+
+		// If nothing sticks, load a generic template
+		else :
+			bp_get_template_part( 'members/single/plugins'  );
+
+		endif;
+
+		/**
+		 * Fires after the display of member body content.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_after_member_body' ); ?>
+
+	</div><!-- #item-body -->
+
+	<?php
+
+	/**
+	 * Fires after the display of member home content.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_after_member_home_content' ); ?>
+
+</div><!-- #buddypress -->
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/member-header.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/member-header.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/member-header.php	(revision 10410)
@@ -0,0 +1,100 @@
+<?php
+/**
+ * BuddyPress - Users Header
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<?php
+
+/**
+ * Fires before the display of a member's header.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_member_header' ); ?>
+
+<div id="item-header-avatar">
+	<a href="<?php bp_displayed_user_link(); ?>">
+
+		<?php bp_displayed_user_avatar( 'type=full' ); ?>
+
+	</a>
+</div><!-- #item-header-avatar -->
+
+<div id="item-header-content">
+
+	<?php if ( bp_is_active( 'activity' ) && bp_activity_do_mentions() ) : ?>
+		<h2 class="user-nicename">@<?php bp_displayed_user_mentionname(); ?></h2>
+	<?php endif; ?>
+
+	<span class="activity"><?php bp_last_activity( bp_displayed_user_id() ); ?></span>
+
+	<?php
+
+	/**
+	 * Fires before the display of the member's header meta.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_before_member_header_meta' ); ?>
+
+	<div id="item-meta">
+
+		<?php if ( bp_is_active( 'activity' ) ) : ?>
+
+			<div id="latest-update">
+
+				<?php bp_activity_latest_update( bp_displayed_user_id() ); ?>
+
+			</div>
+
+		<?php endif; ?>
+
+		<div id="item-buttons">
+
+			<?php
+
+			/**
+			 * Fires in the member header actions section.
+			 *
+			 * @since 1.2.6
+			 */
+			do_action( 'bp_member_header_actions' ); ?>
+
+		</div><!-- #item-buttons -->
+
+		<?php
+
+		 /**
+		  * Fires after the group header actions section.
+		  *
+		  * If you'd like to show specific profile fields here use:
+		  * bp_member_profile_data( 'field=About Me' ); -- Pass the name of the field
+		  *
+		  * @since 1.2.0
+		  */
+		 do_action( 'bp_profile_header_meta' );
+
+		 ?>
+
+	</div><!-- #item-meta -->
+
+</div><!-- #item-header-content -->
+
+<?php
+
+/**
+ * Fires after the display of a member's header.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_member_header' ); ?>
+
+<?php
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/activity/index.php */
+do_action( 'template_notices' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages.php	(revision 10410)
@@ -0,0 +1,92 @@
+<?php
+/**
+ * BuddyPress - Users Messages
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+	<ul>
+
+		<?php bp_get_options_nav(); ?>
+
+	</ul>
+
+	<?php if ( bp_is_messages_inbox() || bp_is_messages_sentbox() ) : ?>
+
+		<div class="message-search"><?php bp_message_search_form(); ?></div>
+
+	<?php endif; ?>
+
+</div><!-- .item-list-tabs -->
+
+<?php
+switch ( bp_current_action() ) :
+
+	// Inbox/Sentbox
+	case 'inbox'   :
+	case 'sentbox' :
+
+		/**
+		 * Fires before the member messages content for inbox and sentbox.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_before_member_messages_content' ); ?>
+
+		<div class="messages">
+			<?php bp_get_template_part( 'members/single/messages/messages-loop' ); ?>
+		</div><!-- .messages -->
+
+		<?php
+
+		/**
+		 * Fires after the member messages content for inbox and sentbox.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_after_member_messages_content' );
+		break;
+
+	// Single Message View
+	case 'view' :
+		bp_get_template_part( 'members/single/messages/single' );
+		break;
+
+	// Compose
+	case 'compose' :
+		bp_get_template_part( 'members/single/messages/compose' );
+		break;
+
+	// Sitewide Notices
+	case 'notices' :
+
+		/**
+		 * Fires before the member messages content for notices.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_before_member_messages_content' ); ?>
+
+		<div class="messages">
+			<?php bp_get_template_part( 'members/single/messages/notices-loop' ); ?>
+		</div><!-- .messages -->
+
+		<?php
+
+		/**
+		 * Fires after the member messages content for inbox and sentbox.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_after_member_messages_content' );
+		break;
+
+	// Any other
+	default :
+		bp_get_template_part( 'members/single/plugins' );
+		break;
+endswitch;
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/compose.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/compose.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/compose.php	(revision 10410)
@@ -0,0 +1,60 @@
+<?php
+/**
+ * BuddyPress - Members Single Messages Compose
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+<form action="<?php bp_messages_form_action('compose' ); ?>" method="post" id="send_message_form" class="standard-form" enctype="multipart/form-data">
+
+	<?php
+
+	/**
+	 * Fires before the display of message compose content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_messages_compose_content' ); ?>
+
+	<label for="send-to-input"><?php _e("Send To (Username or Friend's Name)", 'buddypress' ); ?></label>
+	<ul class="first acfb-holder">
+		<li>
+			<?php bp_message_get_recipient_tabs(); ?>
+			<input type="text" name="send-to-input" class="send-to-input" id="send-to-input" />
+		</li>
+	</ul>
+
+	<?php if ( bp_current_user_can( 'bp_moderate' ) ) : ?>
+		<p><label for="send-notice"><input type="checkbox" id="send-notice" name="send-notice" value="1" /> <?php _e( "This is a notice to all users.", "buddypress" ); ?></label></p>
+	<?php endif; ?>
+
+	<label for="subject"><?php _e( 'Subject', 'buddypress' ); ?></label>
+	<input type="text" name="subject" id="subject" value="<?php bp_messages_subject_value(); ?>" />
+
+	<label for="message_content"><?php _e( 'Message', 'buddypress' ); ?></label>
+	<textarea name="content" id="message_content" rows="15" cols="40"><?php bp_messages_content_value(); ?></textarea>
+
+	<input type="hidden" name="send_to_usernames" id="send-to-usernames" value="<?php bp_message_get_recipient_usernames(); ?>" class="<?php bp_message_get_recipient_usernames(); ?>" />
+
+	<?php
+
+	/**
+	 * Fires after the display of message compose content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_messages_compose_content' ); ?>
+
+	<div class="submit">
+		<input type="submit" value="<?php esc_attr_e( "Send Message", 'buddypress' ); ?>" name="send" id="send" />
+	</div>
+
+	<?php wp_nonce_field( 'messages_send_message' ); ?>
+</form>
+
+<script type="text/javascript">
+	document.getElementById("send-to-input").focus();
+</script>
+
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/message.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/message.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/message.php	(revision 10410)
@@ -0,0 +1,86 @@
+<?php
+/**
+ * BuddyPress - Private Message Content.
+ *
+ * This template is used in /messages/single.php during the message loop to
+ * display each message and when a new message is created via AJAX.
+ *
+ * @since 2.4.0
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+			<div class="message-box <?php bp_the_thread_message_css_class(); ?>">
+
+				<div class="message-metadata">
+
+					<?php
+
+					/**
+					 * Fires before the single message header is displayed.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_before_message_meta' ); ?>
+
+					<?php bp_the_thread_message_sender_avatar( 'type=thumb&width=30&height=30' ); ?>
+
+					<?php if ( bp_get_the_thread_message_sender_link() ) : ?>
+
+						<strong><a href="<?php bp_the_thread_message_sender_link(); ?>" title="<?php bp_the_thread_message_sender_name(); ?>"><?php bp_the_thread_message_sender_name(); ?></a></strong>
+
+					<?php else : ?>
+
+						<strong><?php bp_the_thread_message_sender_name(); ?></strong>
+
+					<?php endif; ?>
+
+					<span class="activity"><?php bp_the_thread_message_time_since(); ?></span>
+
+					<?php if ( bp_is_active( 'messages', 'star' ) ) : ?>
+						<div class="message-star-actions">
+							<?php bp_the_message_star_action_link(); ?>
+						</div>
+					<?php endif; ?>
+
+					<?php
+
+					/**
+					 * Fires after the single message header is displayed.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_after_message_meta' ); ?>
+
+				</div><!-- .message-metadata -->
+
+				<?php
+
+				/**
+				 * Fires before the message content for a private message.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_before_message_content' ); ?>
+
+				<div class="message-content">
+
+					<?php bp_the_thread_message_content(); ?>
+
+				</div><!-- .message-content -->
+
+				<?php
+
+				/**
+				 * Fires after the message content for a private message.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_after_message_content' ); ?>
+
+				<div class="clear"></div>
+
+			</div><!-- .message-box -->
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/messages-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/messages-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/messages-loop.php	(revision 10410)
@@ -0,0 +1,183 @@
+<?php
+/**
+ * BuddyPress - Members Messages Loop
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the members messages loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_member_messages_loop' ); ?>
+
+<?php if ( bp_has_message_threads( bp_ajax_querystring( 'messages' ) ) ) : ?>
+
+	<div class="pagination no-ajax" id="user-pag">
+
+		<div class="pag-count" id="messages-dir-count">
+			<?php bp_messages_pagination_count(); ?>
+		</div>
+
+		<div class="pagination-links" id="messages-dir-pag">
+			<?php bp_messages_pagination(); ?>
+		</div>
+
+	</div><!-- .pagination -->
+
+	<?php
+
+	/**
+	 * Fires after the members messages pagination display.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_after_member_messages_pagination' ); ?>
+
+	<?php
+
+	/**
+	 * Fires before the members messages threads.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_before_member_messages_threads' ); ?>
+
+	<form action="<?php echo bp_loggedin_user_domain() . bp_get_messages_slug() . '/' . bp_current_action() ?>/bulk-manage/" method="post" id="messages-bulk-management">
+
+		<table id="message-threads" class="messages-notices">
+
+			<thead>
+				<tr>
+					<th scope="col" class="thread-checkbox bulk-select-all"><input id="select-all-messages" type="checkbox"><label class="bp-screen-reader-text" for="select-all-messages"><?php _e( 'Select all', 'buddypress' ); ?></label></th>
+					<th scope="col" class="thread-from"><?php _e( 'From', 'buddypress' ); ?></th>
+					<th scope="col" class="thread-info"><?php _e( 'Subject', 'buddypress' ); ?></th>
+
+					<?php
+
+					/**
+					 * Fires inside the messages box table header to add a new column.
+					 *
+					 * This is to primarily add a <th> cell to the messages box table header. Use
+					 * the related 'bp_messages_inbox_list_item' hook to add a <td> cell.
+					 *
+					 * @since 2.3.0
+					 */
+					do_action( 'bp_messages_inbox_list_header' ); ?>
+
+					<?php if ( bp_is_active( 'messages', 'star' ) ) : ?>
+						<th scope="col" class="thread-star"><span class="message-action-star"><span class="icon"></span> <span class="screen-reader-text"><?php _e( 'Star', 'buddypress' ); ?></span></span></th>
+					<?php endif; ?>
+
+					<th scope="col" class="thread-options"><?php _e( 'Actions', 'buddypress' ); ?></th>
+				</tr>
+			</thead>
+
+			<tbody>
+
+				<?php while ( bp_message_threads() ) : bp_message_thread(); ?>
+
+					<tr id="m-<?php bp_message_thread_id(); ?>" class="<?php bp_message_css_class(); ?><?php if ( bp_message_thread_has_unread() ) : ?> unread<?php else: ?> read<?php endif; ?>">
+						<td class="bulk-select-check">
+							<label for="bp-message-thread-<?php bp_message_thread_id(); ?>"><input type="checkbox" name="message_ids[]" id="bp-message-thread-<?php bp_message_thread_id(); ?>" class="message-check" value="<?php bp_message_thread_id(); ?>" /><span class="bp-screen-reader-text"><?php _e( 'Select this message', 'buddypress' ); ?></span></label>
+						</td>
+
+						<?php if ( 'sentbox' != bp_current_action() ) : ?>
+							<td class="thread-from">
+								<?php bp_message_thread_avatar( array( 'width' => 25, 'height' => 25 ) ); ?>
+								<span class="from"><?php _e( 'From:', 'buddypress' ); ?></span> <?php bp_message_thread_from(); ?>
+								<?php bp_message_thread_total_and_unread_count(); ?>
+								<span class="activity"><?php bp_message_thread_last_post_date(); ?></span>
+							</td>
+						<?php else: ?>
+							<td class="thread-from">
+								<?php bp_message_thread_avatar( array( 'width' => 25, 'height' => 25 ) ); ?>
+								<span class="to"><?php _e( 'To:', 'buddypress' ); ?></span> <?php bp_message_thread_to(); ?>
+								<?php bp_message_thread_total_and_unread_count(); ?>
+								<span class="activity"><?php bp_message_thread_last_post_date(); ?></span>
+							</td>
+						<?php endif; ?>
+
+						<td class="thread-info">
+							<p><a href="<?php bp_message_thread_view_link(); ?>" title="<?php esc_attr_e( "View Message", "buddypress" ); ?>"><?php bp_message_thread_subject(); ?></a></p>
+							<p class="thread-excerpt"><?php bp_message_thread_excerpt(); ?></p>
+						</td>
+
+						<?php
+
+						/**
+						 * Fires inside the messages box table row to add a new column.
+						 *
+						 * This is to primarily add a <td> cell to the message box table. Use the
+						 * related 'bp_messages_inbox_list_header' hook to add a <th> header cell.
+						 *
+						 * @since 1.1.0
+						 */
+						do_action( 'bp_messages_inbox_list_item' ); ?>
+
+						<?php if ( bp_is_active( 'messages', 'star' ) ) : ?>
+							<td class="thread-star">
+								<?php bp_the_message_star_action_link( array( 'thread_id' => bp_get_message_thread_id() ) ); ?>
+							</td>
+						<?php endif; ?>
+
+						<td class="thread-options">
+							<?php if ( bp_message_thread_has_unread() ) : ?>
+								<a class="read" href="<?php bp_the_message_thread_mark_read_url();?>"><?php _e( 'Read', 'buddypress' ); ?></a>
+							<?php else : ?>
+								<a class="unread" href="<?php bp_the_message_thread_mark_unread_url();?>"><?php _e( 'Unread', 'buddypress' ); ?></a>
+							<?php endif; ?>
+							 |
+							<a class="delete" href="<?php bp_message_thread_delete_link(); ?>"><?php _e( 'Delete', 'buddypress' ); ?></a>
+						</td>
+					</tr>
+
+				<?php endwhile; ?>
+
+			</tbody>
+
+		</table><!-- #message-threads -->
+
+		<div class="messages-options-nav">
+			<?php bp_messages_bulk_management_dropdown(); ?>
+		</div><!-- .messages-options-nav -->
+
+		<?php wp_nonce_field( 'messages_bulk_nonce', 'messages_bulk_nonce' ); ?>
+	</form>
+
+	<?php
+
+	/**
+	 * Fires after the members messages threads.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_after_member_messages_threads' ); ?>
+
+	<?php
+
+	/**
+	 * Fires and displays member messages options.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_after_member_messages_options' ); ?>
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'Sorry, no messages were found.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif;?>
+
+<?php
+
+/**
+ * Fires after the members messages loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_member_messages_loop' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/notices-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/notices-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/notices-loop.php	(revision 10410)
@@ -0,0 +1,107 @@
+<?php
+/**
+ * BuddyPress - Members Single Messages Notice Loop
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the members notices loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_notices_loop' ); ?>
+
+<?php if ( bp_has_message_threads() ) : ?>
+
+	<div class="pagination no-ajax" id="user-pag">
+
+		<div class="pag-count" id="messages-dir-count">
+			<?php bp_messages_pagination_count(); ?>
+		</div>
+
+		<div class="pagination-links" id="messages-dir-pag">
+			<?php bp_messages_pagination(); ?>
+		</div>
+
+	</div><!-- .pagination -->
+
+	<?php
+
+	/**
+	 * Fires after the members notices pagination display.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_after_notices_pagination' ); ?>
+	<?php
+
+	/**
+	 * Fires before the members notice items.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_before_notices' ); ?>
+
+	<table id="message-threads" class="messages-notices sitewide-notices">
+		<?php while ( bp_message_threads() ) : bp_message_thread(); ?>
+			<tr id="notice-<?php bp_message_notice_id(); ?>" class="<?php bp_message_css_class(); ?>">
+				<td width="1%"></td>
+				<td width="38%">
+					<strong><?php bp_message_notice_subject(); ?></strong>
+					<?php bp_message_notice_text(); ?>
+				</td>
+				<td width="21%">
+
+					<?php if ( bp_messages_is_active_notice() ) : ?>
+
+						<strong><?php bp_messages_is_active_notice(); ?></strong>
+
+					<?php endif; ?>
+
+					<span class="activity"><?php _e( 'Sent:', 'buddypress' ); ?> <?php bp_message_notice_post_date(); ?></span>
+				</td>
+
+				<?php
+
+				/**
+				 * Fires inside the display of a member notice list item.
+				 *
+				 * @since 1.2.0
+				 */
+				do_action( 'bp_notices_list_item' ); ?>
+
+				<td width="10%">
+					<a class="button" href="<?php bp_message_activate_deactivate_link(); ?>" class="confirm"><?php bp_message_activate_deactivate_text(); ?></a>
+					<a class="button" href="<?php bp_message_notice_delete_link(); ?>" class="confirm" title="<?php esc_attr_e( "Delete Message", "buddypress" ); ?>">x</a>
+				</td>
+			</tr>
+		<?php endwhile; ?>
+	</table><!-- #message-threads -->
+
+	<?php
+
+	/**
+	 * Fires after the members notice items.
+	 *
+	 * @since 1.2.0
+	 */
+	do_action( 'bp_after_notices' ); ?>
+
+<?php else: ?>
+
+	<div id="message" class="info">
+		<p><?php _e( 'Sorry, no notices were found.', 'buddypress' ); ?></p>
+	</div>
+
+<?php endif;?>
+
+<?php
+
+/**
+ * Fires after the members notices loop.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_notices_loop' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/single.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/single.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/messages/single.php	(revision 10410)
@@ -0,0 +1,159 @@
+<?php
+/**
+ * BuddyPress - Members Single Message
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+<div id="message-thread">
+
+	<?php
+
+	/**
+	 * Fires before the display of a single member message thread content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_message_thread_content' ); ?>
+
+	<?php if ( bp_thread_has_messages() ) : ?>
+
+		<h3 id="message-subject"><?php bp_the_thread_subject(); ?></h3>
+
+		<p id="message-recipients">
+			<span class="highlight">
+
+				<?php if ( bp_get_thread_recipients_count() <= 1 ) : ?>
+
+					<?php _e( 'You are alone in this conversation.', 'buddypress' ); ?>
+
+				<?php elseif ( bp_get_max_thread_recipients_to_list() <= bp_get_thread_recipients_count() ) : ?>
+
+					<?php printf( __( 'Conversation between %s recipients.', 'buddypress' ), number_format_i18n( bp_get_thread_recipients_count() ) ); ?>
+
+				<?php else : ?>
+
+					<?php printf( __( 'Conversation between %s and you.', 'buddypress' ), bp_get_thread_recipients_list() ); ?>
+
+				<?php endif; ?>
+
+			</span>
+
+			<a class="button confirm" href="<?php bp_the_thread_delete_link(); ?>" title="<?php esc_attr_e( "Delete Conversation", "buddypress" ); ?>"><?php _e( 'Delete', 'buddypress' ); ?></a>
+		</p>
+
+		<?php
+
+		/**
+		 * Fires before the display of the message thread list.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_before_message_thread_list' ); ?>
+
+		<?php while ( bp_thread_messages() ) : bp_thread_the_message(); ?>
+			<?php bp_get_template_part( 'members/single/messages/message' ); ?>
+		<?php endwhile; ?>
+
+		<?php
+
+		/**
+		 * Fires after the display of the message thread list.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_after_message_thread_list' ); ?>
+
+		<?php
+
+		/**
+		 * Fires before the display of the message thread reply form.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_before_message_thread_reply' ); ?>
+
+		<form id="send-reply" action="<?php bp_messages_form_action(); ?>" method="post" class="standard-form">
+
+			<div class="message-box">
+
+				<div class="message-metadata">
+
+					<?php
+
+					/** This action is documented in bp-templates/bp-legacy/buddypress-functions.php */
+					do_action( 'bp_before_message_meta' ); ?>
+
+					<div class="avatar-box">
+						<?php bp_loggedin_user_avatar( 'type=thumb&height=30&width=30' ); ?>
+
+						<strong><?php _e( 'Send a Reply', 'buddypress' ); ?></strong>
+					</div>
+
+					<?php
+
+					/** This action is documented in bp-templates/bp-legacy/buddypress-functions.php */
+					do_action( 'bp_after_message_meta' ); ?>
+
+				</div><!-- .message-metadata -->
+
+				<div class="message-content">
+
+					<?php
+
+					/**
+					 * Fires before the display of the message reply box.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_before_message_reply_box' ); ?>
+
+					<label for="message_content" class="bp-screen-reader-text"><?php _e( 'Reply to Message', 'buddypress' ); ?></label>
+					<textarea name="content" id="message_content" rows="15" cols="40"></textarea>
+
+					<?php
+
+					/**
+					 * Fires after the display of the message reply box.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_after_message_reply_box' ); ?>
+
+					<div class="submit">
+						<input type="submit" name="send" value="<?php esc_attr_e( 'Send Reply', 'buddypress' ); ?>" id="send_reply_button"/>
+					</div>
+
+					<input type="hidden" id="thread_id" name="thread_id" value="<?php bp_the_thread_id(); ?>" />
+					<input type="hidden" id="messages_order" name="messages_order" value="<?php bp_thread_messages_order(); ?>" />
+					<?php wp_nonce_field( 'messages_send_message', 'send_message_nonce' ); ?>
+
+				</div><!-- .message-content -->
+
+			</div><!-- .message-box -->
+
+		</form><!-- #send-reply -->
+
+		<?php
+
+		/**
+		 * Fires after the display of the message thread reply form.
+		 *
+		 * @since 1.1.0
+		 */
+		do_action( 'bp_after_message_thread_reply' ); ?>
+
+	<?php endif; ?>
+
+	<?php
+
+	/**
+	 * Fires after the display of a single member message thread content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_after_message_thread_content' ); ?>
+
+</div>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications.php	(revision 10410)
@@ -0,0 +1,36 @@
+<?php
+/**
+ * BuddyPress - Users Notifications
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+	<ul>
+		<?php bp_get_options_nav(); ?>
+
+		<li id="members-order-select" class="last filter">
+			<?php bp_notifications_sort_order_form(); ?>
+		</li>
+	</ul>
+</div>
+
+<?php
+switch ( bp_current_action() ) :
+
+	case 'unread' :
+		bp_get_template_part( 'members/single/notifications/unread' );
+		break;
+
+	case 'read' :
+		bp_get_template_part( 'members/single/notifications/read' );
+		break;
+
+	// Any other actions.
+	default :
+		bp_get_template_part( 'members/single/plugins' );
+		break;
+endswitch;
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/feedback-no-notifications.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/feedback-no-notifications.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/feedback-no-notifications.php	(revision 10410)
@@ -0,0 +1,38 @@
+<?php
+/**
+ * BuddyPress - Members Feedback No Notifications
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+<div id="message" class="info">
+
+	<?php if ( bp_is_current_action( 'unread' ) ) : ?>
+
+		<?php if ( bp_is_my_profile() ) : ?>
+
+			<p><?php _e( 'You have no unread notifications.', 'buddypress' ); ?></p>
+
+		<?php else : ?>
+
+			<p><?php _e( 'This member has no unread notifications.', 'buddypress' ); ?></p>
+
+		<?php endif; ?>
+
+	<?php else : ?>
+
+		<?php if ( bp_is_my_profile() ) : ?>
+
+			<p><?php _e( 'You have no notifications.', 'buddypress' ); ?></p>
+
+		<?php else : ?>
+
+			<p><?php _e( 'This member has no notifications.', 'buddypress' ); ?></p>
+
+		<?php endif; ?>
+
+	<?php endif; ?>
+
+</div>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/notifications-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/notifications-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/notifications-loop.php	(revision 10410)
@@ -0,0 +1,44 @@
+<?php
+/**
+ * BuddyPress - Members Notifications Loop
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+<form action="" method="post" id="notifications-bulk-management">
+	<table class="notifications">
+		<thead>
+			<tr>
+				<th class="icon"></th>
+				<th class="bulk-select-all"><input id="select-all-notifications" type="checkbox"><label class="bp-screen-reader-text" for="select-all-notifications"><?php _e( 'Select all', 'buddypress' ); ?></label></th>
+				<th class="title"><?php _e( 'Notification', 'buddypress' ); ?></th>
+				<th class="date"><?php _e( 'Date Received', 'buddypress' ); ?></th>
+				<th class="actions"><?php _e( 'Actions',    'buddypress' ); ?></th>
+			</tr>
+		</thead>
+
+		<tbody>
+
+			<?php while ( bp_the_notifications() ) : bp_the_notification(); ?>
+
+				<tr>
+					<td></td>
+					<td class="bulk-select-check"><label for="<?php bp_the_notification_id(); ?>"><input id="<?php bp_the_notification_id(); ?>" type="checkbox" name="notifications[]" value="<?php bp_the_notification_id(); ?>" class="notification-check"><span class="bp-screen-reader-text"><?php _e( 'Select this notification', 'buddypress' ); ?></span></label></td>
+					<td class="notification-description"><?php bp_the_notification_description();  ?></td>
+					<td class="notification-since"><?php bp_the_notification_time_since();   ?></td>
+					<td class="notification-actions"><?php bp_the_notification_action_links(); ?></td>
+				</tr>
+
+			<?php endwhile; ?>
+
+		</tbody>
+	</table>
+
+	<div class="notifications-options-nav">
+		<?php bp_notifications_bulk_management_dropdown(); ?>
+	</div><!-- .notifications-options-nav -->
+
+	<?php wp_nonce_field( 'notifications_bulk_nonce', 'notifications_bulk_nonce' ); ?>
+</form>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/read.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/read.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/read.php	(revision 10410)
@@ -0,0 +1,39 @@
+<?php
+/**
+ * BuddyPress - Members Read Notifications
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<?php if ( bp_has_notifications() ) : ?>
+
+	<div id="pag-top" class="pagination no-ajax">
+		<div class="pag-count" id="notifications-count-top">
+			<?php bp_notifications_pagination_count(); ?>
+		</div>
+
+		<div class="pagination-links" id="notifications-pag-top">
+			<?php bp_notifications_pagination_links(); ?>
+		</div>
+	</div>
+
+	<?php bp_get_template_part( 'members/single/notifications/notifications-loop' ); ?>
+
+	<div id="pag-bottom" class="pagination no-ajax">
+		<div class="pag-count" id="notifications-count-bottom">
+			<?php bp_notifications_pagination_count(); ?>
+		</div>
+
+		<div class="pagination-links" id="notifications-pag-bottom">
+			<?php bp_notifications_pagination_links(); ?>
+		</div>
+	</div>
+
+<?php else : ?>
+
+	<?php bp_get_template_part( 'members/single/notifications/feedback-no-notifications' ); ?>
+
+<?php endif;
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/unread.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/unread.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/notifications/unread.php	(revision 10410)
@@ -0,0 +1,39 @@
+<?php
+/**
+ * BuddyPress - Members Unread Notifications
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<?php if ( bp_has_notifications() ) : ?>
+
+	<div id="pag-top" class="pagination no-ajax">
+		<div class="pag-count" id="notifications-count-top">
+			<?php bp_notifications_pagination_count(); ?>
+		</div>
+
+		<div class="pagination-links" id="notifications-pag-top">
+			<?php bp_notifications_pagination_links(); ?>
+		</div>
+	</div>
+
+	<?php bp_get_template_part( 'members/single/notifications/notifications-loop' ); ?>
+
+	<div id="pag-bottom" class="pagination no-ajax">
+		<div class="pag-count" id="notifications-count-bottom">
+			<?php bp_notifications_pagination_count(); ?>
+		</div>
+
+		<div class="pagination-links" id="notifications-pag-bottom">
+			<?php bp_notifications_pagination_links(); ?>
+		</div>
+	</div>
+
+<?php else : ?>
+
+	<?php bp_get_template_part( 'members/single/notifications/feedback-no-notifications' ); ?>
+
+<?php endif;
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/plugins.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/plugins.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/plugins.php	(revision 10410)
@@ -0,0 +1,66 @@
+<?php
+/**
+ * BuddyPress - Users Plugins Template
+ *
+ * 3rd-party plugins should use this template to easily add template
+ * support to their plugins for the members component.
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+		/**
+		 * Fires at the start of the member plugin template.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_before_member_plugin_template' ); ?>
+
+		<?php if ( ! bp_is_current_component_core() ) : ?>
+
+		<div class="item-list-tabs no-ajax" id="subnav">
+			<ul>
+				<?php bp_get_options_nav(); ?>
+
+				<?php
+
+				/**
+				 * Fires inside the member plugin template nav <ul> tag.
+				 *
+				 * @since 1.2.2
+				 */
+				do_action( 'bp_member_plugin_options_nav' ); ?>
+			</ul>
+		</div><!-- .item-list-tabs -->
+
+		<?php endif; ?>
+
+		<?php if ( has_action( 'bp_template_title' ) ) : ?>
+			<h3><?php
+
+			/**
+			 * Fires inside the member plugin template <h3> tag.
+			 *
+			 * @since 1.0.0
+			 */
+			do_action( 'bp_template_title' ); ?></h3>
+
+		<?php endif; ?>
+
+		<?php
+
+		/**
+		 * Fires and displays the member plugin template content.
+		 *
+		 * @since 1.0.0
+		 */
+		do_action( 'bp_template_content' ); ?>
+
+		<?php
+
+		/**
+		 * Fires at the end of the member plugin template.
+		 *
+		 * @since 1.2.0
+		 */
+		do_action( 'bp_after_member_plugin_template' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile.php	(revision 10410)
@@ -0,0 +1,72 @@
+<?php
+/**
+ * BuddyPress - Users Profile
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+	<ul>
+		<?php bp_get_options_nav(); ?>
+	</ul>
+</div><!-- .item-list-tabs -->
+
+<?php
+
+/**
+ * Fires before the display of member profile content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_before_profile_content' ); ?>
+
+<div class="profile">
+
+<?php switch ( bp_current_action() ) :
+
+	// Edit
+	case 'edit'   :
+		bp_get_template_part( 'members/single/profile/edit' );
+		break;
+
+	// Change Avatar
+	case 'change-avatar' :
+		bp_get_template_part( 'members/single/profile/change-avatar' );
+		break;
+
+	// Change Cover Image
+	case 'change-cover-image' :
+		bp_get_template_part( 'members/single/profile/change-cover-image' );
+		break;
+
+	// Compose
+	case 'public' :
+
+		// Display XProfile
+		if ( bp_is_active( 'xprofile' ) )
+			bp_get_template_part( 'members/single/profile/profile-loop' );
+
+		// Display WordPress profile (fallback)
+		else
+			bp_get_template_part( 'members/single/profile/profile-wp' );
+
+		break;
+
+	// Any other
+	default :
+		bp_get_template_part( 'members/single/plugins' );
+		break;
+endswitch; ?>
+</div><!-- .profile -->
+
+<?php
+
+/**
+ * Fires after the display of member profile content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_after_profile_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/change-avatar.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/change-avatar.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/change-avatar.php	(revision 10410)
@@ -0,0 +1,92 @@
+<?php
+/**
+ * BuddyPress - Members Profile Change Avatar
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<h4><?php _e( 'Change Profile Photo', 'buddypress' ); ?></h4>
+
+<?php
+
+/**
+ * Fires before the display of profile avatar upload content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_before_profile_avatar_upload_content' ); ?>
+
+<?php if ( !(int)bp_get_option( 'bp-disable-avatar-uploads' ) ) : ?>
+
+	<p><?php _e( 'Your profile photo will be used on your profile and throughout the site. If there is a <a href="http://gravatar.com">Gravatar</a> associated with your account email we will use that, or you can upload an image from your computer.', 'buddypress' ); ?></p>
+
+	<form action="" method="post" id="avatar-upload-form" class="standard-form" enctype="multipart/form-data">
+
+		<?php if ( 'upload-image' == bp_get_avatar_admin_step() ) : ?>
+
+			<?php wp_nonce_field( 'bp_avatar_upload' ); ?>
+			<p><?php _e( 'Click below to select a JPG, GIF or PNG format photo from your computer and then click \'Upload Image\' to proceed.', 'buddypress' ); ?></p>
+
+			<p id="avatar-upload">
+				<label for="file" class="bp-screen-reader-text"><?php _e( 'Select an image', 'buddypress' ); ?></label>
+				<input type="file" name="file" id="file" />
+				<input type="submit" name="upload" id="upload" value="<?php esc_attr_e( 'Upload Image', 'buddypress' ); ?>" />
+				<input type="hidden" name="action" id="action" value="bp_avatar_upload" />
+			</p>
+
+			<?php if ( bp_get_user_has_avatar() ) : ?>
+				<p><?php _e( "If you'd like to delete your current profile photo but not upload a new one, please use the delete profile photo button.", 'buddypress' ); ?></p>
+				<p><a class="button edit" href="<?php bp_avatar_delete_link(); ?>" title="<?php esc_attr_e( 'Delete Profile Photo', 'buddypress' ); ?>"><?php _e( 'Delete My Profile Photo', 'buddypress' ); ?></a></p>
+			<?php endif; ?>
+
+		<?php endif; ?>
+
+		<?php if ( 'crop-image' == bp_get_avatar_admin_step() ) : ?>
+
+			<h5><?php _e( 'Crop Your New Profile Photo', 'buddypress' ); ?></h5>
+
+			<img src="<?php bp_avatar_to_crop(); ?>" id="avatar-to-crop" class="avatar" alt="<?php esc_attr_e( 'Profile photo to crop', 'buddypress' ); ?>" />
+
+			<div id="avatar-crop-pane">
+				<img src="<?php bp_avatar_to_crop(); ?>" id="avatar-crop-preview" class="avatar" alt="<?php esc_attr_e( 'Profile photo preview', 'buddypress' ); ?>" />
+			</div>
+
+			<input type="submit" name="avatar-crop-submit" id="avatar-crop-submit" value="<?php esc_attr_e( 'Crop Image', 'buddypress' ); ?>" />
+
+			<input type="hidden" name="image_src" id="image_src" value="<?php bp_avatar_to_crop_src(); ?>" />
+			<input type="hidden" id="x" name="x" />
+			<input type="hidden" id="y" name="y" />
+			<input type="hidden" id="w" name="w" />
+			<input type="hidden" id="h" name="h" />
+
+			<?php wp_nonce_field( 'bp_avatar_cropstore' ); ?>
+
+		<?php endif; ?>
+
+	</form>
+
+	<?php
+	/**
+	 * Load the Avatar UI templates
+	 *
+	 * @since  2.3.0
+	 */
+	bp_avatar_get_templates(); ?>
+
+<?php else : ?>
+
+	<p><?php _e( 'Your profile photo will be used on your profile and throughout the site. To change your profile photo, please create an account with <a href="http://gravatar.com">Gravatar</a> using the same email address as you used to register with this site.', 'buddypress' ); ?></p>
+
+<?php endif; ?>
+
+<?php
+
+/**
+ * Fires after the display of profile avatar upload content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_after_profile_avatar_upload_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/change-cover-image.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/change-cover-image.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/change-cover-image.php	(revision 10410)
@@ -0,0 +1,33 @@
+<?php
+/**
+ * BuddyPress - Members Profile Change Cover Image
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<h4><?php _e( 'Change Cover Image', 'buddypress' ); ?></h4>
+
+<?php
+
+/**
+ * Fires before the display of profile cover image upload content.
+ *
+ * @since 2.4.0
+ */
+do_action( 'bp_before_profile_edit_cover_image' ); ?>
+
+<p><?php _e( 'Your Cover Image will be used to customize the header of your profile.', 'buddypress' ); ?></p>
+
+<?php bp_attachments_get_template_part( 'cover-images/index' ); ?>
+
+<?php
+
+/**
+ * Fires after the display of profile cover image upload content.
+ *
+ * @since 2.4.0
+ */
+do_action( 'bp_after_profile_edit_cover_image' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/edit.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/edit.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/edit.php	(revision 10410)
@@ -0,0 +1,123 @@
+<?php
+/**
+ * BuddyPress - Members Single Profile Edit
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires after the display of member profile edit content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_before_profile_edit_content' );
+
+if ( bp_has_profile( 'profile_group_id=' . bp_get_current_profile_group_id() ) ) :
+	while ( bp_profile_groups() ) : bp_the_profile_group(); ?>
+
+<form action="<?php bp_the_profile_group_edit_form_action(); ?>" method="post" id="profile-edit-form" class="standard-form <?php bp_the_profile_group_slug(); ?>">
+
+	<?php
+
+		/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/profile/profile-wp.php */
+		do_action( 'bp_before_profile_field_content' ); ?>
+
+		<h4><?php printf( __( "Editing '%s' Profile Group", "buddypress" ), bp_get_the_profile_group_name() ); ?></h4>
+
+		<?php if ( bp_profile_has_multiple_groups() ) : ?>
+			<ul class="button-nav">
+
+				<?php bp_profile_group_tabs(); ?>
+
+			</ul>
+		<?php endif ;?>
+
+		<div class="clear"></div>
+
+		<?php while ( bp_profile_fields() ) : bp_the_profile_field(); ?>
+
+			<div<?php bp_field_css_class( 'editfield' ); ?>>
+
+				<?php
+				$field_type = bp_xprofile_create_field_type( bp_get_the_profile_field_type() );
+				$field_type->edit_field_html();
+
+				/**
+				 * Fires before the display of visibility options for the field.
+				 *
+				 * @since 1.7.0
+				 */
+				do_action( 'bp_custom_profile_edit_fields_pre_visibility' );
+				?>
+
+				<?php if ( bp_current_user_can( 'bp_xprofile_change_field_visibility' ) ) : ?>
+					<p class="field-visibility-settings-toggle" id="field-visibility-settings-toggle-<?php bp_the_profile_field_id() ?>">
+						<?php
+						printf(
+							__( 'This field can be seen by: %s', 'buddypress' ),
+							'<span class="current-visibility-level">' . bp_get_the_profile_field_visibility_level_label() . '</span>'
+						);
+						?>
+						<a href="#" class="visibility-toggle-link"><?php _e( 'Change', 'buddypress' ); ?></a>
+					</p>
+
+					<div class="field-visibility-settings" id="field-visibility-settings-<?php bp_the_profile_field_id() ?>">
+						<fieldset>
+							<legend><?php _e( 'Who can see this field?', 'buddypress' ) ?></legend>
+
+							<?php bp_profile_visibility_radio_buttons() ?>
+
+						</fieldset>
+						<a class="field-visibility-settings-close" href="#"><?php _e( 'Close', 'buddypress' ) ?></a>
+					</div>
+				<?php else : ?>
+					<div class="field-visibility-settings-notoggle" id="field-visibility-settings-toggle-<?php bp_the_profile_field_id() ?>">
+						<?php
+						printf(
+							__( 'This field can be seen by: %s', 'buddypress' ),
+							'<span class="current-visibility-level">' . bp_get_the_profile_field_visibility_level_label() . '</span>'
+						);
+						?>
+					</div>
+				<?php endif ?>
+
+				<?php
+
+				/**
+				 * Fires after the visibility options for a field.
+				 *
+				 * @since 1.1.0
+				 */
+				do_action( 'bp_custom_profile_edit_fields' ); ?>
+
+				<p class="description"><?php bp_the_profile_field_description(); ?></p>
+			</div>
+
+		<?php endwhile; ?>
+
+	<?php
+
+	/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/profile/profile-wp.php */
+	do_action( 'bp_after_profile_field_content' ); ?>
+
+	<div class="submit">
+		<input type="submit" name="profile-group-edit-submit" id="profile-group-edit-submit" value="<?php esc_attr_e( 'Save Changes', 'buddypress' ); ?> " />
+	</div>
+
+	<input type="hidden" name="field_ids" id="field_ids" value="<?php bp_the_profile_field_ids(); ?>" />
+
+	<?php wp_nonce_field( 'bp_xprofile_edit' ); ?>
+
+</form>
+
+<?php endwhile; endif; ?>
+
+<?php
+
+/**
+ * Fires after the display of member profile edit content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_after_profile_edit_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/profile-loop.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/profile-loop.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/profile-loop.php	(revision 10410)
@@ -0,0 +1,76 @@
+<?php
+/**
+ * BuddyPress - Members Profile Loop
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/profile/profile-wp.php */
+do_action( 'bp_before_profile_loop_content' ); ?>
+
+<?php if ( bp_has_profile() ) : ?>
+
+	<?php while ( bp_profile_groups() ) : bp_the_profile_group(); ?>
+
+		<?php if ( bp_profile_group_has_fields() ) : ?>
+
+			<?php
+
+			/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/profile/profile-wp.php */
+			do_action( 'bp_before_profile_field_content' ); ?>
+
+			<div class="bp-widget <?php bp_the_profile_group_slug(); ?>">
+
+				<h4><?php bp_the_profile_group_name(); ?></h4>
+
+				<table class="profile-fields">
+
+					<?php while ( bp_profile_fields() ) : bp_the_profile_field(); ?>
+
+						<?php if ( bp_field_has_data() ) : ?>
+
+							<tr<?php bp_field_css_class(); ?>>
+
+								<td class="label"><?php bp_the_profile_field_name(); ?></td>
+
+								<td class="data"><?php bp_the_profile_field_value(); ?></td>
+
+							</tr>
+
+						<?php endif; ?>
+
+						<?php
+
+						/**
+						 * Fires after the display of a field table row for profile data.
+						 *
+						 * @since 1.1.0
+						 */
+						do_action( 'bp_profile_field_item' ); ?>
+
+					<?php endwhile; ?>
+
+				</table>
+			</div>
+
+			<?php
+
+			/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/profile/profile-wp.php */
+			do_action( 'bp_after_profile_field_content' ); ?>
+
+		<?php endif; ?>
+
+	<?php endwhile; ?>
+
+	<?php
+
+	/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/profile/profile-wp.php */
+	do_action( 'bp_profile_field_buttons' ); ?>
+
+<?php endif; ?>
+
+<?php
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/profile/profile-wp.php */
+do_action( 'bp_after_profile_loop_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/profile-wp.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/profile-wp.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/profile/profile-wp.php	(revision 10410)
@@ -0,0 +1,114 @@
+<?php
+/**
+ * BuddyPress - Members Single Profile WP
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of member profile loop content.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_before_profile_loop_content' ); ?>
+
+<?php $ud = get_userdata( bp_displayed_user_id() ); ?>
+
+<?php
+
+	/**
+	 * Fires before the display of member profile field content.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'bp_before_profile_field_content' ); ?>
+
+	<div class="bp-widget wp-profile">
+		<h4><?php bp_is_my_profile() ? _e( 'My Profile', 'buddypress' ) : printf( __( "%s's Profile", 'buddypress' ), bp_get_displayed_user_fullname() ); ?></h4>
+
+		<table class="wp-profile-fields">
+
+			<?php if ( $ud->display_name ) : ?>
+
+				<tr id="wp_displayname">
+					<td class="label"><?php _e( 'Name', 'buddypress' ); ?></td>
+					<td class="data"><?php echo $ud->display_name; ?></td>
+				</tr>
+
+			<?php endif; ?>
+
+			<?php if ( $ud->user_description ) : ?>
+
+				<tr id="wp_desc">
+					<td class="label"><?php _e( 'About Me', 'buddypress' ); ?></td>
+					<td class="data"><?php echo $ud->user_description; ?></td>
+				</tr>
+
+			<?php endif; ?>
+
+			<?php if ( $ud->user_url ) : ?>
+
+				<tr id="wp_website">
+					<td class="label"><?php _e( 'Website', 'buddypress' ); ?></td>
+					<td class="data"><?php echo make_clickable( $ud->user_url ); ?></td>
+				</tr>
+
+			<?php endif; ?>
+
+			<?php if ( $ud->jabber ) : ?>
+
+				<tr id="wp_jabber">
+					<td class="label"><?php _e( 'Jabber', 'buddypress' ); ?></td>
+					<td class="data"><?php echo $ud->jabber; ?></td>
+				</tr>
+
+			<?php endif; ?>
+
+			<?php if ( $ud->aim ) : ?>
+
+				<tr id="wp_aim">
+					<td class="label"><?php _e( 'AOL Messenger', 'buddypress' ); ?></td>
+					<td class="data"><?php echo $ud->aim; ?></td>
+				</tr>
+
+			<?php endif; ?>
+
+			<?php if ( $ud->yim ) : ?>
+
+				<tr id="wp_yim">
+					<td class="label"><?php _e( 'Yahoo Messenger', 'buddypress' ); ?></td>
+					<td class="data"><?php echo $ud->yim; ?></td>
+				</tr>
+
+			<?php endif; ?>
+
+		</table>
+	</div>
+
+<?php
+
+/**
+ * Fires after the display of member profile field content.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_after_profile_field_content' ); ?>
+
+<?php
+
+/**
+ * Fires and displays the profile field buttons.
+ *
+ * @since 1.1.0
+ */
+do_action( 'bp_profile_field_buttons' ); ?>
+
+<?php
+
+/**
+ * Fires after the display of member profile loop content.
+ *
+ * @since 1.2.0
+ */
+do_action( 'bp_after_profile_loop_content' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings.php	(revision 10410)
@@ -0,0 +1,42 @@
+<?php
+/**
+ * BuddyPress - Users Settings
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+?>
+
+<div class="item-list-tabs no-ajax" id="subnav" role="navigation">
+	<ul>
+		<?php if ( bp_core_can_edit_settings() ) : ?>
+
+			<?php bp_get_options_nav(); ?>
+
+		<?php endif; ?>
+	</ul>
+</div>
+
+<?php
+
+switch ( bp_current_action() ) :
+	case 'notifications'  :
+		bp_get_template_part( 'members/single/settings/notifications'  );
+		break;
+	case 'capabilities'   :
+		bp_get_template_part( 'members/single/settings/capabilities'   );
+		break;
+	case 'delete-account' :
+		bp_get_template_part( 'members/single/settings/delete-account' );
+		break;
+	case 'general'        :
+		bp_get_template_part( 'members/single/settings/general'        );
+		break;
+	case 'profile'        :
+		bp_get_template_part( 'members/single/settings/profile'        );
+		break;
+	default:
+		bp_get_template_part( 'members/single/plugins'                 );
+		break;
+endswitch;
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/capabilities.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/capabilities.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/capabilities.php	(revision 10410)
@@ -0,0 +1,48 @@
+<?php
+/**
+ * BuddyPress - Members Settings Capabilities
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/settings/profile.php */
+do_action( 'bp_before_member_settings_template' ); ?>
+
+<form action="<?php echo bp_displayed_user_domain() . bp_get_settings_slug() . '/capabilities/'; ?>" name="account-capabilities-form" id="account-capabilities-form" class="standard-form" method="post">
+
+	<?php
+
+	/**
+	 * Fires before the display of the submit button for user capabilities saving.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_members_capabilities_account_before_submit' ); ?>
+
+	<label for="user-spammer">
+		<input type="checkbox" name="user-spammer" id="user-spammer" value="1" <?php checked( bp_is_user_spammer( bp_displayed_user_id() ) ); ?> />
+		 <?php _e( 'This user is a spammer.', 'buddypress' ); ?>
+	</label>
+
+	<div class="submit">
+		<input type="submit" value="<?php esc_attr_e( 'Save', 'buddypress' ); ?>" id="capabilities-submit" name="capabilities-submit" />
+	</div>
+
+	<?php
+
+	/**
+	 * Fires after the display of the submit button for user capabilities saving.
+	 *
+	 * @since 1.6.0
+	 */
+	do_action( 'bp_members_capabilities_account_after_submit' ); ?>
+
+	<?php wp_nonce_field( 'capabilities' ); ?>
+
+</form>
+
+<?php
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/settings/profile.php */
+do_action( 'bp_after_member_settings_template' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/delete-account.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/delete-account.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/delete-account.php	(revision 10410)
@@ -0,0 +1,62 @@
+<?php
+/**
+ * BuddyPress - Members Settings Delete Account
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/settings/profile.php */
+do_action( 'bp_before_member_settings_template' ); ?>
+
+<div id="message" class="info">
+
+	<?php if ( bp_is_my_profile() ) : ?>
+
+		<p><?php _e( 'Deleting your account will delete all of the content you have created. It will be completely irrecoverable.', 'buddypress' ); ?></p>
+
+	<?php else : ?>
+
+		<p><?php _e( 'Deleting this account will delete all of the content it has created. It will be completely irrecoverable.', 'buddypress' ); ?></p>
+
+	<?php endif; ?>
+
+</div>
+
+<form action="<?php echo bp_displayed_user_domain() . bp_get_settings_slug() . '/delete-account'; ?>" name="account-delete-form" id="account-delete-form" class="standard-form" method="post">
+
+	<?php
+
+	/**
+	 * Fires before the display of the submit button for user delete account submitting.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_members_delete_account_before_submit' ); ?>
+
+	<label for="delete-account-understand">
+		<input type="checkbox" name="delete-account-understand" id="delete-account-understand" value="1" onclick="if(this.checked) { document.getElementById('delete-account-button').disabled = ''; } else { document.getElementById('delete-account-button').disabled = 'disabled'; }" />
+		 <?php _e( 'I understand the consequences.', 'buddypress' ); ?>
+	</label>
+
+	<div class="submit">
+		<input type="submit" disabled="disabled" value="<?php esc_attr_e( 'Delete Account', 'buddypress' ); ?>" id="delete-account-button" name="delete-account-button" />
+	</div>
+
+	<?php
+
+	/**
+	 * Fires after the display of the submit button for user delete account submitting.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_members_delete_account_after_submit' ); ?>
+
+	<?php wp_nonce_field( 'delete-account' ); ?>
+
+</form>
+
+<?php
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/settings/profile.php */
+do_action( 'bp_after_member_settings_template' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/general.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/general.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/general.php	(revision 10410)
@@ -0,0 +1,59 @@
+<?php
+/**
+ * BuddyPress - Members Single Profile
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/settings/profile.php */
+do_action( 'bp_before_member_settings_template' ); ?>
+
+<form action="<?php echo bp_displayed_user_domain() . bp_get_settings_slug() . '/general'; ?>" method="post" class="standard-form" id="settings-form">
+
+	<?php if ( !is_super_admin() ) : ?>
+
+		<label for="pwd"><?php _e( 'Current Password <span>(required to update email or change current password)</span>', 'buddypress' ); ?></label>
+		<input type="password" name="pwd" id="pwd" size="16" value="" class="settings-input small" <?php bp_form_field_attributes( 'password' ); ?>/> &nbsp;<a href="<?php echo wp_lostpassword_url(); ?>" title="<?php esc_attr_e( 'Password Lost and Found', 'buddypress' ); ?>"><?php _e( 'Lost your password?', 'buddypress' ); ?></a>
+
+	<?php endif; ?>
+
+	<label for="email"><?php _e( 'Account Email', 'buddypress' ); ?></label>
+	<input type="email" name="email" id="email" value="<?php echo bp_get_displayed_user_email(); ?>" class="settings-input" <?php bp_form_field_attributes( 'email' ); ?>/>
+
+	<label for="pass1"><?php _e( 'Change Password <span>(leave blank for no change)</span>', 'buddypress' ); ?></label>
+	<input type="password" name="pass1" id="pass1" size="16" value="" class="settings-input small password-entry" <?php bp_form_field_attributes( 'password' ); ?>/> &nbsp;<?php _e( 'New Password', 'buddypress' ); ?><br />
+	<div id="pass-strength-result"></div>
+	<label for="pass2" class="bp-screen-reader-text"><?php _e( 'Repeat New Password', 'buddypress' ); ?></label>
+	<input type="password" name="pass2" id="pass2" size="16" value="" class="settings-input small password-entry-confirm" <?php bp_form_field_attributes( 'password' ); ?>/> &nbsp;<?php _e( 'Repeat New Password', 'buddypress' ); ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of the submit button for user general settings saving.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_core_general_settings_before_submit' ); ?>
+
+	<div class="submit">
+		<input type="submit" name="submit" value="<?php esc_attr_e( 'Save Changes', 'buddypress' ); ?>" id="submit" class="auto" />
+	</div>
+
+	<?php
+
+	/**
+	 * Fires after the display of the submit button for user general settings saving.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_core_general_settings_after_submit' ); ?>
+
+	<?php wp_nonce_field( 'bp_settings_general' ); ?>
+
+</form>
+
+<?php
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/settings/profile.php */
+do_action( 'bp_after_member_settings_template' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/notifications.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/notifications.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/notifications.php	(revision 10410)
@@ -0,0 +1,53 @@
+<?php
+/**
+ * BuddyPress - Members Settings Notifications
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/settings/profile.php */
+do_action( 'bp_before_member_settings_template' ); ?>
+
+<form action="<?php echo bp_displayed_user_domain() . bp_get_settings_slug() . '/notifications'; ?>" method="post" class="standard-form" id="settings-form">
+	<p><?php _e( 'Send an email notice when:', 'buddypress' ); ?></p>
+
+	<?php
+
+	/**
+	 * Fires at the top of the member template notification settings form.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'bp_notification_settings' ); ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of the submit button for user notification saving.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_members_notification_settings_before_submit' ); ?>
+
+	<div class="submit">
+		<input type="submit" name="submit" value="<?php esc_attr_e( 'Save Changes', 'buddypress' ); ?>" id="submit" class="auto" />
+	</div>
+
+	<?php
+
+	/**
+	 * Fires after the display of the submit button for user notification saving.
+	 *
+	 * @since 1.5.0
+	 */
+	do_action( 'bp_members_notification_settings_after_submit' ); ?>
+
+	<?php wp_nonce_field('bp_settings_notifications' ); ?>
+
+</form>
+
+<?php
+
+/** This action is documented in bp-templates/bp-legacy/buddypress/members/single/settings/profile.php */
+do_action( 'bp_after_member_settings_template' ); ?>
Index: /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/profile.php
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/profile.php	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/buddypress/members/single/settings/profile.php	(revision 10410)
@@ -0,0 +1,87 @@
+<?php
+/**
+ * BuddyPress - Members Single Profile
+ *
+ * @package BuddyPress
+ * @subpackage bp-legacy
+ */
+
+/**
+ * Fires before the display of member settings template.
+ *
+ * @since 1.5.0
+ */
+do_action( 'bp_before_member_settings_template' ); ?>
+
+<form action="<?php echo trailingslashit( bp_displayed_user_domain() . bp_get_settings_slug() . '/profile' ); ?>" method="post" class="standard-form" id="settings-form">
+
+	<?php if ( bp_xprofile_get_settings_fields() ) : ?>
+
+		<?php while ( bp_profile_groups() ) : bp_the_profile_group(); ?>
+
+			<?php if ( bp_profile_fields() ) : ?>
+
+				<table class="profile-settings" id="xprofile-settings-<?php bp_the_profile_group_slug(); ?>">
+					<thead>
+						<tr>
+							<th class="title field-group-name"><?php bp_the_profile_group_name(); ?></th>
+							<th class="title"><?php _e( 'Visibility', 'buddypress' ); ?></th>
+						</tr>
+					</thead>
+
+					<tbody>
+
+						<?php while ( bp_profile_fields() ) : bp_the_profile_field(); ?>
+
+							<tr <?php bp_field_css_class(); ?>>
+								<td class="field-name"><?php bp_the_profile_field_name(); ?></td>
+								<td class="field-visibility"><?php bp_profile_settings_visibility_select(); ?></td>
+							</tr>
+
+						<?php endwhile; ?>
+
+					</tbody>
+				</table>
+
+			<?php endif; ?>
+
+		<?php endwhile; ?>
+
+	<?php endif; ?>
+
+	<?php
+
+	/**
+	 * Fires before the display of the submit button for user profile saving.
+	 *
+	 * @since 2.0.0
+	 */
+	do_action( 'bp_core_xprofile_settings_before_submit' ); ?>
+
+	<div class="submit">
+		<input id="submit" type="submit" name="xprofile-settings-submit" value="<?php esc_attr_e( 'Save Settings', 'buddypress' ); ?>" class="auto" />
+	</div>
+
+	<?php
+
+	/**
+	 * Fires after the display of the submit button for user profile saving.
+	 *
+	 * @since 2.0.0
+	 */
+	do_action( 'bp_core_xprofile_settings_after_submit' ); ?>
+
+	<?php wp_nonce_field( 'bp_xprofile_settings' ); ?>
+
+	<input type="hidden" name="field_ids" id="field_ids" value="<?php bp_the_profile_field_ids(); ?>" />
+
+</form>
+
+<?php
+
+/**
+ * Fires after the display of member settings template.
+ *
+ * @since 1.5.0
+ */
+do_action( 'bp_after_member_settings_template' );
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/buddypress-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/buddypress-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/buddypress-rtl.css	(revision 10410)
@@ -0,0 +1,1811 @@
+/*--------------------------------------------------------------
+Hello, this is the BuddyPress Legacy stylesheet.
+
+----------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+----------------------------------------------------------------
+1.0 - Navigation
+	1.1 - Pagination
+2.0 - WordPress
+	2.1 - Images
+3.0 - BuddyPress
+	3.1 - Activity
+		3.1.1 - Activity Listing
+		3.1.2 - Activity Comments
+	3.2 - Toolbar
+	3.3 - Directories - Members, Groups, Blogs, Forums
+	3.4 - Error / Success Messages
+	3.5 - Forms
+	3.6 - Ajax Loading
+	3.7 - Topics and Tables - Forums and General
+	3.8 - Headers, Lists and Tabs - Activity, Groups, Blogs, Forums
+		3.8.1 - Cover Image
+	3.9 - Private Messaging Threads
+	3.10 - Extended Profiles
+	3.11 - Widgets
+4.0 - Media Queries
+	4.1 - Smartphones Landscape
+	4.2 - Smartphones Portrait
+	4.3 - Smartphones - smaller screen sizes
+--------------------------------------------------------------*/
+
+/*--------------------------------------------------------------
+1 - Navigation
+--------------------------------------------------------------*/
+/*--------------------------------------------------------------
+1.1 - Pagination
+--------------------------------------------------------------*/
+#buddypress div.pagination {
+	background: transparent;
+	border: none;
+	color: #888;
+	font-size: small;
+	margin: 0;
+	position: relative;
+	display: block;
+	float: right;
+	width: 100%;
+	padding: 10px 0;
+}
+#buddypress div.pagination .pag-count {
+	float: right;
+	margin-right: 10px;
+}
+#buddypress div.pagination .pagination-links {
+	float: left;
+	margin-left: 10px;
+}
+#buddypress div.pagination .pagination-links span,
+#buddypress div.pagination .pagination-links a {
+	font-size: 90%;
+	padding: 0 5px;
+}
+#buddypress div.pagination .pagination-links a:hover {
+	font-weight: bold;
+}
+#buddypress noscript div.pagination {
+	margin-bottom: 15px;
+}
+#buddypress #nav-above {
+	display: none;
+}
+#buddypress .paged #nav-above {
+	display: block;
+}
+/*--------------------------------------------------------------
+2 - WordPress
+--------------------------------------------------------------*/
+/*--------------------------------------------------------------
+2.1 - Images
+--------------------------------------------------------------*/
+#buddypress img.wp-smiley {
+	border: none !important;
+	clear: none !important;
+	float: none !important;
+	margin: 0 !important;
+	padding: 0 !important;
+}
+#buddypress .clear {
+	clear: right;
+}
+
+/*--------------------------------------------------------------
+3.0 - BuddyPress
+--------------------------------------------------------------*/
+/*--------------------------------------------------------------
+3.1 - Activity
+--------------------------------------------------------------*/
+#buddypress #activity-stream {
+	margin-top: -5px;
+}
+
+#buddypress #activity-stream p {
+	margin: 5px 0;
+}
+
+#buddypress #item-body form#whats-new-form {
+	margin: 0;
+	padding: 0;
+}
+#buddypress .home-page form#whats-new-form {
+	border-bottom: none;
+	padding-bottom: 0;
+}
+#buddypress form#whats-new-form #whats-new-avatar {
+	float: right;
+}
+#buddypress form#whats-new-form #whats-new-content {
+	margin-right: 55px;
+	padding: 0 20px 20px 0;
+}
+#buddypress form#whats-new-form p.activity-greeting {
+	line-height: 0.5em;
+	margin-bottom: 15px;
+	margin-right: 75px;
+}
+#buddypress form#whats-new-form textarea {
+	background: #fff;
+	box-sizing: border-box;
+	color: #555;
+	font-family: inherit;
+	font-size: medium;
+	height: 2.2em;
+	line-height: 1.4;
+	padding: 6px;
+	width: 100%;
+}
+body.no-js #buddypress form#whats-new-form textarea {
+	height: 50px;
+}
+#buddypress form#whats-new-form #whats-new-options select {
+	max-width: 200px;
+	margin-top: 12px;
+}
+#buddypress form#whats-new-form #whats-new-submit {
+	float: left;
+	margin-top: 12px;
+}
+#buddypress #whats-new-options:after {
+	clear: both;
+	content: "";
+	display: table;
+}
+body.no-js #buddypress #whats-new-options {
+	height: auto;
+}
+#buddypress #whats-new:focus {
+	border-color: rgba(31, 179, 221, 0.9) !important;
+	outline-color: rgba(31, 179, 221, 0.9);
+}
+
+/*--------------------------------------------------------------
+3.1.1 - Activity Listing
+--------------------------------------------------------------*/
+#buddypress ul.activity-list li {
+	overflow: hidden;
+	padding: 15px 0 0;
+	list-style: none;
+}
+#buddypress .activity-list .activity-avatar {
+	float: right;
+}
+#buddypress ul.item-list.activity-list li.has-comments {
+	padding-bottom: 15px;
+}
+body.activity-permalink #buddypress ul.activity-list li.has-comments {
+	padding-bottom: 0;
+}
+#buddypress .activity-list li.mini {
+	font-size: 80%;
+	position: relative;
+}
+#buddypress .activity-list li.mini .activity-avatar img.avatar,
+#buddypress .activity-list li.mini .activity-avatar img.FB_profile_pic {
+	height: 20px;
+	margin-right: 30px;
+	width: 20px;
+}
+#buddypress .activity-permalink .activity-list li.mini .activity-avatar img.avatar,
+#buddypress .activity-permalink .activity-list li.mini .activity-avatar img.FB_profile_pic {
+	height: auto;
+	margin-right: 0;
+	width: auto;
+}
+body.activity-permalink #buddypress .activity-list > li:first-child {
+	padding-top: 0;
+}
+#buddypress .activity-list li .activity-content {
+	position: relative;
+}
+#buddypress .activity-list li.mini .activity-content p {
+	margin: 0;
+}
+#buddypress .activity-list li.mini .activity-comments {
+	clear: both;
+	font-size: 120%;
+}
+body.activity-permalink #buddypress li.mini .activity-meta {
+	margin-top: 4px;
+}
+#buddypress .activity-list li .activity-inreplyto {
+	color: #888;
+	font-size: 80%;
+}
+#buddypress .activity-list li .activity-inreplyto > p {
+	margin: 0;
+	display: inline;
+}
+#buddypress .activity-list li .activity-inreplyto blockquote,
+#buddypress .activity-list li .activity-inreplyto div.activity-inner {
+	background: none;
+	border: none;
+	display: inline;
+	margin: 0;
+	overflow: hidden;
+	padding: 0;
+}
+#buddypress .activity-list .activity-content {
+	margin: 0 70px 0 0;
+}
+body.activity-permalink #buddypress .activity-list li .activity-content {
+	border: none;
+	font-size: 100%;
+	line-height: 150%;
+	margin-right: 170px;
+	margin-left: 0;
+	padding: 0;
+}
+body.activity-permalink #buddypress .activity-list li .activity-header > p {
+	margin: 0;
+	padding: 5px 0 0 0;
+}
+#buddypress .activity-list .activity-content .activity-header,
+#buddypress .activity-list .activity-content .comment-header {
+	color: #888;
+	line-height: 220%;
+}
+#buddypress .activity-header {
+	margin-left: 20px;
+}
+#buddypress .activity-header a,
+#buddypress .comment-meta a,
+#buddypress .acomment-meta a {
+	text-decoration: none;
+}
+#buddypress .activity-list .activity-content .activity-header img.avatar {
+	float: none !important;
+	margin: 0 0 -8px 5px !important;
+}
+#buddypress a.bp-secondary-action,
+#buddypress span.highlight {
+	font-size: 80%;
+	padding: 0;
+	margin-left: 5px;
+	text-decoration: none;
+}
+#buddypress .activity-list .activity-content .activity-inner,
+#buddypress .activity-list .activity-content blockquote {
+	margin: 10px 0 5px 10px;
+	overflow: hidden;
+}
+#buddypress .activity-list li.new_forum_post .activity-content .activity-inner,
+#buddypress .activity-list li.new_forum_topic .activity-content .activity-inner {
+	border-right: 2px solid #EAEAEA;
+	margin-right: 5px;
+	padding-right: 10px;
+}
+body.activity-permalink #buddypress .activity-content .activity-inner,
+body.activity-permalink #buddypress .activity-content blockquote {
+	margin-right: 0;
+	margin-top: 5px;
+}
+#buddypress .activity-inner > p {
+	word-wrap: break-word;
+}
+#buddypress .activity-inner > .activity-inner {
+	margin: 0;
+}
+#buddypress .activity-inner > blockquote {
+	margin: 0;
+}
+#buddypress .activity-list .activity-content img.thumbnail {
+	border: 2px solid #eee;
+	float: right;
+	margin: 0 0 5px 10px;
+}
+#buddypress .activity-read-more {
+	margin-right: 1em;
+	white-space: nowrap;
+}
+#buddypress .activity-list li.load-more,
+#buddypress .activity-list li.load-newest {
+	background: #f0f0f0;
+	font-size: 110%;
+	margin: 15px 0;
+	padding: 10px 15px;
+	text-align: center;
+}
+#buddypress .activity-list li.load-more a,
+#buddypress .activity-list li.load-newest a {
+	color: #4D4D4D;
+}
+
+
+/*--------------------------------------------------------------
+3.1.2 - Activity Comments
+--------------------------------------------------------------*/
+#buddypress div.activity-meta {
+	margin: 18px 0 0;
+}
+body.activity-permalink #buddypress div.activity-meta {
+	margin-bottom: 6px;
+}
+#buddypress div.activity-meta a {
+	padding: 4px 8px;
+}
+#buddypress a.activity-time-since {
+	color: #aaa;
+	text-decoration: none;
+}
+#buddypress a.activity-time-since:hover {
+	color: #888;
+	text-decoration: underline;
+}
+#buddypress a.bp-primary-action,
+#buddypress #reply-title small a {
+	font-size: 80%;
+	margin-left: 5px;
+	text-decoration: none;
+}
+#buddypress a.bp-primary-action span,
+#buddypress #reply-title small a span {
+	background: #999;
+	color: #fff;
+	font-size: 90%;
+	margin-right: 2px;
+	padding: 0 5px;
+}
+#buddypress a.bp-primary-action:hover span,
+#buddypress #reply-title small a:hover span {
+	background: #555;
+	color: #fff;
+}
+#buddypress div.activity-comments {
+	margin: 0 70px 0 0;
+	overflow: hidden; /* IE fix */
+	position: relative;
+	width: auto;
+	clear: both;
+}
+body.activity-permalink #buddypress div.activity-comments {
+	background: none;
+	margin-right: 170px;
+	width: auto;
+}
+#buddypress div.activity-comments > ul {
+	padding: 0 10px 0 0;
+}
+#buddypress div.activity-comments ul,
+#buddypress div.activity-comments ul li {
+	border: none;
+	list-style: none;
+}
+#buddypress div.activity-comments ul {
+	clear: both;
+	margin: 0;
+}
+#buddypress div.activity-comments ul li {
+	border-top: 1px solid #eee;
+	padding: 10px 0 0;
+}
+body.activity-permalink #buddypress .activity-list li.mini .activity-comments {
+	clear: none;
+	margin-top: 0;
+}
+body.activity-permalink #buddypress div.activity-comments ul li {
+	border-width: 1px;
+	padding: 10px 0 0 0;
+}
+#buddypress div.activity-comments > ul > li:first-child {
+	border-top: none;
+}
+#buddypress div.activity-comments ul li:last-child {
+	margin-bottom: 0;
+}
+#buddypress div.activity-comments ul li > ul {
+	margin-right: 30px;
+	margin-top: 0;
+	padding-right: 10px;
+}
+body.activity-permalink #buddypress div.activity-comments ul li > ul {
+	margin-top: 10px;
+}
+body.activity-permalink #buddypress div.activity-comments > ul {
+	padding: 0 15px 0 10px;
+}
+#buddypress div.activity-comments div.acomment-avatar img {
+	border-width: 1px;
+	float: right;
+	height: 25px;
+	margin-left: 10px;
+	width: 25px;
+}
+#buddypress div.activity-comments div.acomment-content {
+	font-size: 80%;
+	margin: 5px 40px 0 0;
+}
+#buddypress div.acomment-content .time-since,
+#buddypress div.acomment-content .activity-delete-link,
+#buddypress div.acomment-content .comment-header {
+	display: none;
+}
+body.activity-permalink #buddypress div.activity-comments div.acomment-content {
+	font-size: 90%;
+}
+#buddypress div.activity-comments div.acomment-meta {
+	color: #888;
+	font-size: 80%;
+}
+#buddypress div.activity-comments form.ac-form {
+	display: none;
+	padding: 10px;
+}
+#buddypress div.activity-comments li form.ac-form {
+	margin-left: 15px;
+	clear: both;
+}
+#buddypress div.activity-comments form.root {
+	margin-right: 0;
+}
+#buddypress div.activity-comments div#message {
+	margin-top: 15px;
+	margin-bottom: 0;
+}
+#buddypress div.activity-comments form .ac-textarea {
+	background: #fff;
+	border: 1px inset #ccc;
+	margin-bottom: 10px;
+	padding: 8px;
+}
+#buddypress div.activity-comments form textarea {
+	border: none;
+	background: transparent;
+	box-shadow: none;
+	outline: none;
+	color: #555;
+	font-family: inherit;
+	font-size: 100%;
+	height: 60px;
+	padding: 0;
+	margin: 0;
+	width: 100%;
+}
+#buddypress div.activity-comments form input {
+	margin-top: 5px;
+}
+#buddypress div.activity-comments form div.ac-reply-avatar {
+	float: right;
+}
+#buddypress div.ac-reply-avatar img {
+	border: 1px solid #eee;
+}
+#buddypress div.activity-comments form div.ac-reply-content {
+	color: #888;
+	margin-right: 50px;
+	padding-right: 15px;
+}
+#buddypress div.activity-comments form div.ac-reply-content a {
+	text-decoration: none;
+}
+#buddypress .acomment-options {
+	float: right;
+	margin: 5px 40px 5px 0;
+}
+#buddypress .acomment-options a {
+	color: #999;
+}
+#buddypress .acomment-options a:hover {
+	color: inherit;
+}
+
+/*--------------------------------------------------------------
+3.3 - Directories - Members, Groups, Blogs, Forums
+--------------------------------------------------------------*/
+#buddypress div.dir-search {
+	float: left;
+	margin: -39px 0 0 0;
+}
+#buddypress div.dir-search input[type=text],
+#buddypress li.groups-members-search input[type=text] {
+	font-size: 90%;
+	padding: 1px 3px;
+}
+#buddypress .current-member-type {
+	font-style: italic;
+}
+#buddypress .dir-form {
+	clear: both;
+}
+
+/*--------------------------------------------------------------
+3.4 - Errors / Success Messages
+--------------------------------------------------------------*/
+#buddypress div#message {
+	margin: 0 0 15px;
+}
+#buddypress #message.info {
+	margin-bottom: 0;
+}
+#buddypress div#message.updated {
+	clear: both;
+	display: block;
+}
+#buddypress div#message p,
+#sitewide-notice p {
+	font-size: 90%;
+	display: block;
+	padding: 10px 15px;
+}
+#buddypress div#message.error p {
+	background-color: #fdc;
+	border: 1px solid #a00;
+	clear: right;
+	color: #800;
+}
+#buddypress div#message.warning p {
+	background-color: #ffe0af;
+	border: 1px solid #ffd087;
+	clear: right;
+	color: #800;
+}
+#buddypress div#message.updated p {
+	background-color: #efc;
+	border: 1px solid #591;
+	color: #250;
+}
+#buddypress #pass-strength-result {
+	background-color: #eee;
+	border-color: #ddd;
+	border-style: solid;
+	border-width: 1px;
+	display: none;
+	margin: 5px 0 5px 5px;
+	padding: 5px;
+	text-align: center;
+	width: 150px;
+}
+#buddypress .standard-form #basic-details-section #pass-strength-result {
+	width: 35%;
+}
+#buddypress #pass-strength-result.error,
+#buddypress #pass-strength-result.bad {
+	background-color: #ffb78c;
+	border-color: #ff853c !important;
+	display: block;
+}
+#buddypress #pass-strength-result.good {
+	background-color: #ffec8b;
+	border-color: #fc0 !important;
+	display: block;
+}
+#buddypress #pass-strength-result.short {
+	background-color: #ffa0a0;
+	border-color: #f04040 !important;
+	display: block;
+}
+#buddypress #pass-strength-result.strong {
+	background-color: #c3ff88;
+	border-color: #8dff1c !important;
+	display: block;
+}
+#buddypress .standard-form#signup_form div div.error {
+	background: #faa;
+	color: #a00;
+	margin: 0 0 10px 0;
+	padding: 6px;
+	width: 90%;
+}
+#buddypress div.accept,
+#buddypress div.reject {
+	float: right;
+	margin-right: 10px;
+}
+#buddypress ul.button-nav li {
+	float: right;
+	margin: 0 0 10px 10px;
+	list-style: none;
+}
+#buddypress ul.button-nav li.current a {
+	font-weight: bold;
+}
+#sitewide-notice #message {
+	right: 2%;
+	position: fixed;
+	top: 1em;
+	width: 96%;
+	z-index: 9999;
+}
+#sitewide-notice.admin-bar-on #message {
+	top: 3.3em;
+}
+#sitewide-notice strong {
+	display: block;
+	margin-bottom: -1em;
+}
+
+/*--------------------------------------------------------------
+3.5 - Forms
+--------------------------------------------------------------*/
+#buddypress .standard-form textarea,
+#buddypress .standard-form input[type=text],
+#buddypress .standard-form input[type=text],
+#buddypress .standard-form input[type=color],
+#buddypress .standard-form input[type=date],
+#buddypress .standard-form input[type=datetime],
+#buddypress .standard-form input[type=datetime-local],
+#buddypress .standard-form input[type=email],
+#buddypress .standard-form input[type=month],
+#buddypress .standard-form input[type=number],
+#buddypress .standard-form input[type=range],
+#buddypress .standard-form input[type=search],
+#buddypress .standard-form input[type=tel],
+#buddypress .standard-form input[type=time],
+#buddypress .standard-form input[type=url],
+#buddypress .standard-form input[type=week],
+#buddypress .standard-form select,
+#buddypress .standard-form input[type=password],
+#buddypress .dir-search input[type=search],
+#buddypress .dir-search input[type=text],
+#buddypress .groups-members-search input[type=search],
+#buddypress .groups-members-search input[type=text] {
+	border: 1px solid #ccc;
+	background: #fafafa;
+	border-radius: 0;
+	color: #888;
+	font: inherit;
+	font-size: 100%;
+	padding: 6px;
+}
+#buddypress .standard-form select {
+	padding: 3px;
+}
+#buddypress .standard-form input[type=password] {
+	margin-bottom: 5px;
+}
+#buddypress .standard-form label,
+#buddypress .standard-form span.label {
+	display: block;
+	font-weight: bold;
+	margin: 15px 0 5px 0;
+	width: auto;
+}
+#buddypress .standard-form p label,
+#buddypress .standard-form #invite-list label {
+	font-weight: normal;
+	margin: auto;
+}
+#buddypress .standard-form div.checkbox label:nth-child(n+2),
+#buddypress .standard-form div.radio div label {
+	color: #888;
+	font-size: 100%;
+	font-weight: normal;
+	margin: 5px 0 0 0;
+}
+#buddypress .standard-form#sidebar-login-form label {
+	margin-top: 5px;
+}
+#buddypress .standard-form input[type=text] {
+	width: 75%;
+}
+#buddypress .standard-form#sidebar-login-form input[type=text],
+#buddypress .standard-form#sidebar-login-form input[type=password] {
+	padding: 4px;
+	width: 95%;
+}
+#buddypress .standard-form #basic-details-section input[type=password],
+#buddypress .standard-form #blog-details-section input#signup_blog_url {
+	width: 35%;
+}
+#buddypress .standard-form#signup_form input[type=text],
+#buddypress .standard-form#signup_form textarea,
+#buddypress .form-allowed-tags,
+#buddypress #commentform input[type=text],
+#buddypress #commentform textarea {
+	width: 90%;
+}
+#buddypress .standard-form#signup_form div.submit {
+	float: left;
+}
+#buddypress div#signup-avatar img {
+	margin: 0 0 10px 15px;
+}
+#buddypress .standard-form textarea {
+	width: 75%;
+	height: 120px;
+}
+#buddypress .standard-form textarea#message_content {
+	height: 200px;
+}
+#buddypress .standard-form#send-reply textarea {
+	width: 97.5%;
+}
+#buddypress .standard-form p.description {
+	color: #888;
+	font-size: 80%;
+	margin: 5px 0;
+}
+#buddypress .standard-form div.submit {
+	clear: both;
+	padding: 15px 0 0 0;
+}
+#buddypress .standard-form p.submit {
+	margin-bottom: 0;
+	padding: 15px 0 0 0;
+}
+#buddypress .standard-form div.submit input {
+	margin-left: 15px;
+}
+#buddypress .standard-form div.radio ul {
+	margin: 10px 38px 15px 0;
+	list-style: disc;
+}
+#buddypress .standard-form div.radio ul li {
+	margin-bottom: 5px;
+}
+#buddypress .standard-form a.clear-value {
+	display: block;
+	margin-top: 5px;
+	outline: none;
+}
+#buddypress .standard-form #basic-details-section,
+#buddypress .standard-form #blog-details-section,
+#buddypress .standard-form #profile-details-section {
+	float: right;
+	width: 48%;
+}
+#buddypress .standard-form #profile-details-section {
+	float: left;
+}
+#buddypress .standard-form #blog-details-section,
+#buddypress #notifications-bulk-management {
+	clear: right;
+}
+body.no-js #buddypress #notifications-bulk-management #select-all-notifications,
+body.no-js #buddypress label[for="message-type-select"],
+body.no-js #buddypress #message-type-select,
+body.no-js #buddypress #delete_inbox_messages,
+body.no-js #buddypress #delete_sentbox_messages,
+body.no-js #buddypress #messages-bulk-management #select-all-messages {
+	display: none;
+}
+#buddypress .standard-form input:focus,
+#buddypress .standard-form textarea:focus,
+#buddypress .standard-form select:focus {
+	background: #fafafa;
+	color: #555;
+}
+#buddypress form#send-invite-form {
+	margin-top: 20px;
+}
+#buddypress div#invite-list {
+	background: #f5f5f5;
+	height: 400px;
+	margin: 0 0 10px;
+	overflow: auto;
+	padding: 5px;
+	width: 160px;
+}
+#buddypress button,
+#buddypress a.button,
+#buddypress input[type=submit],
+#buddypress input[type=button],
+#buddypress input[type=reset],
+#buddypress ul.button-nav li a,
+#buddypress .generic-button a,
+#buddypress .comment-reply-link,
+a.bp-title-button {
+	background: #fff; /* Old browsers */
+	border: 1px solid #ccc;
+	color: #777;
+	font-size: small;
+	cursor: pointer;
+	outline: none;
+	padding: 4px 10px;
+	text-align: center;
+	text-decoration: none;
+}
+#buddypress button:hover,
+#buddypress a.button:hover,
+#buddypress a.button:focus,
+#buddypress input[type=submit]:hover,
+#buddypress input[type=button]:hover,
+#buddypress input[type=reset]:hover,
+#buddypress ul.button-nav li a:hover,
+#buddypress ul.button-nav li.current a,
+#buddypress div.generic-button a:hover,
+#buddypress .comment-reply-link:hover {
+	background: #ededed;
+	border: 1px solid #bbb;
+	color: #555;
+	outline: none;
+	text-decoration: none;
+}
+
+#buddypress form.standard-form .left-menu {
+	float: right;
+}
+
+#buddypress form.standard-form .left-menu #invite-list ul{
+	margin:1%;
+	list-style: none;
+}
+
+#buddypress form.standard-form .left-menu #invite-list ul li {
+	margin:0 1% 0 0;
+}
+
+#buddypress form.standard-form .main-column {
+	margin-right: 190px;
+}
+
+#buddypress form.standard-form .main-column ul#friend-list {
+	clear:none;
+	float: right;
+}
+
+#buddypress form.standard-form .main-column ul#friend-list h4 {
+	clear:none;
+}
+
+/* Overrides for embedded WP editors */
+#buddypress .wp-editor-wrap a.button,
+#buddypress .wp-editor-wrap button,
+#buddypress .wp-editor-wrap input[type=submit],
+#buddypress .wp-editor-wrap input[type=button],
+#buddypress .wp-editor-wrap input[type=reset] {
+	padding: 0 8px 1px;
+}
+
+/* Form classes & generic attr styling */
+#buddypress form *[disabled="disabled"]{
+	cursor: default;
+	opacity: .4;
+}
+
+.bp-screen-reader-text {
+	position: absolute;
+	margin: -1px;
+	padding: 0;
+	height: 1px;
+	width: 1px;
+	overflow: hidden;
+	clip: rect(0 0 0 0);
+	border: 0;
+	word-wrap: normal !important;
+}
+
+/*--------------------------------------------------------------
+3.6 - Ajax Loading
+--------------------------------------------------------------*/
+#buddypress a.loading,
+#buddypress input.loading {
+	-webkit-animation: loader-pulsate .5s infinite ease-in-out alternate;
+	-moz-animation: loader-pulsate .5s infinite ease-in-out alternate;
+	border-color: #aaa;
+}
+@-webkit-keyframes loader-pulsate {
+	from {
+		border-color: #aaa;
+		-webkit-box-shadow: 0 0 6px #ccc;
+		box-shadow: 0 0 6px #ccc;
+	}
+	to {
+		border-color: #ccc;
+		-webkit-box-shadow: 0 0 6px #f8f8f8;
+		box-shadow: 0 0 6px #f8f8f8;
+	}
+}
+@-moz-keyframes loader-pulsate {
+	from {
+		border-color: #aaa;
+		-moz-box-shadow: 0 0 6px #ccc;
+		box-shadow: 0 0 6px #ccc;
+	}
+	to {
+		border-color: #ccc;
+		-moz-box-shadow: 0 0 6px #f8f8f8;
+		box-shadow: 0 0 6px #f8f8f8;
+	}
+}
+
+#buddypress a.loading:hover,
+#buddypress input.loading:hover {
+    color: #777;
+}
+#buddypress input[type="submit"].pending,
+#buddypress input[type="button"].pending,
+#buddypress input[type="reset"].pending,
+#buddypress input[type="submit"].disabled,
+#buddypress input[type="button"].disabled,
+#buddypress input[type="reset"].disabled,
+#buddypress input[type="submit"][disabled=disabled],
+#buddypress button.pending,
+#buddypress button.disabled,
+#buddypress div.pending a,
+#buddypress a.disabled {
+    border-color: #eee;
+    color: #bbb;
+    cursor: default;
+}
+#buddypress input[type="submit"]:hover.pending,
+#buddypress input[type="button"]:hover.pending,
+#buddypress input[type="reset"]:hover.pending,
+#buddypress input[type="submit"]:hover.disabled,
+#buddypress input[type="button"]:hover.disabled,
+#buddypress input[type="reset"]:hover.disabled,
+#buddypress button.pending:hover,
+#buddypress button.disabled:hover,
+#buddypress div.pending a:hover,
+#buddypress a.disabled:hover {
+	border-color: #eee;
+    color: #bbb;
+}
+
+/*--------------------------------------------------------------
+3.7 - Forums, Tables and Topics
+--------------------------------------------------------------*/
+#buddypress ul#topic-post-list {
+	margin: 0;
+	width: auto;
+}
+#buddypress ul#topic-post-list li {
+	padding: 15px;
+	position: relative;
+}
+#buddypress ul#topic-post-list li.alt {
+	background: #f5f5f5;
+}
+#buddypress ul#topic-post-list li div.poster-meta {
+	color: #888;
+	margin-bottom: 10px;
+}
+#buddypress ul#topic-post-list li div.post-content {
+	margin-right: 54px;
+}
+#buddypress div.topic-tags {
+	font-size: 80%;
+}
+#buddypress div.admin-links {
+	color: #888;
+	font-size: 80%;
+	position: absolute;
+	top: 15px;
+	left: 25px;
+}
+#buddypress div#topic-meta {
+	margin: 0;
+	padding: 5px 19px 30px;
+	position: relative;
+}
+#buddypress div#topic-meta div.admin-links {
+	left: 19px;
+	top: -36px;
+}
+#buddypress div#topic-meta h3 {
+	margin: 5px 0;
+}
+#buddypress div#new-topic-post {
+	display: none;
+	margin: 20px 0 0 0;
+	padding: 1px 0 0 0;
+}
+#buddypress table.notifications,
+#buddypress table.notifications-settings,
+#buddypress table.profile-settings,
+#buddypress table.profile-fields,
+#buddypress table.wp-profile-fields,
+#buddypress table.messages-notices,
+#buddypress table.forum {
+	width: 100%;
+}
+#buddypress table.notifications thead tr,
+#buddypress table.notifications-settings thead tr,
+#buddypress table.profile-settings thead tr,
+#buddypress table.profile-fields thead tr,
+#buddypress table.wp-profile-fields thead tr,
+#buddypress table.messages-notices thead tr,
+#buddypress table.forum thead tr {
+	background: #eaeaea;
+}
+#buddypress table#message-threads {
+	clear: both;
+}
+#buddypress table.profile-fields {
+	margin-bottom: 20px;
+}
+#buddypress table.profile-fields:last-child {
+	margin-bottom: 0;
+}
+#buddypress table.profile-fields p {
+	margin: 0;
+}
+#buddypress table.profile-fields p:last-child {
+	margin-top: 0;
+}
+#buddypress table.notifications tr td,
+#buddypress table.notifications-settings tr td,
+#buddypress table.profile-settings tr td,
+#buddypress table.profile-fields tr td,
+#buddypress table.wp-profile-fields tr td,
+#buddypress table.messages-notices tr td,
+#buddypress table.forum tr td,
+#buddypress table.notifications tr th,
+#buddypress table.notifications-settings tr th,
+#buddypress table.profile-fields tr th,
+#buddypress table.wp-profile-fields tr th,
+#buddypress table.messages-notices tr th,
+#buddypress table.forum tr th {
+	padding: 8px;
+	vertical-align: middle;
+}
+#buddypress table.notifications tr td.label,
+#buddypress table.notifications-settings tr td.label,
+#buddypress table.profile-fields tr td.label,
+#buddypress table.wp-profile-fields tr td.label,
+#buddypress table.messages-notices tr td.label,
+#buddypress table.forum tr td.label {
+	border-left: 1px solid #eaeaea;
+	font-weight: bold;
+	width: 25%;
+}
+#buddypress #message-threads .thread-info {
+	min-width: 40%;
+}
+#buddypress table tr td.thread-info p {
+	margin: 0;
+}
+#buddypress table tr td.thread-info p.thread-excerpt {
+	color: #888;
+	font-size: 80%;
+	margin-top: 3px;
+}
+#buddypress table.forum td {
+	text-align: center;
+}
+#buddypress table.notifications tr.alt td,
+#buddypress table.notifications-settings tr.alt td,
+#buddypress table.profile-settings tr.alt td,
+#buddypress table.profile-fields tr.alt td,
+#buddypress table.wp-profile-fields tr.alt td,
+#buddypress table.messages-notices tr.alt td,
+#buddypress table.forum tr.alt td {
+	background: #f5f5f5;
+}
+#buddypress table.notification-settings {
+	margin-bottom: 20px;
+	text-align: right;
+}
+#buddypress #groups-notification-settings {
+	margin-bottom: 0;
+}
+#buddypress table.notifications th.icon,
+#buddypress table.notifications td:first-child,
+#buddypress table.notification-settings th.icon,
+#buddypress table.notification-settings td:first-child {
+	display: none;
+}
+#buddypress table.notification-settings th.title,
+#buddypress table.profile-settings th.title {
+	width: 80%;
+}
+#buddypress table.notification-settings .yes,
+#buddypress table.notification-settings .no {
+	text-align: center;
+	width: 40px;
+}
+#buddypress table.forum {
+	margin: 0;
+	width: auto;
+	clear: both;
+}
+#buddypress table.forum tr.sticky td {
+	font-size: 110%;
+	background: #fff9db;
+	border-top: 1px solid #ffe8c4;
+	border-bottom: 1px solid #ffe8c4;
+}
+#buddypress table.forum tr.closed td.td-title {
+	padding-right: 35px;
+}
+#buddypress table.forum td p.topic-text {
+	color: #888;
+	font-size: 100%;
+}
+#buddypress table.forum tr > td:first-child,
+#buddypress table.forum tr > th:first-child {
+	padding-right: 15px;
+}
+#buddypress table.forum tr > td:last-child,
+#buddypress table.forum tr > th:last-child {
+	padding-left: 15px;
+}
+#buddypress table.forum tr th#th-title,
+#buddypress table.forum tr th#th-poster,
+#buddypress table.forum tr th#th-group,
+#buddypress table.forum td.td-poster,
+#buddypress table.forum td.td-group,
+#buddypress table.forum td.td-title {
+	text-align: right;
+}
+#buddypress table.forum tr td.td-title a.topic-title {
+	font-size: 110%;
+}
+#buddypress table.forum td.td-freshness {
+	white-space: nowrap;
+}
+#buddypress table.forum td.td-freshness span.time-since {
+	font-size: 80%;
+	color: #888;
+}
+#buddypress table.forum td img.avatar {
+	float: none;
+	margin: 0 0 -8px 5px;
+}
+#buddypress table.forum td.td-poster,
+#buddypress table.forum td.td-group {
+	min-width: 140px;
+}
+#buddypress table.forum th#th-title {
+	width: 80%;
+}
+#buddypress table.forum th#th-freshness {
+	width: 25%;
+}
+#buddypress table.forum th#th-postcount {
+	width: 15%;
+}
+#buddypress table.forum p.topic-meta {
+	font-size: 80%;
+	margin: 5px 0 0 0;
+}
+
+/*-------------------------------------------------------------------------
+3.8 - Headers, Lists and Tabs - Activity, Groups, Blogs, Forums, Profiles
+-------------------------------------------------------------------------*/
+#buddypress .item-body {
+	margin: 20px 0;
+}
+#buddypress span.activity {
+	display: inline-block;
+	font-size: small;
+	opacity: 0.8;
+	padding: 0;
+}
+#buddypress span.user-nicename {
+	color: #777;
+	display: inline-block;
+	font-size: 120%;
+	font-weight: bold;
+}
+#buddypress div#message p,
+#sitewide-notice p {
+	font-weight: normal;
+	margin-top: 3px;
+	text-decoration: none;
+	background-color: #ffd;
+	border: 1px solid #cb2;
+	color: #440;
+}
+#buddypress #item-header:after {
+	clear: both;
+	content: "";
+	display: table;
+}
+#buddypress div#item-header div#item-header-content {
+	float: right;
+	margin-right: 0;
+}
+#buddypress div#item-header h2 {
+	line-height: 120%;
+	margin: 0 0 15px 0;
+}
+#buddypress div#item-header h2 a {
+	color: #777;
+	text-decoration: none;
+}
+#buddypress div#item-header img.avatar {
+	float: right;
+	margin: 0 0 19px 15px;
+}
+#buddypress div#item-header h2 {
+	margin-bottom: 5px;
+}
+#buddypress div#item-header h2 span.highlight {
+	font-size: 60%;
+	font-weight: normal;
+	line-height: 170%;
+	vertical-align: middle;
+	display: inline-block;
+}
+#buddypress div#item-header h2 span.highlight span {
+	background: #a1dcfa;
+	color: #fff;
+	cursor: pointer;
+	font-weight: bold;
+	font-size: 80%;
+	margin-bottom: 2px;
+	padding: 1px 4px;
+	position: relative;
+	left: -2px;
+	top: -2px;
+	vertical-align: middle;
+}
+#buddypress div#item-header div#item-meta {
+	font-size: 80%;
+	color: #aaa;
+	overflow: hidden;
+	margin: 15px 0 5px 0;
+	padding-bottom: 10px;
+}
+#buddypress div#item-header div#item-actions {
+	float: left;
+	margin: 0 15px 15px 0;
+	text-align: left;
+	width: 20%;
+}
+#buddypress div#item-header div#item-actions h3 {
+	margin: 0 0 5px 0;
+}
+#buddypress div#item-header ul {
+	margin-bottom: 15px;
+	overflow: hidden;
+}
+#buddypress div#item-header ul h5,
+#buddypress div#item-header ul span,
+#buddypress div#item-header ul hr {
+	display: none;
+}
+#buddypress div#item-header ul li {
+	float: left;
+	list-style: none;
+}
+#buddypress div#item-header ul img.avatar,
+#buddypress div#item-header ul.avatars img.avatar {
+	height: 30px;
+	margin: 2px;
+	width: 30px;
+}
+#buddypress div#item-header div.generic-button,
+#buddypress div#item-header a.button {
+	float: right;
+	margin: 10px 0 0 10px;
+}
+body.no-js #buddypress div#item-header .js-self-profile-button {
+	display:none;
+}
+#buddypress div#item-header div#message.info {
+	line-height: 80%;
+}
+#buddypress ul.item-list {
+	border-top: 1px solid #eaeaea;
+	width: 100%;
+	list-style: none;
+	clear: both;
+	margin: 0;
+	padding: 0;
+}
+body.activity-permalink #buddypress ul.item-list,
+body.activity-permalink #buddypress ul.item-list li.activity-item {
+	border: none;
+}
+#buddypress ul.item-list li {
+	border-bottom: 1px solid #eaeaea;
+	padding: 15px 0;
+	margin: 0;
+	position: relative;
+	list-style: none;
+}
+#buddypress ul.single-line li {
+	border: none;
+}
+#buddypress ul.item-list li img.avatar {
+	float: right;
+	margin: 0 0 0 10px;
+}
+#buddypress ul.item-list li div.item-title,
+#buddypress ul.item-list li h4 {
+	font-weight: normal;
+	font-size: 90%;
+	margin: 0;
+	width: 75%;
+}
+#buddypress ul.item-list li div.item-title span {
+	color: #999;
+	font-size: 80%;
+}
+#buddypress ul.item-list li div.item-desc {
+	color: #888;
+	font-size: 80%;
+	margin: 10px 60px 0 0;
+	width: 50%;
+}
+#buddypress ul.item-list li.group-no-avatar div.item-desc {
+	margin-right: 0;
+}
+#buddypress ul.item-list li div.action {
+	position: absolute;
+	top: 15px;
+	left: 0;
+	text-align: left;
+}
+#buddypress ul.item-list li div.meta {
+	color: #888;
+	font-size: 80%;
+	margin-top: 10px;
+}
+#buddypress ul.item-list li h5 span.small {
+	float: left;
+	font-size: 80%;
+	font-weight: normal;
+}
+#buddypress div.item-list-tabs {
+	background: transparent;
+	clear: right;
+	overflow: hidden;
+}
+
+#buddypress div.item-list-tabs ul {
+	margin: 0;
+	padding: 0;
+}
+#buddypress div.item-list-tabs ul li {
+	float: right;
+	margin: 0;
+	list-style: none;
+}
+#buddypress div.item-list-tabs#subnav ul li {
+	margin-top: 0;
+}
+#buddypress div.item-list-tabs ul li.last {
+	float: left;
+	margin: 7px 0 0;
+}
+#buddypress div.item-list-tabs#subnav ul li.last {
+	margin-top: 4px;
+}
+#buddypress div.item-list-tabs ul li.last select {
+	max-width: 185px;
+}
+#buddypress div.item-list-tabs ul li a,
+#buddypress div.item-list-tabs ul li span {
+	display: block;
+	padding: 5px 10px;
+	text-decoration: none;
+}
+#buddypress div.item-list-tabs ul li a span {
+	background: #eee;
+	border-radius: 50%;
+	border: 1px solid #ccc;
+	color: #999;
+	display: inline;
+	font-size: 70%;
+	margin-right: 2px;
+	padding: 3px 6px;
+	text-align: center;
+	vertical-align: middle;
+}
+#buddypress div.item-list-tabs ul li.selected a,
+#buddypress div.item-list-tabs ul li.current a {
+	background-color: #eee;
+	color: #555;
+	opacity: .8;
+	font-weight: bold;
+}
+#buddypress div.item-list-tabs ul li.selected a span,
+#buddypress div.item-list-tabs ul li.current a span,
+#buddypress div.item-list-tabs ul li a:hover span {
+	background-color: #eee;
+}
+#buddypress div.item-list-tabs ul li.selected a span,
+#buddypress div.item-list-tabs ul li.current a span {
+	background-color: #fff;
+}
+#buddypress div#item-nav ul li.loading a {
+	background-position: 12% 50%;
+}
+#buddypress div.item-list-tabs#object-nav {
+	margin-top: 0;
+}
+#buddypress div.item-list-tabs#subnav {
+	background: transparent;
+	margin: 10px 0 10px;
+	overflow: hidden;
+}
+#buddypress #admins-list li,
+#buddypress #mods-list li,
+#buddypress #members-list li {
+	overflow: auto;
+	list-style: none;
+}
+#buddypress #item-buttons:empty {
+	display: none;
+}
+
+/*--------------------------------------------------------------
+3.8.1 - Cover Image
+--------------------------------------------------------------*/
+
+#buddypress #cover-image-container {
+	position: relative;
+	z-index: 0;
+}
+
+#buddypress #header-cover-image {
+	background-color: #c5c5c5;
+	background-position: center top;
+	background-repeat: no-repeat;
+	background-size: cover;
+	border: 0;
+	display: block;
+	right: 0;
+	margin: 0;
+	padding: 0;
+	position: absolute;
+	top: 0;
+	width: 100%;
+	z-index: 1;
+}
+
+#buddypress #item-header-cover-image {
+	padding: 0 1em;
+	position: relative;
+	z-index: 2;
+}
+
+/*--------------------------------------------------------------
+3.9 - Private Messaging Threads
+--------------------------------------------------------------*/
+#buddypress table#message-threads tr.unread td {
+	background: #fff9db;
+	border-top: 1px solid #ffe8c4;
+	border-bottom: 1px solid #ffe8c4;
+	font-weight: bold;
+}
+#buddypress table#message-threads tr.unread td .thread-excerpt,
+#buddypress table#message-threads tr.unread td .activity,
+#buddypress table#message-threads tr.unread td.thread-options {
+	font-weight: normal;
+}
+#buddypress li span.unread-count,
+#buddypress tr.unread span.unread-count {
+	background: #dd0000;
+	color: #fff;
+	font-weight: bold;
+	padding: 2px 8px;
+}
+#buddypress div.item-list-tabs ul li a span.unread-count {
+	padding: 1px 6px;
+	color: #fff;
+}
+#buddypress div#message-thread div.message-box {
+	margin: 0;
+	padding: 15px;
+}
+#buddypress div#message-thread div.alt {
+	background: #f4f4f4;
+}
+#buddypress div#message-thread p#message-recipients {
+	margin: 10px 0 20px 0;
+}
+#buddypress div#message-thread img.avatar {
+	float: right;
+	margin: 0 0 0 10px;
+	vertical-align: middle;
+}
+#buddypress div#message-thread strong {
+	font-size: 100%;
+	margin: 0;
+}
+#buddypress div#message-thread strong a {
+	text-decoration: none;
+}
+#buddypress div#message-thread strong span.activity {
+	margin-top: 4px;
+}
+#buddypress div#message-thread div.message-metadata {
+	overflow: hidden;
+}
+#buddypress div#message-thread div.message-content {
+	margin-right: 45px;
+}
+#buddypress div#message-thread div.message-options {
+	text-align: left;
+}
+#buddypress #message-threads img.avatar {
+	max-width: none;
+}
+#buddypress div.message-search {
+	float: left;
+	margin: 0 20px;
+}
+
+.message-metadata {
+	position: relative;
+}
+.message-star-actions {
+	position: absolute;
+	left: 0;
+	top: 0;
+}
+#buddypress a.message-action-star,
+#buddypress a.message-action-unstar {
+	border-bottom: 0;
+	text-decoration: none;
+	outline: none;
+}
+a.message-action-star {
+	opacity: .7;
+}
+a.message-action-star:hover {
+	opacity: 1;
+}
+.message-action-star span.icon:before,
+.message-action-unstar span.icon:before {
+	font-family: dashicons;
+	font-size: 18px;
+}
+.message-action-star span.icon:before {
+	color: #aaa;
+	content: "\f154";
+}
+.message-action-unstar span.icon:before {
+	color: #FCDD77;
+	content: "\f155";
+}
+
+/*--------------------------------------------------------------
+3.10 - Extended Profiles
+--------------------------------------------------------------*/
+
+#buddypress div.profile h4 {
+	margin-bottom: auto;
+	margin-top: 15px;
+}
+#buddypress #profile-edit-form ul.button-nav {
+	margin-top: 15px;
+}
+body.no-js #buddypress .field-visibility-settings-toggle,
+body.no-js #buddypress .field-visibility-settings-close {
+	display: none;
+}
+#buddypress .field-visibility-settings {
+	display: none;
+	margin-top: 10px;
+}
+	body.no-js #buddypress .field-visibility-settings {
+		display: block;
+	}
+#buddypress .current-visibility-level {
+	font-weight: bold;
+	font-style: normal;
+}
+#buddypress .field-visibility-settings,
+#buddypress .field-visibility-settings-toggle,
+#buddypress .field-visibility-settings-notoggle {
+	color: #888;
+}
+#buddypress .field-visibility-settings-toggle a,
+#buddypress .field-visibility-settings a {
+	font-size: 80%;
+}
+body.register #buddypress div.page ul {
+	list-style: none;
+}
+#buddypress .standard-form .field-visibility-settings label {
+	margin: 0;
+	font-weight: normal;
+}
+#buddypress .field-visibility-settings legend,
+#buddypress .field-visibility-settings-toggle {
+	font-style: italic;
+}
+#buddypress .field-visibility-settings .radio {
+	list-style: none;
+	margin-bottom: 0;
+}
+#buddypress .field-visibility select {
+	margin: 0;
+}
+#buddypress .wp-editor-container {
+	border: 1px solid #dedede;
+}
+#buddypress .html-active button.switch-html {
+	border-bottom-color: transparent;
+	border-bottom-right-radius: 0;
+	border-bottom-left-radius: 0;
+	background: #f5f5f5;
+}
+#buddypress .tmce-active button.switch-tmce {
+	border-bottom-color: transparent;
+	border-bottom-right-radius: 0;
+	border-bottom-left-radius: 0;
+	background: #f5f5f5;
+}
+#buddypress .standard-form .wp-editor-container textarea {
+	width: 100%;
+	padding-top: 0;
+	padding-bottom: 0;
+}
+
+/*--------------------------------------------------------------
+3.11 - Widgets
+--------------------------------------------------------------*/
+
+.widget.buddypress div.item-avatar img.avatar {
+	float: right;
+	margin: 0 0 15px 10px;
+}
+
+.widget.buddypress span.activity {
+	display: inline-block;
+	font-size: small;
+	opacity: 0.8;
+	padding: 0;
+}
+
+.widget.buddypress div.item-options {
+	font-size: 90%;
+	margin: 0 0 1em 0;
+	padding: 1em 0;
+}
+
+.widget.buddypress div.item{
+	margin:0 0 1em 0;
+}
+
+.widget.buddypress div.item-meta,
+.widget.buddypress div.item-content {
+	font-size: 11px;
+	margin-right: 50px;
+}
+
+.widget.buddypress ul.item-list img.avatar {
+	height: 40px;
+	margin-left: 10px;
+	width: 40px;
+}
+.widget.buddypress div.item-avatar img {
+	height: 40px;
+	margin: 1px;
+	width: 40px;
+}
+
+.widget.buddypress div.avatar-block{
+	overflow: hidden;
+}
+
+.widget.buddypress #bp-login-widget-form label {
+	display: block;
+	margin: 1rem 0 .5rem;
+}
+
+.widget.buddypress #bp-login-widget-form #bp-login-widget-submit {
+	margin-left: 10px;
+}
+
+.widget.buddypress .bp-login-widget-user-avatar {
+	float: right;
+}
+
+.bp-login-widget-user-avatar img.avatar {
+	height: 40px;
+	width: 40px;
+}
+
+.widget.buddypress .bp-login-widget-user-links > div {
+	padding-right: 60px;
+}
+
+.widget.buddypress .bp-login-widget-user-links > div {
+	margin-bottom: .5rem;
+}
+
+.widget.buddypress .bp-login-widget-user-links > div.bp-login-widget-user-link a {
+	font-weight: bold;
+}
+
+.widget.buddypress #groups-list,
+.widget.buddypress #members-list,
+.widget.buddypress #friends-list {
+	margin-right: 0;
+	padding-right: 0;
+}
+
+.widget.buddypress #groups-list li,
+.widget.buddypress #members-list li,
+.widget.buddypress #friends-list li {
+	clear: both;
+	list-style-type: none;
+}
+
+/*--------------------------------------------------------------
+4.0 - Media Queries
+--------------------------------------------------------------*/
+/*--------------------------------------------------------------
+4.1 - Smartphones - landscape
+--------------------------------------------------------------*/
+@media only screen and (max-width: 480px ){
+	#buddypress div.dir-search {
+		float: left;
+		margin-top: -50px;
+		text-align: left;
+	}
+	#buddypress div.dir-search input[type="text"] {
+		margin-bottom: 1em;
+		width: 50%;
+	}
+	a.bp-title-button {
+		margin-right: 10px;
+	}
+	#buddypress form.standard-form .main-column div.action{
+		position: relative;
+		margin-bottom:1em;
+	}
+	#buddypress form.standard-form .main-column ul#friend-list h4{
+		width:100%;
+	}
+}
+
+/*--------------------------------------------------------------
+4.2 - Smartphones - portrait
+--------------------------------------------------------------*/
+@media only screen and (max-width: 320px) {
+	#buddypress div.dir-search {
+		clear: right;
+		float: right;
+		margin-top: 0;
+		text-align: right;
+	}
+	#buddypress li#groups-order-select {
+		clear: right;
+		float: right;
+	}
+	#buddypress ul.item-list li div.action {
+		clear: right;
+		float: right;
+		margin-top: 0;
+		margin-right: 70px;
+		position: relative;
+		top: 0;
+		left: 0;
+		text-align: right;
+	}
+	#buddypress ul.item-list li div.item-desc {
+		clear: right;
+		float: right;
+		margin: 10px 0 0;
+		width: auto;
+	}
+	#buddypress li div.item {
+		margin-right: 70px;
+		width: auto;
+	}
+	#buddypress ul.item-list li div.meta {
+		margin-top: 0;
+	}
+	#buddypress .item-desc p {
+		margin: 0 0 10px;
+	}
+	#buddypress div.pagination .pag-count {
+		margin-right: 0;
+	}
+}
+
+/*--------------------------------------------------------------
+4.2 - Smartphones - smaller screen sizes
+--------------------------------------------------------------*/
+@media only screen and (max-width: 240px) {
+	#buddypress div.dir-search {
+		float: right;
+		margin: 0;
+	}
+	#buddypress div.dir-search input[type="text"] {
+		width: 50%;
+	}
+	#buddypress li#groups-order-select {
+		float: right;
+	}
+	#buddypress ul.item-list li img.avatar {
+		width: 30px;
+		height: auto;
+	}
+	#buddypress ul.item-list li div.action,
+	#buddypress li div.item{
+		margin-right: 45px;
+	}
+	h1 a.bp-title-button {
+		clear: right;
+		float: right;
+		margin: 10px 0 20px;
+	}
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/buddypress.css
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/buddypress.css	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/buddypress.css	(revision 10410)
@@ -0,0 +1,1811 @@
+/*--------------------------------------------------------------
+Hello, this is the BuddyPress Legacy stylesheet.
+
+----------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+----------------------------------------------------------------
+1.0 - Navigation
+	1.1 - Pagination
+2.0 - WordPress
+	2.1 - Images
+3.0 - BuddyPress
+	3.1 - Activity
+		3.1.1 - Activity Listing
+		3.1.2 - Activity Comments
+	3.2 - Toolbar
+	3.3 - Directories - Members, Groups, Blogs, Forums
+	3.4 - Error / Success Messages
+	3.5 - Forms
+	3.6 - Ajax Loading
+	3.7 - Topics and Tables - Forums and General
+	3.8 - Headers, Lists and Tabs - Activity, Groups, Blogs, Forums
+		3.8.1 - Cover Image
+	3.9 - Private Messaging Threads
+	3.10 - Extended Profiles
+	3.11 - Widgets
+4.0 - Media Queries
+	4.1 - Smartphones Landscape
+	4.2 - Smartphones Portrait
+	4.3 - Smartphones - smaller screen sizes
+--------------------------------------------------------------*/
+
+/*--------------------------------------------------------------
+1 - Navigation
+--------------------------------------------------------------*/
+/*--------------------------------------------------------------
+1.1 - Pagination
+--------------------------------------------------------------*/
+#buddypress div.pagination {
+	background: transparent;
+	border: none;
+	color: #888;
+	font-size: small;
+	margin: 0;
+	position: relative;
+	display: block;
+	float: left;
+	width: 100%;
+	padding: 10px 0;
+}
+#buddypress div.pagination .pag-count {
+	float: left;
+	margin-left: 10px;
+}
+#buddypress div.pagination .pagination-links {
+	float: right;
+	margin-right: 10px;
+}
+#buddypress div.pagination .pagination-links span,
+#buddypress div.pagination .pagination-links a {
+	font-size: 90%;
+	padding: 0 5px;
+}
+#buddypress div.pagination .pagination-links a:hover {
+	font-weight: bold;
+}
+#buddypress noscript div.pagination {
+	margin-bottom: 15px;
+}
+#buddypress #nav-above {
+	display: none;
+}
+#buddypress .paged #nav-above {
+	display: block;
+}
+/*--------------------------------------------------------------
+2 - WordPress
+--------------------------------------------------------------*/
+/*--------------------------------------------------------------
+2.1 - Images
+--------------------------------------------------------------*/
+#buddypress img.wp-smiley {
+	border: none !important;
+	clear: none !important;
+	float: none !important;
+	margin: 0 !important;
+	padding: 0 !important;
+}
+#buddypress .clear {
+	clear: left;
+}
+
+/*--------------------------------------------------------------
+3.0 - BuddyPress
+--------------------------------------------------------------*/
+/*--------------------------------------------------------------
+3.1 - Activity
+--------------------------------------------------------------*/
+#buddypress #activity-stream {
+	margin-top: -5px;
+}
+
+#buddypress #activity-stream p {
+	margin: 5px 0;
+}
+
+#buddypress #item-body form#whats-new-form {
+	margin: 0;
+	padding: 0;
+}
+#buddypress .home-page form#whats-new-form {
+	border-bottom: none;
+	padding-bottom: 0;
+}
+#buddypress form#whats-new-form #whats-new-avatar {
+	float: left;
+}
+#buddypress form#whats-new-form #whats-new-content {
+	margin-left: 55px;
+	padding: 0 0 20px 20px;
+}
+#buddypress form#whats-new-form p.activity-greeting {
+	line-height: 0.5em;
+	margin-bottom: 15px;
+	margin-left: 75px;
+}
+#buddypress form#whats-new-form textarea {
+	background: #fff;
+	box-sizing: border-box;
+	color: #555;
+	font-family: inherit;
+	font-size: medium;
+	height: 2.2em;
+	line-height: 1.4;
+	padding: 6px;
+	width: 100%;
+}
+body.no-js #buddypress form#whats-new-form textarea {
+	height: 50px;
+}
+#buddypress form#whats-new-form #whats-new-options select {
+	max-width: 200px;
+	margin-top: 12px;
+}
+#buddypress form#whats-new-form #whats-new-submit {
+	float: right;
+	margin-top: 12px;
+}
+#buddypress #whats-new-options:after {
+	clear: both;
+	content: "";
+	display: table;
+}
+body.no-js #buddypress #whats-new-options {
+	height: auto;
+}
+#buddypress #whats-new:focus {
+	border-color: rgba(31, 179, 221, 0.9) !important;
+	outline-color: rgba(31, 179, 221, 0.9);
+}
+
+/*--------------------------------------------------------------
+3.1.1 - Activity Listing
+--------------------------------------------------------------*/
+#buddypress ul.activity-list li {
+	overflow: hidden;
+	padding: 15px 0 0;
+	list-style: none;
+}
+#buddypress .activity-list .activity-avatar {
+	float: left;
+}
+#buddypress ul.item-list.activity-list li.has-comments {
+	padding-bottom: 15px;
+}
+body.activity-permalink #buddypress ul.activity-list li.has-comments {
+	padding-bottom: 0;
+}
+#buddypress .activity-list li.mini {
+	font-size: 80%;
+	position: relative;
+}
+#buddypress .activity-list li.mini .activity-avatar img.avatar,
+#buddypress .activity-list li.mini .activity-avatar img.FB_profile_pic {
+	height: 20px;
+	margin-left: 30px;
+	width: 20px;
+}
+#buddypress .activity-permalink .activity-list li.mini .activity-avatar img.avatar,
+#buddypress .activity-permalink .activity-list li.mini .activity-avatar img.FB_profile_pic {
+	height: auto;
+	margin-left: 0;
+	width: auto;
+}
+body.activity-permalink #buddypress .activity-list > li:first-child {
+	padding-top: 0;
+}
+#buddypress .activity-list li .activity-content {
+	position: relative;
+}
+#buddypress .activity-list li.mini .activity-content p {
+	margin: 0;
+}
+#buddypress .activity-list li.mini .activity-comments {
+	clear: both;
+	font-size: 120%;
+}
+body.activity-permalink #buddypress li.mini .activity-meta {
+	margin-top: 4px;
+}
+#buddypress .activity-list li .activity-inreplyto {
+	color: #888;
+	font-size: 80%;
+}
+#buddypress .activity-list li .activity-inreplyto > p {
+	margin: 0;
+	display: inline;
+}
+#buddypress .activity-list li .activity-inreplyto blockquote,
+#buddypress .activity-list li .activity-inreplyto div.activity-inner {
+	background: none;
+	border: none;
+	display: inline;
+	margin: 0;
+	overflow: hidden;
+	padding: 0;
+}
+#buddypress .activity-list .activity-content {
+	margin: 0 0 0 70px;
+}
+body.activity-permalink #buddypress .activity-list li .activity-content {
+	border: none;
+	font-size: 100%;
+	line-height: 150%;
+	margin-left: 170px;
+	margin-right: 0;
+	padding: 0;
+}
+body.activity-permalink #buddypress .activity-list li .activity-header > p {
+	margin: 0;
+	padding: 5px 0 0 0;
+}
+#buddypress .activity-list .activity-content .activity-header,
+#buddypress .activity-list .activity-content .comment-header {
+	color: #888;
+	line-height: 220%;
+}
+#buddypress .activity-header {
+	margin-right: 20px;
+}
+#buddypress .activity-header a,
+#buddypress .comment-meta a,
+#buddypress .acomment-meta a {
+	text-decoration: none;
+}
+#buddypress .activity-list .activity-content .activity-header img.avatar {
+	float: none !important;
+	margin: 0 5px -8px 0 !important;
+}
+#buddypress a.bp-secondary-action,
+#buddypress span.highlight {
+	font-size: 80%;
+	padding: 0;
+	margin-right: 5px;
+	text-decoration: none;
+}
+#buddypress .activity-list .activity-content .activity-inner,
+#buddypress .activity-list .activity-content blockquote {
+	margin: 10px 10px 5px 0;
+	overflow: hidden;
+}
+#buddypress .activity-list li.new_forum_post .activity-content .activity-inner,
+#buddypress .activity-list li.new_forum_topic .activity-content .activity-inner {
+	border-left: 2px solid #EAEAEA;
+	margin-left: 5px;
+	padding-left: 10px;
+}
+body.activity-permalink #buddypress .activity-content .activity-inner,
+body.activity-permalink #buddypress .activity-content blockquote {
+	margin-left: 0;
+	margin-top: 5px;
+}
+#buddypress .activity-inner > p {
+	word-wrap: break-word;
+}
+#buddypress .activity-inner > .activity-inner {
+	margin: 0;
+}
+#buddypress .activity-inner > blockquote {
+	margin: 0;
+}
+#buddypress .activity-list .activity-content img.thumbnail {
+	border: 2px solid #eee;
+	float: left;
+	margin: 0 10px 5px 0;
+}
+#buddypress .activity-read-more {
+	margin-left: 1em;
+	white-space: nowrap;
+}
+#buddypress .activity-list li.load-more,
+#buddypress .activity-list li.load-newest {
+	background: #f0f0f0;
+	font-size: 110%;
+	margin: 15px 0;
+	padding: 10px 15px;
+	text-align: center;
+}
+#buddypress .activity-list li.load-more a,
+#buddypress .activity-list li.load-newest a {
+	color: #4D4D4D;
+}
+
+
+/*--------------------------------------------------------------
+3.1.2 - Activity Comments
+--------------------------------------------------------------*/
+#buddypress div.activity-meta {
+	margin: 18px 0 0;
+}
+body.activity-permalink #buddypress div.activity-meta {
+	margin-bottom: 6px;
+}
+#buddypress div.activity-meta a {
+	padding: 4px 8px;
+}
+#buddypress a.activity-time-since {
+	color: #aaa;
+	text-decoration: none;
+}
+#buddypress a.activity-time-since:hover {
+	color: #888;
+	text-decoration: underline;
+}
+#buddypress a.bp-primary-action,
+#buddypress #reply-title small a {
+	font-size: 80%;
+	margin-right: 5px;
+	text-decoration: none;
+}
+#buddypress a.bp-primary-action span,
+#buddypress #reply-title small a span {
+	background: #999;
+	color: #fff;
+	font-size: 90%;
+	margin-left: 2px;
+	padding: 0 5px;
+}
+#buddypress a.bp-primary-action:hover span,
+#buddypress #reply-title small a:hover span {
+	background: #555;
+	color: #fff;
+}
+#buddypress div.activity-comments {
+	margin: 0 0 0 70px;
+	overflow: hidden; /* IE fix */
+	position: relative;
+	width: auto;
+	clear: both;
+}
+body.activity-permalink #buddypress div.activity-comments {
+	background: none;
+	margin-left: 170px;
+	width: auto;
+}
+#buddypress div.activity-comments > ul {
+	padding: 0 0 0 10px;
+}
+#buddypress div.activity-comments ul,
+#buddypress div.activity-comments ul li {
+	border: none;
+	list-style: none;
+}
+#buddypress div.activity-comments ul {
+	clear: both;
+	margin: 0;
+}
+#buddypress div.activity-comments ul li {
+	border-top: 1px solid #eee;
+	padding: 10px 0 0;
+}
+body.activity-permalink #buddypress .activity-list li.mini .activity-comments {
+	clear: none;
+	margin-top: 0;
+}
+body.activity-permalink #buddypress div.activity-comments ul li {
+	border-width: 1px;
+	padding: 10px 0 0 0;
+}
+#buddypress div.activity-comments > ul > li:first-child {
+	border-top: none;
+}
+#buddypress div.activity-comments ul li:last-child {
+	margin-bottom: 0;
+}
+#buddypress div.activity-comments ul li > ul {
+	margin-left: 30px;
+	margin-top: 0;
+	padding-left: 10px;
+}
+body.activity-permalink #buddypress div.activity-comments ul li > ul {
+	margin-top: 10px;
+}
+body.activity-permalink #buddypress div.activity-comments > ul {
+	padding: 0 10px 0 15px;
+}
+#buddypress div.activity-comments div.acomment-avatar img {
+	border-width: 1px;
+	float: left;
+	height: 25px;
+	margin-right: 10px;
+	width: 25px;
+}
+#buddypress div.activity-comments div.acomment-content {
+	font-size: 80%;
+	margin: 5px 0 0 40px;
+}
+#buddypress div.acomment-content .time-since,
+#buddypress div.acomment-content .activity-delete-link,
+#buddypress div.acomment-content .comment-header {
+	display: none;
+}
+body.activity-permalink #buddypress div.activity-comments div.acomment-content {
+	font-size: 90%;
+}
+#buddypress div.activity-comments div.acomment-meta {
+	color: #888;
+	font-size: 80%;
+}
+#buddypress div.activity-comments form.ac-form {
+	display: none;
+	padding: 10px;
+}
+#buddypress div.activity-comments li form.ac-form {
+	margin-right: 15px;
+	clear: both;
+}
+#buddypress div.activity-comments form.root {
+	margin-left: 0;
+}
+#buddypress div.activity-comments div#message {
+	margin-top: 15px;
+	margin-bottom: 0;
+}
+#buddypress div.activity-comments form .ac-textarea {
+	background: #fff;
+	border: 1px inset #ccc;
+	margin-bottom: 10px;
+	padding: 8px;
+}
+#buddypress div.activity-comments form textarea {
+	border: none;
+	background: transparent;
+	box-shadow: none;
+	outline: none;
+	color: #555;
+	font-family: inherit;
+	font-size: 100%;
+	height: 60px;
+	padding: 0;
+	margin: 0;
+	width: 100%;
+}
+#buddypress div.activity-comments form input {
+	margin-top: 5px;
+}
+#buddypress div.activity-comments form div.ac-reply-avatar {
+	float: left;
+}
+#buddypress div.ac-reply-avatar img {
+	border: 1px solid #eee;
+}
+#buddypress div.activity-comments form div.ac-reply-content {
+	color: #888;
+	margin-left: 50px;
+	padding-left: 15px;
+}
+#buddypress div.activity-comments form div.ac-reply-content a {
+	text-decoration: none;
+}
+#buddypress .acomment-options {
+	float: left;
+	margin: 5px 0 5px 40px;
+}
+#buddypress .acomment-options a {
+	color: #999;
+}
+#buddypress .acomment-options a:hover {
+	color: inherit;
+}
+
+/*--------------------------------------------------------------
+3.3 - Directories - Members, Groups, Blogs, Forums
+--------------------------------------------------------------*/
+#buddypress div.dir-search {
+	float: right;
+	margin: -39px 0 0 0;
+}
+#buddypress div.dir-search input[type=text],
+#buddypress li.groups-members-search input[type=text] {
+	font-size: 90%;
+	padding: 1px 3px;
+}
+#buddypress .current-member-type {
+	font-style: italic;
+}
+#buddypress .dir-form {
+	clear: both;
+}
+
+/*--------------------------------------------------------------
+3.4 - Errors / Success Messages
+--------------------------------------------------------------*/
+#buddypress div#message {
+	margin: 0 0 15px;
+}
+#buddypress #message.info {
+	margin-bottom: 0;
+}
+#buddypress div#message.updated {
+	clear: both;
+	display: block;
+}
+#buddypress div#message p,
+#sitewide-notice p {
+	font-size: 90%;
+	display: block;
+	padding: 10px 15px;
+}
+#buddypress div#message.error p {
+	background-color: #fdc;
+	border: 1px solid #a00;
+	clear: left;
+	color: #800;
+}
+#buddypress div#message.warning p {
+	background-color: #ffe0af;
+	border: 1px solid #ffd087;
+	clear: left;
+	color: #800;
+}
+#buddypress div#message.updated p {
+	background-color: #efc;
+	border: 1px solid #591;
+	color: #250;
+}
+#buddypress #pass-strength-result {
+	background-color: #eee;
+	border-color: #ddd;
+	border-style: solid;
+	border-width: 1px;
+	display: none;
+	margin: 5px 5px 5px 0;
+	padding: 5px;
+	text-align: center;
+	width: 150px;
+}
+#buddypress .standard-form #basic-details-section #pass-strength-result {
+	width: 35%;
+}
+#buddypress #pass-strength-result.error,
+#buddypress #pass-strength-result.bad {
+	background-color: #ffb78c;
+	border-color: #ff853c !important;
+	display: block;
+}
+#buddypress #pass-strength-result.good {
+	background-color: #ffec8b;
+	border-color: #fc0 !important;
+	display: block;
+}
+#buddypress #pass-strength-result.short {
+	background-color: #ffa0a0;
+	border-color: #f04040 !important;
+	display: block;
+}
+#buddypress #pass-strength-result.strong {
+	background-color: #c3ff88;
+	border-color: #8dff1c !important;
+	display: block;
+}
+#buddypress .standard-form#signup_form div div.error {
+	background: #faa;
+	color: #a00;
+	margin: 0 0 10px 0;
+	padding: 6px;
+	width: 90%;
+}
+#buddypress div.accept,
+#buddypress div.reject {
+	float: left;
+	margin-left: 10px;
+}
+#buddypress ul.button-nav li {
+	float: left;
+	margin: 0 10px 10px 0;
+	list-style: none;
+}
+#buddypress ul.button-nav li.current a {
+	font-weight: bold;
+}
+#sitewide-notice #message {
+	left: 2%;
+	position: fixed;
+	top: 1em;
+	width: 96%;
+	z-index: 9999;
+}
+#sitewide-notice.admin-bar-on #message {
+	top: 3.3em;
+}
+#sitewide-notice strong {
+	display: block;
+	margin-bottom: -1em;
+}
+
+/*--------------------------------------------------------------
+3.5 - Forms
+--------------------------------------------------------------*/
+#buddypress .standard-form textarea,
+#buddypress .standard-form input[type=text],
+#buddypress .standard-form input[type=text],
+#buddypress .standard-form input[type=color],
+#buddypress .standard-form input[type=date],
+#buddypress .standard-form input[type=datetime],
+#buddypress .standard-form input[type=datetime-local],
+#buddypress .standard-form input[type=email],
+#buddypress .standard-form input[type=month],
+#buddypress .standard-form input[type=number],
+#buddypress .standard-form input[type=range],
+#buddypress .standard-form input[type=search],
+#buddypress .standard-form input[type=tel],
+#buddypress .standard-form input[type=time],
+#buddypress .standard-form input[type=url],
+#buddypress .standard-form input[type=week],
+#buddypress .standard-form select,
+#buddypress .standard-form input[type=password],
+#buddypress .dir-search input[type=search],
+#buddypress .dir-search input[type=text],
+#buddypress .groups-members-search input[type=search],
+#buddypress .groups-members-search input[type=text] {
+	border: 1px solid #ccc;
+	background: #fafafa;
+	border-radius: 0;
+	color: #888;
+	font: inherit;
+	font-size: 100%;
+	padding: 6px;
+}
+#buddypress .standard-form select {
+	padding: 3px;
+}
+#buddypress .standard-form input[type=password] {
+	margin-bottom: 5px;
+}
+#buddypress .standard-form label,
+#buddypress .standard-form span.label {
+	display: block;
+	font-weight: bold;
+	margin: 15px 0 5px 0;
+	width: auto;
+}
+#buddypress .standard-form p label,
+#buddypress .standard-form #invite-list label {
+	font-weight: normal;
+	margin: auto;
+}
+#buddypress .standard-form div.checkbox label:nth-child(n+2),
+#buddypress .standard-form div.radio div label {
+	color: #888;
+	font-size: 100%;
+	font-weight: normal;
+	margin: 5px 0 0 0;
+}
+#buddypress .standard-form#sidebar-login-form label {
+	margin-top: 5px;
+}
+#buddypress .standard-form input[type=text] {
+	width: 75%;
+}
+#buddypress .standard-form#sidebar-login-form input[type=text],
+#buddypress .standard-form#sidebar-login-form input[type=password] {
+	padding: 4px;
+	width: 95%;
+}
+#buddypress .standard-form #basic-details-section input[type=password],
+#buddypress .standard-form #blog-details-section input#signup_blog_url {
+	width: 35%;
+}
+#buddypress .standard-form#signup_form input[type=text],
+#buddypress .standard-form#signup_form textarea,
+#buddypress .form-allowed-tags,
+#buddypress #commentform input[type=text],
+#buddypress #commentform textarea {
+	width: 90%;
+}
+#buddypress .standard-form#signup_form div.submit {
+	float: right;
+}
+#buddypress div#signup-avatar img {
+	margin: 0 15px 10px 0;
+}
+#buddypress .standard-form textarea {
+	width: 75%;
+	height: 120px;
+}
+#buddypress .standard-form textarea#message_content {
+	height: 200px;
+}
+#buddypress .standard-form#send-reply textarea {
+	width: 97.5%;
+}
+#buddypress .standard-form p.description {
+	color: #888;
+	font-size: 80%;
+	margin: 5px 0;
+}
+#buddypress .standard-form div.submit {
+	clear: both;
+	padding: 15px 0 0 0;
+}
+#buddypress .standard-form p.submit {
+	margin-bottom: 0;
+	padding: 15px 0 0 0;
+}
+#buddypress .standard-form div.submit input {
+	margin-right: 15px;
+}
+#buddypress .standard-form div.radio ul {
+	margin: 10px 0 15px 38px;
+	list-style: disc;
+}
+#buddypress .standard-form div.radio ul li {
+	margin-bottom: 5px;
+}
+#buddypress .standard-form a.clear-value {
+	display: block;
+	margin-top: 5px;
+	outline: none;
+}
+#buddypress .standard-form #basic-details-section,
+#buddypress .standard-form #blog-details-section,
+#buddypress .standard-form #profile-details-section {
+	float: left;
+	width: 48%;
+}
+#buddypress .standard-form #profile-details-section {
+	float: right;
+}
+#buddypress .standard-form #blog-details-section,
+#buddypress #notifications-bulk-management {
+	clear: left;
+}
+body.no-js #buddypress #notifications-bulk-management #select-all-notifications,
+body.no-js #buddypress label[for="message-type-select"],
+body.no-js #buddypress #message-type-select,
+body.no-js #buddypress #delete_inbox_messages,
+body.no-js #buddypress #delete_sentbox_messages,
+body.no-js #buddypress #messages-bulk-management #select-all-messages {
+	display: none;
+}
+#buddypress .standard-form input:focus,
+#buddypress .standard-form textarea:focus,
+#buddypress .standard-form select:focus {
+	background: #fafafa;
+	color: #555;
+}
+#buddypress form#send-invite-form {
+	margin-top: 20px;
+}
+#buddypress div#invite-list {
+	background: #f5f5f5;
+	height: 400px;
+	margin: 0 0 10px;
+	overflow: auto;
+	padding: 5px;
+	width: 160px;
+}
+#buddypress button,
+#buddypress a.button,
+#buddypress input[type=submit],
+#buddypress input[type=button],
+#buddypress input[type=reset],
+#buddypress ul.button-nav li a,
+#buddypress .generic-button a,
+#buddypress .comment-reply-link,
+a.bp-title-button {
+	background: #fff; /* Old browsers */
+	border: 1px solid #ccc;
+	color: #777;
+	font-size: small;
+	cursor: pointer;
+	outline: none;
+	padding: 4px 10px;
+	text-align: center;
+	text-decoration: none;
+}
+#buddypress button:hover,
+#buddypress a.button:hover,
+#buddypress a.button:focus,
+#buddypress input[type=submit]:hover,
+#buddypress input[type=button]:hover,
+#buddypress input[type=reset]:hover,
+#buddypress ul.button-nav li a:hover,
+#buddypress ul.button-nav li.current a,
+#buddypress div.generic-button a:hover,
+#buddypress .comment-reply-link:hover {
+	background: #ededed;
+	border: 1px solid #bbb;
+	color: #555;
+	outline: none;
+	text-decoration: none;
+}
+
+#buddypress form.standard-form .left-menu {
+	float: left;
+}
+
+#buddypress form.standard-form .left-menu #invite-list ul{
+	margin:1%;
+	list-style: none;
+}
+
+#buddypress form.standard-form .left-menu #invite-list ul li {
+	margin:0 0 0 1%;
+}
+
+#buddypress form.standard-form .main-column {
+	margin-left: 190px;
+}
+
+#buddypress form.standard-form .main-column ul#friend-list {
+	clear:none;
+	float: left;
+}
+
+#buddypress form.standard-form .main-column ul#friend-list h4 {
+	clear:none;
+}
+
+/* Overrides for embedded WP editors */
+#buddypress .wp-editor-wrap a.button,
+#buddypress .wp-editor-wrap button,
+#buddypress .wp-editor-wrap input[type=submit],
+#buddypress .wp-editor-wrap input[type=button],
+#buddypress .wp-editor-wrap input[type=reset] {
+	padding: 0 8px 1px;
+}
+
+/* Form classes & generic attr styling */
+#buddypress form *[disabled="disabled"]{
+	cursor: default;
+	opacity: .4;
+}
+
+.bp-screen-reader-text {
+	position: absolute;
+	margin: -1px;
+	padding: 0;
+	height: 1px;
+	width: 1px;
+	overflow: hidden;
+	clip: rect(0 0 0 0);
+	border: 0;
+	word-wrap: normal !important;
+}
+
+/*--------------------------------------------------------------
+3.6 - Ajax Loading
+--------------------------------------------------------------*/
+#buddypress a.loading,
+#buddypress input.loading {
+	-webkit-animation: loader-pulsate .5s infinite ease-in-out alternate;
+	-moz-animation: loader-pulsate .5s infinite ease-in-out alternate;
+	border-color: #aaa;
+}
+@-webkit-keyframes loader-pulsate {
+	from {
+		border-color: #aaa;
+		-webkit-box-shadow: 0 0 6px #ccc;
+		box-shadow: 0 0 6px #ccc;
+	}
+	to {
+		border-color: #ccc;
+		-webkit-box-shadow: 0 0 6px #f8f8f8;
+		box-shadow: 0 0 6px #f8f8f8;
+	}
+}
+@-moz-keyframes loader-pulsate {
+	from {
+		border-color: #aaa;
+		-moz-box-shadow: 0 0 6px #ccc;
+		box-shadow: 0 0 6px #ccc;
+	}
+	to {
+		border-color: #ccc;
+		-moz-box-shadow: 0 0 6px #f8f8f8;
+		box-shadow: 0 0 6px #f8f8f8;
+	}
+}
+
+#buddypress a.loading:hover,
+#buddypress input.loading:hover {
+    color: #777;
+}
+#buddypress input[type="submit"].pending,
+#buddypress input[type="button"].pending,
+#buddypress input[type="reset"].pending,
+#buddypress input[type="submit"].disabled,
+#buddypress input[type="button"].disabled,
+#buddypress input[type="reset"].disabled,
+#buddypress input[type="submit"][disabled=disabled],
+#buddypress button.pending,
+#buddypress button.disabled,
+#buddypress div.pending a,
+#buddypress a.disabled {
+    border-color: #eee;
+    color: #bbb;
+    cursor: default;
+}
+#buddypress input[type="submit"]:hover.pending,
+#buddypress input[type="button"]:hover.pending,
+#buddypress input[type="reset"]:hover.pending,
+#buddypress input[type="submit"]:hover.disabled,
+#buddypress input[type="button"]:hover.disabled,
+#buddypress input[type="reset"]:hover.disabled,
+#buddypress button.pending:hover,
+#buddypress button.disabled:hover,
+#buddypress div.pending a:hover,
+#buddypress a.disabled:hover {
+	border-color: #eee;
+    color: #bbb;
+}
+
+/*--------------------------------------------------------------
+3.7 - Forums, Tables and Topics
+--------------------------------------------------------------*/
+#buddypress ul#topic-post-list {
+	margin: 0;
+	width: auto;
+}
+#buddypress ul#topic-post-list li {
+	padding: 15px;
+	position: relative;
+}
+#buddypress ul#topic-post-list li.alt {
+	background: #f5f5f5;
+}
+#buddypress ul#topic-post-list li div.poster-meta {
+	color: #888;
+	margin-bottom: 10px;
+}
+#buddypress ul#topic-post-list li div.post-content {
+	margin-left: 54px;
+}
+#buddypress div.topic-tags {
+	font-size: 80%;
+}
+#buddypress div.admin-links {
+	color: #888;
+	font-size: 80%;
+	position: absolute;
+	top: 15px;
+	right: 25px;
+}
+#buddypress div#topic-meta {
+	margin: 0;
+	padding: 5px 19px 30px;
+	position: relative;
+}
+#buddypress div#topic-meta div.admin-links {
+	right: 19px;
+	top: -36px;
+}
+#buddypress div#topic-meta h3 {
+	margin: 5px 0;
+}
+#buddypress div#new-topic-post {
+	display: none;
+	margin: 20px 0 0 0;
+	padding: 1px 0 0 0;
+}
+#buddypress table.notifications,
+#buddypress table.notifications-settings,
+#buddypress table.profile-settings,
+#buddypress table.profile-fields,
+#buddypress table.wp-profile-fields,
+#buddypress table.messages-notices,
+#buddypress table.forum {
+	width: 100%;
+}
+#buddypress table.notifications thead tr,
+#buddypress table.notifications-settings thead tr,
+#buddypress table.profile-settings thead tr,
+#buddypress table.profile-fields thead tr,
+#buddypress table.wp-profile-fields thead tr,
+#buddypress table.messages-notices thead tr,
+#buddypress table.forum thead tr {
+	background: #eaeaea;
+}
+#buddypress table#message-threads {
+	clear: both;
+}
+#buddypress table.profile-fields {
+	margin-bottom: 20px;
+}
+#buddypress table.profile-fields:last-child {
+	margin-bottom: 0;
+}
+#buddypress table.profile-fields p {
+	margin: 0;
+}
+#buddypress table.profile-fields p:last-child {
+	margin-top: 0;
+}
+#buddypress table.notifications tr td,
+#buddypress table.notifications-settings tr td,
+#buddypress table.profile-settings tr td,
+#buddypress table.profile-fields tr td,
+#buddypress table.wp-profile-fields tr td,
+#buddypress table.messages-notices tr td,
+#buddypress table.forum tr td,
+#buddypress table.notifications tr th,
+#buddypress table.notifications-settings tr th,
+#buddypress table.profile-fields tr th,
+#buddypress table.wp-profile-fields tr th,
+#buddypress table.messages-notices tr th,
+#buddypress table.forum tr th {
+	padding: 8px;
+	vertical-align: middle;
+}
+#buddypress table.notifications tr td.label,
+#buddypress table.notifications-settings tr td.label,
+#buddypress table.profile-fields tr td.label,
+#buddypress table.wp-profile-fields tr td.label,
+#buddypress table.messages-notices tr td.label,
+#buddypress table.forum tr td.label {
+	border-right: 1px solid #eaeaea;
+	font-weight: bold;
+	width: 25%;
+}
+#buddypress #message-threads .thread-info {
+	min-width: 40%;
+}
+#buddypress table tr td.thread-info p {
+	margin: 0;
+}
+#buddypress table tr td.thread-info p.thread-excerpt {
+	color: #888;
+	font-size: 80%;
+	margin-top: 3px;
+}
+#buddypress table.forum td {
+	text-align: center;
+}
+#buddypress table.notifications tr.alt td,
+#buddypress table.notifications-settings tr.alt td,
+#buddypress table.profile-settings tr.alt td,
+#buddypress table.profile-fields tr.alt td,
+#buddypress table.wp-profile-fields tr.alt td,
+#buddypress table.messages-notices tr.alt td,
+#buddypress table.forum tr.alt td {
+	background: #f5f5f5;
+}
+#buddypress table.notification-settings {
+	margin-bottom: 20px;
+	text-align: left;
+}
+#buddypress #groups-notification-settings {
+	margin-bottom: 0;
+}
+#buddypress table.notifications th.icon,
+#buddypress table.notifications td:first-child,
+#buddypress table.notification-settings th.icon,
+#buddypress table.notification-settings td:first-child {
+	display: none;
+}
+#buddypress table.notification-settings th.title,
+#buddypress table.profile-settings th.title {
+	width: 80%;
+}
+#buddypress table.notification-settings .yes,
+#buddypress table.notification-settings .no {
+	text-align: center;
+	width: 40px;
+}
+#buddypress table.forum {
+	margin: 0;
+	width: auto;
+	clear: both;
+}
+#buddypress table.forum tr.sticky td {
+	font-size: 110%;
+	background: #fff9db;
+	border-top: 1px solid #ffe8c4;
+	border-bottom: 1px solid #ffe8c4;
+}
+#buddypress table.forum tr.closed td.td-title {
+	padding-left: 35px;
+}
+#buddypress table.forum td p.topic-text {
+	color: #888;
+	font-size: 100%;
+}
+#buddypress table.forum tr > td:first-child,
+#buddypress table.forum tr > th:first-child {
+	padding-left: 15px;
+}
+#buddypress table.forum tr > td:last-child,
+#buddypress table.forum tr > th:last-child {
+	padding-right: 15px;
+}
+#buddypress table.forum tr th#th-title,
+#buddypress table.forum tr th#th-poster,
+#buddypress table.forum tr th#th-group,
+#buddypress table.forum td.td-poster,
+#buddypress table.forum td.td-group,
+#buddypress table.forum td.td-title {
+	text-align: left;
+}
+#buddypress table.forum tr td.td-title a.topic-title {
+	font-size: 110%;
+}
+#buddypress table.forum td.td-freshness {
+	white-space: nowrap;
+}
+#buddypress table.forum td.td-freshness span.time-since {
+	font-size: 80%;
+	color: #888;
+}
+#buddypress table.forum td img.avatar {
+	float: none;
+	margin: 0 5px -8px 0;
+}
+#buddypress table.forum td.td-poster,
+#buddypress table.forum td.td-group {
+	min-width: 140px;
+}
+#buddypress table.forum th#th-title {
+	width: 80%;
+}
+#buddypress table.forum th#th-freshness {
+	width: 25%;
+}
+#buddypress table.forum th#th-postcount {
+	width: 15%;
+}
+#buddypress table.forum p.topic-meta {
+	font-size: 80%;
+	margin: 5px 0 0 0;
+}
+
+/*-------------------------------------------------------------------------
+3.8 - Headers, Lists and Tabs - Activity, Groups, Blogs, Forums, Profiles
+-------------------------------------------------------------------------*/
+#buddypress .item-body {
+	margin: 20px 0;
+}
+#buddypress span.activity {
+	display: inline-block;
+	font-size: small;
+	opacity: 0.8;
+	padding: 0;
+}
+#buddypress span.user-nicename {
+	color: #777;
+	display: inline-block;
+	font-size: 120%;
+	font-weight: bold;
+}
+#buddypress div#message p,
+#sitewide-notice p {
+	font-weight: normal;
+	margin-top: 3px;
+	text-decoration: none;
+	background-color: #ffd;
+	border: 1px solid #cb2;
+	color: #440;
+}
+#buddypress #item-header:after {
+	clear: both;
+	content: "";
+	display: table;
+}
+#buddypress div#item-header div#item-header-content {
+	float: left;
+	margin-left: 0;
+}
+#buddypress div#item-header h2 {
+	line-height: 120%;
+	margin: 0 0 15px 0;
+}
+#buddypress div#item-header h2 a {
+	color: #777;
+	text-decoration: none;
+}
+#buddypress div#item-header img.avatar {
+	float: left;
+	margin: 0 15px 19px 0;
+}
+#buddypress div#item-header h2 {
+	margin-bottom: 5px;
+}
+#buddypress div#item-header h2 span.highlight {
+	font-size: 60%;
+	font-weight: normal;
+	line-height: 170%;
+	vertical-align: middle;
+	display: inline-block;
+}
+#buddypress div#item-header h2 span.highlight span {
+	background: #a1dcfa;
+	color: #fff;
+	cursor: pointer;
+	font-weight: bold;
+	font-size: 80%;
+	margin-bottom: 2px;
+	padding: 1px 4px;
+	position: relative;
+	right: -2px;
+	top: -2px;
+	vertical-align: middle;
+}
+#buddypress div#item-header div#item-meta {
+	font-size: 80%;
+	color: #aaa;
+	overflow: hidden;
+	margin: 15px 0 5px 0;
+	padding-bottom: 10px;
+}
+#buddypress div#item-header div#item-actions {
+	float: right;
+	margin: 0 0 15px 15px;
+	text-align: right;
+	width: 20%;
+}
+#buddypress div#item-header div#item-actions h3 {
+	margin: 0 0 5px 0;
+}
+#buddypress div#item-header ul {
+	margin-bottom: 15px;
+	overflow: hidden;
+}
+#buddypress div#item-header ul h5,
+#buddypress div#item-header ul span,
+#buddypress div#item-header ul hr {
+	display: none;
+}
+#buddypress div#item-header ul li {
+	float: right;
+	list-style: none;
+}
+#buddypress div#item-header ul img.avatar,
+#buddypress div#item-header ul.avatars img.avatar {
+	height: 30px;
+	margin: 2px;
+	width: 30px;
+}
+#buddypress div#item-header div.generic-button,
+#buddypress div#item-header a.button {
+	float: left;
+	margin: 10px 10px 0 0;
+}
+body.no-js #buddypress div#item-header .js-self-profile-button {
+	display:none;
+}
+#buddypress div#item-header div#message.info {
+	line-height: 80%;
+}
+#buddypress ul.item-list {
+	border-top: 1px solid #eaeaea;
+	width: 100%;
+	list-style: none;
+	clear: both;
+	margin: 0;
+	padding: 0;
+}
+body.activity-permalink #buddypress ul.item-list,
+body.activity-permalink #buddypress ul.item-list li.activity-item {
+	border: none;
+}
+#buddypress ul.item-list li {
+	border-bottom: 1px solid #eaeaea;
+	padding: 15px 0;
+	margin: 0;
+	position: relative;
+	list-style: none;
+}
+#buddypress ul.single-line li {
+	border: none;
+}
+#buddypress ul.item-list li img.avatar {
+	float: left;
+	margin: 0 10px 0 0;
+}
+#buddypress ul.item-list li div.item-title,
+#buddypress ul.item-list li h4 {
+	font-weight: normal;
+	font-size: 90%;
+	margin: 0;
+	width: 75%;
+}
+#buddypress ul.item-list li div.item-title span {
+	color: #999;
+	font-size: 80%;
+}
+#buddypress ul.item-list li div.item-desc {
+	color: #888;
+	font-size: 80%;
+	margin: 10px 0 0 60px;
+	width: 50%;
+}
+#buddypress ul.item-list li.group-no-avatar div.item-desc {
+	margin-left: 0;
+}
+#buddypress ul.item-list li div.action {
+	position: absolute;
+	top: 15px;
+	right: 0;
+	text-align: right;
+}
+#buddypress ul.item-list li div.meta {
+	color: #888;
+	font-size: 80%;
+	margin-top: 10px;
+}
+#buddypress ul.item-list li h5 span.small {
+	float: right;
+	font-size: 80%;
+	font-weight: normal;
+}
+#buddypress div.item-list-tabs {
+	background: transparent;
+	clear: left;
+	overflow: hidden;
+}
+
+#buddypress div.item-list-tabs ul {
+	margin: 0;
+	padding: 0;
+}
+#buddypress div.item-list-tabs ul li {
+	float: left;
+	margin: 0;
+	list-style: none;
+}
+#buddypress div.item-list-tabs#subnav ul li {
+	margin-top: 0;
+}
+#buddypress div.item-list-tabs ul li.last {
+	float: right;
+	margin: 7px 0 0;
+}
+#buddypress div.item-list-tabs#subnav ul li.last {
+	margin-top: 4px;
+}
+#buddypress div.item-list-tabs ul li.last select {
+	max-width: 185px;
+}
+#buddypress div.item-list-tabs ul li a,
+#buddypress div.item-list-tabs ul li span {
+	display: block;
+	padding: 5px 10px;
+	text-decoration: none;
+}
+#buddypress div.item-list-tabs ul li a span {
+	background: #eee;
+	border-radius: 50%;
+	border: 1px solid #ccc;
+	color: #999;
+	display: inline;
+	font-size: 70%;
+	margin-left: 2px;
+	padding: 3px 6px;
+	text-align: center;
+	vertical-align: middle;
+}
+#buddypress div.item-list-tabs ul li.selected a,
+#buddypress div.item-list-tabs ul li.current a {
+	background-color: #eee;
+	color: #555;
+	opacity: .8;
+	font-weight: bold;
+}
+#buddypress div.item-list-tabs ul li.selected a span,
+#buddypress div.item-list-tabs ul li.current a span,
+#buddypress div.item-list-tabs ul li a:hover span {
+	background-color: #eee;
+}
+#buddypress div.item-list-tabs ul li.selected a span,
+#buddypress div.item-list-tabs ul li.current a span {
+	background-color: #fff;
+}
+#buddypress div#item-nav ul li.loading a {
+	background-position: 88% 50%;
+}
+#buddypress div.item-list-tabs#object-nav {
+	margin-top: 0;
+}
+#buddypress div.item-list-tabs#subnav {
+	background: transparent;
+	margin: 10px 0 10px;
+	overflow: hidden;
+}
+#buddypress #admins-list li,
+#buddypress #mods-list li,
+#buddypress #members-list li {
+	overflow: auto;
+	list-style: none;
+}
+#buddypress #item-buttons:empty {
+	display: none;
+}
+
+/*--------------------------------------------------------------
+3.8.1 - Cover Image
+--------------------------------------------------------------*/
+
+#buddypress #cover-image-container {
+	position: relative;
+	z-index: 0;
+}
+
+#buddypress #header-cover-image {
+	background-color: #c5c5c5;
+	background-position: center top;
+	background-repeat: no-repeat;
+	background-size: cover;
+	border: 0;
+	display: block;
+	left: 0;
+	margin: 0;
+	padding: 0;
+	position: absolute;
+	top: 0;
+	width: 100%;
+	z-index: 1;
+}
+
+#buddypress #item-header-cover-image {
+	padding: 0 1em;
+	position: relative;
+	z-index: 2;
+}
+
+/*--------------------------------------------------------------
+3.9 - Private Messaging Threads
+--------------------------------------------------------------*/
+#buddypress table#message-threads tr.unread td {
+	background: #fff9db;
+	border-top: 1px solid #ffe8c4;
+	border-bottom: 1px solid #ffe8c4;
+	font-weight: bold;
+}
+#buddypress table#message-threads tr.unread td .thread-excerpt,
+#buddypress table#message-threads tr.unread td .activity,
+#buddypress table#message-threads tr.unread td.thread-options {
+	font-weight: normal;
+}
+#buddypress li span.unread-count,
+#buddypress tr.unread span.unread-count {
+	background: #dd0000;
+	color: #fff;
+	font-weight: bold;
+	padding: 2px 8px;
+}
+#buddypress div.item-list-tabs ul li a span.unread-count {
+	padding: 1px 6px;
+	color: #fff;
+}
+#buddypress div#message-thread div.message-box {
+	margin: 0;
+	padding: 15px;
+}
+#buddypress div#message-thread div.alt {
+	background: #f4f4f4;
+}
+#buddypress div#message-thread p#message-recipients {
+	margin: 10px 0 20px 0;
+}
+#buddypress div#message-thread img.avatar {
+	float: left;
+	margin: 0 10px 0 0;
+	vertical-align: middle;
+}
+#buddypress div#message-thread strong {
+	font-size: 100%;
+	margin: 0;
+}
+#buddypress div#message-thread strong a {
+	text-decoration: none;
+}
+#buddypress div#message-thread strong span.activity {
+	margin-top: 4px;
+}
+#buddypress div#message-thread div.message-metadata {
+	overflow: hidden;
+}
+#buddypress div#message-thread div.message-content {
+	margin-left: 45px;
+}
+#buddypress div#message-thread div.message-options {
+	text-align: right;
+}
+#buddypress #message-threads img.avatar {
+	max-width: none;
+}
+#buddypress div.message-search {
+	float: right;
+	margin: 0 20px;
+}
+
+.message-metadata {
+	position: relative;
+}
+.message-star-actions {
+	position: absolute;
+	right: 0;
+	top: 0;
+}
+#buddypress a.message-action-star,
+#buddypress a.message-action-unstar {
+	border-bottom: 0;
+	text-decoration: none;
+	outline: none;
+}
+a.message-action-star {
+	opacity: .7;
+}
+a.message-action-star:hover {
+	opacity: 1;
+}
+.message-action-star span.icon:before,
+.message-action-unstar span.icon:before {
+	font-family: dashicons;
+	font-size: 18px;
+}
+.message-action-star span.icon:before {
+	color: #aaa;
+	content: "\f154";
+}
+.message-action-unstar span.icon:before {
+	color: #FCDD77;
+	content: "\f155";
+}
+
+/*--------------------------------------------------------------
+3.10 - Extended Profiles
+--------------------------------------------------------------*/
+
+#buddypress div.profile h4 {
+	margin-bottom: auto;
+	margin-top: 15px;
+}
+#buddypress #profile-edit-form ul.button-nav {
+	margin-top: 15px;
+}
+body.no-js #buddypress .field-visibility-settings-toggle,
+body.no-js #buddypress .field-visibility-settings-close {
+	display: none;
+}
+#buddypress .field-visibility-settings {
+	display: none;
+	margin-top: 10px;
+}
+	body.no-js #buddypress .field-visibility-settings {
+		display: block;
+	}
+#buddypress .current-visibility-level {
+	font-weight: bold;
+	font-style: normal;
+}
+#buddypress .field-visibility-settings,
+#buddypress .field-visibility-settings-toggle,
+#buddypress .field-visibility-settings-notoggle {
+	color: #888;
+}
+#buddypress .field-visibility-settings-toggle a,
+#buddypress .field-visibility-settings a {
+	font-size: 80%;
+}
+body.register #buddypress div.page ul {
+	list-style: none;
+}
+#buddypress .standard-form .field-visibility-settings label {
+	margin: 0;
+	font-weight: normal;
+}
+#buddypress .field-visibility-settings legend,
+#buddypress .field-visibility-settings-toggle {
+	font-style: italic;
+}
+#buddypress .field-visibility-settings .radio {
+	list-style: none;
+	margin-bottom: 0;
+}
+#buddypress .field-visibility select {
+	margin: 0;
+}
+#buddypress .wp-editor-container {
+	border: 1px solid #dedede;
+}
+#buddypress .html-active button.switch-html {
+	border-bottom-color: transparent;
+	border-bottom-left-radius: 0;
+	border-bottom-right-radius: 0;
+	background: #f5f5f5;
+}
+#buddypress .tmce-active button.switch-tmce {
+	border-bottom-color: transparent;
+	border-bottom-left-radius: 0;
+	border-bottom-right-radius: 0;
+	background: #f5f5f5;
+}
+#buddypress .standard-form .wp-editor-container textarea {
+	width: 100%;
+	padding-top: 0;
+	padding-bottom: 0;
+}
+
+/*--------------------------------------------------------------
+3.11 - Widgets
+--------------------------------------------------------------*/
+
+.widget.buddypress div.item-avatar img.avatar {
+	float: left;
+	margin: 0 10px 15px 0;
+}
+
+.widget.buddypress span.activity {
+	display: inline-block;
+	font-size: small;
+	opacity: 0.8;
+	padding: 0;
+}
+
+.widget.buddypress div.item-options {
+	font-size: 90%;
+	margin: 0 0 1em 0;
+	padding: 1em 0;
+}
+
+.widget.buddypress div.item{
+	margin:0 0 1em 0;
+}
+
+.widget.buddypress div.item-meta,
+.widget.buddypress div.item-content {
+	font-size: 11px;
+	margin-left: 50px;
+}
+
+.widget.buddypress ul.item-list img.avatar {
+	height: 40px;
+	margin-right: 10px;
+	width: 40px;
+}
+.widget.buddypress div.item-avatar img {
+	height: 40px;
+	margin: 1px;
+	width: 40px;
+}
+
+.widget.buddypress div.avatar-block{
+	overflow: hidden;
+}
+
+.widget.buddypress #bp-login-widget-form label {
+	display: block;
+	margin: 1rem 0 .5rem;
+}
+
+.widget.buddypress #bp-login-widget-form #bp-login-widget-submit {
+	margin-right: 10px;
+}
+
+.widget.buddypress .bp-login-widget-user-avatar {
+	float: left;
+}
+
+.bp-login-widget-user-avatar img.avatar {
+	height: 40px;
+	width: 40px;
+}
+
+.widget.buddypress .bp-login-widget-user-links > div {
+	padding-left: 60px;
+}
+
+.widget.buddypress .bp-login-widget-user-links > div {
+	margin-bottom: .5rem;
+}
+
+.widget.buddypress .bp-login-widget-user-links > div.bp-login-widget-user-link a {
+	font-weight: bold;
+}
+
+.widget.buddypress #groups-list,
+.widget.buddypress #members-list,
+.widget.buddypress #friends-list {
+	margin-left: 0;
+	padding-left: 0;
+}
+
+.widget.buddypress #groups-list li,
+.widget.buddypress #members-list li,
+.widget.buddypress #friends-list li {
+	clear: both;
+	list-style-type: none;
+}
+
+/*--------------------------------------------------------------
+4.0 - Media Queries
+--------------------------------------------------------------*/
+/*--------------------------------------------------------------
+4.1 - Smartphones - landscape
+--------------------------------------------------------------*/
+@media only screen and (max-width: 480px ){
+	#buddypress div.dir-search {
+		float: right;
+		margin-top: -50px;
+		text-align: right;
+	}
+	#buddypress div.dir-search input[type="text"] {
+		margin-bottom: 1em;
+		width: 50%;
+	}
+	a.bp-title-button {
+		margin-left: 10px;
+	}
+	#buddypress form.standard-form .main-column div.action{
+		position: relative;
+		margin-bottom:1em;
+	}
+	#buddypress form.standard-form .main-column ul#friend-list h4{
+		width:100%;
+	}
+}
+
+/*--------------------------------------------------------------
+4.2 - Smartphones - portrait
+--------------------------------------------------------------*/
+@media only screen and (max-width: 320px) {
+	#buddypress div.dir-search {
+		clear: left;
+		float: left;
+		margin-top: 0;
+		text-align: left;
+	}
+	#buddypress li#groups-order-select {
+		clear: left;
+		float: left;
+	}
+	#buddypress ul.item-list li div.action {
+		clear: left;
+		float: left;
+		margin-top: 0;
+		margin-left: 70px;
+		position: relative;
+		top: 0;
+		right: 0;
+		text-align: left;
+	}
+	#buddypress ul.item-list li div.item-desc {
+		clear: left;
+		float: left;
+		margin: 10px 0 0;
+		width: auto;
+	}
+	#buddypress li div.item {
+		margin-left: 70px;
+		width: auto;
+	}
+	#buddypress ul.item-list li div.meta {
+		margin-top: 0;
+	}
+	#buddypress .item-desc p {
+		margin: 0 0 10px;
+	}
+	#buddypress div.pagination .pag-count {
+		margin-left: 0;
+	}
+}
+
+/*--------------------------------------------------------------
+4.2 - Smartphones - smaller screen sizes
+--------------------------------------------------------------*/
+@media only screen and (max-width: 240px) {
+	#buddypress div.dir-search {
+		float: left;
+		margin: 0;
+	}
+	#buddypress div.dir-search input[type="text"] {
+		width: 50%;
+	}
+	#buddypress li#groups-order-select {
+		float: left;
+	}
+	#buddypress ul.item-list li img.avatar {
+		width: 30px;
+		height: auto;
+	}
+	#buddypress ul.item-list li div.action,
+	#buddypress li div.item{
+		margin-left: 45px;
+	}
+	h1 a.bp-title-button {
+		clear: left;
+		float: left;
+		margin: 10px 0 20px;
+	}
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfifteen-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfifteen-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfifteen-rtl.css	(revision 10410)
@@ -0,0 +1,1944 @@
+@charset "UTF-8";
+/*------------------------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentyfifteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentyfifteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the rulesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+--------------------------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+--------------------------------------------------------------------------------
+1.0 Theme Structural Elements & overrides
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 Whats New Activity
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Forums
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - item-header: Groups
+		6.1.2 - item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1 - Management settings screens
+			6.2.1.2 - Group members list
+			6.2.1.3 - Group Send Invites
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading, Widgets, General classes
+------------------------------------------------------------------------------*/
+/**
+*-------------------------------------------------------------------------------
+* @section 1.0 - Theme - Structural Elements & overrides
+*-------------------------------------------------------------------------------
+*/
+#buddypress h1,
+#buddypress h2,
+#buddypress h3,
+#buddypress h4,
+#buddypress h5,
+#buddypress h6 {
+	clear: none;
+}
+
+.buddypress div.clear {
+	display: none;
+}
+
+.buddypress main {
+	padding-top: 4%;
+}
+
+@media screen and (min-width: 59.6875em) {
+	.buddypress main {
+		padding-top: 0;
+	}
+}
+
+.buddypress main article {
+	margin: 0 4%;
+	padding-top: 8.3333%;
+}
+
+@media screen and (min-width: 59.6875em) {
+	.buddypress main article {
+		margin: 0 1px 0 0;
+	}
+}
+
+@media screen and (min-width: 87.6875em) {
+	.buddypress main article {
+		margin: 0 8.3333% 0 4.3333%;
+	}
+}
+
+.buddypress main article .entry-header,
+.buddypress main article .entry-content {
+	padding: 0 3rem 3rem 3rem;
+}
+
+.buddypress main article #buddypress {
+	margin-bottom: 40px;
+}
+
+.buddypress .site-footer {
+	margin: 0 4%;
+}
+
+@media screen and (min-width: 59.6875em) {
+	.buddypress .site-footer {
+		margin: 0 35.2941% 0 0;
+		width: 61.8235%;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+.buddypress #buddypress .item-list-tabs a,
+.buddypress #buddypress .activity-header a,
+.buddypress #buddypress .activity-inner a,
+.buddypress #buddypress .activity-comments a,
+.buddypress #buddypress .item-title a,
+.buddypress #buddypress .load-more a,
+.buddypress #buddypress .field-visibility-settings-toggle a,
+.buddypress #buddypress #latest-update a,
+.buddypress #buddypress table a,
+.buddypress #buddypress .avatar-nav-items a {
+	border-bottom: 0;
+}
+
+.buddypress #buddypress .pagination-links a,
+.buddypress #buddypress .pagination-links span {
+	border-bottom: 0;
+}
+
+/**
+*----------------------------------------------------------
+* @section 2.1 - Navs Object Nav / Sub Nav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+#buddypress {
+	/*__ Horizontal menus __*/
+}
+
+#buddypress div.item-list-tabs ul li.selected a {
+	background: #555;
+	color: #fff;
+	opacity: 1;
+}
+
+#buddypress div.item-list-tabs ul {
+	background-color: #f7f7f7;
+	border-bottom: 1px solid rgba(234, 234, 234, 0.9);
+	border-top: 1px solid rgba(234, 234, 234, 0.9);
+	overflow: hidden;
+	padding: 5px 0;
+}
+
+#buddypress div.item-list-tabs ul li a span {
+	border-radius: 25%;
+}
+
+#buddypress #object-nav ul {
+	overflow: hidden;
+}
+
+#buddypress #object-nav ul li {
+	float: none;
+}
+
+#buddypress #object-nav ul li:not(.selected) a {
+	opacity: 0.7;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #object-nav ul li {
+		float: right;
+	}
+}
+
+#buddypress div#subnav.item-list-tabs {
+	margin-top: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul {
+	background-color: #f7f7f7;
+	border-bottom: 0;
+	padding: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last {
+	background: #fff;
+	margin-top: 0;
+	padding: 5px 0 5px 5px;
+	width: 100%;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last select:focus {
+	background: transparent;
+	border: 0;
+	outline: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last label,
+#buddypress div#subnav.item-list-tabs ul li.last option {
+	font-size: 14px;
+	font-size: 1.4rem;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select {
+	font-style: italic;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div#subnav.item-list-tabs ul li.last {
+		text-align: left;
+	}
+}
+
+#buddypress div.item-list-tabs ul li.selected a,
+#buddypress div.item-list-tabs ul li.current a {
+	background: #555;
+	color: #fff;
+	opacity: 1;
+}
+
+/*__ Vertical menu User Account / Group single screens __*/
+@media screen and (min-width: 55em) {
+	.bp-user #buddypress,
+	.single-item.groups #buddypress {
+		background: #f7f7f7;
+	}
+	#buddypress #item-header,
+	#buddypress #item-body {
+		background: #fff;
+	}
+	#buddypress #object-nav {
+		border-left: 1px solid #ddd;
+		float: right;
+		margin-left: -1px;
+		width: 200px;
+	}
+	#buddypress #object-nav ul {
+		border-bottom: 0;
+		padding: 0;
+	}
+	#buddypress #object-nav ul li {
+		float: none;
+		overflow: hidden;
+	}
+	#buddypress #object-nav ul li span {
+		background: #fff;
+		border-radius: 10%;
+		float: left;
+		margin-left: 2px;
+	}
+	#buddypress #item-body {
+		border-right: 1px solid #ddd;
+		overflow: hidden;
+		padding: 0 20px 0 0;
+		width: auto;
+	}
+	#buddypress #item-body #subnav {
+		margin: 0 -20px 0 0;
+	}
+}
+
+/**
+*----------------------------------------------------------
+* @section 2.2 - Pagination
+*----------------------------------------------------------
+*/
+#buddypress div.pagination {
+	box-shadow: none;
+}
+
+#buddypress div.pagination .pag-count {
+	margin-right: 0;
+}
+
+#buddypress div.pagination .pagination-links {
+	margin-left: 0;
+}
+
+#buddypress div.pagination .pagination-links span,
+#buddypress div.pagination .pagination-links a {
+	height: auto;
+	line-height: 1;
+	padding: 5px;
+}
+
+#buddypress div.pagination .pagination-links .prev,
+#buddypress div.pagination .pagination-links .next {
+	background-color: transparent;
+	color: inherit;
+	overflow: visible;
+	width: auto;
+}
+
+#buddypress div.pagination .pagination-links .prev:before,
+#buddypress div.pagination .pagination-links .next:before {
+	display: none;
+}
+
+#buddypress div.pagination .pagination-links .prev {
+	right: auto;
+	position: static;
+}
+
+#buddypress div.pagination .pagination-links .next {
+	position: static;
+	left: auto;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+#buddypress .item-list .activity-header,
+#buddypress .item-list .activity-meta {
+	font-family: "Noto Sans", sans-serif;
+}
+
+#buddypress ul.item-list li {
+	overflow: hidden !important;
+}
+
+#buddypress ul.item-list li .item-avatar {
+	margin-bottom: 10px;
+	text-align: center;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item-avatar {
+		margin-bottom: 0;
+	}
+}
+
+#buddypress ul.item-list li .item-avatar a {
+	border-bottom: 0;
+}
+
+#buddypress ul.item-list li .item-avatar img.avatar {
+	display: inline-block;
+	float: none;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item-avatar img.avatar {
+		display: block;
+		float: right;
+	}
+}
+
+#buddypress ul.item-list li .item {
+	margin-right: 25%;
+}
+
+#buddypress ul.item-list li .item span.activity {
+	font-style: italic;
+}
+
+#buddypress ul.item-list li .item .item-desc {
+	margin-right: 0;
+	width: 70%;
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li .item-avatar,
+	#buddypress ul.item-list li .item,
+	#buddypress ul.item-list li .action {
+		float: right;
+	}
+	#buddypress ul.item-list li .item {
+		right: 10%;
+		margin-right: 0;
+		position: relative;
+		width: 55%;
+	}
+}
+
+#buddypress ul.item-list li div.action {
+	clear: right;
+	float: none;
+	margin-bottom: -20px;
+	margin-right: 0;
+	padding: 20px 0 5px;
+	position: relative;
+	text-align: right;
+	top: 0;
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li div.action {
+		clear: none;
+		float: left;
+		margin-bottom: 0;
+		padding: 0;
+	}
+}
+
+#buddypress ul.item-list li div.action div {
+	display: inline-block;
+	margin: 10px 0;
+	width: 100%;
+}
+
+#buddypress ul.item-list li div.action div a {
+	display: block;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li div.action div {
+		margin: 0 0 10px 10px;
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li div.action div {
+		clear: left;
+		float: left;
+		margin: 0 0 10px 0;
+	}
+}
+
+#buddypress ul.item-list li div.action .meta {
+	font-style: italic;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.1 - Activity
+*----------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------
+* @section 4.1.1 - Activity Whats New
+*-----------------------------------------------------
+*/
+#buddypress form#whats-new-form p.activity-greeting {
+	line-height: 1.4;
+}
+
+@media screen and (max-width: 30em) {
+	#buddypress form#whats-new-form #whats-new-post-in-box select {
+		font-size: 14px;
+		font-size: 1.4rem;
+		max-width: 120px;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress form#whats-new-form #whats-new-content {
+		clear: right;
+		margin: 10px 0 40px;
+		padding: 10px 0 0;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-submit {
+		float: none;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-submit input {
+		width: 100%;
+	}
+	#buddypress form#whats-new-form #whats-new-options {
+		display: flex;
+		flex-direction: column;
+	}
+	#buddypress form#whats-new-form #whats-new-options #whats-new-submit {
+		order: 2;
+	}
+	#buddypress form#whats-new-form #whats-new-options #whats-new-post-in-box {
+		order: 1;
+	}
+}
+
+#buddypress form#whats-new-form #whats-new-options[style] #whats-new-post-in-box {
+	border: 1px solid rgba(153, 153, 153, 0.5);
+	float: right;
+	line-height: 1.5em;
+	margin-top: 12px;
+	padding-right: 0.2em;
+	width: 100%;
+}
+
+#buddypress form#whats-new-form #whats-new-options[style] #whats-new-post-in-box select {
+	background: none;
+	border: 0;
+	float: left;
+	margin: 0;
+	min-height: 1.5em;
+	outline: 0;
+	padding-right: 0.4em;
+}
+
+@media screen and (min-width: 30em) {
+	#buddypress form#whats-new-form #whats-new-options[style] #whats-new-post-in-box {
+		width: auto;
+	}
+	#buddypress form#whats-new-form #whats-new-options[style] #whats-new-submit {
+		float: left;
+	}
+}
+
+#buddypress #item-body form#whats-new-form {
+	margin: 40px 0;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.2 - Activity Listing
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream li {
+	padding: 25px 0 15px;
+}
+
+#buddypress #activity-stream li .activity-avatar {
+	float: none;
+	text-align: center;
+}
+
+#buddypress #activity-stream li .activity-avatar a {
+	display: inline-block;
+}
+
+#buddypress #activity-stream li .activity-avatar a img.avatar {
+	display: inline;
+	float: none;
+	height: 60px;
+	margin-bottom: 20px;
+	margin-right: 0;
+	width: 60px;
+}
+
+#buddypress #activity-stream li .activity-content {
+	margin-right: 0;
+}
+
+#buddypress #activity-stream li .activity-content .activity-header {
+	font-size: 14px;
+	font-size: 1.4rem;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress #activity-stream li .activity-avatar {
+		float: right;
+		margin-left: 10px;
+		text-align: right;
+	}
+	#buddypress #activity-stream li .activity-avatar a {
+		border-bottom: 0;
+	}
+	#buddypress #activity-stream li .activity-content {
+		margin: 0;
+		overflow: hidden;
+	}
+	#buddypress #activity-stream li .activity-content .activity-header {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+#buddypress #activity-stream li.mini .activity-avatar a img.avatar {
+	height: 30px;
+	margin-right: 15px;
+	width: 30px;
+}
+
+#buddypress #activity-stream li.mini .activity-content .activity-header {
+	font-size: 13px;
+	font-size: 1.3rem;
+}
+
+#buddypress #activity-stream .activity-content {
+	margin-top: -12px;
+}
+
+#buddypress #activity-stream .activity-content .activity-header {
+	line-height: inherit;
+	margin-left: 0;
+}
+
+#buddypress #activity-stream .activity-content .activity-header p {
+	background-color: #f7f7f7;
+	border: 1px solid rgba(234, 234, 234, 0.6);
+	padding: 0 0.2em;
+}
+
+#buddypress #activity-stream .activity-content .activity-header img.avatar {
+	display: inline-block;
+	margin-bottom: 0 !important;
+}
+
+#buddypress #activity-stream .activity-content .activity-meta a {
+	display: block;
+	margin: 0 0 5px;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #activity-stream .activity-content .activity-meta a {
+		display: inline-block;
+		margin: 0;
+	}
+}
+
+#buddypress #activity-stream .load-more {
+	background: #f7f7f7;
+	border: 1px solid transparent;
+	padding: 10px;
+}
+
+#buddypress #activity-stream .load-more:focus,
+#buddypress #activity-stream .load-more:hover {
+	background: #f4f4f4;
+	border: 1px solid rgba(159, 209, 226, 0.3);
+}
+
+#buddypress #activity-stream .load-more:focus a,
+#buddypress #activity-stream .load-more:hover a {
+	font-style: italic;
+}
+
+#buddypress #activity-stream .load-more a {
+	display: block;
+}
+
+/* Single activity view  - activity permalink */
+.activity-permalink #buddypress {
+	background: none;
+}
+
+.activity-permalink #buddypress #activity-stream li.activity-item {
+	padding: 20px 0;
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+	font-size: 16px;
+	font-size: 1.6rem;
+	margin-bottom: 40px;
+}
+
+@media screen and (min-width: 46.25em) {
+	.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+		font-size: 20px;
+		font-size: 2rem;
+	}
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header p {
+	padding: 20px;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.3 - Activity Comments
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream .activity-comments {
+	margin: 10px 0 0;
+}
+
+#buddypress #activity-stream .activity-comments.has-comments {
+	border-right: 1px solid #eaeaea;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form {
+	padding: 20px 0 0;
+}
+
+#buddypress #activity-stream .activity-comments > ul {
+	background: rgba(247, 247, 247, 0.6);
+	margin: 15px 2px 0 0;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #activity-stream .activity-comments > ul {
+		margin-right: 70px;
+	}
+}
+
+#buddypress #activity-stream .activity-comments > ul ul {
+	margin-right: 1%;
+	padding-right: 0;
+}
+
+@media screen and (min-width: 30em) {
+	#buddypress #activity-stream .activity-comments > ul ul {
+		margin-right: 1%;
+		padding-right: 1em;
+	}
+}
+
+#buddypress #activity-stream .activity-comments > ul ul li {
+	border-right: 1px solid #eaeaea;
+	border-top: 0;
+	padding-right: 0.2em;
+}
+
+#buddypress #activity-stream .activity-comments > ul .acomment-meta {
+	border-bottom: 1px solid #eaeaea;
+	font-style: italic;
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress #activity-stream .activity-comments > ul .acomment-avatar {
+		display: block;
+		text-align: center;
+	}
+	#buddypress #activity-stream .activity-comments > ul .acomment-avatar a,
+	#buddypress #activity-stream .activity-comments > ul .acomment-avatar img.avatar {
+		display: inline;
+		float: none;
+	}
+	#buddypress #activity-stream .activity-comments > ul .acomment-content,
+	#buddypress #activity-stream .activity-comments > ul .acomment-meta,
+	#buddypress #activity-stream .activity-comments > ul .acomment-options {
+		margin: 5px;
+	}
+	#buddypress #activity-stream .activity-comments > ul .acomment-content {
+		padding: 0 10px;
+	}
+}
+
+#buddypress #activity-stream .activity-comments .ac-reply-avatar {
+	display: none;
+}
+
+#buddypress #activity-stream .activity-comments .ac-reply-content {
+	margin-right: 0;
+	padding-right: 0;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #activity-stream .activity-comments .ac-reply-avatar {
+		display: block;
+	}
+	#buddypress #activity-stream .activity-comments .ac-reply-content {
+		overflow: hidden;
+	}
+}
+
+#buddypress #activity-stream .activity-comments .ac-form input[type="submit"],
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	color: rgba(51, 51, 51, 0.8);
+	display: inline-block;
+	font-family: inherit;
+	font-size: 12px;
+	font-size: 1.2rem;
+	font-weight: normal;
+	line-height: 1.2;
+	padding: 4px 10px;
+	text-transform: lowercase;
+	width: 100px;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	border: 1px solid rgba(153, 153, 153, 0.7);
+	text-align: center;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:focus,
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:hover {
+	background: #ededed;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.2 - Members Loop
+*----------------------------------------------------------
+*/
+@media screen and (min-width: 59.6875em) {
+	#buddypress #members-list li .item-avatar,
+	#buddypress #members-list li .item {
+		float: right;
+	}
+	#buddypress #members-list li .action {
+		float: left;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens: User Account & Single Group Screens
+*-------------------------------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------------
+* @subsection 6.1 - Item Header Global
+*-----------------------------------------------------------
+*/
+@media screen and (max-width: 46.25em) {
+	.bp-user main header.entry-header,
+	.single-item.groups main header.entry-header {
+		padding-bottom: 1rem;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user h1,
+	.bp-user #item-header-content,
+	.single-item.groups h1,
+	.single-item.groups #item-header-content {
+		text-align: center;
+	}
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #item-header .generic-button,
+	.single-item.groups #buddypress #item-header .generic-button {
+		float: none;
+		margin: 1.5em 0 0 0;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress h1,
+	.single-item.groups #buddypress h1 {
+		margin-bottom: 0;
+	}
+	.bp-user #buddypress #item-header-avatar img.avatar,
+	.single-item.groups #buddypress #item-header-avatar img.avatar {
+		margin-left: 0;
+	}
+	.bp-user #buddypress #item-header-content,
+	.single-item.groups #buddypress #item-header-content {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.1 - item-header Groups
+*
+* Group Specific Item Header
+*-----------------------------------------------------
+*/
+@media screen and (max-width: 46.25em) {
+	.single-item.groups #buddypress #item-header #item-meta {
+		margin-bottom: 20px;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header {
+		display: flex;
+		flex-direction: column;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		order: 1;
+		text-align: center;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a {
+		border-bottom: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a img {
+		display: inline-block;
+		float: none;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		order: 2;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions {
+		order: 3;
+	}
+}
+
+.single-item.groups #buddypress div#item-header {
+	padding-bottom: 40px;
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions {
+	margin: 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		clear: none;
+		float: left;
+		width: 50%;
+	}
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions h3 {
+	background: #555;
+	color: #fff;
+	font-size: 14px;
+	font-size: 1.4rem;
+	padding: 0.2em;
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header div#item-actions h3 {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header #item-header-avatar,
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		float: right;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		width: 21%;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		margin-right: 4%;
+		width: 40%;
+	}
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		float: left;
+		width: 28%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.2 - Item Header User Accounts
+*
+* User Accounts Specific Item Header
+*-----------------------------------------------------
+*/
+.bp-user #buddypress #item-header {
+	padding: 20px 0;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar {
+	text-align: center;
+	width: 100%;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar a {
+	border-bottom: 0;
+	display: inline-block;
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #item-header #item-header-avatar {
+		float: right;
+		width: 20%;
+	}
+	.bp-user #buddypress #item-header #item-header-avatar img.avatar,
+	.bp-user #buddypress #item-header #item-header-avatar a {
+		float: right;
+	}
+	.bp-user #buddypress #item-header #item-header-content {
+		float: left;
+		margin-left: 5%;
+		width: 69%;
+	}
+}
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*-----------------------------------------------------------
+*/
+#item-body h1,
+#item-body h2,
+#item-body h3,
+#item-body h4,
+#item-body h5,
+#item-body h6 {
+	margin: 1em 0;
+}
+
+#item-body h1 a,
+#item-body h2 a,
+#item-body h3 a,
+#item-body h4 a,
+#item-body h5 a,
+#item-body h6 a {
+	border-bottom: 0;
+}
+
+/**
+*----------------------------------------------------
+* @subsection 6.2.1 - Item Body Groups
+*
+* Groups specific item body rules -  screens
+*----------------------------------------------------
+*/
+/**
+*-----------------------------------------
+* @subsection 6.2.1.1 - Group Management
+*-----------------------------------------
+*/
+.groups #group-settings-form h4 {
+	background: #555;
+	color: #fff;
+	padding: 0.2em;
+}
+
+.groups #group-settings-form #request-list h4 {
+	background: none;
+	color: inherit;
+}
+
+.groups.edit-details #group-settings-form label {
+	background: #555;
+	-webkit-border-top-left-radius: 4px;
+	border-top-left-radius: 4px;
+	-webkit-border-top-right-radius: 4px;
+	border-top-right-radius: 4px;
+	background-clip: padding-box;
+	color: #fff;
+	display: inline-block;
+	margin-bottom: 0;
+	padding: 0.2em;
+	width: 80%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.groups.edit-details #group-settings-form label {
+		width: 60%;
+	}
+}
+
+.groups.edit-details #group-settings-form textarea + p label {
+	background: none;
+	color: inherit;
+	font-size: 14px;
+	font-size: 1.4rem;
+	width: auto;
+}
+
+.groups.group-settings #group-settings-form div.radio label,
+.groups.group-settings #create-group-form div.radio label {
+	border: 1px solid #eaeaea;
+	padding: 0.2em;
+}
+
+.groups.group-settings #group-settings-form div.radio ul,
+.groups.group-settings #create-group-form div.radio ul {
+	color: rgba(51, 51, 51, 0.6);
+	font-size: 14px;
+	font-size: 1.4rem;
+}
+
+.groups.group-avatar form > p {
+	margin-top: 20px;
+}
+
+.groups.manage-members #group-settings-form .item-list li {
+	border-bottom: 1px solid #eaeaea;
+}
+
+.groups.manage-members #group-settings-form .item-list li img,
+.groups.manage-members #group-settings-form .item-list li h5 {
+	float: right;
+}
+
+.groups.manage-members #group-settings-form .item-list li img > a,
+.groups.manage-members #group-settings-form .item-list li h5 > a {
+	border-bottom: 0;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small {
+	clear: right;
+	display: block;
+	float: none;
+	margin-top: 10px;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small a {
+	display: inline-block;
+	margin: 5px 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.groups.manage-members #group-settings-form .item-list li span.small a {
+		width: auto;
+	}
+}
+
+.groups.manage-members #group-settings-form .item-list li h5 {
+	margin: 0;
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.2 - Group members
+*-----------------------------------------
+*/
+/* Members loop */
+@media screen and (max-width: 30em) {
+	.groups.group-members #buddypress #members-group-list li > a {
+		border-bottom: 0;
+		display: block;
+		margin-bottom: 10px;
+		text-align: center;
+	}
+	.groups.group-members #buddypress #members-group-list li > a img.avatar {
+		display: inline;
+		float: none;
+	}
+}
+
+.groups.group-members #buddypress #members-group-list li h5 {
+	display: inline-block;
+	font-size: 14px;
+	font-size: 1.4rem;
+	margin: 0;
+}
+
+@media screen and (min-width: 59.6875em) {
+	.groups.group-members #buddypress #members-group-list li h5 {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+.groups.group-members #buddypress #members-group-list li h5 a {
+	border-bottom: 0;
+}
+
+.groups.group-members #buddypress #members-group-list li span.activity {
+	font-size: 12px;
+	font-size: 1.2rem;
+}
+
+.groups.group-members #buddypress #members-group-list li .action {
+	top: 0;
+}
+
+.groups.group-members #subnav li {
+	width: 100%;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li {
+		background: #fff;
+		padding: 20px 0;
+	}
+}
+
+.groups.group-members #subnav li #search-members-form {
+	float: left;
+	margin: 5px 0 0 5px;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li #search-members-form {
+		margin: 0;
+		width: 100%;
+	}
+	.groups.group-members #subnav li #search-members-form label input[type="text"] {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.3 - Group Send Invites
+*-----------------------------------------
+*/
+.groups.group-invites #item-body .item-list .action {
+	margin-bottom: 0;
+}
+
+@media screen and (min-width: 46.25em) {
+	.groups.group-invites #buddypress #item-body #message {
+		margin-top: 0;
+	}
+}
+
+@media screen and (min-width: 55em) {
+	.groups.group-invites #buddypress #send-invite-form {
+		margin-top: 0;
+	}
+}
+
+@media screen and (max-width: 46.25em) {
+	.groups.group-invites #item-body .left-menu {
+		float: none;
+		margin: 10px 0;
+	}
+	.groups.group-invites #item-body .left-menu #invite-list {
+		height: auto;
+		width: auto;
+	}
+	.groups.group-invites #item-body .main-column {
+		margin-right: 0;
+	}
+	.groups.group-invites #item-body .submit input {
+		display: inline-block;
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.2.2 - Item Body User Accounts
+*
+* User Account specific item body rules
+*-----------------------------------------------------
+*/
+.bp-user .entry-title {
+	margin-bottom: 0.5em;
+}
+
+/**
+*--------------------------------------------
+* @subsection 6.2.2.1 - classes, pag, filters
+*--------------------------------------------
+*/
+.bp-user #buddypress table th {
+	font-size: 14px;
+	font-size: 1.4rem;
+}
+
+.bp-user #buddypress table td {
+	font-size: 12px;
+	font-size: 1.2rem;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress table th {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 14px;
+		font-size: 1.4rem;
+	}
+}
+
+@media screen and (min-width: 77.5em) {
+	.bp-user #buddypress table th {
+		font-size: 18px;
+		font-size: 1.8rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+.bp-user #buddypress .pag-count {
+	font-style: italic;
+}
+
+.bp-user #buddypress .notifications-options-nav,
+.bp-user #buddypress .messages-options-nav {
+	float: right;
+	width: 100%;
+}
+
+@media screen and (min-width: 30em) {
+	.bp-user #buddypress .notifications-options-nav,
+	.bp-user #buddypress .messages-options-nav {
+		width: 50%;
+	}
+}
+
+.bp-user #buddypress .notifications-options-nav select,
+.bp-user #buddypress .notifications-options-nav input,
+.bp-user #buddypress .messages-options-nav select,
+.bp-user #buddypress .messages-options-nav input {
+	font-size: 14px;
+	font-size: 1.4rem;
+	outline: 0;
+	padding: 0;
+}
+
+.bp-user #buddypress .notifications-options-nav select,
+.bp-user #buddypress .messages-options-nav select {
+	float: right;
+	margin-left: 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 30em) {
+	.bp-user #buddypress .notifications-options-nav select,
+	.bp-user #buddypress .messages-options-nav select {
+		width: 60%;
+	}
+}
+
+.bp-user #buddypress .notifications-options-nav input,
+.bp-user #buddypress .messages-options-nav input {
+	float: left;
+	font-family: "Noto Serif", serif;
+	line-height: 1.5;
+	margin-top: 10px;
+	width: 100%;
+}
+
+.bp-user #buddypress .notifications-options-nav input[disabled]:hover,
+.bp-user #buddypress .messages-options-nav input[disabled]:hover {
+	background: none;
+}
+
+@media screen and (min-width: 30em) {
+	.bp-user #buddypress .notifications-options-nav input,
+	.bp-user #buddypress .messages-options-nav input {
+		margin-top: 0;
+		width: 38%;
+	}
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.2 - Extended Profiles
+	*-------------------------------------------
+	*/
+.bp-user #buddypress .profile .bp-widget h4 {
+	background: #6e6e6e;
+	color: #fff;
+	margin-bottom: 0;
+	padding: 0.4em;
+}
+
+.bp-user #buddypress .profile .bp-widget table {
+	margin-top: 0;
+}
+
+/* Edit Profile */
+.bp-user #buddypress .profile #profile-edit-form .button-nav:before,
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	content: " ";
+	display: table;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	clear: both;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav {
+	border-bottom: 1px solid #eaeaea;
+	margin-right: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li {
+	float: right;
+	margin-bottom: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li.current {
+	border: 1px solid #eaeaea;
+	border-bottom-color: #fff;
+	margin-bottom: -1px;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav a {
+	background: none;
+	border: 0;
+	font-size: 18px;
+	font-size: 1.8rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-toggle,
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings {
+	font-size: 14px;
+	font-size: 1.4rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-close,
+.bp-user #buddypress .profile #profile-edit-form .visibility-toggle-link {
+	background: #555;
+	color: #fff;
+	padding: 0.2em 0.5em;
+}
+
+.bp-user #buddypress .profile .bp-avatar #bp-delete-avatar a {
+	font-size: inherit;
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.3 - Groups
+	*-------------------------------------------
+	*/
+@media screen and (min-width: 77.5em) {
+	.bp-user #buddypress #groups-list li .item {
+		right: 5%;
+		width: 50%;
+	}
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.5 - Private Messaging
+	*-------------------------------------------
+	*/
+.bp-user #buddypress {
+	/* Auto Complete background */
+	/* Sitewide Notices loop */
+}
+
+.bp-user #buddypress #message-thread a {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress #message-thread #message-subject {
+	background: #6e6e6e;
+	color: #fff;
+	padding: 0.3em 0.2em 0.3em 0;
+}
+
+.bp-user #buddypress #message-thread #message-recipients {
+	font-style: italic;
+}
+
+.bp-user #buddypress #message-thread #message-recipients a.confirm {
+	border: 1px solid #eaeaea;
+	font-style: normal;
+}
+
+.bp-user #buddypress #message-thread .message-metadata {
+	overflow: hidden;
+}
+
+.bp-user #buddypress #message-thread .message-metadata img.avatar {
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #message-thread .message-metadata img.avatar {
+		float: right;
+	}
+}
+
+.bp-user #buddypress #message-thread .message-metadata .message-star-actions {
+	float: left;
+	margin-left: 5px;
+	position: static;
+}
+
+.bp-user #buddypress #message-thread .message-content {
+	background: #f7f7f7;
+	border: 1px solid #eaeaea;
+	margin: 10px 0 0 0;
+	padding: 0.3em;
+}
+
+.bp-user #buddypress #message-thread #send-reply .message-content {
+	background: #fff;
+	border: 0;
+}
+
+.bp-user #buddypress #message-thread .alt {
+	background: #fff;
+}
+
+.bp-user #buddypress #message-threads thead tr {
+	background: #6e6e6e;
+}
+
+.bp-user #buddypress #message-threads tr td {
+	background: #fff;
+	display: inline-block;
+	float: right;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-star,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-bottom-color: #b7b7b7;
+	border-bottom-width: 2px;
+	height: 2.4em;
+	padding-bottom: 0.2em;
+	padding-top: 0.2em;
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #message-threads tr td.thread-star,
+	.bp-user #buddypress #message-threads tr td.thread-options {
+		padding-top: 0;
+	}
+}
+
+.bp-user #buddypress #message-threads tr td.bulk-select-check,
+.bp-user #buddypress #message-threads tr td.thread-from {
+	height: 2.6em;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr td.bulk-select-check,
+	.bp-user #buddypress #message-threads tr td.thread-from {
+		height: 5.2em;
+	}
+}
+
+.bp-user #buddypress #message-threads tr td.thread-from,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-right: 0 !important;
+	width: -webkit-calc(100% - 30px);
+	width: -moz-calc(100% - 30px);
+	width: calc(100% - 30px);
+	margin-right: 0;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info {
+	padding-right: 41px;
+	width: 100%;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info a::after {
+	content: " –  " attr(title);
+	font-size: 12px;
+	font-size: 1.2rem;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options {
+	text-align: left;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options a {
+	font-size: 12px;
+	font-size: 1.2rem;
+	line-height: 2.2;
+}
+
+.bp-user #buddypress #message-threads tr span.from {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads tr span.activity {
+	display: block;
+	float: left;
+	line-height: 2;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr span.activity {
+		clear: both;
+		font-size: 11px;
+		font-size: 1.1rem;
+		width: 100%;
+	}
+}
+
+.bp-user #buddypress #message-threads tr.unread td {
+	background: #dce5ff;
+	border-bottom-color: #b7b7b7;
+	border-color: #d1d1d1;
+}
+
+.bp-user #buddypress #message-threads th {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all {
+	border-bottom: 0;
+	display: inline-block;
+	text-align: right;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all,
+.bp-user #buddypress #message-threads td.bulk-select-check,
+.bp-user #buddypress #message-threads td.thread-star {
+	border-left: 0;
+	width: 30px;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab {
+	margin-left: 0;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab a {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab a img {
+	display: inline;
+	height: 20px;
+	margin-left: 0.5em;
+	width: 20px !important;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td {
+	width: 100%;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td strong {
+	background: #6e6e6e;
+	color: #fff;
+	display: block;
+	margin-bottom: 0.4em;
+	padding-right: 0.2em;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td a {
+	display: inline-block;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child + td + td {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child + td + td span {
+	line-height: 1;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child {
+	border-bottom-color: #b7b7b7;
+	line-height: 1;
+	text-align: left;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child a:last-child {
+	line-height: 0;
+	margin-right: 0.7em;
+	text-indent: -999em;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child a:last-child::after {
+	content: attr(title);
+	display: block;
+	line-height: initial;
+	text-indent: 0;
+}
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+.bp-user #buddypress #settings-form > p {
+	font-size: 20px;
+	font-size: 2rem;
+	margin: 20px 0 10px;
+}
+
+.bp-user #buddypress table.notification-settings th.title {
+	width: 75%;
+}
+
+.bp-user #buddypress table.notification-settings td.yes,
+.bp-user #buddypress table.notification-settings td.no {
+	vertical-align: middle;
+}
+
+.bp-user #buddypress table.profile-settings {
+	width: 100%;
+}
+
+.bp-user #buddypress table.profile-settings th.field-group-name,
+.bp-user #buddypress table.profile-settings td.field-name {
+	width: 50%;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress table.profile-settings th.field-group-name,
+	.bp-user #buddypress table.profile-settings td.field-name {
+		width: 70%;
+	}
+}
+
+.bp-user #buddypress table.profile-settings th.title,
+.bp-user #buddypress table.profile-settings td.field-visibility {
+	width: 30%;
+}
+
+.bp-user #buddypress table.profile-settings td.field-visibility select {
+	width: 100%;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+#buddypress div.activity-comments form .ac-textarea {
+	background: #f7f7f7;
+	border: 1px solid rgba(153, 153, 153, 0.3);
+}
+
+#buddypress div.activity-comments form .ac-textarea textarea {
+	background: none;
+	border: 0;
+}
+
+#buddypress select {
+	border: 1px solid rgba(153, 153, 153, 0.5);
+}
+
+#buddypress .wp-editor-wrap a.button,
+#buddypress .wp-editor-wrap button,
+#buddypress .wp-editor-wrap input[type=submit],
+#buddypress .wp-editor-wrap input[type=button],
+#buddypress .wp-editor-wrap input[type=reset] {
+	padding: 0 5px 1px;
+}
+
+#buddypress .standard-form li,
+#buddypress .group-create-form li {
+	float: none;
+	list-style: none;
+}
+
+#buddypress .standard-form input[type='text'],
+#buddypress .standard-form textarea,
+#buddypress .group-create-form input[type='text'],
+#buddypress .group-create-form textarea {
+	width: 100%;
+}
+
+#buddypress div.dir-search,
+#buddypress div.message-search,
+#buddypress
+li.groups-members-search {
+	float: none;
+	margin: 10px 0;
+}
+
+#buddypress div.dir-search form,
+#buddypress div.message-search form,
+#buddypress
+li.groups-members-search form {
+	border: 1px solid rgba(153, 153, 153, 0.4);
+	overflow: hidden;
+}
+
+#buddypress div.dir-search form label,
+#buddypress div.message-search form label,
+#buddypress
+li.groups-members-search form label {
+	float: right;
+	width: 80%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress
+li.groups-members-search form input[type='text'] {
+	float: right;
+	margin: 0;
+	width: 80%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='text'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress
+li.groups-members-search form input[type='text'],
+#buddypress
+li.groups-members-search form input[type='submit'] {
+	font-size: 14px;
+	font-size: 1.4rem;
+	border: 0;
+	line-height: inherit;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress
+li.groups-members-search form input[type='text'] {
+	border-left: 1px solid rgba(153, 153, 153, 0.4);
+	padding: 0.2em 0.2em 0.2em 0;
+}
+
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress
+li.groups-members-search form input[type='submit'] {
+	float: left;
+	font-weight: normal;
+	padding: 0.2em 1em;
+	text-align: center;
+	text-transform: none;
+	width: 20%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div.dir-search,
+	#buddypress div.message-search,
+	#buddypress li.groups-members-search {
+		float: left;
+		margin-bottom: 5px !important;
+	}
+	#buddypress div.dir-search form label,
+	#buddypress div.dir-search form input[type='text'],
+	#buddypress div.dir-search form input[type='submit'],
+	#buddypress div.message-search form label,
+	#buddypress div.message-search form input[type='text'],
+	#buddypress div.message-search form input[type='submit'],
+	#buddypress li.groups-members-search form label,
+	#buddypress li.groups-members-search form input[type='text'],
+	#buddypress li.groups-members-search form input[type='submit'] {
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 77.5em) {
+	#buddypress .dir-search form input[type='text'],
+	#buddypress .message-search form input[type='text'] {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+	#buddypress .dir-search form input[type='submit'],
+	#buddypress .message-search form input[type='submit'] {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables - General
+*-------------------------------------------------------------------------------
+*/
+#buddypress table {
+	font-size: 14px;
+	font-size: 1.4rem;
+	margin: 20px 0;
+}
+
+#buddypress table tr th {
+	background: #6e6e6e;
+	border-color: #b7b7b7;
+	color: #fff;
+}
+
+#buddypress table p {
+	margin-bottom: 0.5em;
+}
+
+@media screen and (min-width: 55em) {
+	#buddypress table {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+/*__ User Account tables __*/
+#buddypress .notifications th,
+#buddypress .messages-notices th {
+	width: 30%;
+}
+
+#buddypress .notifications th.bulk-select-all,
+#buddypress .messages-notices th.bulk-select-all {
+	text-align: center;
+	width: 10%;
+}
+
+#buddypress .notifications .bulk-select-check,
+#buddypress .notifications .thread-star,
+#buddypress .messages-notices .bulk-select-check,
+#buddypress .messages-notices .thread-star {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions,
+#buddypress .notifications td.thread-options,
+#buddypress .messages-notices .notification-actions,
+#buddypress .messages-notices td.thread-options {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions a,
+#buddypress .notifications td.thread-options a,
+#buddypress .messages-notices .notification-actions a,
+#buddypress .messages-notices td.thread-options a {
+	display: inline-block;
+	margin: 0;
+	padding: 0;
+}
+
+#buddypress .notifications td .button,
+#buddypress .messages-notices td .button {
+	border: 0;
+	display: block;
+	padding: 0;
+	text-align: center;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+#buddypress #item-body div#message {
+	margin-top: 20px;
+}
+
+@media screen and (min-width: 55em) {
+	#buddypress #item-body div#message {
+		margin-left: 20px;
+	}
+}
+
+#buddypress div#message p {
+	font-size: 18px;
+	font-size: 1.8rem;
+	font-weight: bold;
+}
+
+#buddypress div#message.info p {
+	background: #c6e4ee;
+	border: 1px solid #78bed6;
+	color: #1e5264;
+}
+
+#buddypress div#message.updated p {
+	background: #dee6b2;
+	border: 1px solid #becc66;
+	color: #454d19;
+}
+
+.delete-group #buddypress div#message.info p {
+	background: #db7e7e;
+	border: 1px solid #be3535;
+	color: #1f0808;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+#buddypress {
+	/* Auto Complete background */
+}
+
+#buddypress .acfb-holder li.friend-tab {
+	background: #9fd1e2;
+	border: inherit;
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfifteen.css
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfifteen.css	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfifteen.css	(revision 10410)
@@ -0,0 +1,1944 @@
+@charset "UTF-8";
+/*------------------------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentyfifteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentyfifteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the rulesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+--------------------------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+--------------------------------------------------------------------------------
+1.0 Theme Structural Elements & overrides
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 Whats New Activity
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Forums
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - item-header: Groups
+		6.1.2 - item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1 - Management settings screens
+			6.2.1.2 - Group members list
+			6.2.1.3 - Group Send Invites
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading, Widgets, General classes
+------------------------------------------------------------------------------*/
+/**
+*-------------------------------------------------------------------------------
+* @section 1.0 - Theme - Structural Elements & overrides
+*-------------------------------------------------------------------------------
+*/
+#buddypress h1,
+#buddypress h2,
+#buddypress h3,
+#buddypress h4,
+#buddypress h5,
+#buddypress h6 {
+	clear: none;
+}
+
+.buddypress div.clear {
+	display: none;
+}
+
+.buddypress main {
+	padding-top: 4%;
+}
+
+@media screen and (min-width: 59.6875em) {
+	.buddypress main {
+		padding-top: 0;
+	}
+}
+
+.buddypress main article {
+	margin: 0 4%;
+	padding-top: 8.3333%;
+}
+
+@media screen and (min-width: 59.6875em) {
+	.buddypress main article {
+		margin: 0 0 0 1px;
+	}
+}
+
+@media screen and (min-width: 87.6875em) {
+	.buddypress main article {
+		margin: 0 4.3333% 0 8.3333%;
+	}
+}
+
+.buddypress main article .entry-header,
+.buddypress main article .entry-content {
+	padding: 0 3rem 3rem 3rem;
+}
+
+.buddypress main article #buddypress {
+	margin-bottom: 40px;
+}
+
+.buddypress .site-footer {
+	margin: 0 4%;
+}
+
+@media screen and (min-width: 59.6875em) {
+	.buddypress .site-footer {
+		margin: 0 0 0 35.2941%;
+		width: 61.8235%;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+.buddypress #buddypress .item-list-tabs a,
+.buddypress #buddypress .activity-header a,
+.buddypress #buddypress .activity-inner a,
+.buddypress #buddypress .activity-comments a,
+.buddypress #buddypress .item-title a,
+.buddypress #buddypress .load-more a,
+.buddypress #buddypress .field-visibility-settings-toggle a,
+.buddypress #buddypress #latest-update a,
+.buddypress #buddypress table a,
+.buddypress #buddypress .avatar-nav-items a {
+	border-bottom: 0;
+}
+
+.buddypress #buddypress .pagination-links a,
+.buddypress #buddypress .pagination-links span {
+	border-bottom: 0;
+}
+
+/**
+*----------------------------------------------------------
+* @section 2.1 - Navs Object Nav / Sub Nav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+#buddypress {
+	/*__ Horizontal menus __*/
+}
+
+#buddypress div.item-list-tabs ul li.selected a {
+	background: #555;
+	color: #fff;
+	opacity: 1;
+}
+
+#buddypress div.item-list-tabs ul {
+	background-color: #f7f7f7;
+	border-bottom: 1px solid rgba(234, 234, 234, 0.9);
+	border-top: 1px solid rgba(234, 234, 234, 0.9);
+	overflow: hidden;
+	padding: 5px 0;
+}
+
+#buddypress div.item-list-tabs ul li a span {
+	border-radius: 25%;
+}
+
+#buddypress #object-nav ul {
+	overflow: hidden;
+}
+
+#buddypress #object-nav ul li {
+	float: none;
+}
+
+#buddypress #object-nav ul li:not(.selected) a {
+	opacity: 0.7;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #object-nav ul li {
+		float: left;
+	}
+}
+
+#buddypress div#subnav.item-list-tabs {
+	margin-top: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul {
+	background-color: #f7f7f7;
+	border-bottom: 0;
+	padding: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last {
+	background: #fff;
+	margin-top: 0;
+	padding: 5px 5px 5px 0;
+	width: 100%;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last select:focus {
+	background: transparent;
+	border: 0;
+	outline: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last label,
+#buddypress div#subnav.item-list-tabs ul li.last option {
+	font-size: 14px;
+	font-size: 1.4rem;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select {
+	font-style: italic;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div#subnav.item-list-tabs ul li.last {
+		text-align: right;
+	}
+}
+
+#buddypress div.item-list-tabs ul li.selected a,
+#buddypress div.item-list-tabs ul li.current a {
+	background: #555;
+	color: #fff;
+	opacity: 1;
+}
+
+/*__ Vertical menu User Account / Group single screens __*/
+@media screen and (min-width: 55em) {
+	.bp-user #buddypress,
+	.single-item.groups #buddypress {
+		background: #f7f7f7;
+	}
+	#buddypress #item-header,
+	#buddypress #item-body {
+		background: #fff;
+	}
+	#buddypress #object-nav {
+		border-right: 1px solid #ddd;
+		float: left;
+		margin-right: -1px;
+		width: 200px;
+	}
+	#buddypress #object-nav ul {
+		border-bottom: 0;
+		padding: 0;
+	}
+	#buddypress #object-nav ul li {
+		float: none;
+		overflow: hidden;
+	}
+	#buddypress #object-nav ul li span {
+		background: #fff;
+		border-radius: 10%;
+		float: right;
+		margin-right: 2px;
+	}
+	#buddypress #item-body {
+		border-left: 1px solid #ddd;
+		overflow: hidden;
+		padding: 0 0 0 20px;
+		width: auto;
+	}
+	#buddypress #item-body #subnav {
+		margin: 0 0 0 -20px;
+	}
+}
+
+/**
+*----------------------------------------------------------
+* @section 2.2 - Pagination
+*----------------------------------------------------------
+*/
+#buddypress div.pagination {
+	box-shadow: none;
+}
+
+#buddypress div.pagination .pag-count {
+	margin-left: 0;
+}
+
+#buddypress div.pagination .pagination-links {
+	margin-right: 0;
+}
+
+#buddypress div.pagination .pagination-links span,
+#buddypress div.pagination .pagination-links a {
+	height: auto;
+	line-height: 1;
+	padding: 5px;
+}
+
+#buddypress div.pagination .pagination-links .prev,
+#buddypress div.pagination .pagination-links .next {
+	background-color: transparent;
+	color: inherit;
+	overflow: visible;
+	width: auto;
+}
+
+#buddypress div.pagination .pagination-links .prev:before,
+#buddypress div.pagination .pagination-links .next:before {
+	display: none;
+}
+
+#buddypress div.pagination .pagination-links .prev {
+	left: auto;
+	position: static;
+}
+
+#buddypress div.pagination .pagination-links .next {
+	position: static;
+	right: auto;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+#buddypress .item-list .activity-header,
+#buddypress .item-list .activity-meta {
+	font-family: "Noto Sans", sans-serif;
+}
+
+#buddypress ul.item-list li {
+	overflow: hidden !important;
+}
+
+#buddypress ul.item-list li .item-avatar {
+	margin-bottom: 10px;
+	text-align: center;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item-avatar {
+		margin-bottom: 0;
+	}
+}
+
+#buddypress ul.item-list li .item-avatar a {
+	border-bottom: 0;
+}
+
+#buddypress ul.item-list li .item-avatar img.avatar {
+	display: inline-block;
+	float: none;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item-avatar img.avatar {
+		display: block;
+		float: left;
+	}
+}
+
+#buddypress ul.item-list li .item {
+	margin-left: 25%;
+}
+
+#buddypress ul.item-list li .item span.activity {
+	font-style: italic;
+}
+
+#buddypress ul.item-list li .item .item-desc {
+	margin-left: 0;
+	width: 70%;
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li .item-avatar,
+	#buddypress ul.item-list li .item,
+	#buddypress ul.item-list li .action {
+		float: left;
+	}
+	#buddypress ul.item-list li .item {
+		left: 10%;
+		margin-left: 0;
+		position: relative;
+		width: 55%;
+	}
+}
+
+#buddypress ul.item-list li div.action {
+	clear: left;
+	float: none;
+	margin-bottom: -20px;
+	margin-left: 0;
+	padding: 20px 0 5px;
+	position: relative;
+	text-align: left;
+	top: 0;
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li div.action {
+		clear: none;
+		float: right;
+		margin-bottom: 0;
+		padding: 0;
+	}
+}
+
+#buddypress ul.item-list li div.action div {
+	display: inline-block;
+	margin: 10px 0;
+	width: 100%;
+}
+
+#buddypress ul.item-list li div.action div a {
+	display: block;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li div.action div {
+		margin: 0 10px 10px 0;
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li div.action div {
+		clear: right;
+		float: right;
+		margin: 0 0 10px 0;
+	}
+}
+
+#buddypress ul.item-list li div.action .meta {
+	font-style: italic;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.1 - Activity
+*----------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------
+* @section 4.1.1 - Activity Whats New
+*-----------------------------------------------------
+*/
+#buddypress form#whats-new-form p.activity-greeting {
+	line-height: 1.4;
+}
+
+@media screen and (max-width: 30em) {
+	#buddypress form#whats-new-form #whats-new-post-in-box select {
+		font-size: 14px;
+		font-size: 1.4rem;
+		max-width: 120px;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress form#whats-new-form #whats-new-content {
+		clear: left;
+		margin: 10px 0 40px;
+		padding: 10px 0 0;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-submit {
+		float: none;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-submit input {
+		width: 100%;
+	}
+	#buddypress form#whats-new-form #whats-new-options {
+		display: flex;
+		flex-direction: column;
+	}
+	#buddypress form#whats-new-form #whats-new-options #whats-new-submit {
+		order: 2;
+	}
+	#buddypress form#whats-new-form #whats-new-options #whats-new-post-in-box {
+		order: 1;
+	}
+}
+
+#buddypress form#whats-new-form #whats-new-options[style] #whats-new-post-in-box {
+	border: 1px solid rgba(153, 153, 153, 0.5);
+	float: left;
+	line-height: 1.5em;
+	margin-top: 12px;
+	padding-left: 0.2em;
+	width: 100%;
+}
+
+#buddypress form#whats-new-form #whats-new-options[style] #whats-new-post-in-box select {
+	background: none;
+	border: 0;
+	float: right;
+	margin: 0;
+	min-height: 1.5em;
+	outline: 0;
+	padding-left: 0.4em;
+}
+
+@media screen and (min-width: 30em) {
+	#buddypress form#whats-new-form #whats-new-options[style] #whats-new-post-in-box {
+		width: auto;
+	}
+	#buddypress form#whats-new-form #whats-new-options[style] #whats-new-submit {
+		float: right;
+	}
+}
+
+#buddypress #item-body form#whats-new-form {
+	margin: 40px 0;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.2 - Activity Listing
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream li {
+	padding: 25px 0 15px;
+}
+
+#buddypress #activity-stream li .activity-avatar {
+	float: none;
+	text-align: center;
+}
+
+#buddypress #activity-stream li .activity-avatar a {
+	display: inline-block;
+}
+
+#buddypress #activity-stream li .activity-avatar a img.avatar {
+	display: inline;
+	float: none;
+	height: 60px;
+	margin-bottom: 20px;
+	margin-left: 0;
+	width: 60px;
+}
+
+#buddypress #activity-stream li .activity-content {
+	margin-left: 0;
+}
+
+#buddypress #activity-stream li .activity-content .activity-header {
+	font-size: 14px;
+	font-size: 1.4rem;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress #activity-stream li .activity-avatar {
+		float: left;
+		margin-right: 10px;
+		text-align: left;
+	}
+	#buddypress #activity-stream li .activity-avatar a {
+		border-bottom: 0;
+	}
+	#buddypress #activity-stream li .activity-content {
+		margin: 0;
+		overflow: hidden;
+	}
+	#buddypress #activity-stream li .activity-content .activity-header {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+#buddypress #activity-stream li.mini .activity-avatar a img.avatar {
+	height: 30px;
+	margin-left: 15px;
+	width: 30px;
+}
+
+#buddypress #activity-stream li.mini .activity-content .activity-header {
+	font-size: 13px;
+	font-size: 1.3rem;
+}
+
+#buddypress #activity-stream .activity-content {
+	margin-top: -12px;
+}
+
+#buddypress #activity-stream .activity-content .activity-header {
+	line-height: inherit;
+	margin-right: 0;
+}
+
+#buddypress #activity-stream .activity-content .activity-header p {
+	background-color: #f7f7f7;
+	border: 1px solid rgba(234, 234, 234, 0.6);
+	padding: 0 0.2em;
+}
+
+#buddypress #activity-stream .activity-content .activity-header img.avatar {
+	display: inline-block;
+	margin-bottom: 0 !important;
+}
+
+#buddypress #activity-stream .activity-content .activity-meta a {
+	display: block;
+	margin: 0 0 5px;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #activity-stream .activity-content .activity-meta a {
+		display: inline-block;
+		margin: 0;
+	}
+}
+
+#buddypress #activity-stream .load-more {
+	background: #f7f7f7;
+	border: 1px solid transparent;
+	padding: 10px;
+}
+
+#buddypress #activity-stream .load-more:focus,
+#buddypress #activity-stream .load-more:hover {
+	background: #f4f4f4;
+	border: 1px solid rgba(159, 209, 226, 0.3);
+}
+
+#buddypress #activity-stream .load-more:focus a,
+#buddypress #activity-stream .load-more:hover a {
+	font-style: italic;
+}
+
+#buddypress #activity-stream .load-more a {
+	display: block;
+}
+
+/* Single activity view  - activity permalink */
+.activity-permalink #buddypress {
+	background: none;
+}
+
+.activity-permalink #buddypress #activity-stream li.activity-item {
+	padding: 20px 0;
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+	font-size: 16px;
+	font-size: 1.6rem;
+	margin-bottom: 40px;
+}
+
+@media screen and (min-width: 46.25em) {
+	.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+		font-size: 20px;
+		font-size: 2rem;
+	}
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header p {
+	padding: 20px;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.3 - Activity Comments
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream .activity-comments {
+	margin: 10px 0 0;
+}
+
+#buddypress #activity-stream .activity-comments.has-comments {
+	border-left: 1px solid #eaeaea;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form {
+	padding: 20px 0 0;
+}
+
+#buddypress #activity-stream .activity-comments > ul {
+	background: rgba(247, 247, 247, 0.6);
+	margin: 15px 0 0 2px;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #activity-stream .activity-comments > ul {
+		margin-left: 70px;
+	}
+}
+
+#buddypress #activity-stream .activity-comments > ul ul {
+	margin-left: 1%;
+	padding-left: 0;
+}
+
+@media screen and (min-width: 30em) {
+	#buddypress #activity-stream .activity-comments > ul ul {
+		margin-left: 1%;
+		padding-left: 1em;
+	}
+}
+
+#buddypress #activity-stream .activity-comments > ul ul li {
+	border-left: 1px solid #eaeaea;
+	border-top: 0;
+	padding-left: 0.2em;
+}
+
+#buddypress #activity-stream .activity-comments > ul .acomment-meta {
+	border-bottom: 1px solid #eaeaea;
+	font-style: italic;
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress #activity-stream .activity-comments > ul .acomment-avatar {
+		display: block;
+		text-align: center;
+	}
+	#buddypress #activity-stream .activity-comments > ul .acomment-avatar a,
+	#buddypress #activity-stream .activity-comments > ul .acomment-avatar img.avatar {
+		display: inline;
+		float: none;
+	}
+	#buddypress #activity-stream .activity-comments > ul .acomment-content,
+	#buddypress #activity-stream .activity-comments > ul .acomment-meta,
+	#buddypress #activity-stream .activity-comments > ul .acomment-options {
+		margin: 5px;
+	}
+	#buddypress #activity-stream .activity-comments > ul .acomment-content {
+		padding: 0 10px;
+	}
+}
+
+#buddypress #activity-stream .activity-comments .ac-reply-avatar {
+	display: none;
+}
+
+#buddypress #activity-stream .activity-comments .ac-reply-content {
+	margin-left: 0;
+	padding-left: 0;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #activity-stream .activity-comments .ac-reply-avatar {
+		display: block;
+	}
+	#buddypress #activity-stream .activity-comments .ac-reply-content {
+		overflow: hidden;
+	}
+}
+
+#buddypress #activity-stream .activity-comments .ac-form input[type="submit"],
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	color: rgba(51, 51, 51, 0.8);
+	display: inline-block;
+	font-family: inherit;
+	font-size: 12px;
+	font-size: 1.2rem;
+	font-weight: normal;
+	line-height: 1.2;
+	padding: 4px 10px;
+	text-transform: lowercase;
+	width: 100px;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	border: 1px solid rgba(153, 153, 153, 0.7);
+	text-align: center;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:focus,
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:hover {
+	background: #ededed;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.2 - Members Loop
+*----------------------------------------------------------
+*/
+@media screen and (min-width: 59.6875em) {
+	#buddypress #members-list li .item-avatar,
+	#buddypress #members-list li .item {
+		float: left;
+	}
+	#buddypress #members-list li .action {
+		float: right;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens: User Account & Single Group Screens
+*-------------------------------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------------
+* @subsection 6.1 - Item Header Global
+*-----------------------------------------------------------
+*/
+@media screen and (max-width: 46.25em) {
+	.bp-user main header.entry-header,
+	.single-item.groups main header.entry-header {
+		padding-bottom: 1rem;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user h1,
+	.bp-user #item-header-content,
+	.single-item.groups h1,
+	.single-item.groups #item-header-content {
+		text-align: center;
+	}
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #item-header .generic-button,
+	.single-item.groups #buddypress #item-header .generic-button {
+		float: none;
+		margin: 1.5em 0 0 0;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress h1,
+	.single-item.groups #buddypress h1 {
+		margin-bottom: 0;
+	}
+	.bp-user #buddypress #item-header-avatar img.avatar,
+	.single-item.groups #buddypress #item-header-avatar img.avatar {
+		margin-right: 0;
+	}
+	.bp-user #buddypress #item-header-content,
+	.single-item.groups #buddypress #item-header-content {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.1 - item-header Groups
+*
+* Group Specific Item Header
+*-----------------------------------------------------
+*/
+@media screen and (max-width: 46.25em) {
+	.single-item.groups #buddypress #item-header #item-meta {
+		margin-bottom: 20px;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header {
+		display: flex;
+		flex-direction: column;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		order: 1;
+		text-align: center;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a {
+		border-bottom: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a img {
+		display: inline-block;
+		float: none;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		order: 2;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions {
+		order: 3;
+	}
+}
+
+.single-item.groups #buddypress div#item-header {
+	padding-bottom: 40px;
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions {
+	margin: 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		clear: none;
+		float: right;
+		width: 50%;
+	}
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions h3 {
+	background: #555;
+	color: #fff;
+	font-size: 14px;
+	font-size: 1.4rem;
+	padding: 0.2em;
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header div#item-actions h3 {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header #item-header-avatar,
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		float: left;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		width: 21%;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		margin-left: 4%;
+		width: 40%;
+	}
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		float: right;
+		width: 28%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.2 - Item Header User Accounts
+*
+* User Accounts Specific Item Header
+*-----------------------------------------------------
+*/
+.bp-user #buddypress #item-header {
+	padding: 20px 0;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar {
+	text-align: center;
+	width: 100%;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar a {
+	border-bottom: 0;
+	display: inline-block;
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #item-header #item-header-avatar {
+		float: left;
+		width: 20%;
+	}
+	.bp-user #buddypress #item-header #item-header-avatar img.avatar,
+	.bp-user #buddypress #item-header #item-header-avatar a {
+		float: left;
+	}
+	.bp-user #buddypress #item-header #item-header-content {
+		float: right;
+		margin-right: 5%;
+		width: 69%;
+	}
+}
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*-----------------------------------------------------------
+*/
+#item-body h1,
+#item-body h2,
+#item-body h3,
+#item-body h4,
+#item-body h5,
+#item-body h6 {
+	margin: 1em 0;
+}
+
+#item-body h1 a,
+#item-body h2 a,
+#item-body h3 a,
+#item-body h4 a,
+#item-body h5 a,
+#item-body h6 a {
+	border-bottom: 0;
+}
+
+/**
+*----------------------------------------------------
+* @subsection 6.2.1 - Item Body Groups
+*
+* Groups specific item body rules -  screens
+*----------------------------------------------------
+*/
+/**
+*-----------------------------------------
+* @subsection 6.2.1.1 - Group Management
+*-----------------------------------------
+*/
+.groups #group-settings-form h4 {
+	background: #555;
+	color: #fff;
+	padding: 0.2em;
+}
+
+.groups #group-settings-form #request-list h4 {
+	background: none;
+	color: inherit;
+}
+
+.groups.edit-details #group-settings-form label {
+	background: #555;
+	-webkit-border-top-right-radius: 4px;
+	border-top-right-radius: 4px;
+	-webkit-border-top-left-radius: 4px;
+	border-top-left-radius: 4px;
+	background-clip: padding-box;
+	color: #fff;
+	display: inline-block;
+	margin-bottom: 0;
+	padding: 0.2em;
+	width: 80%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.groups.edit-details #group-settings-form label {
+		width: 60%;
+	}
+}
+
+.groups.edit-details #group-settings-form textarea + p label {
+	background: none;
+	color: inherit;
+	font-size: 14px;
+	font-size: 1.4rem;
+	width: auto;
+}
+
+.groups.group-settings #group-settings-form div.radio label,
+.groups.group-settings #create-group-form div.radio label {
+	border: 1px solid #eaeaea;
+	padding: 0.2em;
+}
+
+.groups.group-settings #group-settings-form div.radio ul,
+.groups.group-settings #create-group-form div.radio ul {
+	color: rgba(51, 51, 51, 0.6);
+	font-size: 14px;
+	font-size: 1.4rem;
+}
+
+.groups.group-avatar form > p {
+	margin-top: 20px;
+}
+
+.groups.manage-members #group-settings-form .item-list li {
+	border-bottom: 1px solid #eaeaea;
+}
+
+.groups.manage-members #group-settings-form .item-list li img,
+.groups.manage-members #group-settings-form .item-list li h5 {
+	float: left;
+}
+
+.groups.manage-members #group-settings-form .item-list li img > a,
+.groups.manage-members #group-settings-form .item-list li h5 > a {
+	border-bottom: 0;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small {
+	clear: left;
+	display: block;
+	float: none;
+	margin-top: 10px;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small a {
+	display: inline-block;
+	margin: 5px 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.groups.manage-members #group-settings-form .item-list li span.small a {
+		width: auto;
+	}
+}
+
+.groups.manage-members #group-settings-form .item-list li h5 {
+	margin: 0;
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.2 - Group members
+*-----------------------------------------
+*/
+/* Members loop */
+@media screen and (max-width: 30em) {
+	.groups.group-members #buddypress #members-group-list li > a {
+		border-bottom: 0;
+		display: block;
+		margin-bottom: 10px;
+		text-align: center;
+	}
+	.groups.group-members #buddypress #members-group-list li > a img.avatar {
+		display: inline;
+		float: none;
+	}
+}
+
+.groups.group-members #buddypress #members-group-list li h5 {
+	display: inline-block;
+	font-size: 14px;
+	font-size: 1.4rem;
+	margin: 0;
+}
+
+@media screen and (min-width: 59.6875em) {
+	.groups.group-members #buddypress #members-group-list li h5 {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+.groups.group-members #buddypress #members-group-list li h5 a {
+	border-bottom: 0;
+}
+
+.groups.group-members #buddypress #members-group-list li span.activity {
+	font-size: 12px;
+	font-size: 1.2rem;
+}
+
+.groups.group-members #buddypress #members-group-list li .action {
+	top: 0;
+}
+
+.groups.group-members #subnav li {
+	width: 100%;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li {
+		background: #fff;
+		padding: 20px 0;
+	}
+}
+
+.groups.group-members #subnav li #search-members-form {
+	float: right;
+	margin: 5px 5px 0 0;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li #search-members-form {
+		margin: 0;
+		width: 100%;
+	}
+	.groups.group-members #subnav li #search-members-form label input[type="text"] {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.3 - Group Send Invites
+*-----------------------------------------
+*/
+.groups.group-invites #item-body .item-list .action {
+	margin-bottom: 0;
+}
+
+@media screen and (min-width: 46.25em) {
+	.groups.group-invites #buddypress #item-body #message {
+		margin-top: 0;
+	}
+}
+
+@media screen and (min-width: 55em) {
+	.groups.group-invites #buddypress #send-invite-form {
+		margin-top: 0;
+	}
+}
+
+@media screen and (max-width: 46.25em) {
+	.groups.group-invites #item-body .left-menu {
+		float: none;
+		margin: 10px 0;
+	}
+	.groups.group-invites #item-body .left-menu #invite-list {
+		height: auto;
+		width: auto;
+	}
+	.groups.group-invites #item-body .main-column {
+		margin-left: 0;
+	}
+	.groups.group-invites #item-body .submit input {
+		display: inline-block;
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.2.2 - Item Body User Accounts
+*
+* User Account specific item body rules
+*-----------------------------------------------------
+*/
+.bp-user .entry-title {
+	margin-bottom: 0.5em;
+}
+
+/**
+*--------------------------------------------
+* @subsection 6.2.2.1 - classes, pag, filters
+*--------------------------------------------
+*/
+.bp-user #buddypress table th {
+	font-size: 14px;
+	font-size: 1.4rem;
+}
+
+.bp-user #buddypress table td {
+	font-size: 12px;
+	font-size: 1.2rem;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress table th {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 14px;
+		font-size: 1.4rem;
+	}
+}
+
+@media screen and (min-width: 77.5em) {
+	.bp-user #buddypress table th {
+		font-size: 18px;
+		font-size: 1.8rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+.bp-user #buddypress .pag-count {
+	font-style: italic;
+}
+
+.bp-user #buddypress .notifications-options-nav,
+.bp-user #buddypress .messages-options-nav {
+	float: left;
+	width: 100%;
+}
+
+@media screen and (min-width: 30em) {
+	.bp-user #buddypress .notifications-options-nav,
+	.bp-user #buddypress .messages-options-nav {
+		width: 50%;
+	}
+}
+
+.bp-user #buddypress .notifications-options-nav select,
+.bp-user #buddypress .notifications-options-nav input,
+.bp-user #buddypress .messages-options-nav select,
+.bp-user #buddypress .messages-options-nav input {
+	font-size: 14px;
+	font-size: 1.4rem;
+	outline: 0;
+	padding: 0;
+}
+
+.bp-user #buddypress .notifications-options-nav select,
+.bp-user #buddypress .messages-options-nav select {
+	float: left;
+	margin-right: 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 30em) {
+	.bp-user #buddypress .notifications-options-nav select,
+	.bp-user #buddypress .messages-options-nav select {
+		width: 60%;
+	}
+}
+
+.bp-user #buddypress .notifications-options-nav input,
+.bp-user #buddypress .messages-options-nav input {
+	float: right;
+	font-family: "Noto Serif", serif;
+	line-height: 1.5;
+	margin-top: 10px;
+	width: 100%;
+}
+
+.bp-user #buddypress .notifications-options-nav input[disabled]:hover,
+.bp-user #buddypress .messages-options-nav input[disabled]:hover {
+	background: none;
+}
+
+@media screen and (min-width: 30em) {
+	.bp-user #buddypress .notifications-options-nav input,
+	.bp-user #buddypress .messages-options-nav input {
+		margin-top: 0;
+		width: 38%;
+	}
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.2 - Extended Profiles
+	*-------------------------------------------
+	*/
+.bp-user #buddypress .profile .bp-widget h4 {
+	background: #6e6e6e;
+	color: #fff;
+	margin-bottom: 0;
+	padding: 0.4em;
+}
+
+.bp-user #buddypress .profile .bp-widget table {
+	margin-top: 0;
+}
+
+/* Edit Profile */
+.bp-user #buddypress .profile #profile-edit-form .button-nav:before,
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	content: " ";
+	display: table;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	clear: both;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav {
+	border-bottom: 1px solid #eaeaea;
+	margin-left: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li {
+	float: left;
+	margin-bottom: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li.current {
+	border: 1px solid #eaeaea;
+	border-bottom-color: #fff;
+	margin-bottom: -1px;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav a {
+	background: none;
+	border: 0;
+	font-size: 18px;
+	font-size: 1.8rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-toggle,
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings {
+	font-size: 14px;
+	font-size: 1.4rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-close,
+.bp-user #buddypress .profile #profile-edit-form .visibility-toggle-link {
+	background: #555;
+	color: #fff;
+	padding: 0.2em 0.5em;
+}
+
+.bp-user #buddypress .profile .bp-avatar #bp-delete-avatar a {
+	font-size: inherit;
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.3 - Groups
+	*-------------------------------------------
+	*/
+@media screen and (min-width: 77.5em) {
+	.bp-user #buddypress #groups-list li .item {
+		left: 5%;
+		width: 50%;
+	}
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.5 - Private Messaging
+	*-------------------------------------------
+	*/
+.bp-user #buddypress {
+	/* Auto Complete background */
+	/* Sitewide Notices loop */
+}
+
+.bp-user #buddypress #message-thread a {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress #message-thread #message-subject {
+	background: #6e6e6e;
+	color: #fff;
+	padding: 0.3em 0 0.3em 0.2em;
+}
+
+.bp-user #buddypress #message-thread #message-recipients {
+	font-style: italic;
+}
+
+.bp-user #buddypress #message-thread #message-recipients a.confirm {
+	border: 1px solid #eaeaea;
+	font-style: normal;
+}
+
+.bp-user #buddypress #message-thread .message-metadata {
+	overflow: hidden;
+}
+
+.bp-user #buddypress #message-thread .message-metadata img.avatar {
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #message-thread .message-metadata img.avatar {
+		float: left;
+	}
+}
+
+.bp-user #buddypress #message-thread .message-metadata .message-star-actions {
+	float: right;
+	margin-right: 5px;
+	position: static;
+}
+
+.bp-user #buddypress #message-thread .message-content {
+	background: #f7f7f7;
+	border: 1px solid #eaeaea;
+	margin: 10px 0 0 0;
+	padding: 0.3em;
+}
+
+.bp-user #buddypress #message-thread #send-reply .message-content {
+	background: #fff;
+	border: 0;
+}
+
+.bp-user #buddypress #message-thread .alt {
+	background: #fff;
+}
+
+.bp-user #buddypress #message-threads thead tr {
+	background: #6e6e6e;
+}
+
+.bp-user #buddypress #message-threads tr td {
+	background: #fff;
+	display: inline-block;
+	float: left;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-star,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-bottom-color: #b7b7b7;
+	border-bottom-width: 2px;
+	height: 2.4em;
+	padding-bottom: 0.2em;
+	padding-top: 0.2em;
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #message-threads tr td.thread-star,
+	.bp-user #buddypress #message-threads tr td.thread-options {
+		padding-top: 0;
+	}
+}
+
+.bp-user #buddypress #message-threads tr td.bulk-select-check,
+.bp-user #buddypress #message-threads tr td.thread-from {
+	height: 2.6em;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr td.bulk-select-check,
+	.bp-user #buddypress #message-threads tr td.thread-from {
+		height: 5.2em;
+	}
+}
+
+.bp-user #buddypress #message-threads tr td.thread-from,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-left: 0 !important;
+	width: -webkit-calc(100% - 30px);
+	width: -moz-calc(100% - 30px);
+	width: calc(100% - 30px);
+	margin-left: 0;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info {
+	padding-left: 41px;
+	width: 100%;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info a::after {
+	content: " –  " attr(title);
+	font-size: 12px;
+	font-size: 1.2rem;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options {
+	text-align: right;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options a {
+	font-size: 12px;
+	font-size: 1.2rem;
+	line-height: 2.2;
+}
+
+.bp-user #buddypress #message-threads tr span.from {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads tr span.activity {
+	display: block;
+	float: right;
+	line-height: 2;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr span.activity {
+		clear: both;
+		font-size: 11px;
+		font-size: 1.1rem;
+		width: 100%;
+	}
+}
+
+.bp-user #buddypress #message-threads tr.unread td {
+	background: #dce5ff;
+	border-bottom-color: #b7b7b7;
+	border-color: #d1d1d1;
+}
+
+.bp-user #buddypress #message-threads th {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all {
+	border-bottom: 0;
+	display: inline-block;
+	text-align: left;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all,
+.bp-user #buddypress #message-threads td.bulk-select-check,
+.bp-user #buddypress #message-threads td.thread-star {
+	border-right: 0;
+	width: 30px;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab {
+	margin-right: 0;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab a {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab a img {
+	display: inline;
+	height: 20px;
+	margin-right: 0.5em;
+	width: 20px !important;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td {
+	width: 100%;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td strong {
+	background: #6e6e6e;
+	color: #fff;
+	display: block;
+	margin-bottom: 0.4em;
+	padding-left: 0.2em;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td a {
+	display: inline-block;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child + td + td {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child + td + td span {
+	line-height: 1;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child {
+	border-bottom-color: #b7b7b7;
+	line-height: 1;
+	text-align: right;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child a:last-child {
+	line-height: 0;
+	margin-left: 0.7em;
+	text-indent: -999em;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child a:last-child::after {
+	content: attr(title);
+	display: block;
+	line-height: initial;
+	text-indent: 0;
+}
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+.bp-user #buddypress #settings-form > p {
+	font-size: 20px;
+	font-size: 2rem;
+	margin: 20px 0 10px;
+}
+
+.bp-user #buddypress table.notification-settings th.title {
+	width: 75%;
+}
+
+.bp-user #buddypress table.notification-settings td.yes,
+.bp-user #buddypress table.notification-settings td.no {
+	vertical-align: middle;
+}
+
+.bp-user #buddypress table.profile-settings {
+	width: 100%;
+}
+
+.bp-user #buddypress table.profile-settings th.field-group-name,
+.bp-user #buddypress table.profile-settings td.field-name {
+	width: 50%;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress table.profile-settings th.field-group-name,
+	.bp-user #buddypress table.profile-settings td.field-name {
+		width: 70%;
+	}
+}
+
+.bp-user #buddypress table.profile-settings th.title,
+.bp-user #buddypress table.profile-settings td.field-visibility {
+	width: 30%;
+}
+
+.bp-user #buddypress table.profile-settings td.field-visibility select {
+	width: 100%;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+#buddypress div.activity-comments form .ac-textarea {
+	background: #f7f7f7;
+	border: 1px solid rgba(153, 153, 153, 0.3);
+}
+
+#buddypress div.activity-comments form .ac-textarea textarea {
+	background: none;
+	border: 0;
+}
+
+#buddypress select {
+	border: 1px solid rgba(153, 153, 153, 0.5);
+}
+
+#buddypress .wp-editor-wrap a.button,
+#buddypress .wp-editor-wrap button,
+#buddypress .wp-editor-wrap input[type=submit],
+#buddypress .wp-editor-wrap input[type=button],
+#buddypress .wp-editor-wrap input[type=reset] {
+	padding: 0 5px 1px;
+}
+
+#buddypress .standard-form li,
+#buddypress .group-create-form li {
+	float: none;
+	list-style: none;
+}
+
+#buddypress .standard-form input[type='text'],
+#buddypress .standard-form textarea,
+#buddypress .group-create-form input[type='text'],
+#buddypress .group-create-form textarea {
+	width: 100%;
+}
+
+#buddypress div.dir-search,
+#buddypress div.message-search,
+#buddypress
+li.groups-members-search {
+	float: none;
+	margin: 10px 0;
+}
+
+#buddypress div.dir-search form,
+#buddypress div.message-search form,
+#buddypress
+li.groups-members-search form {
+	border: 1px solid rgba(153, 153, 153, 0.4);
+	overflow: hidden;
+}
+
+#buddypress div.dir-search form label,
+#buddypress div.message-search form label,
+#buddypress
+li.groups-members-search form label {
+	float: left;
+	width: 80%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress
+li.groups-members-search form input[type='text'] {
+	float: left;
+	margin: 0;
+	width: 80%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='text'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress
+li.groups-members-search form input[type='text'],
+#buddypress
+li.groups-members-search form input[type='submit'] {
+	font-size: 14px;
+	font-size: 1.4rem;
+	border: 0;
+	line-height: inherit;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress
+li.groups-members-search form input[type='text'] {
+	border-right: 1px solid rgba(153, 153, 153, 0.4);
+	padding: 0.2em 0 0.2em 0.2em;
+}
+
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress
+li.groups-members-search form input[type='submit'] {
+	float: right;
+	font-weight: normal;
+	padding: 0.2em 1em;
+	text-align: center;
+	text-transform: none;
+	width: 20%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div.dir-search,
+	#buddypress div.message-search,
+	#buddypress li.groups-members-search {
+		float: right;
+		margin-bottom: 5px !important;
+	}
+	#buddypress div.dir-search form label,
+	#buddypress div.dir-search form input[type='text'],
+	#buddypress div.dir-search form input[type='submit'],
+	#buddypress div.message-search form label,
+	#buddypress div.message-search form input[type='text'],
+	#buddypress div.message-search form input[type='submit'],
+	#buddypress li.groups-members-search form label,
+	#buddypress li.groups-members-search form input[type='text'],
+	#buddypress li.groups-members-search form input[type='submit'] {
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 77.5em) {
+	#buddypress .dir-search form input[type='text'],
+	#buddypress .message-search form input[type='text'] {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+	#buddypress .dir-search form input[type='submit'],
+	#buddypress .message-search form input[type='submit'] {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables - General
+*-------------------------------------------------------------------------------
+*/
+#buddypress table {
+	font-size: 14px;
+	font-size: 1.4rem;
+	margin: 20px 0;
+}
+
+#buddypress table tr th {
+	background: #6e6e6e;
+	border-color: #b7b7b7;
+	color: #fff;
+}
+
+#buddypress table p {
+	margin-bottom: 0.5em;
+}
+
+@media screen and (min-width: 55em) {
+	#buddypress table {
+		font-size: 16px;
+		font-size: 1.6rem;
+	}
+}
+
+/*__ User Account tables __*/
+#buddypress .notifications th,
+#buddypress .messages-notices th {
+	width: 30%;
+}
+
+#buddypress .notifications th.bulk-select-all,
+#buddypress .messages-notices th.bulk-select-all {
+	text-align: center;
+	width: 10%;
+}
+
+#buddypress .notifications .bulk-select-check,
+#buddypress .notifications .thread-star,
+#buddypress .messages-notices .bulk-select-check,
+#buddypress .messages-notices .thread-star {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions,
+#buddypress .notifications td.thread-options,
+#buddypress .messages-notices .notification-actions,
+#buddypress .messages-notices td.thread-options {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions a,
+#buddypress .notifications td.thread-options a,
+#buddypress .messages-notices .notification-actions a,
+#buddypress .messages-notices td.thread-options a {
+	display: inline-block;
+	margin: 0;
+	padding: 0;
+}
+
+#buddypress .notifications td .button,
+#buddypress .messages-notices td .button {
+	border: 0;
+	display: block;
+	padding: 0;
+	text-align: center;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+#buddypress #item-body div#message {
+	margin-top: 20px;
+}
+
+@media screen and (min-width: 55em) {
+	#buddypress #item-body div#message {
+		margin-right: 20px;
+	}
+}
+
+#buddypress div#message p {
+	font-size: 18px;
+	font-size: 1.8rem;
+	font-weight: bold;
+}
+
+#buddypress div#message.info p {
+	background: #c6e4ee;
+	border: 1px solid #78bed6;
+	color: #1e5264;
+}
+
+#buddypress div#message.updated p {
+	background: #dee6b2;
+	border: 1px solid #becc66;
+	color: #454d19;
+}
+
+.delete-group #buddypress div#message.info p {
+	background: #db7e7e;
+	border: 1px solid #be3535;
+	color: #1f0808;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+#buddypress {
+	/* Auto Complete background */
+}
+
+#buddypress .acfb-holder li.friend-tab {
+	background: #9fd1e2;
+	border: inherit;
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfifteen.scss
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfifteen.scss	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfifteen.scss	(revision 10410)
@@ -0,0 +1,2307 @@
+// Stylesheet Guidence Notes
+// Table of content represents a guide to sections of the sheet.
+// Note that not all sections may be described in the body of the
+// rulesets only those that require styles for a given theme.
+// Sub sections might not exist but can be added as required
+// as per BP Lists - 'activity' section.
+
+// We follow the dictats of the parent theme in respect of media queries,
+// font sizing etc. Media queries are used in a modular sense (OOCSS)
+// rather than described as blocks to the end of the sheet, this aids ease
+// of managing media queries, scalability & flexibility .
+
+// Twentyfifteen Media Queries / Breakpoints
+// @media screen and (max-width: 22em) // Additional for xtra small mobile only
+// @media screen and (max-width: 30em) // Additional small only
+// @media screen and (min-width: 38.75em)
+// @media screen and (min-width: 46.25em)
+// @media screen and (min-width: 55em)
+// @media screen and (min-width: 59.6875em)
+// @media screen and (min-width: 77.5em)
+// @media screen and (min-width: 87.6875em)
+
+// Mixins and Variables
+
+// Common margin values
+$spacing-val-lg: 40px;
+$spacing-val-md: 20px;
+$spacing-val-sm: 10px;
+$spacing-val-xs: 5px;
+
+// A simple mixin to handle font-sizing to match 2015 62.5% approach
+@mixin font-size($font-size: 16) {
+	$rem-font-value: ($font-size / 10);
+	font-size: ($font-size * 1px);
+	font-size: $rem-font-value + rem;
+}
+
+// To allow mixins to pass VendorPrefix scss-lint tests we disable lint-test
+// for specific blocks / rulesets
+// until such time as exemption lists can be built for the scss-lint.yml?
+// Any vendor prefix properties / mixins need to be in this block
+// scss-lint:disable VendorPrefix
+
+// Border border-radius mixins
+@mixin border-radius($radius) {
+	-webkit-border-radius: $radius;
+	-moz-border-radius: $radius;
+	-ms-border-radius: $radius;
+	border-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-top-radius($radius) {
+	-webkit-border-top-right-radius: $radius;
+	border-top-right-radius: $radius;
+	-webkit-border-top-left-radius: $radius;
+	border-top-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-right-radius($radius) {
+	-webkit-border-bottom-right-radius: $radius;
+	border-bottom-right-radius: $radius;
+	-webkit-border-top-right-radius: $radius;
+	border-top-right-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-bottom-radius($radius) {
+	-webkit-border-bottom-right-radius: $radius;
+	border-bottom-right-radius: $radius;
+	-webkit-border-bottom-left-radius: $radius;
+	border-bottom-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-left-radius($radius) {
+	-webkit-border-bottom-left-radius: $radius;
+	border-bottom-left-radius: $radius;
+	-webkit-border-top-left-radius: $radius;
+	border-top-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+// Box sizing
+@mixin box-model($box-model) {
+	-webkit-box-sizing: $box-model;
+	-moz-box-sizing: $box-model;
+	box-sizing: $box-model;
+}
+
+// Calc
+@mixin calc($property, $expression) {
+	#{$property}: -webkit-calc(#{$expression});
+	#{$property}: -moz-calc(#{$expression});
+	#{$property}: calc(#{$expression});
+}
+
+// Re-enable the VendorPrefix lint test
+// scss-lint:enable VendorPrefix
+
+// BP message boxes
+@mixin message-box($background, $text-color: null) {
+
+	@if $text-color {
+		// if a param was passed through
+		$text-color: $text-color;
+	} @else {
+		$text-color: darken($background, 50%);
+	}
+
+	background: lighten($background, 10%);
+	border: 1px solid darken($background, 10%);
+	color: $text-color;
+}
+
+// 2015 font family
+@mixin default-font-sans() {
+	font-family: "Noto Sans", sans-serif;
+}
+
+@mixin default-font-serif() {
+	font-family: "Noto Serif", serif;
+}
+
+// Variables: color definitions
+$content-background: #fff;
+$light-background:   #f7f7f7;
+$medium-background:  #ccc;
+$dark-background:    #555;
+$border-color:       #999;    // border color is varied using rgba
+$border-light:       #eaeaea; // BP dividers
+$border-med:         #ddd;
+$body-text:          #333;    // 2015 body text color
+$bp-button-hover:    #ededed; // this is the default BP button hover background
+$notice-error:       #c85a6e;
+$notice-warning:     #d05656;
+$notice-info:        #9fd1e2;
+$notice-update:      #ced98c;
+$stripe-odd:         #ebf0ff;
+$stripe-even:        #dbe5ff;
+$unread:             #dce5ff;
+
+/*------------------------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentyfifteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentyfifteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the rulesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+--------------------------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+--------------------------------------------------------------------------------
+1.0 Theme Structural Elements & overrides
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 Whats New Activity
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Forums
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - item-header: Groups
+		6.1.2 - item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1 - Management settings screens
+			6.2.1.2 - Group members list
+			6.2.1.3 - Group Send Invites
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading, Widgets, General classes
+------------------------------------------------------------------------------*/
+
+/**
+*-------------------------------------------------------------------------------
+* @section 1.0 - Theme - Structural Elements & overrides
+*-------------------------------------------------------------------------------
+*/
+
+// Twentyfifteen  adds a generic grouped ruleset of clear:both;
+// for all heading h# elements this is problematic where floats are concerned
+// We reset this here under our #buddypress namespace
+#buddypress {
+	h1,
+	h2,
+	h3,
+	h4,
+	h5,
+	h6 {
+		clear: none;
+	}
+}
+
+// As BP screens have specific content lets adjust the themes primary elements
+// to maximise the screen real estate for BP pages.
+
+.buddypress {
+	div.clear {display: none;}
+	.site-content {
+		@media screen and (min-width: 77.5em) {
+		}
+	}
+
+	main {
+		padding-top: 4%;
+		@media screen and (min-width: 59.6875em) {
+			padding-top: 0;
+		}
+
+		article {
+			margin: 0 4%;
+			padding-top: 8.3333%;
+			@media screen and (min-width: 59.6875em) {
+				margin: 0 0 0 1px;
+			}
+			@media screen and (min-width: 87.6875em) {
+				margin: 0 4.3333% 0 8.3333%;
+			}
+
+			.entry-header,
+			.entry-content {
+				padding: 0 3rem 3rem 3rem;
+			}
+
+			#buddypress {
+				margin-bottom: $spacing-val-lg;
+			}
+		} // .article
+	} // .main
+
+	.site-footer {
+		margin: 0 4%;
+		@media screen and (min-width: 59.6875em) {
+			margin: 0 0 0 35.2941%;
+			width: 61.8235%;
+		}
+	}
+} // .buddypress (body class)
+
+/**
+*-------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+
+// Remove 2015 border bottom on BP links/spans
+
+.buddypress #buddypress {
+	.item-list-tabs,
+	.activity-header,
+	.activity-inner,
+	.activity-comments,
+	.item-title,
+	.load-more,
+	.field-visibility-settings-toggle,
+	#latest-update,
+	table,
+	.avatar-nav-items {
+		a { border-bottom: 0; }
+	}
+
+	.pagination-links {
+		a,
+		span { border-bottom: 0; }
+	}
+} // close .buddypress #buddypress
+
+/**
+*----------------------------------------------------------
+* @section 2.1 - Navs Object Nav / Sub Nav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+
+// We have a need to override BP specifity
+// so some rulesets sets are seemingly over weighted in selectors
+
+#buddypress {
+
+	// active/current states all navs
+	div.item-list-tabs {
+		ul {
+			li.selected {
+				a {
+					background: $dark-background;
+					color: #fff;
+					opacity: 1;
+				}
+			}
+		}
+	}
+	// Global Nav Styles
+	div.item-list-tabs {
+		ul {
+			background-color: $light-background;
+			border-bottom: 1px solid rgba($border-light, 0.9);
+			border-top: 1px solid rgba($border-light, 0.9);
+			overflow: hidden;
+			padding: $spacing-val-xs 0;
+
+			li {
+				a {
+					span {border-radius: 25%;}
+				}
+			}
+		}
+	}
+
+	/*__ Horizontal menus __*/
+	#object-nav {
+
+		ul {
+			overflow: hidden;
+			li {float: none;}
+			li:not(.selected) {
+				a {opacity: 0.7;}
+			}
+
+			@media screen and (min-width: 38.75em) {
+				li {
+					float: left;
+				}
+			}
+		}
+	}
+
+	div#subnav.item-list-tabs {
+		margin-top: 0;
+
+		ul {
+			background-color: $light-background;
+			border-bottom: 0;
+			padding: 0;
+
+			li.last {
+				background: $content-background;
+				margin-top: 0;
+				padding: $spacing-val-xs $spacing-val-xs $spacing-val-xs 0;
+				width: 100%;
+
+				select,
+				select:focus {
+					background: transparent;
+					border: 0;
+					outline: 0;
+				}
+
+				select,
+				label,
+				option {
+					@include font-size(14);
+				}
+
+				select {
+					font-style: italic;
+				}
+			}
+
+			@media screen and (min-width: 38.75em) {
+				li.last {
+					text-align: right;
+				}
+			}
+		} // close ul
+	} // close #subnav
+
+	// active/current states all navs
+	div.item-list-tabs {
+		ul {
+			li.selected,
+			li.current {
+				a {
+					background: $dark-background;
+					color: #fff;
+					opacity: 1;
+				}
+			}
+		}
+	}
+
+} // close #buddypress
+
+	/*__ Vertical menu User Account / Group single screens __*/
+
+// This block contains rules to re-factor the item-body structural element
+// to sit alongside the vert menu
+
+@media screen and (min-width: 55em) {
+	.bp-user #buddypress,
+	.single-item.groups #buddypress {
+		background: $light-background;
+	}
+
+	#buddypress {
+		#item-header,
+		#item-body {
+			background: $content-background;
+		}
+
+		#object-nav {
+			border-right: 1px solid $border-med;
+			float: left;
+			margin-right: -1px;
+			width: 200px;
+
+			ul {
+				border-bottom: 0;
+				padding: 0;
+
+				li {
+					float: none;
+					overflow: hidden;
+
+					span {
+						background: $content-background;
+						border-radius: 10%;
+						float: right;
+						margin-right: 2px;
+					}
+				}
+			}
+		}
+
+		#item-body {
+			border-left: 1px solid $border-med;
+			overflow: hidden;
+			padding: 0 0 0 $spacing-val-md;
+			width: auto;
+
+			#subnav {
+				margin: 0 0 0 (-$spacing-val-md);
+			}
+		}
+
+	} // close  #buddypress
+} // close @media
+
+/**
+*----------------------------------------------------------
+* @section 2.2 - Pagination
+*----------------------------------------------------------
+*/
+
+#buddypress {
+	div.pagination {
+		box-shadow: none;
+		.pag-count {margin-left: 0;}
+		.pagination-links {
+			margin-right: 0;
+
+			span,
+			a {
+				height: auto;
+				line-height: 1;
+				padding: 5px;
+			}
+
+			.prev,
+			.next {
+				background-color: transparent;
+				color: inherit;
+				overflow: visible;
+				width: auto;
+				&:before {display: none;}
+			}
+
+			.prev {
+				left: auto;
+				position: static;
+			}
+
+			.next {
+				position: static;
+				right: auto;
+			}
+		} // close .pagination-links
+	} // close .pagination
+} // close #buddypress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+
+// Adjusts Meta items and various elements to match 2015 font-family
+#buddypress {
+	.item-list {
+		.activity-header,
+		.activity-meta {
+			@include default-font-sans();
+		}
+	}
+}
+
+// Trying to position the action div absolute has bad consequences where
+// aditional items are displayed, floating is an alternative that
+// allows natural flow expansion.
+// This block:
+// * Manages li items, Stacks them small screen, floated left at wider screens.
+// * Adjusts display of action buttons at small screen mobile up.
+// Adjusts Meta items to match 2015 font-family
+
+#buddypress {
+	ul.item-list {
+		li {
+			overflow: hidden !important;
+
+			// Undo BP floats to center avatars initially mobile up
+			.item-avatar {
+				margin-bottom: $spacing-val-sm;
+				@media screen and (min-width: 38.75em) { margin-bottom: 0; }
+				text-align: center;
+				a {border-bottom: 0;}
+				img.avatar {
+					display: inline-block;
+					float: none;
+					// Quite early on we need to move avatars to the left(right)
+					@media screen and (min-width: 38.75em) {
+						display: block;
+						float: left;
+					}
+				}
+			}
+
+			.item {
+				margin-left: 25%;
+
+				span.activity {font-style: italic;}
+
+				.item-desc {
+					margin-left: 0;
+					width: 70%;
+				}
+			}
+
+			@media screen and (min-width: 59.6875em) {
+				.item-avatar,
+				.item,
+				.action {
+					float: left;
+				}
+
+				.item {
+					left: 10%;
+					margin-left: 0;
+					position: relative;
+					width: 55%;
+				}
+			}// close @media
+
+			div.action {
+				clear: left;
+				float: none;
+				margin-bottom: -$spacing-val-md;
+				// remove BP margin left
+				margin-left: 0;
+				padding: $spacing-val-md 0 $spacing-val-xs;
+				// push the actions to the right
+				@media screen and (min-width: 59.6875em) {
+					clear: none;
+					float: right;
+					margin-bottom: 0;
+					padding: 0;
+				}
+				position: relative;
+				text-align: left;
+				top: 0;
+
+				div {
+					display: inline-block;
+					margin: $spacing-val-sm 0;
+					width: 100%;
+
+					a {
+						display: block;
+						width: 100%;
+					}
+					// Provisionally lets keep the 'action' div
+					//position but inline-block the buttons
+
+					@media screen and (min-width: 38.75em) {
+						// keep buttons to a row small tablet up
+						margin: 0 $spacing-val-sm $spacing-val-sm 0;
+						width: auto;
+					} // close @media
+
+					@media screen and (min-width: 59.6875em) {
+						// take buttons to block to stack them when floated right
+						clear: right;
+						float: right;
+						margin: 0 0 $spacing-val-sm 0;
+					} // close @media
+
+				} // close div
+
+				.meta {
+					font-style: italic;
+				}
+
+			} // close .action
+
+		} // close li
+	} // close .item-list
+} // close #buddypress
+
+/**
+*----------------------------------------------------------
+* @section 4.1 - Activity
+*----------------------------------------------------------
+*/
+
+/**
+*-----------------------------------------------------
+* @section 4.1.1 - Activity Whats New
+*-----------------------------------------------------
+*/
+
+#buddypress {
+	form#whats-new-form {
+
+		// Line-height issue inherited from BP rules, override.
+		p.activity-greeting {
+			line-height: 1.4;
+		}
+
+		// corrective measure for clipped elements due to JS inline styling
+
+		@media screen and (max-width: 30em) {
+			#whats-new-post-in-box {
+				select {
+					@include font-size(14);
+					max-width: 120px;
+				}
+			}
+		}
+
+		@media screen and (max-width: 38.75em) {
+			#whats-new-content {
+				clear: left;
+				margin: $spacing-val-sm 0 $spacing-val-lg;
+				padding: $spacing-val-sm 0 0;
+
+				#whats-new-submit {
+					float: none;
+
+					input {width: 100%;}
+				}
+			}
+
+			#whats-new-options {
+				display: flex;
+				flex-direction: column;
+
+				#whats-new-submit {order: 2;}
+				#whats-new-post-in-box {order: 1;}
+			}
+		} // close @media
+
+		#whats-new-options[style] {
+			#whats-new-post-in-box {
+				border: 1px solid rgba($border-color, 0.5);
+				float: left;
+				line-height: 1.5em;
+				margin-top: 12px;
+				padding-left: 0.2em;
+				width: 100%;
+
+				select {
+					background: none;
+					border: 0;
+					float: right;
+					margin: 0;
+					min-height: 1.5em;
+					outline: 0;
+					padding-left: 0.4em;
+				}
+			}
+
+			@media screen and (min-width: 30em) {
+				#whats-new-post-in-box {
+					width: auto;
+				}
+
+				#whats-new-submit {
+					float: right;
+				}
+			}
+
+		} // close #whats-new-options
+
+	} // close #whats-new-form
+
+	// User account form requires matching bp default specificity
+	#item-body {
+		form#whats-new-form {
+			margin: $spacing-val-lg 0;
+		}
+	}
+} // close #buddypress
+
+/**
+*-----------------------------------------------------
+* @section 4.1.2 - Activity Listing
+*-----------------------------------------------------
+*/
+
+#buddypress {
+
+	#activity-stream {
+
+		// re-size activity avatars and stack small screen
+		// refactor main elements positioning medium up.
+		li {
+			padding: 25px 0 15px;
+
+			.activity-avatar {
+				float: none;
+				text-align: center;
+
+				a {
+					display: inline-block;
+
+					img.avatar {
+						display: inline;
+						float: none;
+						height: 60px;
+						margin-bottom: $spacing-val-md;
+						margin-left: 0;
+						width: 60px;
+					}
+				}
+			} // close .activity-avatar
+
+			.activity-content {
+				margin-left: 0;
+
+				.activity-header {
+					@include font-size(14);
+				}
+			}
+
+			@media screen and (min-width: 46.25em) {
+
+				.activity-avatar {
+					float: left;
+					margin-right: $spacing-val-sm;
+					text-align: left;
+					a {border-bottom: 0;}
+				}
+
+				.activity-content {
+					margin: 0;
+					overflow: hidden;
+
+					.activity-header {
+						@include font-size(16);
+					}
+				}
+
+			} // close @media
+
+		} // close li
+
+		li.mini {
+			.activity-avatar {
+				a {
+					img.avatar {
+						height: 30px;
+						margin-left: 15px;
+						width: 30px;
+					}
+				}
+			}
+
+			.activity-content {
+				.activity-header {
+					@include font-size(13);
+				}
+			}
+		}
+
+		.activity-content {
+			margin-top: -12px;
+
+			.activity-header {
+				line-height: inherit;
+				margin-right: 0;
+
+				p {
+					background-color: $light-background;
+					border: 1px solid rgba($border-light, 0.6);
+					padding: 0 0.2em;
+				}
+
+				img.avatar {
+					display: inline-block;
+					margin-bottom: 0 !important; // have to match to BP
+				}
+			} // close .activity-header
+
+			.activity-meta {
+				a {
+					display: block;
+					margin: 0 0 $spacing-val-xs;
+					@media screen and (min-width: 38.75em) {
+						display: inline-block;
+						margin: 0;
+					}
+				}
+			}
+
+		} // close .activity-content
+
+		.load-more {
+			background: $light-background;
+			border: 1px solid transparent;
+			padding: $spacing-val-sm;
+
+			&:focus,
+			&:hover {
+				background: darken($light-background, 1%);
+				border: 1px solid rgba(#9fd1e2, 0.3);
+				a { font-style: italic; }
+			}
+
+			a {
+				display: block;
+			}
+		}
+
+	} // close #activity-stream
+} // close #buddypress
+
+/* Single activity view  - activity permalink */
+
+.activity-permalink {
+	#buddypress {
+		background: none; // counter the .bp-user faux column background
+		#activity-stream {
+
+			li.activity-item {
+				padding: $spacing-val-md 0;
+			}
+
+			li.mini {
+				.activity-header {
+					@include font-size(16);
+					@media screen and (min-width: 46.25em) {
+						@include font-size(20);
+					}
+					margin-bottom: $spacing-val-lg;
+					p {padding: $spacing-val-md;}
+				}
+			}
+		}
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.3 - Activity Comments
+*-----------------------------------------------------
+*/
+#buddypress {
+
+	#activity-stream {
+		.activity-comments {
+			margin: $spacing-val-sm 0 0;
+
+			&.has-comments {
+				border-left: 1px solid $border-light;
+			}
+
+			.ac-form {
+				padding: $spacing-val-md 0 0;
+			}
+
+			> ul {
+				background: rgba($light-background, 0.6);
+				margin: 15px 0 0 2px;
+				@media screen and (min-width: 38.75em) {
+					margin-left: 70px;
+				}
+
+				ul {
+					// all nested ul margins & padding flattened initially
+					margin-left: 1%;
+					padding-left: 0;
+
+					@media screen and (min-width: 30em) {
+						margin-left: 1%;
+						padding-left: 1em;
+					}
+				}
+
+				ul li {
+					border-left: 1px solid $border-light;
+					border-top: 0;
+					padding-left: 0.2em;
+				}
+
+				.acomment-meta {border-bottom: 1px solid $border-light; font-style: italic;}
+
+				// Display the user avatar stacked up to 30em
+				// reduce/widen the content margins
+				@media screen and (max-width: 38.75em) {
+					.acomment-avatar {
+						display: block;
+						text-align: center;
+
+						a,
+						img.avatar {display: inline; float: none;}
+					}
+
+					.acomment-content,
+					.acomment-meta,
+					.acomment-options {
+						margin: $spacing-val-xs;
+					}
+
+					.acomment-content {padding: 0 $spacing-val-sm;}
+				} // close @media
+
+			} // close the ul li comments construct
+
+			.ac-reply-avatar {display: none;}
+
+			.ac-reply-content {
+				margin-left: 0;
+				padding-left: 0;
+			}
+
+			@media screen and (min-width: 38.75em) {
+				.ac-reply-avatar {display: block;}
+
+				.ac-reply-content {
+					overflow: hidden;
+				}
+			}
+
+			.ac-form {
+
+				input[type="submit"],
+				.ac-reply-cancel {
+					color: rgba($body-text, 0.8);
+					display: inline-block;
+					font-family: inherit;
+					@include font-size(12);
+					font-weight: normal;
+					line-height: 1.2;
+					padding: 4px 10px;
+					text-transform: lowercase;
+					width: 100px;
+				}
+
+				.ac-reply-cancel {
+					border: 1px solid rgba($border-color, 0.7);
+					text-align: center;
+
+					&:focus,
+					&:hover {
+						background: $bp-button-hover;
+					}
+				}
+
+			} // close .ac-form
+
+		} // close .activity-comments
+	} // close .activity
+
+} // close #buddypress
+
+/**
+*----------------------------------------------------------
+* @section 4.2 - Members Loop
+*----------------------------------------------------------
+*/
+
+#buddypress {
+	#members-list {
+
+		@media screen and (min-width: 59.6875em) {
+			li {
+				.item-avatar,
+				.item {
+					float: left;
+				}
+
+				.action {
+					float: right;
+				}
+			}
+		}
+
+	} // close #members-list
+} //close #BuddyPress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens: User Account & Single Group Screens
+*-------------------------------------------------------------------------------
+*/
+
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.1 - Item Header Global
+*-----------------------------------------------------------
+*/
+
+.bp-user,
+.single-item.groups {
+	@media screen and (max-width: 46.25em) {
+		main {
+			header.entry-header {
+				padding-bottom: 1rem;
+			}
+		}
+	}
+
+	@media screen and (max-width: 38.75em) {
+		h1,
+		#item-header-content {
+			text-align: center;
+		}
+	}
+
+	#buddypress {
+		@media screen and (max-width: 46.25em) {
+			#item-header {
+				.generic-button {
+					float: none;
+					margin: 1.5em 0 0 0;
+				}
+			}
+		}
+
+		@media screen and (max-width: 38.75em) {
+			h1 {
+				margin-bottom: 0;
+			}
+
+			#item-header-avatar {
+				img.avatar {
+					margin-right: 0;
+				}
+			}
+
+			#item-header-content {
+				width: 100%;
+			}
+		}
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.1 - item-header Groups
+*
+* Group Specific Item Header
+*-----------------------------------------------------
+*/
+
+.single-item.groups {
+	#buddypress {
+		@media screen and (max-width: 46.25em) {
+			#item-header {
+				#item-meta {
+					margin-bottom: $spacing-val-md;
+				}
+			}
+		}
+
+		// Move visual flow of avatar & item-actions at narrow width,
+		// avatar first after group title
+		@media screen and (max-width: 38.75em) {
+			div#item-header {
+				display: flex;
+				flex-direction: column;
+
+				#item-header-avatar {
+					order: 1;
+					text-align: center;
+
+					a {
+						border-bottom: 0;
+
+						img {
+							display: inline-block;
+							float: none;
+						}
+					}
+				}
+
+				#item-header-content {order: 2;}
+
+				#item-actions {order: 3;}
+			}
+		} // close @media
+
+		div#item-header {
+			padding-bottom: $spacing-val-lg;
+
+			div#item-actions {
+				margin: 0;
+				width: 100%;
+
+				@media screen and (min-width: 38.75em) {
+					clear: none;
+					float: right;
+					width: 50%;
+				}
+
+				h3 {
+					background: $dark-background;
+					color: $content-background;
+					@include font-size(14);
+
+					@media screen and (min-width: 46.25em) {
+						@include font-size(16);
+					}
+					padding: 0.2em;
+				}
+			} // close item-actions
+
+			@media screen and (min-width: 46.25em) {
+
+				#item-header-avatar,
+				#item-header-content {
+					float: left;
+				}
+
+				#item-header-avatar {
+					width: 21%;
+				}
+
+				#item-header-content {
+					margin-left: 4%;
+					width: 40%;
+				}
+
+				div#item-actions {
+					float: right;
+					width: 28%;
+				}
+
+			} // close @media
+
+		} // close #item-header
+	} // close #buddypress
+} // close .groups
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.2 - Item Header User Accounts
+*
+* User Accounts Specific Item Header
+*-----------------------------------------------------
+*/
+
+.bp-user {
+	#buddypress {
+		#item-header {
+			padding: $spacing-val-md 0;
+
+			#item-header-avatar {
+				text-align: center;
+				width: 100%;
+
+				a {
+					border-bottom: 0;
+					display: inline-block;
+					float: none;
+				}
+			}
+
+			@media screen and (min-width: 46.25em) {
+				#item-header-avatar {
+					float: left;
+					width: 20%;
+
+					img.avatar,
+					a {
+						float: left;
+					}
+				}
+
+				#item-header-content {
+					float: right;
+					margin-right: 5%;
+					width: 69%;
+				}
+
+			} // close @media
+
+		} // close #item-header
+
+	} // close #buddypress
+} // close .bp-user
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*-----------------------------------------------------------
+*/
+// Manage the margins inherited from 2015 inside the item-body sections
+// generally far too large a top margin for headings in our lists
+#item-body {
+	h1,
+	h2,
+	h3,
+	h4,
+	h5,
+	h6 {
+		margin: 1em 0;
+
+		a {border-bottom: 0;}
+	}
+}
+/**
+*----------------------------------------------------
+* @subsection 6.2.1 - Item Body Groups
+*
+* Groups specific item body rules -  screens
+*----------------------------------------------------
+*/
+
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.1 - Group Management
+*-----------------------------------------
+*/
+
+// headings settings screens & general global settings styles
+.groups {
+	#group-settings-form {
+		h4 {
+			background: $dark-background;
+			color: $content-background;
+			padding: 0.2em;
+		}
+		// remove backgrounds from request list member names.
+		#request-list {
+			h4 {
+				background: none;
+				color: inherit;
+			}
+		}
+	}
+}
+
+.groups.edit-details {
+	#group-settings-form {
+
+		label {
+			background: $dark-background;
+			@include border-top-radius(4px);
+			color: $content-background;
+			display: inline-block;
+			margin-bottom: 0;
+			padding: 0.2em;
+			width: 80%;
+
+			@media screen and (min-width: 38.75em) {
+				width: 60%;
+			}
+
+		}
+
+		textarea + p label {
+			background: none;
+			color: inherit;
+			@include font-size(14);
+			width: auto;
+		}
+
+	}
+} // close .groups.edit-details
+
+.groups.group-settings {
+	#group-settings-form,
+	#create-group-form {
+
+		div.radio {
+			label {
+				border: 1px solid $border-light;
+				padding: 0.2em;
+			}
+
+			ul {
+				color: rgba($body-text, 0.6);
+				@include font-size(14);
+			}
+		}
+
+	}
+} // close .groups.group-settings
+
+.groups.group-avatar {
+
+	form > p {
+		margin-top: $spacing-val-md;
+	}
+
+}
+
+.groups.manage-members {
+	#group-settings-form {
+		.item-list {
+			li {
+				border-bottom: 1px solid $border-light;
+
+				img,
+				h5 {
+					float: left;
+
+					> a {border-bottom: 0;}
+				}
+
+				span.small {
+					clear: left;
+					display: block;
+					float: none;
+					margin-top: $spacing-val-sm;
+
+					a {
+						display: inline-block;
+						margin: $spacing-val-xs 0;
+						width: 100%;
+
+						@media screen and (min-width: 38.75em) {
+							width: auto;
+						}
+					}
+				}
+
+				h5 {
+					margin: 0;
+				}
+
+			}
+		}
+	}
+} // close .groups.manage-members
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.2 - Group members
+*-----------------------------------------
+*/
+
+/* Members loop */
+
+.groups.group-members {
+	#buddypress {
+		#members-group-list {
+			li {
+
+				@media screen and (max-width: 30em) {
+					> a {
+						border-bottom: 0;
+						display: block;
+						margin-bottom: $spacing-val-sm;
+						text-align: center;
+
+						img.avatar {
+							display: inline;
+							float: none;
+						}
+					}
+				}
+
+				h5 {
+					display: inline-block;
+					@include font-size(14);
+
+					@media screen and (min-width: 59.6875em) {
+						@include font-size(16);
+					}
+					margin: 0;
+
+					a {border-bottom: 0;}
+				} //  close h5
+
+				span.activity {
+					@include font-size(12);
+				}
+
+				.action {
+					top: 0;
+				}
+			} // close li
+		}
+	}
+}
+
+// Massage the members search for groups nav specifically.
+.groups.group-members {
+	#subnav {
+		li {
+			@media screen and (max-width: 38.75em) {
+				background: $content-background;
+				padding: $spacing-val-md 0;
+			}
+			width: 100%;
+
+			#search-members-form {
+				float: right;
+
+				@media screen and (max-width: 38.75em) {
+					margin: 0;
+					width: 100%;
+
+					label {
+						input[type="text"] {width: 100%;}
+					}
+				}
+
+				margin: $spacing-val-xs $spacing-val-xs 0 0;
+			}
+		}
+	}
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.3 - Group Send Invites
+*-----------------------------------------
+*/
+
+.groups.group-invites {
+	#item-body {
+		.item-list {
+			.action {
+				margin-bottom: 0;
+			}
+		}
+	}
+
+	@media screen and (min-width: 46.25em) {
+		#buddypress {
+			#item-body {
+				#message {
+					margin-top: 0;
+				}
+			}
+		}
+	}
+
+	@media screen and (min-width: 55em) {
+		#buddypress {
+			#send-invite-form { margin-top: 0;}
+		}
+	}
+
+	#item-body {
+		@media screen and (max-width: 46.25em) {
+			.left-menu {
+				float: none;
+				margin: $spacing-val-sm 0;
+
+				#invite-list {
+					height: auto;
+					width: auto;
+ 			}
+ 		}
+
+			.main-column {
+				margin-left: 0;
+ 		}
+
+			.submit {
+				input {
+					display: inline-block;
+					width: 100%;
+ 			}
+ 		}
+ 	}
+ }
+
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.2.2 - Item Body User Accounts
+*
+* User Account specific item body rules
+*-----------------------------------------------------
+*/
+
+.bp-user {
+	.entry-title {
+		margin-bottom: 0.5em;
+	}
+}
+
+/**
+*--------------------------------------------
+* @subsection 6.2.2.1 - classes, pag, filters
+*--------------------------------------------
+*/
+
+.bp-user {
+
+	#buddypress {
+
+		table {
+			th {@include font-size(14)}
+			td {@include font-size(12)}
+
+			@media screen and (min-width: 46.25em) {
+				th {@include font-size(16)}
+				td {@include font-size(14)}
+			}
+
+			@media screen and (min-width: 77.5em) {
+				th {@include font-size(18)}
+				td {@include font-size(16)}
+			}
+		}
+
+		.pag-count {font-style: italic;}
+
+		.notifications-options-nav,
+		.messages-options-nav {
+			float: left;
+			width: 100%;
+			@media screen and (min-width: 30em) {
+				width: 50%;
+			}
+
+			select,
+			input {
+				@include font-size(14);
+				outline: 0;
+				padding: 0;
+			}
+
+			select {
+				float: left;
+				margin-right: 0;
+				width: 100%;
+
+				@media screen and (min-width: 30em) {
+					width: 60%;
+				}
+			}
+
+			input {
+				float: right;
+				@include default-font-serif();
+				line-height: 1.5;
+				margin-top: $spacing-val-sm;
+				width: 100%;
+
+				&[disabled]:hover {background: none;}
+
+				@media screen and (min-width: 30em) {
+					margin-top: 0;
+					width: 38%;
+				}
+			}
+
+		} // close .notifications-options-nav
+
+	} // close #BuddyPress
+
+} // close .bp-user
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.2 - Extended Profiles
+	*-------------------------------------------
+	*/
+
+.bp-user {
+	#buddypress {
+		.profile {
+			.bp-widget {
+				h4 {
+					background: lighten($dark-background, 10%);
+					color: #fff;
+					margin-bottom: 0;
+					padding: 0.4em;
+				}
+
+				table {margin-top: 0;}
+			}
+		}
+	}
+}
+
+/* Edit Profile */
+.bp-user {
+	#buddypress {
+
+		.profile {
+			#profile-edit-form {
+				.button-nav:before,
+				.button-nav:after {
+					content: " ";
+					display: table;
+				}
+
+				.button-nav:after {
+					clear: both;
+				}
+
+				ul.button-nav {
+					border-bottom: 1px solid $border-light;
+					margin-left: 0;
+
+					li {
+						float: left;
+						margin-bottom: 0;
+
+						&.current {
+							border: 1px solid $border-light;
+							border-bottom-color: #fff;
+							margin-bottom: -1px;
+						}
+					} // li
+
+					a {
+						background: none;
+						border: 0;
+						@include font-size(18)
+					}
+				} //.button-nav
+
+				.field-visibility-settings-toggle,
+				.field-visibility-settings {
+					@include font-size(14);
+				}
+
+				.field-visibility-settings-close,
+				.visibility-toggle-link {
+					background: $dark-background;
+					color: #fff;
+					padding: 0.2em 0.5em;
+				}
+			} // close profile form
+
+			.bp-avatar {
+				#bp-delete-avatar {
+					a {font-size: inherit; }
+				}
+			}
+		} // close .profile
+
+	} // close #BuddyPress
+} // close .bp-user
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.3 - Groups
+	*-------------------------------------------
+	*/
+.bp-user {
+
+	#buddypress {
+
+		#groups-list {
+			li {
+				.item {
+					@media screen and (min-width: 77.5em) {
+						left: 5%;
+						width: 50%;
+					}
+				}
+			}
+		}
+
+	} // close #BuddyPress
+
+} // close .bp-user
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.5 - Private Messaging
+	*-------------------------------------------
+	*/
+
+.bp-user {
+
+	#buddypress {
+
+		#message-thread {
+		//Single message view
+			a {
+				border-bottom: 0;
+			}
+
+			#message-subject {
+				background: lighten($dark-background, 10%);
+				color: $content-background;
+				padding: 0.3em 0 0.3em 0.2em;
+			}
+
+			#message-recipients {
+				font-style: italic;
+
+				a.confirm {
+					border: 1px solid $border-light;
+					font-style: normal;
+				}
+			}
+
+			.message-metadata {
+				overflow: hidden;
+
+				img.avatar {
+					float: none;
+				}
+
+				@media screen and (min-width: 46.25em) {
+					img.avatar {
+						float: left;
+					}
+				}
+
+				.message-star-actions {
+					float: right;
+					margin-right: $spacing-val-xs;
+					position: static;
+				}
+			} // close .message-meta
+
+			.message-content {
+				background: $light-background;
+				border: 1px solid $border-light;
+				margin: $spacing-val-sm 0 0 0;
+				padding: 0.3em;
+			}
+
+			#send-reply {
+				.message-content {
+					background: $content-background;
+					border: 0;
+				}
+			}
+
+			.alt {
+				background: $content-background;
+			}
+
+		} // close message-thread  Singular view!
+
+		#message-threads {
+			thead {
+				tr {
+					background: lighten($dark-background, 10%);
+				}
+			}
+
+			tr {
+				td {
+					background: $content-background;
+					display: inline-block;
+					float: left;
+				}
+
+				td.thread-star,
+				td.thread-options {
+					border-bottom-color: darken($border-light, 20%);
+					border-bottom-width: 2px;
+					height: 2.4em;
+					padding-bottom: 0.2em;
+					padding-top: 0.2em;
+
+					@media screen and (max-width: 46.25em) {
+						padding-top: 0;
+					}
+				}
+
+				td.bulk-select-check,
+				td.thread-from {
+					height: 2.6em;
+					@media screen and (max-width: 38.75em) {
+						height: 5.2em;
+					}
+				}
+
+				td.thread-from,
+				td.thread-options {
+					border-left: 0 !important;
+					@include calc(width, "100% - 30px");
+					margin-left: 0;
+				}
+
+				td.thread-info {
+					padding-left: 41px;
+					width: 100%;
+
+					a::after {
+						content: " \2013 \00a0"attr(title);
+						@include font-size(12);
+					}
+				}
+
+				td.thread-options {
+					text-align: right;
+
+					a {
+						@include font-size(12);
+						line-height: 2.2;
+					}
+				}
+
+				span.from {
+					display: none;
+				}
+
+				span.activity {
+					display: block;
+					float: right;
+					line-height: 2;
+					@media screen and (max-width: 38.75em) {
+						clear: both;
+						@include font-size(11);
+						width: 100%;
+					}
+				}
+			}
+
+			tr.unread {
+				td {
+					background: $unread;
+					border-bottom-color: darken($border-light, 20%);
+					border-color: darken($border-light, 10%);
+				}
+			}
+
+			th {
+				display: none;
+			}
+
+			th.bulk-select-all {
+				border-bottom: 0;
+				display: inline-block;
+				text-align: left;
+			}
+
+			th.bulk-select-all,
+			td.bulk-select-check,
+			td.thread-star {
+				border-right: 0;
+				width: 30px;
+			}
+		}
+
+	/* Auto Complete background */
+		.acfb-holder {
+
+			li.friend-tab {
+				margin-right: 0;
+
+				a {
+					border-bottom: 0;
+
+					img {
+						// this is the tiny user avatar
+						display: inline;
+						height: 20px;
+						margin-right: 0.5em;
+						width: 20px !important; // override inline style
+					}
+				}
+			}
+		}
+
+		/* Sitewide Notices loop */
+		#message-threads.sitewide-notices {
+			td {
+				width: 100%;
+
+				strong {
+					background: lighten($dark-background, 10%);
+					color: $content-background;
+					display: block;
+					margin-bottom: 0.4em;
+					padding-left: 0.2em;
+				}
+
+				a {
+					display: inline-block;
+				}
+			}
+
+			td:first-child {
+				display: none;
+			}
+
+			td:first-child + td + td {
+			// notice date
+				border-bottom: 0;
+
+				span {
+					line-height: 1;
+				}
+			}
+
+			td:last-child {
+			// notice actions
+				border-bottom-color: darken($border-light, 20%);
+				line-height: 1;
+				text-align: right;
+
+				a:last-child {
+				// hide the 'x' text
+					line-height: 0;
+					margin-left: 0.7em;
+					text-indent: -999em;
+				}
+
+				a:last-child::after {
+					content: attr(title);
+					display: block;
+					line-height: initial;
+					text-indent: 0;
+				}
+			}
+		}
+
+	} // close #BuddyPress
+
+} // close .bp-user
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+
+.bp-user {
+	#buddypress {
+
+		#settings-form {
+			// 'p' = email notification screen sub heading
+			> p {
+				@include font-size(20);
+				margin: $spacing-val-md 0 $spacing-val-sm;
+			}
+		}
+
+		table.notification-settings {
+			th.title {width: 75%;}
+			td.yes,
+			td.no {
+				vertical-align: middle;
+			}
+		}
+		// Profile visibility table td widths
+		table.profile-settings {
+			width: 100%;
+
+			th.field-group-name,
+			td.field-name {
+				width: 50%;
+			}
+
+			@media screen and (min-width: 46.25em) {
+				th.field-group-name,
+				td.field-name {
+					width: 70%;
+				}
+			}
+
+			th.title,
+			td.field-visibility {
+				width: 30%;
+			}
+
+			td.field-visibility {
+				select {width: 100%;}
+			}
+		}
+
+	} // close #BuddyPress
+
+} // close .bp-user
+
+/**
+*-------------------------------------------------------------------------------
+* @section 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+
+#buddypress {
+
+// Match BP form elements to Themes(2015) styling where styles
+// are not inherited or being overriden.
+
+	div.activity-comments {
+		form {
+			.ac-textarea {
+				background: $light-background;
+				border: 1px solid rgba($border-color, 0.3);
+
+				textarea {
+					background: none;
+					border: 0;
+				}
+			}
+		}
+	}
+
+	select {border: 1px solid rgba($border-color, 0.5);}
+
+	// Overrides for embedded WP editors.
+	.wp-editor-wrap {
+		a.button,
+		button,
+		input[type=submit],
+		input[type=button],
+		input[type=reset] {
+			padding: 0 5px 1px;
+		}
+	}
+
+} // close #buddypress
+
+
+#buddypress {
+
+	// Manage form control widths override BP 75% width.
+
+	.standard-form,
+	.group-create-form {
+		li {
+			float: none;
+			list-style: none;
+		}
+
+		input[type='text'],
+		textarea {
+			width: 100%;
+		}
+	}
+
+	div.dir-search,
+	div.message-search,
+	li.groups-members-search
+	 {
+		float: none;
+		margin: $spacing-val-sm 0;
+
+		// Stylise the seach form elements, in part this deals with padding
+		// issues on the submit & sizing issues between bp styles & twentyfifteen
+
+		form { // *sigh* only to bludgeon over specified rules
+			border: 1px solid rgba($border-color, 0.4);
+			overflow: hidden;
+
+			label {
+				float: left;
+				width: 80%;
+			}
+
+			input[type='text'] {
+				float: left;
+				margin: 0;
+				width: 80%;
+			}
+
+			input[type='text'],
+			input[type='submit'] {
+				@include font-size(14);
+				border: 0;
+				line-height: inherit;
+			}
+
+			input[type='text'] {
+				border-right: 1px solid rgba($border-color, 0.4);
+				padding: 0.2em 0 0.2em 0.2em;
+			}
+
+			input[type='submit'] {
+				float: right;
+				font-weight: normal;
+				padding: 0.2em 1em;
+				text-align: center;
+				text-transform: none;
+				width: 20%;
+			}
+		}// close form
+
+	} // close .dir-search, .message-search
+
+	// Shift the search parent to the right and allow to shrinkwrap
+	@media screen and (min-width: 38.75em) {
+		div.dir-search,
+		div.message-search,
+		li.groups-members-search {
+			float: right;
+			margin-bottom: 5px !important;
+
+			form {
+				label,
+				input[type='text'],
+				input[type='submit'] {
+					width: auto;
+				}
+			}
+		}
+	} // close @media
+
+	// Attempt to replicate the 2015 breakpoint font resizing for buttons/submits
+//	@media screen and (min-width: 59.6875em) {
+//		.dir-search,
+//	 .message-search {
+	//		form {
+	//			input[type='text'] { @include font-size(14) ;}
+	//			input[type='submit'] {@include font-size(14);}
+	//		}
+	//	}
+//	}
+
+	@media screen and (min-width: 77.5em) {
+
+		.dir-search,
+		.message-search {
+			form {
+				input[type='text'] { @include font-size(16) ;}
+				input[type='submit'] { @include font-size(16) ;}
+			}
+		}
+	}
+
+} // close #buddypress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables - General
+*-------------------------------------------------------------------------------
+*/
+
+// Adjust table font sizes, default too large proportionally
+// This approach will require refinement and perhaps re-location to
+// a more general typography section to manage BP elements grouped under
+// breakpoints. Provide top/bottom margins for tables, lacking in BP styles
+#buddypress {
+	table {
+		@include font-size(14);
+		margin: $spacing-val-md 0;
+
+		tr th {
+			background: lighten($dark-background, 10%);
+			border-color: darken($border-light, 20%);
+			color: $content-background;
+		}
+
+		// Reduce the themes inherited paragraph margins in tables
+		p {margin-bottom: 0.5em;}
+	}
+
+	@media screen and (min-width: 55em) {
+		table {@include font-size(16);}
+	}
+}
+
+/*__ User Account tables __*/
+
+#buddypress {
+
+	// Manage some table cells widths that are disproportionate to their content
+	.notifications,
+	.messages-notices {
+		th {
+			width: 30%;
+
+			&.bulk-select-all {
+				text-align: center;
+				width: 10%;
+			}
+		}
+
+		.bulk-select-check,
+		.thread-star {
+			text-align: center;
+		}
+
+		.notification-actions,
+		td.thread-options {
+			text-align: center;
+
+			a {
+				display: inline-block;
+				margin: 0;
+				padding: 0;
+			}
+		}
+
+		// Notices action buttons, this maybe better moved, temp for now to address
+		// styling issues - this will need styling ideas
+		td {
+			.button {
+				border: 0;
+				display: block;
+				padding: 0;
+				text-align: center;
+			}
+		}
+
+	} // .notifications, .message-notices
+} // #buddypress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+
+// message args ($variable-color, text-color)
+// Leave text-color undefined to automagically set text color
+// to background color darker by 50%
+
+#buddypress {
+	#item-body {
+		div#message {
+			margin-top: $spacing-val-md;
+			@media screen and (min-width: 55em) {
+				margin-right: $spacing-val-md;
+			}
+		}
+	}
+
+	div#message {
+		p {
+			@include font-size(18);
+			font-weight: bold;
+		}
+
+		&.info {
+			p {
+				@include message-box($notice-info);
+			}
+		}
+
+		&.updated {
+			p {
+				@include message-box($notice-update);
+			}
+		}
+
+	} // close #message
+
+} // close  #buddypress
+
+// Without direct classes on our mesages ( 'warning' )
+// we need to use the body classes
+.delete-group {
+	#buddypress {
+		div#message.info {
+
+			p {
+				@include message-box($notice-warning);
+			}
+		}
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+
+#buddypress {
+	/* Auto Complete background */
+	.acfb-holder {
+
+		li.friend-tab {
+			background: $notice-info;
+			border: inherit;
+	 }
+	}
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfourteen-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfourteen-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfourteen-rtl.css	(revision 10410)
@@ -0,0 +1,1458 @@
+@charset "UTF-8";
+/*------------------------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentyfourteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentyfourteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the ruelesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+--------------------------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+--------------------------------------------------------------------------------
+1.0 Theme Structural Elements
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 - Activity Whats new
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Forums
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - Item-header: Groups;
+		6.1.2 -  item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1- Management settings screens
+			6.2.1.2 - Group members list
+			6.2.1.3 - Group Send Invites
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading
+11.0 - Widgets
+------------------------------------------------------------------------------*/
+/**
+*-------------------------------------------------------------------------------
+* @section - 1.0 - Theme - Structural Elements
+*-------------------------------------------------------------------------------
+*/
+#buddypress h1,
+#buddypress h2,
+#buddypress h3,
+#buddypress h4,
+#buddypress h5,
+#buddypress h6 {
+	clear: none;
+}
+
+.buddypress .site-content {
+	padding-bottom: 20px;
+	padding-top: 4%;
+}
+
+@media screen and (min-width: 59.6875em) {
+	.buddypress .site-content {
+		padding-top: 0;
+	}
+}
+
+.buddypress .site-content article {
+	margin: 0 4%;
+	padding-top: 0;
+}
+
+.buddypress .site-content article .entry-header,
+.buddypress .site-content article .entry-content {
+	max-width: 100%;
+	padding: 0;
+}
+
+.buddypress .site-content article #buddypress {
+	margin-bottom: 40px;
+}
+
+/**
+* ------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+/**
+*----------------------------------------------------------
+* @subsection 2.1 - Object Nav / Subnav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+#buddypress .item-list-tabs ul li {
+	width: 100%;
+}
+
+@media screen and (min-width: 400px) {
+	#buddypress .item-list-tabs ul li {
+		width: 50%;
+	}
+}
+
+@media screen and (min-width: 594px) {
+	#buddypress .item-list-tabs ul li {
+		width: auto;
+	}
+}
+
+.directory #buddypress .item-list-tabs ul:before,
+.directory #buddypress .item-list-tabs ul:after {
+	content: " ";
+	display: table;
+}
+
+.directory #buddypress .item-list-tabs ul:after {
+	clear: both;
+}
+
+.directory #buddypress .item-list-tabs ul {
+	border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+	padding-bottom: 10px;
+}
+
+@media screen and (min-width: 783px) {
+	.directory #buddypress .item-list-tabs ul {
+		padding-bottom: 0;
+	}
+	.directory #buddypress .item-list-tabs ul li.selected {
+		border: 1px solid rgba(0, 0, 0, 0.2);
+		border-bottom-color: #fff;
+		-webkit-border-top-left-radius: 4px;
+		border-top-left-radius: 4px;
+		-webkit-border-top-right-radius: 4px;
+		border-top-right-radius: 4px;
+		background-clip: padding-box;
+		margin-bottom: -1px;
+	}
+	.directory #buddypress .item-list-tabs ul li.selected a {
+		background: none;
+		outline: none;
+	}
+}
+
+.directory #buddypress #subnav ul {
+	border-bottom: 0;
+}
+
+/* List last filters global */
+#buddypress #subnav li#activity-filter-select.last {
+	line-height: 2.1;
+	margin-top: 0;
+}
+
+@media screen and (max-width: 783px) {
+	#buddypress #object-nav ul {
+		border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+		overflow: hidden;
+		padding-bottom: 10px;
+	}
+	#buddypress div#subnav.item-list-tabs {
+		margin-bottom: 40px;
+	}
+	#buddypress div#subnav.item-list-tabs ul li {
+		width: auto;
+	}
+}
+
+/*__ Vertical Menu User Account Screens __*/
+@media screen and (min-width: 783px) {
+	.bp-user #buddypress,
+	.single-item.groups #buddypress {
+		background: #d8d8d8;
+	}
+	#buddypress #item-header,
+	#buddypress #item-body {
+		background: #fff;
+	}
+	#buddypress #item-body {
+		border-right: 1px solid #bebebe;
+	}
+	#buddypress #object-nav {
+		background: #d8d8d8;
+		border-left: 1px solid #bebebe;
+		float: right;
+		margin-left: -1px;
+		width: 200px;
+	}
+	#buddypress #object-nav ul {
+		border-bottom: 0;
+		border-top: 1px solid #bebebe;
+		padding: 0;
+	}
+	#buddypress #object-nav ul li {
+		float: none;
+		overflow: hidden;
+	}
+	#buddypress #object-nav ul li:focus,
+	#buddypress #object-nav ul li:hover {
+		background: #bebebe;
+	}
+	#buddypress #object-nav ul li span {
+		border-radius: 10%;
+		float: left;
+		margin-left: 15px;
+	}
+	#buddypress #object-nav ul li.current {
+		background: #f1f1f1;
+	}
+	#buddypress #item-body {
+		overflow: hidden;
+		padding: 0 20px;
+		width: auto;
+	}
+	#buddypress #item-body #subnav {
+		border: 1px solid #bebebe;
+		border-right: 0;
+		border-left: 0;
+		margin: 0 -20px 20px;
+	}
+	#buddypress #item-body #subnav li:focus,
+	#buddypress #item-body #subnav li:hover {
+		background: #bebebe;
+	}
+	#buddypress #item-body #subnav li.current {
+		background: #f1f1f1;
+	}
+	#buddypress #item-body #subnav li#activity-filter-select:focus,
+	#buddypress #item-body #subnav li#activity-filter-select:hover {
+		background: none;
+	}
+	#buddypress #item-body #subnav li#activity-filter-select select {
+		max-width: 130px;
+	}
+}
+
+/**
+*----------------------------------------------------------
+* @subsection 2.2 - Pagination
+*----------------------------------------------------------
+*/
+#buddypress div.pagination .pag-count {
+	margin-right: 0;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+#buddypress .item-list .item-title a {
+	display: block;
+	font-size: 22px;
+	font-size: 1.375rem;
+	text-decoration: none;
+}
+
+#buddypress .item-list .item-title span.update,
+#buddypress .item-list .item-title span a {
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+#buddypress ul.item-list li {
+	overflow: hidden !important;
+}
+
+#buddypress ul.item-list li .item-avatar {
+	float: none;
+	margin-bottom: 20px;
+	text-align: center;
+}
+
+#buddypress ul.item-list li .item-avatar a img.avatar {
+	float: none;
+}
+
+#buddypress ul.item-list li div.item-title {
+	float: none;
+	width: 100%;
+}
+
+#buddypress ul.item-list li div.item-title > a {
+	text-align: center;
+}
+
+#buddypress ul.item-list li .item {
+	margin-right: 0;
+}
+
+#buddypress ul.item-list li div.item-desc {
+	margin-right: 0;
+	width: auto;
+}
+
+#buddypress ul.item-list li div.action {
+	clear: both;
+	float: none;
+	margin: 0;
+	position: relative;
+	text-align: right;
+}
+
+#buddypress ul.item-list li div.action div {
+	margin: 0 0 10px;
+}
+
+#buddypress ul.item-list li .activity-meta,
+#buddypress ul.item-list li .action {
+	float: none;
+}
+
+#buddypress ul.item-list li .activity-meta a,
+#buddypress ul.item-list li .activity-meta div,
+#buddypress ul.item-list li .action a,
+#buddypress ul.item-list li .action div {
+	display: block;
+	margin: 5px 0;
+}
+
+@media screen and (min-width: 594px) {
+	#buddypress ul.item-list li .item-avatar {
+		float: right;
+		margin-bottom: 0;
+	}
+	#buddypress ul.item-list li div.item-title > a {
+		text-align: right;
+	}
+	#buddypress ul.item-list li .item {
+		float: left;
+		margin-right: 5%;
+		width: 80%;
+	}
+	#buddypress ul.item-list li .activity-meta {
+		clear: both;
+	}
+	#buddypress ul.item-list li .activity-meta a,
+	#buddypress ul.item-list li .activity-meta div {
+		display: inline-block;
+		margin: 5px 0 0 5px;
+	}
+}
+
+@media screen and (min-width: 783px) {
+	#buddypress ul.item-list li .item {
+		float: right;
+		width: 55%;
+	}
+	#buddypress ul.item-list li .item div.item-desc {
+		margin: 0;
+		width: auto;
+	}
+	#buddypress ul.item-list li div.action {
+		clear: none;
+		float: left;
+		top: 0;
+		width: 25%;
+	}
+	#buddypress ul.item-list li div.action div {
+		display: block;
+	}
+	#buddypress ul.item-list li div.action div a {
+		-webkit-box-sizing: border-box;
+		-moz-box-sizing: border-box;
+		box-sizing: border-box;
+		display: block;
+		width: 100%;
+	}
+	#buddypress ul.item-list li div.action .meta {
+		text-align: center;
+	}
+}
+
+#buddypress ul.item-list li .meta,
+#buddypress ul.item-list li .item-meta .activity {
+	font-style: italic;
+}
+
+/**
+*------------------------------------------------------------
+* @section 4.1 - Activity
+*------------------------------------------------------------
+*/
+/**
+*-------------------------------------------------
+* @subsection 4.1.1 - Activity What's New
+*-------------------------------------------------
+*/
+#buddypress form#whats-new-form {
+	margin: 40px 0;
+}
+
+#buddypress form#whats-new-form #whats-new-avatar {
+	float: none;
+	margin-bottom: 20px;
+	text-align: center;
+}
+
+#buddypress form#whats-new-form #whats-new-content,
+#buddypress form#whats-new-form p.activity-greeting {
+	margin-right: 0;
+	padding-right: 0;
+}
+
+#buddypress form#whats-new-form p.activity-greeting {
+	clear: right;
+	line-height: inherit;
+	margin-bottom: 5px;
+}
+
+@media screen and (max-width: 594px) {
+	#buddypress form#whats-new-form #whats-new-content #whats-new-options {
+		display: flex;
+		flex-direction: column;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-options #whats-new-submit {
+		order: 2;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-options #whats-new-submit input {
+		width: 100%;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-options #whats-new-post-in-box {
+		margin-top: 10px;
+		order: 1;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-options #whats-new-post-in-box select {
+		margin-top: 5px;
+		max-width: 100%;
+		width: 100%;
+	}
+}
+
+@media screen and (min-width: 594px) {
+	#buddypress form#whats-new-form #whats-new-avatar {
+		float: right;
+	}
+	#buddypress form#whats-new-form #whats-new-content,
+	#buddypress form#whats-new-form p.activity-greeting {
+		clear: none;
+		overflow: hidden;
+		padding-right: 20px;
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 4.1.2 - Activity Listing
+*-------------------------------------------------
+*/
+#buddypress #activity-stream {
+	margin-top: 40px;
+}
+
+#buddypress .activity-list li.activity-item:not(.mini) .activity-avatar {
+	float: none;
+	margin-bottom: 10px;
+	text-align: center;
+}
+
+#buddypress .activity-list li.activity-item:not(.mini) .activity-avatar img.avatar {
+	display: inline;
+	float: none;
+}
+
+#buddypress .activity-list li.mini .activity-avatar img.avatar {
+	margin-right: 0;
+}
+
+#buddypress .activity-list li.mini .activity-meta a {
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+#buddypress .activity-list .activity-content {
+	margin: 0;
+}
+
+#buddypress .activity-list .activity-content .activity-header {
+	margin: -0.5em 0 0;
+}
+
+#buddypress .activity-list .activity-content .activity-header > p {
+	line-height: 1.4;
+	margin-top: -10px;
+}
+
+#buddypress .activity-list .activity-content .activity-header img.avatar {
+	margin-bottom: 0 !important;
+	vertical-align: middle;
+}
+
+#buddypress .activity-list .activity-content .activity-inner {
+	margin: 10px 0;
+}
+
+@media screen and (min-width: 594px) {
+	#buddypress .activity-list li.activity-item:not(.mini) .activity-avatar {
+		float: right;
+		margin: 0 0 0 20px;
+	}
+	#buddypress .activity-list li.activity-item:not(.mini) .activity-avatar img.avatar {
+		display: inline;
+		float: none;
+	}
+	#buddypress .activity-list li.mini .activity-avatar {
+		margin: 0 5% 0 10px;
+	}
+	#buddypress .activity-list .activity-content {
+		margin-right: 0;
+		overflow: hidden;
+	}
+}
+
+body.activity-permalink #buddypress {
+	background: transparent;
+}
+
+@media screen and (max-width: 760px) {
+	body.activity-permalink #buddypress .activity-list li .activity-content,
+	body.activity-permalink #buddypress .activity-list li .activity-comments {
+		margin-right: 0;
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 4.1.3 - Activity Comments
+*-------------------------------------------------
+*/
+#buddypress .activity .activity-comments {
+	margin-right: 0;
+	margin-top: 5px;
+}
+
+@media screen and (min-width: 594px) {
+	#buddypress .activity .activity-comments {
+		margin-right: 70px;
+	}
+}
+
+#buddypress .activity .activity-comments ul {
+	background: rgba(247, 247, 247, 0.6);
+	margin: 30px 2px 0 0;
+}
+
+#buddypress .activity .activity-comments ul ul {
+	border-right: 1px solid rgba(0, 0, 0, 0.1);
+	padding-right: 2%;
+}
+
+@media screen and (max-width: 400px) {
+	#buddypress .activity .activity-comments .acomment-avatar {
+		display: block;
+		text-align: center;
+	}
+	#buddypress .activity .activity-comments .acomment-avatar a,
+	#buddypress .activity .activity-comments .acomment-avatar img.avatar {
+		display: inline;
+		float: none;
+	}
+	#buddypress .activity .activity-comments .acomment-content,
+	#buddypress .activity .activity-comments .acomment-meta,
+	#buddypress .activity .activity-comments .acomment-options {
+		margin: 5px;
+	}
+	#buddypress .activity .activity-comments .acomment-content {
+		padding: 0 10px;
+	}
+}
+
+#buddypress .activity .activity-comments .ac-form input[type="submit"],
+#buddypress .activity .activity-comments .ac-form .ac-reply-cancel {
+	color: rgba(43, 43, 43, 0.8);
+	display: inline-block;
+	font-family: inherit;
+	font-size: 12px;
+	font-size: 0.75rem;
+	font-weight: normal;
+	line-height: 1.2;
+	padding: 4px 10px;
+	text-transform: lowercase;
+	width: 100px;
+}
+
+#buddypress .activity .activity-comments .ac-form .ac-reply-cancel {
+	border: 1px solid rgba(0, 0, 0, 0.2);
+	text-align: center;
+}
+
+#buddypress .activity .activity-comments .ac-form .ac-reply-cancel:focus,
+#buddypress .activity .activity-comments .ac-form .ac-reply-cancel:hover {
+	background: #ededed;
+}
+
+/**
+*------------------------------------------------------------
+* @section 4.2 - Members Loop
+*------------------------------------------------------------
+*/
+/*
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens
+*-------------------------------------------------------------------------------
+*/
+/* groups & user account page entry title */
+.single-item.groups .entry-title,
+.bp-user .entry-title {
+	background: #4d4d4d;
+	color: #fff;
+	font-size: 22px;
+	font-size: 1.375rem;
+	margin-bottom: 10px;
+	padding: 0.2em;
+}
+
+@media screen and (min-width: 673px) {
+	.single-item.groups .entry-title,
+	.bp-user .entry-title {
+		font-size: 28px;
+		font-size: 1.75rem;
+	}
+}
+
+/**
+*------------------------------------------------------------
+* @subsection 6.1 - Item Header: Global
+*------------------------------------------------------------
+*/
+#buddypress div#item-header {
+	padding-bottom: 10px;
+}
+
+#buddypress div#item-header img.avatar {
+	margin: 0 0 10px 20px;
+}
+
+#buddypress div#item-header #message {
+	clear: both;
+	margin-bottom: 5px;
+	padding-top: 10px;
+}
+
+#buddypress div#item-header #message p {
+	margin-bottom: 0;
+}
+
+/**
+*-------------------------------------------------
+* @subsection 6.1.1 - item-header: Groups
+*-------------------------------------------------
+*/
+@media screen and (max-width: 594px) {
+	.single-item.groups #buddypress div#item-header {
+		display: flex;
+		flex-direction: column;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		order: 1;
+		text-align: center;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a {
+		border-bottom: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a img {
+		display: inline-block;
+		float: none;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		order: 2;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions {
+		margin-right: 0;
+		order: 3;
+		text-align: right;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions ul {
+		margin-right: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions ul li {
+		float: right;
+	}
+	.single-item.groups #buddypress div#item-header #message {
+		order: 4;
+	}
+}
+
+.single-item.groups #buddypress #item-header #item-actions {
+	float: none;
+	margin: 0 20px 0 0;
+	width: auto;
+}
+
+.single-item.groups #buddypress #item-header #item-actions h3 {
+	font-size: 20px;
+	font-size: 1.25rem;
+}
+
+.single-item.groups #buddypress #item-header #item-header-avatar {
+	text-align: center;
+}
+
+.single-item.groups #buddypress #item-header #item-header-content {
+	float: none;
+	overflow: hidden;
+}
+
+@media screen and (min-width: 594px) {
+	.single-item.groups #buddypress #item-header #item-actions {
+		float: left;
+	}
+	.single-item.groups #buddypress #item-header #item-header-avatar {
+		float: right;
+	}
+	.single-item.groups #buddypress #item-header #item-header-content {
+		float: none;
+		overflow: hidden;
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 6.1.2 - item-header: User Accounts
+*-------------------------------------------------
+*/
+.bp-user #buddypress #item-header #item-header-avatar {
+	text-align: center;
+	width: 100%;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar img {
+	float: none;
+}
+
+.bp-user #buddypress #item-header #latest-update {
+	margin-bottom: 10px;
+}
+
+@media screen and (max-width: 594px) {
+	.bp-user #buddypress #item-header #item-buttons div.generic-button {
+		display: inline-block;
+		float: none;
+		margin: 0 0 5px 0;
+		width: 100%;
+	}
+	.bp-user #buddypress #item-header #item-buttons div.generic-button a {
+		-webkit-box-sizing: border-box;
+		-moz-box-sizing: border-box;
+		box-sizing: border-box;
+		display: block;
+		width: 100%;
+	}
+}
+
+@media screen and (min-width: 673px) {
+	.bp-user #buddypress #item-header #item-header-avatar {
+		float: right;
+		overflow: hidden;
+		text-align: right;
+		width: 20%;
+	}
+	.bp-user #buddypress #item-header #item-header-content {
+		float: left;
+		width: 78%;
+	}
+	.bp-user #buddypress #item-header #item-header-content #latest-update {
+		margin-bottom: 20px;
+	}
+}
+
+/**
+*------------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*------------------------------------------------------------
+*/
+/**
+*-------------------------------------------------
+* @subsection 6.2.1 - item-body: Groups
+*-------------------------------------------------
+*/
+/**
+	*------------------------------
+	* 6.2.1.1 - Groups Management
+	*------------------------------
+	*/
+/* Settings */
+#group-settings-form .radio strong,
+#create-group-form .radio strong {
+	font-size: 18px;
+	font-size: 1.125rem;
+}
+
+#group-settings-form .radio ul,
+#create-group-form .radio ul {
+	color: #777777;
+}
+
+#group-settings-form #group-creation-previous,
+#create-group-form #group-creation-previous {
+	margin: 10px 0;
+}
+
+/* Manage Members */
+.groups.manage-members #group-settings-form h4 {
+	margin-top: 40px;
+}
+
+.groups.manage-members #group-settings-form .item-list {
+	background: #f7f7f7;
+	border-top: 0;
+	padding: 0 0.5em;
+}
+
+.groups.manage-members #group-settings-form .item-list li {
+	border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.groups.manage-members #group-settings-form .item-list li:last-child {
+	border-bottom: 0;
+}
+
+.groups.manage-members #group-settings-form .item-list li img,
+.groups.manage-members #group-settings-form .item-list li h5 {
+	clear: none;
+	float: right;
+}
+
+.groups.manage-members #group-settings-form .item-list li img > a,
+.groups.manage-members #group-settings-form .item-list li h5 > a {
+	border-bottom: 0;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small {
+	display: block;
+	float: none;
+	margin-top: 10px;
+	width: 100%;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small a {
+	display: inline-block;
+	margin: 5px 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.groups.manage-members #group-settings-form .item-list li span.small a {
+		width: auto;
+	}
+}
+
+.groups.manage-members #group-settings-form .item-list li h5 {
+	margin: 0;
+}
+
+/**
+	*------------------------------
+	* 6.2.1.2 - Group Members List
+	*------------------------------
+	*/
+#buddypress #members-group-list .item-list h5 {
+	display: inline-block;
+	margin: 0;
+}
+
+#buddypress #members-group-list .item-list span.activity {
+	padding-right: 10px;
+}
+
+#buddypress #item-body #subnav li.groups-members-search:focus,
+#buddypress #item-body #subnav li.groups-members-search:hover,
+#buddypress #item-body #subnav li.last:focus,
+#buddypress #item-body #subnav li.last:hover {
+	background: none;
+}
+
+.groups.group-members #subnav ul:before,
+.groups.group-members #subnav ul:after {
+	content: " ";
+	display: table;
+}
+
+.groups.group-members #subnav ul:after {
+	clear: both;
+}
+
+.groups.group-members #subnav ul {
+	padding: 5px 0;
+}
+
+@media screen and (max-width: 400px) {
+	.groups.group-members #subnav li {
+		background: #fff;
+		padding: 10px 0;
+		width: 100% !important;
+	}
+}
+
+.groups.group-members #subnav li #search-members-form {
+	-webkit-box-sizing: border-box;
+	-moz-box-sizing: border-box;
+	box-sizing: border-box;
+	float: right;
+}
+
+@media screen and (max-width: 400px) {
+	.groups.group-members #subnav li #search-members-form {
+		margin: 0;
+		width: 100%;
+	}
+	.groups.group-members #subnav li #search-members-form label input[type="text"] {
+		width: 100%;
+	}
+}
+
+@media screen and (min-width: 783px) {
+	.groups.group-members #subnav li #search-members-form {
+		margin-right: 3px;
+	}
+}
+
+/**
+	*------------------------------
+	* 6.2.1.3 - Groups Send Invites
+	*------------------------------
+	*/
+@media screen and (min-width: 783px) {
+	.send-invites #buddypress #item-body {
+		border-top: 1px solid #bebebe;
+	}
+}
+
+@media screen and (max-width: 400px) {
+	.send-invites #buddypress #item-body .left-menu {
+		float: none;
+	}
+	.send-invites #buddypress #item-body .left-menu #invite-list {
+		height: auto;
+		width: auto;
+	}
+	.send-invites #buddypress #item-body .main-column {
+		margin-right: 0;
+	}
+}
+
+.send-invites #buddypress #item-body li img.avatar {
+	display: block;
+	float: none;
+	margin: 10px auto;
+}
+
+.send-invites #buddypress #item-body li .action {
+	margin-top: 10px;
+	text-align: right;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.send-invites #buddypress #item-body li .action {
+		text-align: left;
+	}
+}
+
+@media screen and (min-width: 594px) {
+	.send-invites #buddypress #item-body #invite-list {
+		width: 160px;
+	}
+	.send-invites #buddypress #item-body .main-column {
+		margin-right: 190px;
+	}
+	.send-invites #buddypress #item-body li img.avatar {
+		float: right;
+		margin: 0 0 0 10px;
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 6.2.2 - item-body: User Accounts
+*-------------------------------------------------
+*/
+/**
+	*------------------------------
+	* 6.2.2.2 - Extended Profiles
+	*------------------------------
+	*/
+#buddypress .profile .bp-widget h4 {
+	background: #999;
+	color: #fff;
+	padding: 0.4em 0.2em;
+}
+
+#buddypress .profile #profile-edit-form .button-nav:before,
+#buddypress .profile #profile-edit-form .button-nav:after {
+	content: " ";
+	display: table;
+}
+
+#buddypress .profile #profile-edit-form .button-nav:after {
+	clear: both;
+}
+
+#buddypress .profile #profile-edit-form ul.button-nav {
+	border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+	margin-right: 0;
+}
+
+#buddypress .profile #profile-edit-form ul.button-nav li {
+	float: right;
+	margin-bottom: 0;
+}
+
+#buddypress .profile #profile-edit-form ul.button-nav li.current {
+	border: 1px solid rgba(0, 0, 0, 0.1);
+	border-bottom-color: #fff;
+	margin-bottom: -1px;
+}
+
+#buddypress .profile #profile-edit-form ul.button-nav a {
+	background: none;
+	border: 0;
+	font-size: 18px;
+	font-size: 1.125rem;
+}
+
+#buddypress .profile #profile-edit-form .field-visibility-settings-toggle,
+#buddypress .profile #profile-edit-form .field-visibility-settings {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+#buddypress .profile #profile-edit-form .field-visibility-settings-close,
+#buddypress .profile #profile-edit-form .visibility-toggle-link {
+	background: #000;
+	color: #fff;
+	padding: 0.2em 0.5em;
+}
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.5 - Messaging
+	*------------------------------
+	*/
+#buddypress {
+	/* Read Single thread */
+	/* Sitewide Notices loop */
+}
+
+#buddypress table#message-threads {
+	border-right: 1px solid rgba(0, 0, 0, 0.2);
+	border-left: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+#buddypress table#message-threads tbody > tr:first-child td.bulk-select-check,
+#buddypress table#message-threads tbody > tr:first-child td.thread-from {
+	border-top: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+#buddypress table#message-threads tr td {
+	background: #fff;
+	border-left: 0 !important;
+	-webkit-box-sizing: border-box;
+	-moz-box-sizing: border-box;
+	box-sizing: border-box;
+	display: inline-block;
+	float: right;
+}
+
+#buddypress table#message-threads tr td.thread-star,
+#buddypress table#message-threads tr td.thread-options {
+	border-bottom-color: rgba(0, 0, 0, 0.2);
+	border-bottom-width: 2px;
+	border-right: 0;
+	height: 2.4em;
+	padding-bottom: 0.2em;
+	padding-top: 0.2em;
+}
+
+@media screen and (max-width: 549px) {
+	#buddypress table#message-threads tr td.thread-star,
+	#buddypress table#message-threads tr td.thread-options {
+		padding-top: 0;
+	}
+}
+
+#buddypress table#message-threads tr td.bulk-select-check,
+#buddypress table#message-threads tr td.thread-from {
+	border-right: 0;
+	height: 2.6em;
+}
+
+@media screen and (max-width: 320px) {
+	#buddypress table#message-threads tr td.bulk-select-check,
+	#buddypress table#message-threads tr td.thread-from {
+		height: 5.2em;
+	}
+}
+
+#buddypress table#message-threads tr td.thread-from,
+#buddypress table#message-threads tr td.thread-options {
+	border-right: 0;
+	width: -webkit-calc(100% - 30px);
+	width: -moz-calc(100% - 30px);
+	width: calc(100% - 30px);
+	margin-right: 0;
+}
+
+#buddypress table#message-threads tr td.thread-info {
+	border-right: 0;
+	padding-right: 41px;
+	width: 100%;
+}
+
+#buddypress table#message-threads tr td.thread-info a::after {
+	content: " –  " attr(title);
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+#buddypress table#message-threads tr td.thread-options {
+	text-align: left;
+}
+
+#buddypress table#message-threads tr td.thread-options a {
+	font-size: 12px;
+	font-size: 0.75rem;
+	line-height: 2.2;
+}
+
+#buddypress table#message-threads tr span.from {
+	display: none;
+}
+
+#buddypress table#message-threads tr span.activity {
+	display: block;
+	float: left;
+	line-height: 2;
+}
+
+@media screen and (max-width: 320px) {
+	#buddypress table#message-threads tr span.activity {
+		clear: both;
+		font-size: 11px;
+		font-size: 0.6875rem;
+		width: 100%;
+	}
+}
+
+#buddypress table#message-threads tr.unread td {
+	background: #f1f4ff;
+	border-color: rgba(0, 0, 0, 0.2);
+	border-top: 0;
+	border-width: 1px;
+}
+
+#buddypress table#message-threads th {
+	display: none;
+}
+
+#buddypress table#message-threads th.bulk-select-all {
+	border-bottom: 0;
+	display: inline-block;
+	text-align: right;
+}
+
+#buddypress table#message-threads th.bulk-select-all,
+#buddypress table#message-threads td.bulk-select-check,
+#buddypress table#message-threads td.thread-star {
+	border-left: 0;
+	width: 30px;
+}
+
+#buddypress #message-thread #message-subject {
+	background: #4d4d4d;
+	color: #fff;
+	padding: 0.5em;
+}
+
+#buddypress #message-thread .message-box .message-metadata {
+	margin-bottom: 10px;
+}
+
+#buddypress #message-thread .message-box .message-content {
+	clear: right;
+	margin-right: 0;
+}
+
+#buddypress #message-threads.sitewide-notices td {
+	width: 100%;
+}
+
+#buddypress #message-threads.sitewide-notices td strong {
+	background: #4d4d4d;
+	color: #fff;
+	font-size: 16px;
+	font-size: 1rem;
+	display: block;
+	margin-bottom: 0.4em;
+	padding: 0.2em;
+}
+
+#buddypress #message-threads.sitewide-notices td a {
+	display: inline-block;
+	line-height: initial;
+}
+
+#buddypress #message-threads.sitewide-notices td:first-child {
+	display: none;
+}
+
+#buddypress #message-threads.sitewide-notices td:first-child + td + td {
+	border-bottom: 0;
+}
+
+#buddypress #message-threads.sitewide-notices td:first-child + td + td span {
+	line-height: 1;
+}
+
+#buddypress #message-threads.sitewide-notices td:last-child {
+	border-bottom-color: rgba(0, 0, 0, 0.2);
+	line-height: 1;
+	text-align: left;
+}
+
+#buddypress #message-threads.sitewide-notices td:last-child a:last-child {
+	line-height: 0;
+	margin-right: 0.7em;
+	text-indent: -999em;
+}
+
+#buddypress #message-threads.sitewide-notices td:last-child a:last-child::after {
+	content: attr(title);
+	display: block;
+	line-height: initial;
+	text-indent: 0;
+}
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+#buddypress #settings-form > p {
+	font-size: 20px;
+	font-size: 1.25rem;
+}
+
+#buddypress table.notification-settings td.yes,
+#buddypress table.notification-settings td.no {
+	vertical-align: middle;
+}
+
+#buddypress table.profile-settings {
+	width: 100%;
+}
+
+#buddypress table.profile-settings th.field-group-name,
+#buddypress table.profile-settings td.field-name {
+	width: 50%;
+}
+
+@media screen and (min-width: 401px) {
+	#buddypress table.profile-settings th.field-group-name,
+	#buddypress table.profile-settings td.field-name {
+		width: 70%;
+	}
+}
+
+#buddypress table.profile-settings td.field-visibility select {
+	width: 100%;
+}
+
+/*------------------------------------------------------------------------------
+* @section - 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+#buddypress input[type="text"],
+#buddypress input[type="email"],
+#buddypress input[type="password"],
+#buddypress input[type="submit"],
+#buddypress select,
+#buddypress textarea {
+	border: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+#buddypress .standard-form input[type="text"],
+#buddypress .standard-form input[type="email"],
+#buddypress .standard-form input[type="password"],
+#buddypress .standard-form input[type="submit"],
+#buddypress .standard-form textarea {
+	width: 100%;
+}
+
+#buddypress #send-reply.standard-form textarea {
+	width: 100%;
+}
+
+#buddypress div.activity-comments form .ac-textarea {
+	background: #f7f7f7;
+	border: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+#buddypress div.activity-comments form .ac-textarea textarea {
+	background: none;
+	border: 0;
+}
+
+#buddypress div.dir-search,
+#buddypress div.message-search,
+#buddypress li.groups-members-search {
+	clear: both;
+	float: none;
+	margin: 10px 0;
+}
+
+#buddypress div.dir-search form,
+#buddypress div.message-search form,
+#buddypress li.groups-members-search form {
+	border: 1px solid #ccc;
+	overflow: hidden;
+}
+
+#buddypress div.dir-search form label,
+#buddypress div.message-search form label,
+#buddypress li.groups-members-search form label {
+	float: right;
+	width: 80%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	float: right;
+	margin: 0;
+	width: 80%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='text'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	border: 0;
+	font-size: 14px;
+	font-size: 0.875rem;
+	line-height: inherit;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	border-left: 1px solid #ccc;
+	padding: 0.2em 0.2em 0.2em 0;
+}
+
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	float: left;
+	font-weight: normal;
+	padding: 0.2em 1em;
+	text-align: center;
+	text-transform: none;
+	width: 20%;
+}
+
+@media screen and (min-width: 401px) {
+	#buddypress div.dir-search,
+	#buddypress div.message-search {
+		clear: none;
+		float: left;
+	}
+	#buddypress div.dir-search form label,
+	#buddypress div.dir-search form input[type='submit'],
+	#buddypress div.dir-search form input[type='text'],
+	#buddypress div.message-search form label,
+	#buddypress div.message-search form input[type='submit'],
+	#buddypress div.message-search form input[type='text'] {
+		width: auto;
+	}
+	#buddypress div.message-search {
+		margin: 3px 0 0;
+	}
+	#buddypress li.groups-members-search form label,
+	#buddypress li.groups-members-search form input[type='submit'],
+	#buddypress li.groups-members-search form input[type='text'] {
+		width: auto;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables General
+*-------------------------------------------------------------------------------
+*/
+#buddypress table {
+	border-color: rgba(0, 0, 0, 0.2);
+}
+
+/*__ User Account tables __*/
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+#buddypress div#message p {
+	font-size: 18px;
+	font-size: 1.125rem;
+	font-weight: bold;
+}
+
+#buddypress div#message.info p {
+	background: #c6e4ee;
+	border: 1px solid #78bed6;
+	color: #1e5264;
+}
+
+#buddypress div#message.updated p {
+	background: #dee6b2;
+	border: 1px solid #becc66;
+	color: #454d19;
+}
+
+.delete-group #buddypress div#message.info p {
+	background: #db7e7e;
+	border: 1px solid #be3535;
+	color: #1f0808;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+#buddypress {
+	/* Auto Complete background */
+}
+
+#buddypress .acfb-holder li.friend-tab {
+	background: #9fd1e2;
+	border: inherit;
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfourteen.css
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfourteen.css	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfourteen.css	(revision 10410)
@@ -0,0 +1,1458 @@
+@charset "UTF-8";
+/*------------------------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentyfourteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentyfourteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the ruelesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+--------------------------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+--------------------------------------------------------------------------------
+1.0 Theme Structural Elements
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 - Activity Whats new
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Forums
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - Item-header: Groups;
+		6.1.2 -  item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1- Management settings screens
+			6.2.1.2 - Group members list
+			6.2.1.3 - Group Send Invites
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading
+11.0 - Widgets
+------------------------------------------------------------------------------*/
+/**
+*-------------------------------------------------------------------------------
+* @section - 1.0 - Theme - Structural Elements
+*-------------------------------------------------------------------------------
+*/
+#buddypress h1,
+#buddypress h2,
+#buddypress h3,
+#buddypress h4,
+#buddypress h5,
+#buddypress h6 {
+	clear: none;
+}
+
+.buddypress .site-content {
+	padding-bottom: 20px;
+	padding-top: 4%;
+}
+
+@media screen and (min-width: 59.6875em) {
+	.buddypress .site-content {
+		padding-top: 0;
+	}
+}
+
+.buddypress .site-content article {
+	margin: 0 4%;
+	padding-top: 0;
+}
+
+.buddypress .site-content article .entry-header,
+.buddypress .site-content article .entry-content {
+	max-width: 100%;
+	padding: 0;
+}
+
+.buddypress .site-content article #buddypress {
+	margin-bottom: 40px;
+}
+
+/**
+* ------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+/**
+*----------------------------------------------------------
+* @subsection 2.1 - Object Nav / Subnav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+#buddypress .item-list-tabs ul li {
+	width: 100%;
+}
+
+@media screen and (min-width: 400px) {
+	#buddypress .item-list-tabs ul li {
+		width: 50%;
+	}
+}
+
+@media screen and (min-width: 594px) {
+	#buddypress .item-list-tabs ul li {
+		width: auto;
+	}
+}
+
+.directory #buddypress .item-list-tabs ul:before,
+.directory #buddypress .item-list-tabs ul:after {
+	content: " ";
+	display: table;
+}
+
+.directory #buddypress .item-list-tabs ul:after {
+	clear: both;
+}
+
+.directory #buddypress .item-list-tabs ul {
+	border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+	padding-bottom: 10px;
+}
+
+@media screen and (min-width: 783px) {
+	.directory #buddypress .item-list-tabs ul {
+		padding-bottom: 0;
+	}
+	.directory #buddypress .item-list-tabs ul li.selected {
+		border: 1px solid rgba(0, 0, 0, 0.2);
+		border-bottom-color: #fff;
+		-webkit-border-top-right-radius: 4px;
+		border-top-right-radius: 4px;
+		-webkit-border-top-left-radius: 4px;
+		border-top-left-radius: 4px;
+		background-clip: padding-box;
+		margin-bottom: -1px;
+	}
+	.directory #buddypress .item-list-tabs ul li.selected a {
+		background: none;
+		outline: none;
+	}
+}
+
+.directory #buddypress #subnav ul {
+	border-bottom: 0;
+}
+
+/* List last filters global */
+#buddypress #subnav li#activity-filter-select.last {
+	line-height: 2.1;
+	margin-top: 0;
+}
+
+@media screen and (max-width: 783px) {
+	#buddypress #object-nav ul {
+		border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+		overflow: hidden;
+		padding-bottom: 10px;
+	}
+	#buddypress div#subnav.item-list-tabs {
+		margin-bottom: 40px;
+	}
+	#buddypress div#subnav.item-list-tabs ul li {
+		width: auto;
+	}
+}
+
+/*__ Vertical Menu User Account Screens __*/
+@media screen and (min-width: 783px) {
+	.bp-user #buddypress,
+	.single-item.groups #buddypress {
+		background: #d8d8d8;
+	}
+	#buddypress #item-header,
+	#buddypress #item-body {
+		background: #fff;
+	}
+	#buddypress #item-body {
+		border-left: 1px solid #bebebe;
+	}
+	#buddypress #object-nav {
+		background: #d8d8d8;
+		border-right: 1px solid #bebebe;
+		float: left;
+		margin-right: -1px;
+		width: 200px;
+	}
+	#buddypress #object-nav ul {
+		border-bottom: 0;
+		border-top: 1px solid #bebebe;
+		padding: 0;
+	}
+	#buddypress #object-nav ul li {
+		float: none;
+		overflow: hidden;
+	}
+	#buddypress #object-nav ul li:focus,
+	#buddypress #object-nav ul li:hover {
+		background: #bebebe;
+	}
+	#buddypress #object-nav ul li span {
+		border-radius: 10%;
+		float: right;
+		margin-right: 15px;
+	}
+	#buddypress #object-nav ul li.current {
+		background: #f1f1f1;
+	}
+	#buddypress #item-body {
+		overflow: hidden;
+		padding: 0 20px;
+		width: auto;
+	}
+	#buddypress #item-body #subnav {
+		border: 1px solid #bebebe;
+		border-left: 0;
+		border-right: 0;
+		margin: 0 -20px 20px;
+	}
+	#buddypress #item-body #subnav li:focus,
+	#buddypress #item-body #subnav li:hover {
+		background: #bebebe;
+	}
+	#buddypress #item-body #subnav li.current {
+		background: #f1f1f1;
+	}
+	#buddypress #item-body #subnav li#activity-filter-select:focus,
+	#buddypress #item-body #subnav li#activity-filter-select:hover {
+		background: none;
+	}
+	#buddypress #item-body #subnav li#activity-filter-select select {
+		max-width: 130px;
+	}
+}
+
+/**
+*----------------------------------------------------------
+* @subsection 2.2 - Pagination
+*----------------------------------------------------------
+*/
+#buddypress div.pagination .pag-count {
+	margin-left: 0;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+#buddypress .item-list .item-title a {
+	display: block;
+	font-size: 22px;
+	font-size: 1.375rem;
+	text-decoration: none;
+}
+
+#buddypress .item-list .item-title span.update,
+#buddypress .item-list .item-title span a {
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+#buddypress ul.item-list li {
+	overflow: hidden !important;
+}
+
+#buddypress ul.item-list li .item-avatar {
+	float: none;
+	margin-bottom: 20px;
+	text-align: center;
+}
+
+#buddypress ul.item-list li .item-avatar a img.avatar {
+	float: none;
+}
+
+#buddypress ul.item-list li div.item-title {
+	float: none;
+	width: 100%;
+}
+
+#buddypress ul.item-list li div.item-title > a {
+	text-align: center;
+}
+
+#buddypress ul.item-list li .item {
+	margin-left: 0;
+}
+
+#buddypress ul.item-list li div.item-desc {
+	margin-left: 0;
+	width: auto;
+}
+
+#buddypress ul.item-list li div.action {
+	clear: both;
+	float: none;
+	margin: 0;
+	position: relative;
+	text-align: left;
+}
+
+#buddypress ul.item-list li div.action div {
+	margin: 0 0 10px;
+}
+
+#buddypress ul.item-list li .activity-meta,
+#buddypress ul.item-list li .action {
+	float: none;
+}
+
+#buddypress ul.item-list li .activity-meta a,
+#buddypress ul.item-list li .activity-meta div,
+#buddypress ul.item-list li .action a,
+#buddypress ul.item-list li .action div {
+	display: block;
+	margin: 5px 0;
+}
+
+@media screen and (min-width: 594px) {
+	#buddypress ul.item-list li .item-avatar {
+		float: left;
+		margin-bottom: 0;
+	}
+	#buddypress ul.item-list li div.item-title > a {
+		text-align: left;
+	}
+	#buddypress ul.item-list li .item {
+		float: right;
+		margin-left: 5%;
+		width: 80%;
+	}
+	#buddypress ul.item-list li .activity-meta {
+		clear: both;
+	}
+	#buddypress ul.item-list li .activity-meta a,
+	#buddypress ul.item-list li .activity-meta div {
+		display: inline-block;
+		margin: 5px 5px 0 0;
+	}
+}
+
+@media screen and (min-width: 783px) {
+	#buddypress ul.item-list li .item {
+		float: left;
+		width: 55%;
+	}
+	#buddypress ul.item-list li .item div.item-desc {
+		margin: 0;
+		width: auto;
+	}
+	#buddypress ul.item-list li div.action {
+		clear: none;
+		float: right;
+		top: 0;
+		width: 25%;
+	}
+	#buddypress ul.item-list li div.action div {
+		display: block;
+	}
+	#buddypress ul.item-list li div.action div a {
+		-webkit-box-sizing: border-box;
+		-moz-box-sizing: border-box;
+		box-sizing: border-box;
+		display: block;
+		width: 100%;
+	}
+	#buddypress ul.item-list li div.action .meta {
+		text-align: center;
+	}
+}
+
+#buddypress ul.item-list li .meta,
+#buddypress ul.item-list li .item-meta .activity {
+	font-style: italic;
+}
+
+/**
+*------------------------------------------------------------
+* @section 4.1 - Activity
+*------------------------------------------------------------
+*/
+/**
+*-------------------------------------------------
+* @subsection 4.1.1 - Activity What's New
+*-------------------------------------------------
+*/
+#buddypress form#whats-new-form {
+	margin: 40px 0;
+}
+
+#buddypress form#whats-new-form #whats-new-avatar {
+	float: none;
+	margin-bottom: 20px;
+	text-align: center;
+}
+
+#buddypress form#whats-new-form #whats-new-content,
+#buddypress form#whats-new-form p.activity-greeting {
+	margin-left: 0;
+	padding-left: 0;
+}
+
+#buddypress form#whats-new-form p.activity-greeting {
+	clear: left;
+	line-height: inherit;
+	margin-bottom: 5px;
+}
+
+@media screen and (max-width: 594px) {
+	#buddypress form#whats-new-form #whats-new-content #whats-new-options {
+		display: flex;
+		flex-direction: column;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-options #whats-new-submit {
+		order: 2;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-options #whats-new-submit input {
+		width: 100%;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-options #whats-new-post-in-box {
+		margin-top: 10px;
+		order: 1;
+	}
+	#buddypress form#whats-new-form #whats-new-content #whats-new-options #whats-new-post-in-box select {
+		margin-top: 5px;
+		max-width: 100%;
+		width: 100%;
+	}
+}
+
+@media screen and (min-width: 594px) {
+	#buddypress form#whats-new-form #whats-new-avatar {
+		float: left;
+	}
+	#buddypress form#whats-new-form #whats-new-content,
+	#buddypress form#whats-new-form p.activity-greeting {
+		clear: none;
+		overflow: hidden;
+		padding-left: 20px;
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 4.1.2 - Activity Listing
+*-------------------------------------------------
+*/
+#buddypress #activity-stream {
+	margin-top: 40px;
+}
+
+#buddypress .activity-list li.activity-item:not(.mini) .activity-avatar {
+	float: none;
+	margin-bottom: 10px;
+	text-align: center;
+}
+
+#buddypress .activity-list li.activity-item:not(.mini) .activity-avatar img.avatar {
+	display: inline;
+	float: none;
+}
+
+#buddypress .activity-list li.mini .activity-avatar img.avatar {
+	margin-left: 0;
+}
+
+#buddypress .activity-list li.mini .activity-meta a {
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+#buddypress .activity-list .activity-content {
+	margin: 0;
+}
+
+#buddypress .activity-list .activity-content .activity-header {
+	margin: -0.5em 0 0;
+}
+
+#buddypress .activity-list .activity-content .activity-header > p {
+	line-height: 1.4;
+	margin-top: -10px;
+}
+
+#buddypress .activity-list .activity-content .activity-header img.avatar {
+	margin-bottom: 0 !important;
+	vertical-align: middle;
+}
+
+#buddypress .activity-list .activity-content .activity-inner {
+	margin: 10px 0;
+}
+
+@media screen and (min-width: 594px) {
+	#buddypress .activity-list li.activity-item:not(.mini) .activity-avatar {
+		float: left;
+		margin: 0 20px 0 0;
+	}
+	#buddypress .activity-list li.activity-item:not(.mini) .activity-avatar img.avatar {
+		display: inline;
+		float: none;
+	}
+	#buddypress .activity-list li.mini .activity-avatar {
+		margin: 0 10px 0 5%;
+	}
+	#buddypress .activity-list .activity-content {
+		margin-left: 0;
+		overflow: hidden;
+	}
+}
+
+body.activity-permalink #buddypress {
+	background: transparent;
+}
+
+@media screen and (max-width: 760px) {
+	body.activity-permalink #buddypress .activity-list li .activity-content,
+	body.activity-permalink #buddypress .activity-list li .activity-comments {
+		margin-left: 0;
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 4.1.3 - Activity Comments
+*-------------------------------------------------
+*/
+#buddypress .activity .activity-comments {
+	margin-left: 0;
+	margin-top: 5px;
+}
+
+@media screen and (min-width: 594px) {
+	#buddypress .activity .activity-comments {
+		margin-left: 70px;
+	}
+}
+
+#buddypress .activity .activity-comments ul {
+	background: rgba(247, 247, 247, 0.6);
+	margin: 30px 0 0 2px;
+}
+
+#buddypress .activity .activity-comments ul ul {
+	border-left: 1px solid rgba(0, 0, 0, 0.1);
+	padding-left: 2%;
+}
+
+@media screen and (max-width: 400px) {
+	#buddypress .activity .activity-comments .acomment-avatar {
+		display: block;
+		text-align: center;
+	}
+	#buddypress .activity .activity-comments .acomment-avatar a,
+	#buddypress .activity .activity-comments .acomment-avatar img.avatar {
+		display: inline;
+		float: none;
+	}
+	#buddypress .activity .activity-comments .acomment-content,
+	#buddypress .activity .activity-comments .acomment-meta,
+	#buddypress .activity .activity-comments .acomment-options {
+		margin: 5px;
+	}
+	#buddypress .activity .activity-comments .acomment-content {
+		padding: 0 10px;
+	}
+}
+
+#buddypress .activity .activity-comments .ac-form input[type="submit"],
+#buddypress .activity .activity-comments .ac-form .ac-reply-cancel {
+	color: rgba(43, 43, 43, 0.8);
+	display: inline-block;
+	font-family: inherit;
+	font-size: 12px;
+	font-size: 0.75rem;
+	font-weight: normal;
+	line-height: 1.2;
+	padding: 4px 10px;
+	text-transform: lowercase;
+	width: 100px;
+}
+
+#buddypress .activity .activity-comments .ac-form .ac-reply-cancel {
+	border: 1px solid rgba(0, 0, 0, 0.2);
+	text-align: center;
+}
+
+#buddypress .activity .activity-comments .ac-form .ac-reply-cancel:focus,
+#buddypress .activity .activity-comments .ac-form .ac-reply-cancel:hover {
+	background: #ededed;
+}
+
+/**
+*------------------------------------------------------------
+* @section 4.2 - Members Loop
+*------------------------------------------------------------
+*/
+/*
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens
+*-------------------------------------------------------------------------------
+*/
+/* groups & user account page entry title */
+.single-item.groups .entry-title,
+.bp-user .entry-title {
+	background: #4d4d4d;
+	color: #fff;
+	font-size: 22px;
+	font-size: 1.375rem;
+	margin-bottom: 10px;
+	padding: 0.2em;
+}
+
+@media screen and (min-width: 673px) {
+	.single-item.groups .entry-title,
+	.bp-user .entry-title {
+		font-size: 28px;
+		font-size: 1.75rem;
+	}
+}
+
+/**
+*------------------------------------------------------------
+* @subsection 6.1 - Item Header: Global
+*------------------------------------------------------------
+*/
+#buddypress div#item-header {
+	padding-bottom: 10px;
+}
+
+#buddypress div#item-header img.avatar {
+	margin: 0 20px 10px 0;
+}
+
+#buddypress div#item-header #message {
+	clear: both;
+	margin-bottom: 5px;
+	padding-top: 10px;
+}
+
+#buddypress div#item-header #message p {
+	margin-bottom: 0;
+}
+
+/**
+*-------------------------------------------------
+* @subsection 6.1.1 - item-header: Groups
+*-------------------------------------------------
+*/
+@media screen and (max-width: 594px) {
+	.single-item.groups #buddypress div#item-header {
+		display: flex;
+		flex-direction: column;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		order: 1;
+		text-align: center;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a {
+		border-bottom: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a img {
+		display: inline-block;
+		float: none;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		order: 2;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions {
+		margin-left: 0;
+		order: 3;
+		text-align: left;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions ul {
+		margin-left: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions ul li {
+		float: left;
+	}
+	.single-item.groups #buddypress div#item-header #message {
+		order: 4;
+	}
+}
+
+.single-item.groups #buddypress #item-header #item-actions {
+	float: none;
+	margin: 0 0 0 20px;
+	width: auto;
+}
+
+.single-item.groups #buddypress #item-header #item-actions h3 {
+	font-size: 20px;
+	font-size: 1.25rem;
+}
+
+.single-item.groups #buddypress #item-header #item-header-avatar {
+	text-align: center;
+}
+
+.single-item.groups #buddypress #item-header #item-header-content {
+	float: none;
+	overflow: hidden;
+}
+
+@media screen and (min-width: 594px) {
+	.single-item.groups #buddypress #item-header #item-actions {
+		float: right;
+	}
+	.single-item.groups #buddypress #item-header #item-header-avatar {
+		float: left;
+	}
+	.single-item.groups #buddypress #item-header #item-header-content {
+		float: none;
+		overflow: hidden;
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 6.1.2 - item-header: User Accounts
+*-------------------------------------------------
+*/
+.bp-user #buddypress #item-header #item-header-avatar {
+	text-align: center;
+	width: 100%;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar img {
+	float: none;
+}
+
+.bp-user #buddypress #item-header #latest-update {
+	margin-bottom: 10px;
+}
+
+@media screen and (max-width: 594px) {
+	.bp-user #buddypress #item-header #item-buttons div.generic-button {
+		display: inline-block;
+		float: none;
+		margin: 0 0 5px 0;
+		width: 100%;
+	}
+	.bp-user #buddypress #item-header #item-buttons div.generic-button a {
+		-webkit-box-sizing: border-box;
+		-moz-box-sizing: border-box;
+		box-sizing: border-box;
+		display: block;
+		width: 100%;
+	}
+}
+
+@media screen and (min-width: 673px) {
+	.bp-user #buddypress #item-header #item-header-avatar {
+		float: left;
+		overflow: hidden;
+		text-align: left;
+		width: 20%;
+	}
+	.bp-user #buddypress #item-header #item-header-content {
+		float: right;
+		width: 78%;
+	}
+	.bp-user #buddypress #item-header #item-header-content #latest-update {
+		margin-bottom: 20px;
+	}
+}
+
+/**
+*------------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*------------------------------------------------------------
+*/
+/**
+*-------------------------------------------------
+* @subsection 6.2.1 - item-body: Groups
+*-------------------------------------------------
+*/
+/**
+	*------------------------------
+	* 6.2.1.1 - Groups Management
+	*------------------------------
+	*/
+/* Settings */
+#group-settings-form .radio strong,
+#create-group-form .radio strong {
+	font-size: 18px;
+	font-size: 1.125rem;
+}
+
+#group-settings-form .radio ul,
+#create-group-form .radio ul {
+	color: #777777;
+}
+
+#group-settings-form #group-creation-previous,
+#create-group-form #group-creation-previous {
+	margin: 10px 0;
+}
+
+/* Manage Members */
+.groups.manage-members #group-settings-form h4 {
+	margin-top: 40px;
+}
+
+.groups.manage-members #group-settings-form .item-list {
+	background: #f7f7f7;
+	border-top: 0;
+	padding: 0 0.5em;
+}
+
+.groups.manage-members #group-settings-form .item-list li {
+	border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+.groups.manage-members #group-settings-form .item-list li:last-child {
+	border-bottom: 0;
+}
+
+.groups.manage-members #group-settings-form .item-list li img,
+.groups.manage-members #group-settings-form .item-list li h5 {
+	clear: none;
+	float: left;
+}
+
+.groups.manage-members #group-settings-form .item-list li img > a,
+.groups.manage-members #group-settings-form .item-list li h5 > a {
+	border-bottom: 0;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small {
+	display: block;
+	float: none;
+	margin-top: 10px;
+	width: 100%;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small a {
+	display: inline-block;
+	margin: 5px 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.groups.manage-members #group-settings-form .item-list li span.small a {
+		width: auto;
+	}
+}
+
+.groups.manage-members #group-settings-form .item-list li h5 {
+	margin: 0;
+}
+
+/**
+	*------------------------------
+	* 6.2.1.2 - Group Members List
+	*------------------------------
+	*/
+#buddypress #members-group-list .item-list h5 {
+	display: inline-block;
+	margin: 0;
+}
+
+#buddypress #members-group-list .item-list span.activity {
+	padding-left: 10px;
+}
+
+#buddypress #item-body #subnav li.groups-members-search:focus,
+#buddypress #item-body #subnav li.groups-members-search:hover,
+#buddypress #item-body #subnav li.last:focus,
+#buddypress #item-body #subnav li.last:hover {
+	background: none;
+}
+
+.groups.group-members #subnav ul:before,
+.groups.group-members #subnav ul:after {
+	content: " ";
+	display: table;
+}
+
+.groups.group-members #subnav ul:after {
+	clear: both;
+}
+
+.groups.group-members #subnav ul {
+	padding: 5px 0;
+}
+
+@media screen and (max-width: 400px) {
+	.groups.group-members #subnav li {
+		background: #fff;
+		padding: 10px 0;
+		width: 100% !important;
+	}
+}
+
+.groups.group-members #subnav li #search-members-form {
+	-webkit-box-sizing: border-box;
+	-moz-box-sizing: border-box;
+	box-sizing: border-box;
+	float: left;
+}
+
+@media screen and (max-width: 400px) {
+	.groups.group-members #subnav li #search-members-form {
+		margin: 0;
+		width: 100%;
+	}
+	.groups.group-members #subnav li #search-members-form label input[type="text"] {
+		width: 100%;
+	}
+}
+
+@media screen and (min-width: 783px) {
+	.groups.group-members #subnav li #search-members-form {
+		margin-left: 3px;
+	}
+}
+
+/**
+	*------------------------------
+	* 6.2.1.3 - Groups Send Invites
+	*------------------------------
+	*/
+@media screen and (min-width: 783px) {
+	.send-invites #buddypress #item-body {
+		border-top: 1px solid #bebebe;
+	}
+}
+
+@media screen and (max-width: 400px) {
+	.send-invites #buddypress #item-body .left-menu {
+		float: none;
+	}
+	.send-invites #buddypress #item-body .left-menu #invite-list {
+		height: auto;
+		width: auto;
+	}
+	.send-invites #buddypress #item-body .main-column {
+		margin-left: 0;
+	}
+}
+
+.send-invites #buddypress #item-body li img.avatar {
+	display: block;
+	float: none;
+	margin: 10px auto;
+}
+
+.send-invites #buddypress #item-body li .action {
+	margin-top: 10px;
+	text-align: left;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.send-invites #buddypress #item-body li .action {
+		text-align: right;
+	}
+}
+
+@media screen and (min-width: 594px) {
+	.send-invites #buddypress #item-body #invite-list {
+		width: 160px;
+	}
+	.send-invites #buddypress #item-body .main-column {
+		margin-left: 190px;
+	}
+	.send-invites #buddypress #item-body li img.avatar {
+		float: left;
+		margin: 0 10px 0 0;
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 6.2.2 - item-body: User Accounts
+*-------------------------------------------------
+*/
+/**
+	*------------------------------
+	* 6.2.2.2 - Extended Profiles
+	*------------------------------
+	*/
+#buddypress .profile .bp-widget h4 {
+	background: #999;
+	color: #fff;
+	padding: 0.4em 0.2em;
+}
+
+#buddypress .profile #profile-edit-form .button-nav:before,
+#buddypress .profile #profile-edit-form .button-nav:after {
+	content: " ";
+	display: table;
+}
+
+#buddypress .profile #profile-edit-form .button-nav:after {
+	clear: both;
+}
+
+#buddypress .profile #profile-edit-form ul.button-nav {
+	border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+	margin-left: 0;
+}
+
+#buddypress .profile #profile-edit-form ul.button-nav li {
+	float: left;
+	margin-bottom: 0;
+}
+
+#buddypress .profile #profile-edit-form ul.button-nav li.current {
+	border: 1px solid rgba(0, 0, 0, 0.1);
+	border-bottom-color: #fff;
+	margin-bottom: -1px;
+}
+
+#buddypress .profile #profile-edit-form ul.button-nav a {
+	background: none;
+	border: 0;
+	font-size: 18px;
+	font-size: 1.125rem;
+}
+
+#buddypress .profile #profile-edit-form .field-visibility-settings-toggle,
+#buddypress .profile #profile-edit-form .field-visibility-settings {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+#buddypress .profile #profile-edit-form .field-visibility-settings-close,
+#buddypress .profile #profile-edit-form .visibility-toggle-link {
+	background: #000;
+	color: #fff;
+	padding: 0.2em 0.5em;
+}
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.5 - Messaging
+	*------------------------------
+	*/
+#buddypress {
+	/* Read Single thread */
+	/* Sitewide Notices loop */
+}
+
+#buddypress table#message-threads {
+	border-left: 1px solid rgba(0, 0, 0, 0.2);
+	border-right: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+#buddypress table#message-threads tbody > tr:first-child td.bulk-select-check,
+#buddypress table#message-threads tbody > tr:first-child td.thread-from {
+	border-top: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+#buddypress table#message-threads tr td {
+	background: #fff;
+	border-right: 0 !important;
+	-webkit-box-sizing: border-box;
+	-moz-box-sizing: border-box;
+	box-sizing: border-box;
+	display: inline-block;
+	float: left;
+}
+
+#buddypress table#message-threads tr td.thread-star,
+#buddypress table#message-threads tr td.thread-options {
+	border-bottom-color: rgba(0, 0, 0, 0.2);
+	border-bottom-width: 2px;
+	border-left: 0;
+	height: 2.4em;
+	padding-bottom: 0.2em;
+	padding-top: 0.2em;
+}
+
+@media screen and (max-width: 549px) {
+	#buddypress table#message-threads tr td.thread-star,
+	#buddypress table#message-threads tr td.thread-options {
+		padding-top: 0;
+	}
+}
+
+#buddypress table#message-threads tr td.bulk-select-check,
+#buddypress table#message-threads tr td.thread-from {
+	border-left: 0;
+	height: 2.6em;
+}
+
+@media screen and (max-width: 320px) {
+	#buddypress table#message-threads tr td.bulk-select-check,
+	#buddypress table#message-threads tr td.thread-from {
+		height: 5.2em;
+	}
+}
+
+#buddypress table#message-threads tr td.thread-from,
+#buddypress table#message-threads tr td.thread-options {
+	border-left: 0;
+	width: -webkit-calc(100% - 30px);
+	width: -moz-calc(100% - 30px);
+	width: calc(100% - 30px);
+	margin-left: 0;
+}
+
+#buddypress table#message-threads tr td.thread-info {
+	border-left: 0;
+	padding-left: 41px;
+	width: 100%;
+}
+
+#buddypress table#message-threads tr td.thread-info a::after {
+	content: " –  " attr(title);
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+#buddypress table#message-threads tr td.thread-options {
+	text-align: right;
+}
+
+#buddypress table#message-threads tr td.thread-options a {
+	font-size: 12px;
+	font-size: 0.75rem;
+	line-height: 2.2;
+}
+
+#buddypress table#message-threads tr span.from {
+	display: none;
+}
+
+#buddypress table#message-threads tr span.activity {
+	display: block;
+	float: right;
+	line-height: 2;
+}
+
+@media screen and (max-width: 320px) {
+	#buddypress table#message-threads tr span.activity {
+		clear: both;
+		font-size: 11px;
+		font-size: 0.6875rem;
+		width: 100%;
+	}
+}
+
+#buddypress table#message-threads tr.unread td {
+	background: #f1f4ff;
+	border-color: rgba(0, 0, 0, 0.2);
+	border-top: 0;
+	border-width: 1px;
+}
+
+#buddypress table#message-threads th {
+	display: none;
+}
+
+#buddypress table#message-threads th.bulk-select-all {
+	border-bottom: 0;
+	display: inline-block;
+	text-align: left;
+}
+
+#buddypress table#message-threads th.bulk-select-all,
+#buddypress table#message-threads td.bulk-select-check,
+#buddypress table#message-threads td.thread-star {
+	border-right: 0;
+	width: 30px;
+}
+
+#buddypress #message-thread #message-subject {
+	background: #4d4d4d;
+	color: #fff;
+	padding: 0.5em;
+}
+
+#buddypress #message-thread .message-box .message-metadata {
+	margin-bottom: 10px;
+}
+
+#buddypress #message-thread .message-box .message-content {
+	clear: left;
+	margin-left: 0;
+}
+
+#buddypress #message-threads.sitewide-notices td {
+	width: 100%;
+}
+
+#buddypress #message-threads.sitewide-notices td strong {
+	background: #4d4d4d;
+	color: #fff;
+	font-size: 16px;
+	font-size: 1rem;
+	display: block;
+	margin-bottom: 0.4em;
+	padding: 0.2em;
+}
+
+#buddypress #message-threads.sitewide-notices td a {
+	display: inline-block;
+	line-height: initial;
+}
+
+#buddypress #message-threads.sitewide-notices td:first-child {
+	display: none;
+}
+
+#buddypress #message-threads.sitewide-notices td:first-child + td + td {
+	border-bottom: 0;
+}
+
+#buddypress #message-threads.sitewide-notices td:first-child + td + td span {
+	line-height: 1;
+}
+
+#buddypress #message-threads.sitewide-notices td:last-child {
+	border-bottom-color: rgba(0, 0, 0, 0.2);
+	line-height: 1;
+	text-align: right;
+}
+
+#buddypress #message-threads.sitewide-notices td:last-child a:last-child {
+	line-height: 0;
+	margin-left: 0.7em;
+	text-indent: -999em;
+}
+
+#buddypress #message-threads.sitewide-notices td:last-child a:last-child::after {
+	content: attr(title);
+	display: block;
+	line-height: initial;
+	text-indent: 0;
+}
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+#buddypress #settings-form > p {
+	font-size: 20px;
+	font-size: 1.25rem;
+}
+
+#buddypress table.notification-settings td.yes,
+#buddypress table.notification-settings td.no {
+	vertical-align: middle;
+}
+
+#buddypress table.profile-settings {
+	width: 100%;
+}
+
+#buddypress table.profile-settings th.field-group-name,
+#buddypress table.profile-settings td.field-name {
+	width: 50%;
+}
+
+@media screen and (min-width: 401px) {
+	#buddypress table.profile-settings th.field-group-name,
+	#buddypress table.profile-settings td.field-name {
+		width: 70%;
+	}
+}
+
+#buddypress table.profile-settings td.field-visibility select {
+	width: 100%;
+}
+
+/*------------------------------------------------------------------------------
+* @section - 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+#buddypress input[type="text"],
+#buddypress input[type="email"],
+#buddypress input[type="password"],
+#buddypress input[type="submit"],
+#buddypress select,
+#buddypress textarea {
+	border: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+#buddypress .standard-form input[type="text"],
+#buddypress .standard-form input[type="email"],
+#buddypress .standard-form input[type="password"],
+#buddypress .standard-form input[type="submit"],
+#buddypress .standard-form textarea {
+	width: 100%;
+}
+
+#buddypress #send-reply.standard-form textarea {
+	width: 100%;
+}
+
+#buddypress div.activity-comments form .ac-textarea {
+	background: #f7f7f7;
+	border: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+#buddypress div.activity-comments form .ac-textarea textarea {
+	background: none;
+	border: 0;
+}
+
+#buddypress div.dir-search,
+#buddypress div.message-search,
+#buddypress li.groups-members-search {
+	clear: both;
+	float: none;
+	margin: 10px 0;
+}
+
+#buddypress div.dir-search form,
+#buddypress div.message-search form,
+#buddypress li.groups-members-search form {
+	border: 1px solid #ccc;
+	overflow: hidden;
+}
+
+#buddypress div.dir-search form label,
+#buddypress div.message-search form label,
+#buddypress li.groups-members-search form label {
+	float: left;
+	width: 80%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	float: left;
+	margin: 0;
+	width: 80%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='text'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	border: 0;
+	font-size: 14px;
+	font-size: 0.875rem;
+	line-height: inherit;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	border-right: 1px solid #ccc;
+	padding: 0.2em 0 0.2em 0.2em;
+}
+
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	float: right;
+	font-weight: normal;
+	padding: 0.2em 1em;
+	text-align: center;
+	text-transform: none;
+	width: 20%;
+}
+
+@media screen and (min-width: 401px) {
+	#buddypress div.dir-search,
+	#buddypress div.message-search {
+		clear: none;
+		float: right;
+	}
+	#buddypress div.dir-search form label,
+	#buddypress div.dir-search form input[type='submit'],
+	#buddypress div.dir-search form input[type='text'],
+	#buddypress div.message-search form label,
+	#buddypress div.message-search form input[type='submit'],
+	#buddypress div.message-search form input[type='text'] {
+		width: auto;
+	}
+	#buddypress div.message-search {
+		margin: 3px 0 0;
+	}
+	#buddypress li.groups-members-search form label,
+	#buddypress li.groups-members-search form input[type='submit'],
+	#buddypress li.groups-members-search form input[type='text'] {
+		width: auto;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables General
+*-------------------------------------------------------------------------------
+*/
+#buddypress table {
+	border-color: rgba(0, 0, 0, 0.2);
+}
+
+/*__ User Account tables __*/
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+#buddypress div#message p {
+	font-size: 18px;
+	font-size: 1.125rem;
+	font-weight: bold;
+}
+
+#buddypress div#message.info p {
+	background: #c6e4ee;
+	border: 1px solid #78bed6;
+	color: #1e5264;
+}
+
+#buddypress div#message.updated p {
+	background: #dee6b2;
+	border: 1px solid #becc66;
+	color: #454d19;
+}
+
+.delete-group #buddypress div#message.info p {
+	background: #db7e7e;
+	border: 1px solid #be3535;
+	color: #1f0808;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+#buddypress {
+	/* Auto Complete background */
+}
+
+#buddypress .acfb-holder li.friend-tab {
+	background: #9fd1e2;
+	border: inherit;
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfourteen.scss
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfourteen.scss	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentyfourteen.scss	(revision 10410)
@@ -0,0 +1,1853 @@
+// Stylesheet Guidence Notes
+// Table of content represents a guide to sections of the sheet.
+// Note that not all sections may be described in the body of the
+// rulesets only those that require styles for a given theme.
+// Sub sections might not exist but can be added as required
+// as per BP Lists - 'activity' section.
+
+// We follow the dictats of the parent theme in respect of media queries,
+// font sizing etc. Media queries are used in a modular sense (OOCSS)
+// rather than described as blocks to the end of the sheet, this aids ease
+// of managing media queries, scalability & flexibility.
+
+// Twentyfourteen Media Queries / Breakpoints
+//@media screen and (max-width: 400px)
+//@media screen and (min-width: 401px)
+//@media screen and (min-width: 594px)
+//@media screen and (min-width: 673px)
+//@media screen and (min-width: 783px)
+//@media screen and (min-width: 810px)
+//@media screen and (min-width: 846px)
+//@media screen and (min-width: 1008px)
+//@media screen and (min-width: 1040px)
+//@media screen and (min-width: 1110px)
+
+// Mixins and Variables
+
+// Common margin values
+$spacing-val-lg: 40px;
+$spacing-val-md: 20px;
+$spacing-val-sm: 10px;
+$spacing-val-xs: 5px;
+
+// A simple mixin to handle font-sizing to match 2014 body 100% approach
+@mixin font-size($font-size: 16) {
+	$rem-font-value: ($font-size / 16);
+	font-size: ($font-size * 1px);
+	font-size: $rem-font-value + rem;
+}
+
+// To allow mixins to pass VendorPrefix scss-lint tests we disable lint-test
+// for specific blocks / rulesets
+// until such time as exemption lists can be built for the scss-lint.yml?
+// Any vendor prefix properties / mixins need to be in this block
+// scss-lint:disable VendorPrefix
+
+// Border border-radius mixins
+@mixin border-radius($radius) {
+	-webkit-border-radius: $radius;
+	-moz-border-radius: $radius;
+	-ms-border-radius: $radius;
+	border-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-top-radius($radius) {
+	-webkit-border-top-right-radius: $radius;
+	border-top-right-radius: $radius;
+	-webkit-border-top-left-radius: $radius;
+	border-top-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-right-radius($radius) {
+	-webkit-border-bottom-right-radius: $radius;
+	border-bottom-right-radius: $radius;
+	-webkit-border-top-right-radius: $radius;
+	border-top-right-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-bottom-radius($radius) {
+	-webkit-border-bottom-right-radius: $radius;
+	border-bottom-right-radius: $radius;
+	-webkit-border-bottom-left-radius: $radius;
+	border-bottom-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-left-radius($radius) {
+	-webkit-border-bottom-left-radius: $radius;
+	border-bottom-left-radius: $radius;
+	-webkit-border-top-left-radius: $radius;
+	border-top-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+// Box sizing
+@mixin box-model($box-model: border-box) {
+	-webkit-box-sizing: $box-model;
+	-moz-box-sizing: $box-model;
+	box-sizing: $box-model;
+}
+
+// Calc
+@mixin calc($property, $expression) {
+	#{$property}: -webkit-calc(#{$expression});
+	#{$property}: -moz-calc(#{$expression});
+	#{$property}: calc(#{$expression});
+}
+
+// Re-enable the VendorPrefix lint test
+// scss-lint:enable VendorPrefix
+
+// BP message boxes
+@mixin message-box($background, $text-color: null) {
+
+	@if $text-color {
+		// if a param was passed through
+		$text-color: $text-color;
+	} @else {
+		$text-color: darken($background, 50%);
+	}
+
+	background: lighten($background, 10%);
+	border: 1px solid darken($background, 10%);
+	color: $text-color;
+}
+
+// titles dark backgrounds
+@mixin title-background-dark() {
+	background: lighten($dark-background, 30%);
+	color: $dark-back-text-color;
+}
+
+// Variabals: color definitions
+$content-background:     #fff;
+$dark-back-text-color:   #fff;
+$light-background:       #f7f7f7;
+$medium-background:      #999;
+$dark-background:        #000;
+$border-color:           #000; // border color can be varied using rgba
+$border-med:             rgba(#000, 0.2);
+$border-light:           rgba(#000, 0.1); // BP dividers
+$body-text:              #2b2b2b; // 2015 body text color
+$bp-button-hover:        #ededed; // this is the default BP button hover background
+$notice-warning:         #d05656;
+$notice-info:            #9fd1e2;
+$notice-update:          #ced98c;
+$stripe-odd:             #ebf0ff;
+$stripe-even:            #dbe5ff;
+$unread:                 #cddaff;
+
+// 2014 font family
+@mixin default-font() {
+	font-family: Lato, sans-serif;
+}
+
+/*------------------------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentyfourteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentyfourteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the ruelesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+--------------------------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+--------------------------------------------------------------------------------
+1.0 Theme Structural Elements
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 - Activity Whats new
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Forums
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - Item-header: Groups;
+		6.1.2 -  item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1- Management settings screens
+			6.2.1.2 - Group members list
+			6.2.1.3 - Group Send Invites
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading
+11.0 - Widgets
+------------------------------------------------------------------------------*/
+
+/**
+*-------------------------------------------------------------------------------
+* @section - 1.0 - Theme - Structural Elements
+*-------------------------------------------------------------------------------
+*/
+
+// Twentyfourteen  adds a generic grouped ruleset of clear:both;
+// for all heading h# elements this is problematic where floats are concerned
+// We reset this here under our #buddypress namespace
+#buddypress {
+	h1,
+	h2,
+	h3,
+	h4,
+	h5,
+	h6 {
+		clear: none;
+	}
+}
+
+// As BP screens have specific content lets adjust the themes primary elements
+// to maximise the screen real estate for BP pages.
+
+.buddypress {
+
+	.site-content {
+		padding-bottom: $spacing-val-md;
+		padding-top: 4%;
+
+		@media screen and (min-width: 59.6875em) {
+			padding-top: 0;
+		}
+
+		article {
+			margin: 0 4%;
+			padding-top: 0;
+
+			.entry-header,
+			.entry-content {
+				max-width: 100%;
+				padding: 0;
+			}
+
+			#buddypress {
+				margin-bottom: $spacing-val-lg;
+			}
+
+		} // .article
+	} // .content
+} // .buddypress ( body class )
+
+/**
+* ------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+
+/**
+*----------------------------------------------------------
+* @subsection 2.1 - Object Nav / Subnav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+#buddypress {
+	.item-list-tabs {
+		ul {
+
+			li {
+				width: 100%;
+			}
+
+			@media screen and (min-width: 400px) {
+				li {
+					width: 50%;
+				}
+			}
+			@media screen and (min-width: 594px) {
+				li {
+					width: auto;
+				}
+			}
+		}
+	}
+}
+
+.directory #buddypress {
+	.item-list-tabs {
+
+		ul:before,
+		ul:after {
+			content: " ";
+			display: table;
+		}
+
+		ul:after {
+			clear: both;
+		}
+
+		ul {
+			border-bottom: 1px solid $border-med;
+			padding-bottom: $spacing-val-sm;
+		}
+
+		@media screen and (min-width: 783px) {
+			ul {
+				padding-bottom: 0;
+
+				li.selected {
+					border: 1px solid $border-med;
+					border-bottom-color: $content-background;
+					@include border-top-radius(4px);
+					margin-bottom: -1px;
+
+					a {
+						background: none;
+						outline: none;
+					}
+				}
+			}
+		} // close @media
+	} // close .item-list-tabs
+
+	#subnav {
+		ul {
+			border-bottom: 0;
+		}
+
+	} // close #subnav
+} // close .directory #buddypress
+
+/* List last filters global */
+#buddypress {
+	#subnav {
+		li#activity-filter-select.last {
+			line-height: 2.1;
+			margin-top: 0;
+		}
+	}
+}
+
+// Add a little bottom margin & border seperation to object/subnav
+// & keep subnav to horizontal up to vert nav display for user/group screens
+
+@media screen and (max-width: 783px) {
+
+	#buddypress {
+		#object-nav {
+			ul {
+				border-bottom: 1px solid $border-med;
+				overflow: hidden;
+				padding-bottom: $spacing-val-sm;
+			}
+		}
+
+		div#subnav.item-list-tabs {
+			margin-bottom: $spacing-val-lg;
+
+			ul {
+				li {
+					width: auto;
+				}
+			}
+		}
+	}
+
+} // close @media
+
+/*__ Vertical Menu User Account Screens __*/
+
+// This block contains the rules to re-factor the item-body structural element
+// to sit alongside the vert menu & menu item styles.
+
+// Boders & backgrounds for user/group navs rely on one color
+// set this here for convenience of changing and clarity
+$nav-background-border: #bebebe;
+
+@media screen and (min-width: 783px) {
+
+	.bp-user,
+	.single-item.groups {
+		#buddypress {
+			background: lighten($nav-background-border, 10%);
+		}
+	}
+
+	#buddypress {
+
+		#item-header,
+		#item-body {
+			background: #fff;
+		}
+
+		#item-body {border-left: 1px solid $nav-background-border;}
+
+		#object-nav {
+			background: lighten($nav-background-border, 10%);
+			border-right: 1px solid $nav-background-border;
+			float: left;
+			margin-right: -1px;
+			width: 200px;
+
+			ul {
+				border-bottom: 0;
+				border-top: 1px solid $nav-background-border;
+				padding: 0;
+
+				li {
+					float: none;
+					overflow: hidden;
+
+					&:focus,
+					&:hover {
+						background: $nav-background-border;
+					}
+
+					span {
+						border-radius: 10%;
+						float: right;
+						margin-right: 15px;
+					}
+				}
+
+				li.current {
+					background: lighten($nav-background-border, 20%);
+				}
+			}
+		}
+
+		#item-body {
+			overflow: hidden;
+			padding: 0 $spacing-val-md;
+			width: auto;
+
+			#subnav {
+				border: 1px solid $nav-background-border;
+				border-left: 0;
+				border-right: 0;
+				margin: 0 (-$spacing-val-md) $spacing-val-md;
+
+				li:focus,
+				li:hover {
+					background: $nav-background-border;
+				}
+
+				li.current {
+					background: lighten($nav-background-border, 20%);
+				}
+
+				li#activity-filter-select {
+
+					&:focus,
+					&:hover {
+						background: none;
+					}
+
+					select {
+						max-width: 130px;
+					}
+				}
+
+			}
+		}
+	} // close  #buddypress
+} // close @media
+
+/**
+*----------------------------------------------------------
+* @subsection 2.2 - Pagination
+*----------------------------------------------------------
+*/
+
+#buddypress {
+	div.pagination {
+		.pag-count {
+			margin-left: 0;
+		}
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+#buddypress {
+	.item-list {
+		.item-title {
+
+			a {
+				display: block;
+				@include font-size(22);
+				text-decoration: none;
+			}
+
+			span.update,
+			span a {
+				@include font-size(12);
+			}
+		}
+	}
+}
+
+// This block:
+// - Manages li items, Stacks them small screen, floated left at wider screens.
+// - Adjusts display of action buttons at small screen
+
+#buddypress {
+	ul.item-list {
+		li {
+			overflow: hidden !important; // buddypress styles over specified
+
+			.item-avatar {
+				float: none;
+				margin-bottom: $spacing-val-md;
+				text-align: center;
+
+				a {
+					img.avatar {
+						float: none;
+					}
+				}
+			}
+
+			div.item-title {
+				float: none;
+				width: 100%;
+
+				> a {text-align: center;}
+			}
+
+			.item {
+				margin-left: 0;
+			}
+
+			// groups group description
+			div.item-desc {
+				margin-left: 0;
+				width: auto;
+			}
+
+			div.action {
+				clear: both;
+				float: none;
+				margin: 0;
+				position: relative;
+				text-align: left;
+
+				div {
+					margin: 0 0 $spacing-val-sm;
+				}
+
+			} // close .action
+
+			// item-list buttons blocked/full width for mobile/small screens
+
+			.activity-meta,
+			.action {
+				float: none;
+
+				a,
+				div {
+					display: block;
+					margin: $spacing-val-xs 0;
+				}
+			}
+
+			@media screen and (min-width: 594px) {
+				.item-avatar {
+					float: left;
+					margin-bottom: 0;
+				}
+
+				div.item-title > a {text-align: left;}
+
+				.item {
+					float: right;
+					margin-left: 5%;
+					width: 80%;
+				}
+
+				.activity-meta {
+					clear: both;
+
+					a,
+					div {
+						display: inline-block;
+						margin: $spacing-val-xs $spacing-val-xs 0 0;
+					}
+				}
+
+			} // close @media
+
+			@media screen and (min-width: 783px) {
+				.item {
+					float: left;
+					width: 55%;
+
+					div.item-desc {
+						margin: 0;
+						width: auto;
+					}
+				}
+
+				div.action {
+					clear: none;
+					float: right;
+					top: 0;
+					width: 25%;
+
+					div {
+						display: block;
+
+						a {
+							@include box-model(border-box);
+							display: block;
+							width: 100%;
+						}
+					}
+
+					.meta {
+						text-align: center;
+					}
+				} // close .action
+
+			} // close @media
+
+			.meta,
+			.item-meta .activity {
+				font-style: italic;
+			}
+
+		} // close li
+	} // ul.item-list
+} // close #budypress
+
+/**
+*------------------------------------------------------------
+* @section 4.1 - Activity
+*------------------------------------------------------------
+*/
+
+/**
+*-------------------------------------------------
+* @subsection 4.1.1 - Activity What's New
+*-------------------------------------------------
+*/
+#buddypress {
+	form#whats-new-form {
+		margin: $spacing-val-lg 0;
+
+		#whats-new-avatar {
+			float: none;
+			margin-bottom: $spacing-val-md;
+			text-align: center;
+		}
+
+		#whats-new-content,
+		p.activity-greeting {
+			margin-left: 0;
+			padding-left: 0;
+		}
+
+		// Line-height issue inherited from BP rules, override.
+		p.activity-greeting {
+			clear: left;
+			line-height: inherit;
+			margin-bottom: $spacing-val-xs;
+		}
+
+		#whats-new-content {
+			@media screen and (max-width: 594px) {
+				#whats-new-options {
+					display: flex;
+					flex-direction: column;
+
+					#whats-new-submit {
+						order: 2;
+
+						input {
+							width: 100%;
+						}
+					}
+
+					#whats-new-post-in-box {
+						margin-top: $spacing-val-sm;
+						order: 1;
+
+						select {
+							margin-top: $spacing-val-xs;
+							max-width: 100%;
+							width: 100%;
+						}
+					}
+				}
+			}
+		} // close whats-new-content.active
+
+		@media screen and (min-width: 594px) {
+			#whats-new-avatar {
+				float: left;
+			}
+
+			#whats-new-content,
+			p.activity-greeting {
+				clear: none;
+				overflow: hidden;
+				padding-left: $spacing-val-md;
+			}
+		}
+	} // close form#whats-new-form
+
+} // close #buddypress
+
+/**
+*-------------------------------------------------
+* @subsection 4.1.2 - Activity Listing
+*-------------------------------------------------
+*/
+
+#buddypress {
+	#activity-stream {margin-top: $spacing-val-lg;}
+	.activity-list {
+
+		li.activity-item:not(.mini) {
+			.activity-avatar {
+				float: none;
+				margin-bottom: $spacing-val-sm;
+				text-align: center;
+
+				img.avatar {
+					display: inline;
+					float: none;
+				}
+			}
+		}
+
+		li.mini {
+			.activity-avatar {
+				img.avatar {
+					margin-left: 0;
+				}
+			}
+
+			.activity-meta {
+				a {@include font-size(12);}
+			}
+		}
+
+		.activity-content {
+			margin: 0;
+
+			.activity-header {
+				margin: -0.5em 0 0;
+
+				> p {
+					line-height: 1.4;
+					margin-top: -$spacing-val-sm;
+				}
+
+				// BP adds !important. need to override prop val
+				img.avatar {
+					margin-bottom: 0 !important;
+					vertical-align: middle;
+				}
+			}
+
+			.activity-inner {margin: $spacing-val-sm 0;}
+		}
+
+		@media screen and (min-width: 594px) {
+			li.activity-item:not(.mini) {
+				.activity-avatar {
+					float: left;
+					margin: 0 $spacing-val-md 0 0;
+
+					img.avatar {
+						display: inline;
+						float: none;
+					}
+				}
+			}
+
+			li.mini {
+				.activity-avatar {
+					margin: 0 $spacing-val-sm 0 5%;
+				}
+			}
+
+			.activity-content {
+				margin-left: 0;
+				overflow: hidden;
+			}
+		}
+
+	} // close .activity-list
+} // close #buddypress
+
+// Activity Single Item 'Permalink' Page
+body.activity-permalink {
+	#buddypress {
+		// remove user account faux column background
+		background: transparent;
+
+		.activity-list {
+			li {
+				@media screen and (max-width: 760px) {
+					.activity-content,
+					.activity-comments {
+						margin-left: 0;
+					}
+				}
+			}
+		}
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 4.1.3 - Activity Comments
+*-------------------------------------------------
+*/
+
+#buddypress {
+	.activity {
+		.activity-comments {
+		//	border-left: 1px solid $border-light;
+			margin-left: 0;
+			margin-top: 5px;
+
+			@media screen and (min-width: 594px) {
+				margin-left: 70px;
+			}
+
+			ul {
+				background: rgba($light-background, 0.6);
+				margin: 30px 0 0 2px;
+			}
+
+			ul ul {
+				border-left: 1px solid $border-light;
+				padding-left: 2%;
+			}
+
+			// Display the user avatar stacked up to 594px
+			// reduce/widen the content margins
+			@media screen and (max-width: 400px) {
+				.acomment-avatar {
+					display: block;
+					text-align: center;
+
+					a,
+					img.avatar {display: inline; float: none;}
+				}
+
+				.acomment-content,
+				.acomment-meta,
+				.acomment-options {
+					margin: $spacing-val-xs;
+				}
+
+				.acomment-content {padding: 0 $spacing-val-sm;}
+			} // close @media
+
+			.ac-form {
+
+				input[type="submit"],
+				.ac-reply-cancel {
+					color: rgba($body-text, 0.8);
+					display: inline-block;
+					font-family: inherit;
+					@include font-size(12);
+					font-weight: normal;
+					line-height: 1.2;
+					padding: 4px 10px;
+					text-transform: lowercase;
+					width: 100px;
+				}
+
+				.ac-reply-cancel {
+					border: 1px solid $border-med;
+					text-align: center;
+
+					&:focus,
+					&:hover {
+						background: $bp-button-hover;
+					}
+				}
+
+			} // close .ac-form
+
+		} // close .activity-comments
+	} // close .activity
+
+} // close #buddypress
+
+
+/**
+*------------------------------------------------------------
+* @section 4.2 - Members Loop
+*------------------------------------------------------------
+*/
+
+
+
+/*
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens
+*-------------------------------------------------------------------------------
+*/
+/* groups & user account page entry title */
+.single-item.groups,
+.bp-user {
+
+	.entry-title {
+		@include title-background-dark();
+		@include font-size(22);
+		margin-bottom: $spacing-val-sm;
+		padding: 0.2em;
+	}
+
+	@media screen and (min-width: 673px) {
+		.entry-title {
+			@include font-size(28);
+		}
+	}
+
+} // close #buddypress
+/**
+*------------------------------------------------------------
+* @subsection 6.1 - Item Header: Global
+*------------------------------------------------------------
+*/
+// override BP margins - users/groups manage their own styles later on
+#buddypress {
+	div#item-header {
+		padding-bottom: $spacing-val-sm;
+
+		img.avatar {
+			margin: 0 $spacing-val-md $spacing-val-sm 0;
+		}
+
+		// Prevent messages block abutting bottom of floats, with padding-top
+		#message {
+			clear: both;
+			margin-bottom: $spacing-val-xs;
+			padding-top: $spacing-val-sm;
+
+			p {margin-bottom: 0;}
+		}
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 6.1.1 - item-header: Groups
+*-------------------------------------------------
+*/
+.single-item.groups {
+	#buddypress {
+
+		@media screen and (max-width: 594px) {
+			div#item-header {
+				display: flex;
+				flex-direction: column;
+
+				#item-header-avatar {
+					order: 1;
+					text-align: center;
+
+					a {
+						border-bottom: 0;
+
+						img {
+							display: inline-block;
+							float: none;
+						}
+					}
+				}
+
+				#item-header-content {order: 2;}
+
+				#item-actions {
+					margin-left: 0;
+					order: 3;
+					text-align: left;
+
+					ul {
+						margin-left: 0;
+
+						li {
+							float: left;
+						}
+					}
+				} // close #item-actions
+
+				#message {
+					order: 4;
+				}
+
+			}
+		} // close @media
+
+		#item-header {
+
+			#item-actions {
+				float: none;
+				margin: 0 0 0 $spacing-val-md;
+				width: auto;
+
+				h3 {
+					@include font-size(20);
+				}
+			}
+
+			#item-header-avatar {
+				text-align: center;
+			}
+
+			#item-header-content {
+				float: none;
+				overflow: hidden;
+			}
+
+			@media screen and (min-width: 594px) {
+				#item-actions {
+					float: right;
+				}
+
+				#item-header-avatar {
+					float: left;
+				}
+
+				#item-header-content {
+					float: none;
+					overflow: hidden;
+				}
+			}// close @media
+
+		}
+	}
+} // close .single-item.groups
+
+/**
+*-------------------------------------------------
+* @subsection 6.1.2 - item-header: User Accounts
+*-------------------------------------------------
+*/
+
+.bp-user #buddypress {
+	#item-header {
+
+		#item-header-avatar {
+			text-align: center;
+			width: 100%;
+
+			img {
+				float: none;
+			}
+		}
+
+		#latest-update {
+			margin-bottom: $spacing-val-sm;
+		}
+
+		@media screen and (max-width: 594px) {
+			#item-buttons {
+				div.generic-button {
+					display: inline-block;
+					float: none;
+					margin: 0 0 $spacing-val-xs 0;
+					width: 100%;
+
+					a {
+						@include box-model();
+						display: block;
+						width: 100%;
+					}
+				}
+			}
+		}
+
+		@media screen and (min-width: 673px) {
+			#item-header-avatar {
+				float: left;
+				overflow: hidden;
+				text-align: left;
+				width: 20%;
+			}
+
+			#item-header-content {
+				float: right;
+				width: 78%;
+
+				#latest-update {
+					margin-bottom: $spacing-val-md;
+				}
+			}
+		}
+
+	} // close #item-header
+
+} // close .bp-user #buddypress
+
+/**
+*------------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*------------------------------------------------------------
+*/
+
+/**
+*-------------------------------------------------
+* @subsection 6.2.1 - item-body: Groups
+*-------------------------------------------------
+*/
+
+/**
+	*------------------------------
+	* 6.2.1.1 - Groups Management
+	*------------------------------
+	*/
+
+/* Settings */
+
+#group-settings-form,
+#create-group-form {
+
+	.radio {
+		strong {
+			@include font-size(18);
+		}
+
+		ul {
+			color: lighten($body-text, 30%);
+		}
+	}
+
+	#group-creation-previous {margin: $spacing-val-sm 0;}
+}
+
+/* Manage Members */
+.groups.manage-members {
+	#group-settings-form {
+
+		h4 {margin-top: $spacing-val-lg;}
+
+		.item-list {
+			background: $light-background;
+			border-top: 0;
+			padding: 0 0.5em;
+
+			li {
+				border-bottom: 1px solid $border-light;
+				&:last-child {border-bottom: 0;}
+
+				img,
+				h5 {
+					clear: none;
+					float: left;
+					> a {border-bottom: 0;}
+				}
+
+				span.small {
+					display: block;
+					float: none;
+					margin-top: $spacing-val-sm;
+					width: 100%;
+
+					a {
+						display: inline-block;
+						margin: $spacing-val-xs 0;
+						width: 100%;
+
+						@media screen and (min-width: 38.75em) {
+							width: auto;
+						}
+					}
+				}
+
+				h5 {
+					margin: 0;
+				}
+
+			}
+		}
+	}
+} // close .groups.manage-members
+
+/**
+	*------------------------------
+	* 6.2.1.2 - Group Members List
+	*------------------------------
+	*/
+
+#buddypress {
+	#members-group-list {
+		.item-list {
+			h5 {
+				display: inline-block;
+				margin: 0;
+			}
+
+			span.activity {
+				padding-left: $spacing-val-sm;
+			}
+		}
+	}
+} // close #buddypress
+
+#buddypress {
+	#item-body {
+		#subnav {
+			li.groups-members-search:focus,
+			li.groups-members-search:hover,
+			li.last:focus,
+			li.last:hover {
+				background: none;
+			}
+		}
+	}
+}
+
+// Need to target groups members via body classes
+.groups.group-members {
+	#subnav {
+
+		ul:before,
+		ul:after {
+			content: " ";
+			display: table;
+		}
+
+		ul:after {clear: both; }
+
+		ul {
+			padding: $spacing-val-xs 0;
+		}
+
+
+		li {
+
+			@media screen and (max-width: 400px) {
+				background: $content-background;
+				padding: $spacing-val-sm 0;
+				width: 100% !important;
+			}
+
+			#search-members-form {
+				@include box-model(border-box);
+				float: left;
+				@media screen and (max-width: 400px) {
+					margin: 0;
+					width: 100%;
+
+					label {
+						input[type="text"] {width: 100%;}
+					}
+				}
+				@media screen and (min-width: 783px) {
+					margin-left: 3px;
+				}
+			}
+
+		}
+
+	} // close #subnav
+} // close .groups.group-members
+
+/**
+	*------------------------------
+	* 6.2.1.3 - Groups Send Invites
+	*------------------------------
+	*/
+
+.send-invites {
+	#buddypress {
+		#item-body {
+			@media screen and (min-width: 783px) {
+				border-top: 1px solid $nav-background-border;
+			}
+
+			@media screen and (max-width: 400px) {
+				.left-menu {
+					float: none;
+
+					#invite-list {
+						height: auto;
+						width: auto;
+					}
+				}
+
+				.main-column {
+					margin-left: 0;
+				}
+			}
+
+			li {
+				img.avatar {
+					display: block;
+					float: none;
+					margin: $spacing-val-sm auto;
+				}
+
+				.action {
+					margin-top: $spacing-val-sm;
+					text-align: left;
+					width: 100%;
+
+					@media screen and (min-width: 38.75em) {
+						text-align: right;
+					}
+				}
+			}
+
+			@media screen and (min-width: 594px) {
+				#invite-list {width: 160px;}
+				.main-column {margin-left: 190px;}
+
+				li {
+					img.avatar {
+						float: left;
+						margin: 0 $spacing-val-sm 0 0;
+					}
+				}
+			}
+
+		}
+	}
+}
+
+/**
+*-------------------------------------------------
+* @subsection 6.2.2 - item-body: User Accounts
+*-------------------------------------------------
+*/
+
+
+/**
+	*------------------------------
+	* 6.2.2.2 - Extended Profiles
+	*------------------------------
+	*/
+
+
+#buddypress {
+	.profile {
+
+		.bp-widget {
+			h4 {
+				background: $medium-background;
+				color: #fff;
+				padding: 0.4em 0.2em;
+			}
+		}
+
+		// Edit profile
+
+		#profile-edit-form {
+			.button-nav:before,
+			.button-nav:after {
+				content: " ";
+				display: table;
+			}
+
+			.button-nav:after {
+				clear: both;
+			}
+
+			ul.button-nav {
+				border-bottom: 1px solid $border-light;
+				margin-left: 0;
+
+				li {
+					float: left;
+					margin-bottom: 0;
+
+					&.current {
+						border: 1px solid $border-light;
+						border-bottom-color: #fff;
+						margin-bottom: -1px;
+					}
+				} // li
+
+				a {
+					background: none;
+					border: 0;
+					@include font-size(18)
+				}
+			} //.button-nav
+
+			.field-visibility-settings-toggle,
+			.field-visibility-settings {
+				@include font-size(14);
+			}
+
+			.field-visibility-settings-close,
+			.visibility-toggle-link {
+				background: $dark-background;
+				color: #fff;
+				padding: 0.2em 0.5em;
+			}
+		} // close profile form
+
+	} // close .profile
+} // close #buddypress - user acounts item-body
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.5 - Messaging
+	*------------------------------
+	*/
+
+#buddypress {
+
+	table#message-threads {
+		border-left: 1px solid $border-med;
+		border-right: 1px solid $border-med;
+
+		tbody > tr:first-child {
+			td.bulk-select-check,
+			td.thread-from {
+				border-top: 1px solid $border-med;
+		 }
+		}
+
+		tr {
+
+			td {
+				background: $content-background;
+				border-right: 0 !important;
+				@include box-model(border-box);
+				display: inline-block;
+				float: left;
+			}
+
+			td.thread-star,
+			td.thread-options {
+				border-bottom-color: darken($border-med, 5%);
+				border-bottom-width: 2px;
+				border-left: 0;
+				height: 2.4em;
+				padding-bottom: 0.2em;
+				padding-top: 0.2em;
+
+				@media screen and (max-width: 549px) {
+					padding-top: 0;
+				}
+			}
+
+			td.bulk-select-check,
+			td.thread-from {
+				border-left: 0;
+				height: 2.6em;
+				@media screen and (max-width: 320px) {
+					height: 5.2em;
+				}
+			}
+
+			td.thread-from,
+			td.thread-options {
+				border-left: 0;
+				@include calc(width, "100% - 30px");
+				margin-left: 0;
+			}
+
+			td.thread-info {
+				border-left: 0;
+				padding-left: 41px;
+				width: 100%;
+
+				a::after {
+					content: " \2013 \00a0"attr(title);
+					@include font-size(12);
+				}
+			}
+
+			td.thread-options {
+				text-align: right;
+
+				a {
+					@include font-size(12);
+					line-height: 2.2;
+				}
+			}
+
+			span.from {
+				display: none;
+			}
+
+			span.activity {
+				display: block;
+				float: right;
+				line-height: 2;
+				@media screen and (max-width: 320px) {
+					clear: both;
+					@include font-size(11);
+					width: 100%;
+				}
+			}
+		}
+
+		tr.unread {
+			td {
+				background: lighten($unread, 7%);
+				border-color: $border-med;
+				border-top: 0;
+				border-width: 1px;
+			}
+		}
+
+		th {
+			display: none;
+		}
+
+		th.bulk-select-all {
+			border-bottom: 0;
+			display: inline-block;
+			text-align: left;
+		}
+
+		th.bulk-select-all,
+		td.bulk-select-check,
+		td.thread-star {
+			border-right: 0;
+			width: 30px;
+		}
+
+	} // close #messages-threads
+
+	/* Read Single thread */
+	#message-thread {
+
+		#message-subject {
+			@include title-background-dark();
+			padding: 0.5em;
+		}
+
+		.message-box {
+			.message-metadata {
+				margin-bottom: $spacing-val-sm;
+			}
+
+			.message-content {
+				clear: left;
+				margin-left: 0;
+			}
+		}
+	}
+
+	/* Sitewide Notices loop */
+	#message-threads.sitewide-notices {
+		td {
+			width: 100%;
+
+			strong {
+				@include title-background-dark();
+				@include font-size(16);
+				display: block;
+				margin-bottom: 0.4em;
+				padding: 0.2em;
+			}
+
+			a {
+				display: inline-block;
+				line-height: initial;
+			}
+		}
+
+		td:first-child {
+			display: none;
+		}
+
+		td:first-child + td + td {
+		// notice date
+			border-bottom: 0;
+
+			span {
+				line-height: 1;
+			}
+		}
+
+		td:last-child {
+		// notice actions
+			border-bottom-color: $border-med;
+			line-height: 1;
+			text-align: right;
+
+			a:last-child {
+			// hide the 'x' text
+				line-height: 0;
+				margin-left: 0.7em;
+				text-indent: -999em;
+			}
+
+			a:last-child::after {
+				content: attr(title);
+				display: block;
+				line-height: initial;
+				text-indent: 0;
+			}
+		}
+	}
+
+} // close #buddypress - user acounts item-body
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+
+#buddypress {
+	#settings-form {
+		// 'p' = email notification screen sub heading
+		> p { @include font-size(20); }
+	}
+
+	table.notification-settings {
+		td.yes,
+		td.no {
+			vertical-align: middle;
+		}
+	}
+	// Profile table td widths
+	table.profile-settings {
+		width: 100%;
+
+		th.field-group-name,
+		td.field-name {
+			width: 50%;
+		}
+
+		@media screen and (min-width: 401px) {
+			th.field-group-name,
+			td.field-name {
+				width: 70%;
+			}
+		}
+
+		td.field-visibility {
+			select {width: 100%;}
+		}
+	}
+} // close #buddypress - user acounts item-body
+
+/*------------------------------------------------------------------------------
+* @section - 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+
+// Match BP form elements to Themes(2014) styling where styles
+// are not inherited or being overriden.
+#buddypress {
+
+	input[type="text"],
+	input[type="email"],
+	input[type="password"],
+	input[type="submit"],
+	select,
+	textarea {
+		border: 1px solid $border-med;
+	}
+
+	.standard-form {
+		input[type="text"],
+		input[type="email"],
+		input[type="password"],
+		input[type="submit"],
+		textarea {
+			width: 100%;
+		}
+	}
+
+	// BP :(
+	#send-reply.standard-form textarea {width: 100%;}
+
+	div.activity-comments {
+		form {
+			.ac-textarea {
+				background: $light-background;
+				border: 1px solid $border-med;
+
+				textarea {
+					background: none;
+					border: 0;
+				}
+			}
+		}
+	}
+
+} // close #buddypress
+
+#buddypress {
+
+	div.dir-search,
+	div.message-search,
+	li.groups-members-search {
+		clear: both;
+		float: none;
+		margin: $spacing-val-sm 0;
+
+// Stylise the seach form elements, in part this deals with padding
+// issues on the submit & sizing issues between bp styles & twentyfourteen
+		form {
+			border: 1px solid #ccc;
+			overflow: hidden;
+
+			label {
+				float: left;
+				width: 80%;
+			}
+
+			input[type='text'] {
+				float: left;
+				margin: 0;
+				width: 80%;
+			}
+
+			input[type='text'],
+			input[type='submit'] {
+				border: 0;
+				@include font-size(14);
+				line-height: inherit;
+			}
+
+			input[type='text'] {
+				border-right: 1px solid #ccc;
+				padding: 0.2em 0 0.2em 0.2em;
+			}
+
+			input[type='submit'] {
+				float: right;
+				font-weight: normal;
+				padding: 0.2em 1em;
+				text-align: center;
+				text-transform: none;
+				width: 20%;
+			}
+
+			}// close form
+
+	} // close .dir-search, .message-search
+
+	// Shift the search parent to the right and allow to shrinkwrap
+	@media screen and (min-width: 401px) {
+		div.dir-search,
+		div.message-search {
+			clear: none;
+			float: right;
+
+			form {
+				label,
+				input[type='submit'],
+				input[type='text'] {
+					width: auto;
+				}
+			}
+		}
+
+		div.message-search { margin: 3px 0 0;}
+
+		li.groups-members-search {
+			form {
+				label,
+				input[type='submit'],
+				input[type='text'] {
+					width: auto;
+				}
+			}
+		}
+
+	} // close @media
+
+} // close #buddypress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables General
+*-------------------------------------------------------------------------------
+*/
+#buddypress {
+	table {
+		border-color: $border-med;
+	}
+}
+/*__ User Account tables __*/
+
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+// message args ($variable-color, text-color)
+// Leave text-color undefined to automagically set text color
+// to background color darker by 50%
+#buddypress {
+
+	div#message {
+		p {
+			@include font-size(18);
+			font-weight: bold;
+		}
+
+		&.info {
+			p {
+				@include message-box($notice-info);
+			}
+		}
+
+		&.updated {
+			p {
+				@include message-box($notice-update);
+			}
+		}
+
+	} // close #message
+
+} // close  #buddypress
+
+// Without direct classes on our messages ( 'warning' )
+// we need to use the body classes
+// todo: If this is class .warning is included in main BP styles remove from here
+.delete-group {
+	#buddypress {
+		div#message.info {
+			p {
+				@include message-box($notice-warning);
+			}
+		}
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+
+#buddypress {
+	/* Auto Complete background */
+	.acfb-holder {
+
+		li.friend-tab {
+			background: $notice-info;
+			border: inherit;
+	 }
+	}
+}
+
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentysixteen-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentysixteen-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentysixteen-rtl.css	(revision 10410)
@@ -0,0 +1,1870 @@
+@charset "UTF-8";
+/*--------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentysixteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentysixteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the rulesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+----------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+----------------------------------------------------------------
+1.0 Theme Structural Elements & overrides
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 Whats New Activity
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Register, activation
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - item-header: Groups
+		6.1.2 - item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1 - Management settings screens
+			6.2.1.2 - Group members list
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading, Widgets, General classes
+--------------------------------------------------------------*/
+/**
+*-------------------------------------------------------------------------------
+* @section 1.0 - Theme - Structural Elements. overrides, Globals
+*-------------------------------------------------------------------------------
+*/
+.buddypress div.clear {
+	display: none;
+}
+
+.buddypress #page a {
+	box-shadow: none;
+	text-decoration: none !important;
+}
+
+.buddypress .entry-title {
+	text-align: center;
+}
+
+@media screen and (min-width: 61.5625em) {
+	.buddypress .entry-title {
+		text-align: right;
+	}
+}
+
+@media screen and (min-width: 44.375em) {
+	.buddypress #primary {
+		float: none;
+		margin: 0;
+		width: auto;
+	}
+	.buddypress #primary .entry-header {
+		margin: 0;
+	}
+	.buddypress #primary .entry-content,
+	.buddypress #primary .content-bottom-widgets {
+		margin-right: 7.6923%;
+		margin-left: 7.6923%;
+	}
+	.buddypress .sidebar {
+		float: none;
+		margin-right: 0;
+		padding: 0 7.6923%;
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 61.5625em) {
+	.buddypress #primary {
+		float: right;
+		margin-left: -100%;
+		width: 70%;
+	}
+	.buddypress #primary .entry-content,
+	.buddypress #primary .content-bottom-widgets {
+		margin-right: 0;
+		margin-left: 0;
+	}
+	.buddypress .sidebar {
+		float: right;
+		margin-right: 75%;
+		padding: 0;
+		width: 25%;
+	}
+}
+
+.buddypress.no-sidebar #primary {
+	float: none;
+	margin: 0;
+	width: auto;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+/**
+*----------------------------------------------------------
+* @section 2.1 - Navs Object Nav / Sub Nav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+#buddypress div.item-list-tabs ul li.selected a {
+	background: inherit;
+	opacity: 1;
+}
+
+#buddypress div.item-list-tabs ul {
+	background-color: #f7f5e7;
+	border-bottom: 1px solid rgba(234, 234, 234, 0.9);
+	border-top: 1px solid rgba(234, 234, 234, 0.9);
+	overflow: hidden;
+	padding: 0;
+}
+
+#buddypress div.item-list-tabs ul li a span {
+	border-radius: 25%;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #object-nav {
+		background: #f7f5e7;
+		border: 1px solid #eaeaea;
+		overflow: visible;
+		padding: 10px;
+	}
+	.bp-user #buddypress #object-nav ul {
+		border: 0;
+		height: 0;
+		transition: height 0.3s ease-in-out 0.7s;
+		visibility: hidden;
+	}
+	.bp-user #buddypress #object-nav:before {
+		content: "Menu \021E9";
+		display: inline;
+		opacity: 0.7;
+	}
+	.bp-user #buddypress #object-nav:hover:before,
+	.bp-user #buddypress #object-nav:focus:before {
+		content: "Menu \021E7";
+	}
+	.bp-user #buddypress #object-nav:hover ul,
+	.bp-user #buddypress #object-nav:focus ul {
+		height: 320px;
+		opacity: 1;
+		overflow-y: auto;
+		visibility: visible;
+	}
+	.bp-user #buddypress #subnav {
+		clear: both;
+	}
+}
+
+/*__ Horizontal menus __*/
+#buddypress #object-nav ul {
+	overflow: hidden;
+}
+
+#buddypress #object-nav ul li {
+	float: none;
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress #object-nav ul li:not(:last-child) {
+		border-bottom: 1px solid #eaeaea;
+	}
+}
+
+#buddypress #object-nav ul li:not(.selected) a {
+	opacity: 0.7;
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress #object-nav ul li:not(.selected):focus,
+	#buddypress #object-nav ul li:not(.selected):hover {
+		background: #f4f2df;
+	}
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #object-nav ul li {
+		float: right;
+	}
+}
+
+#buddypress div#subnav.item-list-tabs {
+	margin-top: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul {
+	background-color: #f7f7f7;
+	border-bottom: 0;
+	margin-top: 5px;
+	padding: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last {
+	background: #fff;
+	margin-top: 0;
+	padding: 5px 0 5px 5px;
+	width: 100%;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last select:focus {
+	background: transparent;
+	border: 0;
+	outline: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last label,
+#buddypress div#subnav.item-list-tabs ul li.last option {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select {
+	font-style: italic;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div#subnav.item-list-tabs ul li.last {
+		text-align: left;
+	}
+}
+
+/*__ Vertical menu User Account / Group single screens __*/
+@media screen and (min-width: 55em) {
+	body.no-sidebar #buddypress #item-header,
+	body.no-sidebar #buddypress #item-body {
+		background: #fff;
+	}
+	body.no-sidebar #buddypress #object-nav {
+		border-left: 1px solid #ddd;
+		float: right;
+		margin-left: -1px;
+		width: 200px;
+	}
+	body.no-sidebar #buddypress #object-nav ul {
+		background: none;
+		border-bottom: 0;
+		padding: 0;
+	}
+	body.no-sidebar #buddypress #object-nav ul li {
+		float: none;
+		overflow: hidden;
+	}
+	body.no-sidebar #buddypress #object-nav ul li span {
+		background: #fff;
+		border-radius: 10%;
+		float: left;
+		margin-left: 2px;
+	}
+	body.no-sidebar #buddypress #item-body {
+		border-right: 1px solid #ddd;
+		overflow: hidden;
+		padding: 0 20px 0 0;
+		width: auto;
+	}
+	body.no-sidebar #buddypress #item-body #subnav {
+		margin: 0 -20px 0 0;
+	}
+	body.no-sidebar #buddypress #item-body #subnav ul {
+		margin-top: 0;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	#main #buddypress #group-create-tabs.item-list-tabs ul:before,
+	#main #buddypress #group-create-tabs.item-list-tabs ul:after {
+		content: " ";
+		display: table;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul:after {
+		clear: both;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul {
+		background: none;
+		border: 0;
+		border-bottom: 1px solid #ddd;
+		overflow: visible;
+		padding-bottom: 0;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li {
+		float: right;
+		width: auto;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.selected,
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.current {
+		border: 1px solid #ddd;
+		border-bottom-color: #fff;
+		-webkit-border-top-left-radius: 4px;
+		border-top-left-radius: 4px;
+		-webkit-border-top-right-radius: 4px;
+		border-top-right-radius: 4px;
+		background-clip: padding-box;
+		margin-bottom: -1px;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.selected a,
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.current a {
+		background: none;
+		color: #141412;
+		outline: none;
+	}
+	#main #buddypress #subnav ul {
+		border-bottom: 0;
+	}
+}
+
+/**
+*----------------------------------------------------------
+* @section 2.2 - Pagination
+*----------------------------------------------------------
+*/
+#buddypress div.pagination {
+	box-shadow: none;
+	min-height: 0;
+}
+
+#buddypress div.pagination:before,
+#buddypress div.pagination:after {
+	height: 0;
+	width: 0;
+}
+
+#buddypress div.pagination .pag-count {
+	margin-right: 0;
+}
+
+#buddypress div.pagination .pagination-links {
+	margin-left: 0;
+}
+
+#buddypress div.pagination .pagination-links span,
+#buddypress div.pagination .pagination-links a {
+	height: auto;
+	line-height: 1;
+	padding: 5px;
+}
+
+#buddypress div.pagination .pagination-links .prev,
+#buddypress div.pagination .pagination-links .next {
+	background-color: transparent;
+	color: inherit;
+	overflow: visible;
+	width: auto;
+}
+
+#buddypress div.pagination .pagination-links .prev:before,
+#buddypress div.pagination .pagination-links .next:before {
+	display: none;
+}
+
+#buddypress div.pagination .pagination-links .prev {
+	right: auto;
+	position: static;
+}
+
+#buddypress div.pagination .pagination-links .next {
+	position: static;
+	left: auto;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+#buddypress .item-list .activity-header,
+#buddypress .item-list .activity-meta {
+	font-family: "Source Sans Pro", Helvetica, sans-serif;
+}
+
+#buddypress .activity-meta .button:focus,
+#buddypress .activity-meta .button:hover {
+	background: inherit;
+	color: #000;
+}
+
+#buddypress .action .generic-button a:focus,
+#buddypress .action .generic-button a:hover {
+	background: inherit;
+	color: #000;
+}
+
+#buddypress ul.item-list li {
+	overflow: hidden !important;
+}
+
+#buddypress ul.item-list li .item-avatar {
+	margin-bottom: 10px;
+	text-align: center;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item-avatar {
+		margin-bottom: 0;
+	}
+}
+
+#buddypress ul.item-list li .item-avatar a {
+	border-bottom: 0;
+}
+
+#buddypress ul.item-list li .item-avatar img.avatar {
+	display: inline-block;
+	float: none;
+	margin-bottom: 10px;
+}
+
+@media screen and (min-width: 44.375em) {
+	#buddypress ul.item-list li .item-avatar img.avatar {
+		display: block;
+		float: right;
+	}
+}
+
+#buddypress ul.item-list li .item {
+	overflow: hidden;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress ul.item-list li .item {
+		margin-right: 15%;
+	}
+}
+
+#buddypress ul.item-list li .item span.activity {
+	font-style: italic;
+}
+
+#buddypress ul.item-list li .item .item-desc {
+	margin-right: 0;
+	width: 94%;
+}
+
+#buddypress ul.item-list li .item .item-title {
+	font-size: 18px;
+	font-size: 1.125rem;
+	line-height: 1.2;
+	text-align: center;
+	width: 100%;
+}
+
+@media screen and (min-width: 44.375em) {
+	#buddypress ul.item-list li .item .item-title {
+		text-align: right;
+	}
+}
+
+#buddypress ul.item-list li .item .item-title .update {
+	display: block;
+	font-size: 12px;
+	font-size: 0.75rem;
+	padding: 10px 0;
+	text-align: right;
+}
+
+@media screen and (min-width: 61.5625em) {
+	#buddypress ul.item-list li .item .item-title .update {
+		font-size: 14px;
+		font-size: 0.875rem;
+	}
+}
+
+@media screen and (min-width: 61.5625em) {
+	#buddypress ul.item-list li .item-avatar,
+	#buddypress ul.item-list li .item,
+	#buddypress ul.item-list li .action {
+		float: right;
+	}
+	#buddypress ul.item-list li .item {
+		right: 5%;
+		margin-right: 0;
+		position: relative;
+		width: 55%;
+	}
+	#buddypress ul.item-list li .item .item-title {
+		font-size: 22px;
+		font-size: 1.375rem;
+	}
+}
+
+#buddypress ul.item-list li div.action {
+	clear: right;
+	float: none;
+	margin-bottom: -20px;
+	margin-right: 0;
+	padding: 20px 0 5px;
+	position: relative;
+	text-align: right;
+	top: 0;
+}
+
+@media screen and (min-width: 61.5625em) {
+	#buddypress ul.item-list li div.action {
+		clear: none;
+		float: left;
+		margin-bottom: 0;
+		padding: 0;
+	}
+}
+
+#buddypress ul.item-list li div.action div {
+	display: inline-block;
+	margin: 10px 0;
+	width: 100%;
+}
+
+#buddypress ul.item-list li div.action div a {
+	display: block;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li div.action div {
+		margin: 0 0 10px 10px;
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 61.5625em) {
+	#buddypress ul.item-list li div.action div {
+		clear: left;
+		float: left;
+		margin: 0 0 10px 0;
+	}
+}
+
+#buddypress ul.item-list li div.action .meta {
+	font-style: italic;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.1 - Activity
+*----------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------
+* @section 4.1.1 - Activity Whats New
+*-----------------------------------------------------
+*/
+#buddypress form#whats-new-form p.activity-greeting {
+	line-height: 1.4;
+}
+
+@media screen and (max-width: 46.25em) {
+	#buddypress form#whats-new-form #whats-new-content {
+		clear: right;
+		margin: 10px 0 20px;
+		padding: 10px 0 0;
+	}
+}
+
+#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box {
+	border: 1px solid rgba(212, 208, 186, 0.5);
+	float: right;
+	line-height: 1.5em;
+	margin-top: 12px;
+	padding-right: 0.2em;
+	width: 100%;
+}
+
+#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box select {
+	background: none;
+	border: 0;
+	float: left;
+	margin: 0;
+	min-height: 1.5em;
+	padding-right: 0.4em;
+}
+
+@media screen and (min-width: 30em) {
+	#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box {
+		width: auto;
+	}
+	#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-submit {
+		float: left;
+	}
+}
+
+#buddypress #item-body form#whats-new-form {
+	margin: 40px 0;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.2 - Activity Listing
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream li {
+	padding: 25px 0 15px;
+}
+
+#buddypress #activity-stream li .activity-avatar {
+	float: none;
+	text-align: center;
+}
+
+#buddypress #activity-stream li .activity-avatar a {
+	display: inline-block;
+}
+
+#buddypress #activity-stream li .activity-avatar a img.avatar {
+	display: inline;
+	float: none;
+	height: 60px;
+	margin-bottom: 20px;
+	margin-right: 0;
+	width: 60px;
+}
+
+#buddypress #activity-stream li .activity-content {
+	margin-right: 0;
+}
+
+#buddypress #activity-stream li .activity-content .activity-header {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress #activity-stream li .activity-avatar {
+		float: right;
+		margin-left: 10px;
+		text-align: right;
+	}
+	#buddypress #activity-stream li .activity-avatar a {
+		border-bottom: 0;
+	}
+	#buddypress #activity-stream li .activity-content {
+		margin: 0;
+		overflow: hidden;
+	}
+	#buddypress #activity-stream li .activity-content .activity-header {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+#buddypress #activity-stream li.mini .activity-avatar a img.avatar {
+	height: 30px;
+	margin-right: 15px;
+	width: 30px;
+}
+
+#buddypress #activity-stream li.mini .activity-content .activity-header {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+#buddypress #activity-stream .activity-content {
+	margin-top: -12px;
+}
+
+#buddypress #activity-stream .activity-content .activity-header {
+	line-height: inherit;
+	margin-left: 0;
+}
+
+#buddypress #activity-stream .activity-content .activity-header p {
+	background-color: #f7f7f7;
+	border: 1px solid rgba(234, 234, 234, 0.6);
+	margin-top: 0;
+	padding: 0 0.2em;
+}
+
+#buddypress #activity-stream .activity-content .activity-header img.avatar {
+	display: inline-block;
+	margin-bottom: 0 !important;
+}
+
+#buddypress #activity-stream .activity-content .activity-meta a {
+	display: block;
+	margin-bottom: 5px;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #activity-stream .activity-content .activity-meta a {
+		display: inline-block;
+		margin-bottom: 0;
+	}
+}
+
+#buddypress #activity-stream .load-more {
+	background: #f7f7f7;
+	border: 1px solid transparent;
+	padding: 10px;
+}
+
+#buddypress #activity-stream .load-more:focus,
+#buddypress #activity-stream .load-more:hover {
+	background: #f4f4f4;
+	border: 1px solid rgba(159, 209, 226, 0.3);
+}
+
+#buddypress #activity-stream .load-more:focus a,
+#buddypress #activity-stream .load-more:hover a {
+	font-style: italic;
+}
+
+#buddypress #activity-stream .load-more a {
+	display: block;
+}
+
+/* Single activity view  - activity permalink */
+.activity-permalink #buddypress #activity-stream li.activity-item {
+	padding: 20px;
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+	font-size: 16px;
+	font-size: 1rem;
+	margin-bottom: 40px;
+}
+
+@media screen and (min-width: 46.25em) {
+	.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+		font-size: 20px;
+		font-size: 1.25rem;
+	}
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header p {
+	padding: 20px;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.3 - Activity Comments
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream .activity-comments {
+	border-right: 1px solid #eaeaea;
+	margin-top: 5px;
+}
+
+#buddypress #activity-stream .activity-comments ul {
+	background: rgba(247, 247, 247, 0.6);
+	margin: 15px 2px 0 0;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form input[type="submit"],
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	color: rgba(20, 20, 18, 0.8);
+	display: inline-block;
+	font-family: inherit;
+	font-size: 12px;
+	font-size: 0.75rem;
+	font-weight: normal;
+	line-height: 1.2;
+	padding: 4px 10px;
+	text-transform: lowercase;
+	width: 100px;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	border: 1px solid rgba(212, 208, 186, 0.7);
+	text-align: center;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:focus,
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:hover {
+	background: #ededed;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.2 - Members Loop
+*----------------------------------------------------------
+*/
+@media screen and (min-width: 61.5625em) {
+	#buddypress #members-list li .item-avatar,
+	#buddypress #members-list li .item {
+		float: right;
+	}
+	#buddypress #members-list li .action {
+		float: left;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 5.0 - Directories
+*-------------------------------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------------
+* @subsection Registration
+*-----------------------------------------------------------
+*/
+#buddypress #signup_form.standard-form #profile-details-section,
+#buddypress #signup_form.standard-form #basic-details-section,
+#buddypress #signup_form.standard-form #blog-details-section {
+	float: none;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #signup_form.standard-form #profile-details-section,
+	#buddypress #signup_form.standard-form #basic-details-section,
+	#buddypress #signup_form.standard-form #blog-details-section {
+		width: 48%;
+	}
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #signup_form.standard-form #profile-details-section {
+		float: left;
+	}
+	#buddypress #signup_form.standard-form #basic-details-section {
+		float: right;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens: User Account & Single Group Screens
+*-------------------------------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------------
+* @subsection 6.1 - Item Header Global
+*-----------------------------------------------------------
+*/
+.bp-user #buddypress #item-header-content #item-meta,
+.single-item.groups #buddypress #item-header-content #item-meta {
+	font-size: 14px;
+	font-size: 0.875rem;
+	text-align: right;
+}
+
+.bp-user #buddypress #item-header-content #item-meta p,
+.single-item.groups #buddypress #item-header-content #item-meta p {
+	margin-bottom: 0.5em;
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user main header.entry-header,
+	.single-item.groups main header.entry-header {
+		padding-bottom: 1rem;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user h1,
+	.bp-user #item-header-content,
+	.single-item.groups h1,
+	.single-item.groups #item-header-content {
+		text-align: center;
+	}
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #item-header .generic-button,
+	.single-item.groups #buddypress #item-header .generic-button {
+		float: none;
+		margin: 1.5em 0 0 0;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress h1,
+	.single-item.groups #buddypress h1 {
+		margin-bottom: 0;
+	}
+	.bp-user #buddypress #item-header-avatar img.avatar,
+	.single-item.groups #buddypress #item-header-avatar img.avatar {
+		margin-left: 0;
+	}
+	.bp-user #buddypress #item-header-content,
+	.single-item.groups #buddypress #item-header-content {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.1 - item-header Groups
+*
+* Group Specific Item Header
+*-----------------------------------------------------
+*/
+@media screen and (max-width: 46.25em) {
+	.single-item.groups #buddypress #item-header #item-meta {
+		margin-bottom: 20px;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header {
+		display: flex;
+		flex-direction: column;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		order: 1;
+		text-align: center;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a {
+		border-bottom: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a img {
+		display: inline-block;
+		float: none;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		order: 2;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions {
+		order: 3;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions h3 {
+		border-bottom: 1px solid #eaeaea;
+		text-align: center;
+	}
+}
+
+.single-item.groups #buddypress div#item-header {
+	padding-bottom: 40px;
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions {
+	margin: 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		border-right: 1px solid #eaeaea;
+		clear: none;
+		float: left;
+		padding-right: 0.2em;
+		width: 30%;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		width: 40%;
+	}
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions ul {
+	margin-top: 0;
+	padding-right: 0;
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions h3 {
+	font-size: 14px;
+	font-size: 0.875rem;
+	padding: 0.2em;
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header div#item-actions h3 {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header #item-header-avatar,
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		float: right;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		width: 21%;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		margin-right: 4%;
+		width: 40%;
+	}
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		float: left;
+		width: 28%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.2 - Item Header User Accounts
+*
+* User Accounts Specific Item Header
+*-----------------------------------------------------
+*/
+.bp-user #buddypress #item-header {
+	padding: 20px 0;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar {
+	text-align: center;
+	width: 100%;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar img.avatar,
+.bp-user #buddypress #item-header #item-header-avatar a {
+	border-bottom: 0;
+	display: inline-block;
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #item-header #item-header-avatar {
+		float: right;
+		width: 20%;
+	}
+	.bp-user #buddypress #item-header #item-header-avatar a {
+		float: right;
+	}
+	.bp-user #buddypress #item-header #item-header-content {
+		float: left;
+		margin-left: 5%;
+		width: 69%;
+	}
+}
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*-----------------------------------------------------------
+*/
+/**
+*----------------------------------------------------
+* @subsection 6.2.1 - Item Body Groups
+*
+* Groups specific item body rules -  screens
+*----------------------------------------------------
+*/
+/**
+*-----------------------------------------
+* @subsection 6.2.1.1 - Group Management
+*-----------------------------------------
+*/
+.groups #group-settings-form h4 {
+	background: #555;
+	color: #fff;
+	padding: 0.2em;
+}
+
+.groups.edit-details #group-settings-form label {
+	margin-bottom: 0;
+	padding: 0.2em;
+	width: 80%;
+}
+
+.groups.edit-details #group-settings-form textarea + p label {
+	background: none;
+	color: inherit;
+	font-size: 14px;
+	font-size: 0.875rem;
+	width: auto;
+}
+
+.groups.edit-details #group-settings-form textarea {
+	height: auto;
+	min-height: 100px;
+	overflow: auto;
+}
+
+.groups.group-settings #group-settings-form div.radio label {
+	border: 1px solid #eaeaea;
+	padding: 0.2em;
+}
+
+.groups.group-settings #group-settings-form div.radio label ul {
+	color: rgba(20, 20, 18, 0.6);
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.groups.group-avatar form > p {
+	margin-top: 20px;
+}
+
+.groups.manage-members #group-settings-form .item-list li {
+	border-bottom: 1px solid #eaeaea;
+}
+
+.groups.manage-members #group-settings-form .item-list li img,
+.groups.manage-members #group-settings-form .item-list li h5 {
+	float: right;
+}
+
+.groups.manage-members #group-settings-form .item-list li img > a,
+.groups.manage-members #group-settings-form .item-list li h5 > a {
+	border-bottom: 0;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small {
+	clear: right;
+	display: block;
+	float: none;
+	margin-top: 10px;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small a {
+	display: inline-block;
+	margin: 5px 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.groups.manage-members #group-settings-form .item-list li span.small a {
+		width: auto;
+	}
+}
+
+.groups.manage-members #group-settings-form .item-list li h5 {
+	margin: 0;
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.2 - Group members
+*-----------------------------------------
+*/
+.groups.group-members #subnav li {
+	width: 100%;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li {
+		background: #fff;
+		padding: 20px 0;
+	}
+}
+
+.groups.group-members #subnav li #search-members-form {
+	float: left;
+	margin: 5px 0 0 5px;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li #search-members-form {
+		margin: 0;
+		width: 100%;
+	}
+	.groups.group-members #subnav li #search-members-form label input[type="text"] {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.2.2 - Item Body User Accounts
+*
+* User Account specific item body rules
+*-----------------------------------------------------
+*/
+.bp-user .entry-title {
+	margin-bottom: 0.5em;
+}
+
+/**
+*--------------------------------------------
+* @subsection 6.2.2.1 - classes, pag, filters
+*--------------------------------------------
+*/
+.bp-user #buddypress table th {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.bp-user #buddypress table td {
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+@media screen and (min-width: 61.5625em) {
+	.bp-user #buddypress table th {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 14px;
+		font-size: 0.875rem;
+	}
+}
+
+@media screen and (min-width: 75em) {
+	.bp-user #buddypress table th {
+		font-size: 18px;
+		font-size: 1.125rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+.bp-user #buddypress .pag-count {
+	font-style: italic;
+}
+
+.bp-user #buddypress .notifications-options-nav {
+	border: 1px solid rgba(212, 208, 186, 0.5);
+	float: right;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.bp-user #buddypress .notifications-options-nav {
+		width: 300px;
+	}
+}
+
+.bp-user #buddypress .notifications-options-nav select,
+.bp-user #buddypress .notifications-options-nav input {
+	border: 0;
+	font-size: 14px;
+	font-size: 0.875rem;
+	outline: 0;
+	padding: 0;
+}
+
+.bp-user #buddypress .notifications-options-nav select {
+	float: right;
+	margin-left: 0;
+	width: 60%;
+}
+
+.bp-user #buddypress .notifications-options-nav input {
+	float: left;
+	font-family: inherit;
+	line-height: 20px;
+	width: 40%;
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.2 - Extended Profiles
+	*-------------------------------------------
+	*/
+.bp-user #buddypress .profile {
+	/* Edit profile */
+}
+
+.bp-user #buddypress .profile .bp-widget h4 {
+	background: #6e6e6e;
+	color: #fff;
+	margin-bottom: 0;
+	padding: 0.4em;
+}
+
+.bp-user #buddypress .profile .bp-widget table {
+	margin-top: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .button-nav:before,
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	content: " ";
+	display: table;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	clear: both;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav {
+	border-bottom: 1px solid #eaeaea;
+	margin-right: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li {
+	float: right;
+	margin-bottom: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li.current {
+	border: 1px solid #eaeaea;
+	border-bottom-color: #fff;
+	margin-bottom: -1px;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav a {
+	background: none;
+	border: 0;
+	font-size: 18px;
+	font-size: 1.125rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-toggle,
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-close,
+.bp-user #buddypress .profile #profile-edit-form .visibility-toggle-link {
+	background: #555;
+	color: #fff;
+	padding: 0.2em 0.5em;
+}
+
+.bp-user #buddypress .profile .bp-avatar #bp-delete-avatar a {
+	font-size: inherit;
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.3 - Groups
+	*-------------------------------------------
+	*/
+@media screen and (min-width: 77.5em) {
+	.bp-user #buddypress #groups-list li .item {
+		right: 5%;
+		width: 50%;
+	}
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.5 - Private Messaging
+	*-------------------------------------------
+	*/
+.bp-user #buddypress {
+	/* Sitewide Notices loop */
+}
+
+.bp-user #buddypress #message-thread a {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress #message-thread #message-subject {
+	background: #6e6e6e;
+	color: #fff;
+	padding: 0.3em 0.2em 0.3em 0;
+}
+
+.bp-user #buddypress #message-thread #message-recipients {
+	font-style: italic;
+}
+
+.bp-user #buddypress #message-thread #message-recipients a.confirm {
+	border: 1px solid #eaeaea;
+	font-style: normal;
+}
+
+.bp-user #buddypress #message-thread .message-metadata {
+	overflow: hidden;
+}
+
+.bp-user #buddypress #message-thread .message-metadata img.avatar {
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #message-thread .message-metadata img.avatar {
+		float: right;
+	}
+}
+
+.bp-user #buddypress #message-thread .message-metadata .message-star-actions {
+	float: left;
+	margin-left: 5px;
+	position: static;
+}
+
+.bp-user #buddypress #message-thread .message-content {
+	background: #f7f7f7;
+	border: 1px solid #eaeaea;
+	margin: 10px 0 0 0;
+	padding: 0.3em;
+}
+
+.bp-user #buddypress #message-thread #send-reply .message-content {
+	background: #fff;
+	border: 0;
+}
+
+.bp-user #buddypress #message-thread .alt {
+	background: #fff;
+}
+
+.bp-user #buddypress #message-threads thead tr {
+	background: #6e6e6e;
+}
+
+.bp-user #buddypress #message-threads tr td {
+	background: #fff;
+	display: inline-block;
+	float: right;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-star,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-bottom-color: #ccc;
+	border-bottom-width: 2px;
+	height: 2.4em;
+	padding-bottom: 0.2em;
+	padding-top: 0.2em;
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #message-threads tr td.thread-star,
+	.bp-user #buddypress #message-threads tr td.thread-options {
+		padding-top: 0;
+	}
+}
+
+.bp-user #buddypress #message-threads tr td.thread-star {
+	vertical-align: middle;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-star .message-action-star {
+	line-height: 1.2;
+}
+
+.bp-user #buddypress #message-threads tr td.bulk-select-check,
+.bp-user #buddypress #message-threads tr td.thread-from {
+	height: 3em;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr td.bulk-select-check,
+	.bp-user #buddypress #message-threads tr td.thread-from {
+		height: 5.2em;
+	}
+}
+
+.bp-user #buddypress #message-threads tr td.thread-from,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-right: 0 !important;
+	width: -webkit-calc(100% - 30px);
+	width: -moz-calc(100% - 30px);
+	width: calc(100% - 30px);
+	margin-right: 0;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info {
+	padding-right: 41px;
+	width: 100%;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info a::after {
+	content: " ‖  " attr(title);
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options {
+	text-align: left;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options a {
+	font-size: 12px;
+	font-size: 0.75rem;
+	line-height: 2.2;
+}
+
+.bp-user #buddypress #message-threads tr span.from {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads tr span.activity {
+	display: block;
+	float: left;
+	line-height: 2;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr span.activity {
+		clear: both;
+		font-size: 11px;
+		font-size: 0.6875rem;
+		width: 100%;
+	}
+}
+
+.bp-user #buddypress #message-threads tr.unread td {
+	background: #dce5ff;
+	border-color: #d4d0ba;
+}
+
+.bp-user #buddypress #message-threads th {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all {
+	border-bottom: 0;
+	display: inline-block;
+	text-align: right;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all,
+.bp-user #buddypress #message-threads td.bulk-select-check,
+.bp-user #buddypress #message-threads td.thread-star {
+	border-left: 0;
+	width: 30px;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td {
+	width: 100%;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td strong {
+	background: #6e6e6e;
+	color: #fff;
+	display: block;
+	margin-bottom: 0.4em;
+	padding-right: 0.2em;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td a {
+	display: inline-block;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:nth-child(2) strong {
+	margin: -8px -8px 8px;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child + td + td {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child + td + td span {
+	line-height: 1;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child {
+	border-bottom-color: #b7b7b7;
+	line-height: 1;
+	text-align: left;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child a:last-child {
+	line-height: 0;
+	margin-right: 0.7em;
+	text-indent: -999em;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child a:last-child::after {
+	content: attr(title);
+	display: block;
+	line-height: initial;
+	text-indent: 0;
+}
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+.bp-user #buddypress #settings-form > p {
+	font-size: 20px;
+	font-size: 1.25rem;
+	margin: 20px 0 10px;
+}
+
+.bp-user #buddypress table.notification-settings td.yes,
+.bp-user #buddypress table.notification-settings td.no {
+	vertical-align: middle;
+}
+
+.bp-user #buddypress table.profile-settings {
+	width: 100%;
+}
+
+.bp-user #buddypress table.profile-settings th.field-group-name,
+.bp-user #buddypress table.profile-settings td.field-name {
+	width: 50%;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress table.profile-settings th.field-group-name,
+	.bp-user #buddypress table.profile-settings td.field-name {
+		width: 70%;
+	}
+}
+
+.bp-user #buddypress table.profile-settings th.title,
+.bp-user #buddypress table.profile-settings td.field-visibility {
+	width: 30%;
+}
+
+.bp-user #buddypress table.profile-settings td.field-visibility select {
+	width: 100%;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+#main #buddypress .standard-form li {
+	float: none;
+}
+
+#main #buddypress .standard-form input[type='text'],
+#main #buddypress .standard-form input[type='email'],
+#main #buddypress .standard-form input[type='password'],
+#main #buddypress .standard-form textarea {
+	width: 100%;
+}
+
+#buddypress div.activity-comments form .ac-textarea {
+	background: #f7f7f7;
+	border: 1px solid rgba(212, 208, 186, 0.5);
+}
+
+#buddypress div.activity-comments form .ac-textarea textarea {
+	background: none;
+	border: 0;
+}
+
+#buddypress .standard-form input[type="text"],
+#buddypress .standard-form input[type="email"],
+#buddypress .standard-form input[type="password"],
+#buddypress .standard-form button,
+#buddypress .standard-form select,
+#buddypress .standard-form textarea {
+	border-color: rgba(212, 208, 186, 0.5);
+	border-width: 1px;
+}
+
+#buddypress #signup_form.standard-form div.submit {
+	float: none;
+}
+
+#buddypress #signup_form.standard-form div.submit input {
+	margin-left: 0;
+}
+
+#buddypress div.dir-search,
+#buddypress div.message-search,
+#buddypress li.groups-members-search {
+	float: none;
+	margin: 10px 0;
+}
+
+#buddypress div.dir-search form,
+#buddypress div.message-search form,
+#buddypress li.groups-members-search form {
+	border: 1px solid rgba(212, 208, 186, 0.6);
+	overflow: hidden;
+}
+
+#buddypress div.dir-search form label,
+#buddypress div.message-search form label,
+#buddypress li.groups-members-search form label {
+	float: right;
+	width: 70%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	float: right;
+	margin: 0;
+	width: 100%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='text'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	font-size: 14px;
+	font-size: 0.875rem;
+	border: 0;
+	line-height: inherit;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	border-left: 1px solid rgba(212, 208, 186, 0.6);
+	padding: 0.2em 0.2em 0.2em 0;
+}
+
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	float: left;
+	font-weight: normal;
+	padding: 0.2em 1em;
+	text-align: center;
+	text-transform: none;
+	width: 30%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div.dir-search,
+	#buddypress div.message-search,
+	#buddypress li.groups-members-search {
+		float: left;
+		margin-bottom: 5px !important;
+	}
+	#buddypress div.dir-search form label,
+	#buddypress div.dir-search form input[type='text'],
+	#buddypress div.dir-search form input[type='submit'],
+	#buddypress div.message-search form label,
+	#buddypress div.message-search form input[type='text'],
+	#buddypress div.message-search form input[type='submit'],
+	#buddypress li.groups-members-search form label,
+	#buddypress li.groups-members-search form input[type='text'],
+	#buddypress li.groups-members-search form input[type='submit'] {
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 75em) {
+	#buddypress .dir-search form input[type='text'],
+	#buddypress .message-search form input[type='text'] {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+	#buddypress .dir-search form input[type='submit'],
+	#buddypress .message-search form input[type='submit'] {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables - General
+*-------------------------------------------------------------------------------
+*/
+#buddypress table {
+	font-size: 14px;
+	font-size: 0.875rem;
+	margin: 20px 0;
+}
+
+#buddypress table tr th {
+	background: #6e6e6e;
+	border-color: #b7b7b7;
+	color: #fff;
+}
+
+#buddypress table p {
+	margin-bottom: 0.5em;
+}
+
+@media screen and (min-width: 55em) {
+	#buddypress table {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+/*__ User Account tables __*/
+#buddypress .notifications th,
+#buddypress .messages-notices th {
+	width: 30%;
+}
+
+#buddypress .notifications th.bulk-select-all,
+#buddypress .messages-notices th.bulk-select-all {
+	text-align: center;
+	width: 10%;
+}
+
+#buddypress .notifications .bulk-select-check,
+#buddypress .notifications .thread-star,
+#buddypress .messages-notices .bulk-select-check,
+#buddypress .messages-notices .thread-star {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions,
+#buddypress .notifications td.thread-options,
+#buddypress .messages-notices .notification-actions,
+#buddypress .messages-notices td.thread-options {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions a,
+#buddypress .notifications td.thread-options a,
+#buddypress .messages-notices .notification-actions a,
+#buddypress .messages-notices td.thread-options a {
+	display: inline-block;
+	margin: 0;
+	padding: 0;
+}
+
+#buddypress .notifications td .button,
+#buddypress .messages-notices td .button {
+	border: 0;
+	display: block;
+	padding: 0;
+	text-align: center;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+#buddypress div#message p {
+	font-size: 18px;
+	font-size: 1.125rem;
+	font-weight: bold;
+}
+
+#buddypress div#message.info p {
+	background: #c6e4ee;
+	border: 1px solid #78bed6;
+	color: #1e5264;
+}
+
+#buddypress div#message.updated p {
+	background: #dee6b2;
+	border: 1px solid #becc66;
+	color: #454d19;
+}
+
+.delete-group #buddypress div#message.info p {
+	background: #db7e7e;
+	border: 1px solid #be3535;
+	color: #1f0808;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+#buddypress {
+	/* Auto Complete background */
+}
+
+#buddypress .acfb-holder li.friend-tab {
+	background: #9fd1e2;
+	border: inherit;
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentysixteen.css
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentysixteen.css	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentysixteen.css	(revision 10410)
@@ -0,0 +1,1870 @@
+@charset "UTF-8";
+/*--------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentysixteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentysixteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the rulesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+----------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+----------------------------------------------------------------
+1.0 Theme Structural Elements & overrides
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 Whats New Activity
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Register, activation
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - item-header: Groups
+		6.1.2 - item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1 - Management settings screens
+			6.2.1.2 - Group members list
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading, Widgets, General classes
+--------------------------------------------------------------*/
+/**
+*-------------------------------------------------------------------------------
+* @section 1.0 - Theme - Structural Elements. overrides, Globals
+*-------------------------------------------------------------------------------
+*/
+.buddypress div.clear {
+	display: none;
+}
+
+.buddypress #page a {
+	box-shadow: none;
+	text-decoration: none !important;
+}
+
+.buddypress .entry-title {
+	text-align: center;
+}
+
+@media screen and (min-width: 61.5625em) {
+	.buddypress .entry-title {
+		text-align: left;
+	}
+}
+
+@media screen and (min-width: 44.375em) {
+	.buddypress #primary {
+		float: none;
+		margin: 0;
+		width: auto;
+	}
+	.buddypress #primary .entry-header {
+		margin: 0;
+	}
+	.buddypress #primary .entry-content,
+	.buddypress #primary .content-bottom-widgets {
+		margin-left: 7.6923%;
+		margin-right: 7.6923%;
+	}
+	.buddypress .sidebar {
+		float: none;
+		margin-left: 0;
+		padding: 0 7.6923%;
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 61.5625em) {
+	.buddypress #primary {
+		float: left;
+		margin-right: -100%;
+		width: 70%;
+	}
+	.buddypress #primary .entry-content,
+	.buddypress #primary .content-bottom-widgets {
+		margin-left: 0;
+		margin-right: 0;
+	}
+	.buddypress .sidebar {
+		float: left;
+		margin-left: 75%;
+		padding: 0;
+		width: 25%;
+	}
+}
+
+.buddypress.no-sidebar #primary {
+	float: none;
+	margin: 0;
+	width: auto;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+/**
+*----------------------------------------------------------
+* @section 2.1 - Navs Object Nav / Sub Nav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+#buddypress div.item-list-tabs ul li.selected a {
+	background: inherit;
+	opacity: 1;
+}
+
+#buddypress div.item-list-tabs ul {
+	background-color: #f7f5e7;
+	border-bottom: 1px solid rgba(234, 234, 234, 0.9);
+	border-top: 1px solid rgba(234, 234, 234, 0.9);
+	overflow: hidden;
+	padding: 0;
+}
+
+#buddypress div.item-list-tabs ul li a span {
+	border-radius: 25%;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #object-nav {
+		background: #f7f5e7;
+		border: 1px solid #eaeaea;
+		overflow: visible;
+		padding: 10px;
+	}
+	.bp-user #buddypress #object-nav ul {
+		border: 0;
+		height: 0;
+		transition: height 0.3s ease-in-out 0.7s;
+		visibility: hidden;
+	}
+	.bp-user #buddypress #object-nav:before {
+		content: "Menu \021E9";
+		display: inline;
+		opacity: 0.7;
+	}
+	.bp-user #buddypress #object-nav:hover:before,
+	.bp-user #buddypress #object-nav:focus:before {
+		content: "Menu \021E7";
+	}
+	.bp-user #buddypress #object-nav:hover ul,
+	.bp-user #buddypress #object-nav:focus ul {
+		height: 320px;
+		opacity: 1;
+		overflow-y: auto;
+		visibility: visible;
+	}
+	.bp-user #buddypress #subnav {
+		clear: both;
+	}
+}
+
+/*__ Horizontal menus __*/
+#buddypress #object-nav ul {
+	overflow: hidden;
+}
+
+#buddypress #object-nav ul li {
+	float: none;
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress #object-nav ul li:not(:last-child) {
+		border-bottom: 1px solid #eaeaea;
+	}
+}
+
+#buddypress #object-nav ul li:not(.selected) a {
+	opacity: 0.7;
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress #object-nav ul li:not(.selected):focus,
+	#buddypress #object-nav ul li:not(.selected):hover {
+		background: #f4f2df;
+	}
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #object-nav ul li {
+		float: left;
+	}
+}
+
+#buddypress div#subnav.item-list-tabs {
+	margin-top: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul {
+	background-color: #f7f7f7;
+	border-bottom: 0;
+	margin-top: 5px;
+	padding: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last {
+	background: #fff;
+	margin-top: 0;
+	padding: 5px 5px 5px 0;
+	width: 100%;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last select:focus {
+	background: transparent;
+	border: 0;
+	outline: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last label,
+#buddypress div#subnav.item-list-tabs ul li.last option {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select {
+	font-style: italic;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div#subnav.item-list-tabs ul li.last {
+		text-align: right;
+	}
+}
+
+/*__ Vertical menu User Account / Group single screens __*/
+@media screen and (min-width: 55em) {
+	body.no-sidebar #buddypress #item-header,
+	body.no-sidebar #buddypress #item-body {
+		background: #fff;
+	}
+	body.no-sidebar #buddypress #object-nav {
+		border-right: 1px solid #ddd;
+		float: left;
+		margin-right: -1px;
+		width: 200px;
+	}
+	body.no-sidebar #buddypress #object-nav ul {
+		background: none;
+		border-bottom: 0;
+		padding: 0;
+	}
+	body.no-sidebar #buddypress #object-nav ul li {
+		float: none;
+		overflow: hidden;
+	}
+	body.no-sidebar #buddypress #object-nav ul li span {
+		background: #fff;
+		border-radius: 10%;
+		float: right;
+		margin-right: 2px;
+	}
+	body.no-sidebar #buddypress #item-body {
+		border-left: 1px solid #ddd;
+		overflow: hidden;
+		padding: 0 0 0 20px;
+		width: auto;
+	}
+	body.no-sidebar #buddypress #item-body #subnav {
+		margin: 0 0 0 -20px;
+	}
+	body.no-sidebar #buddypress #item-body #subnav ul {
+		margin-top: 0;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	#main #buddypress #group-create-tabs.item-list-tabs ul:before,
+	#main #buddypress #group-create-tabs.item-list-tabs ul:after {
+		content: " ";
+		display: table;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul:after {
+		clear: both;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul {
+		background: none;
+		border: 0;
+		border-bottom: 1px solid #ddd;
+		overflow: visible;
+		padding-bottom: 0;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li {
+		float: left;
+		width: auto;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.selected,
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.current {
+		border: 1px solid #ddd;
+		border-bottom-color: #fff;
+		-webkit-border-top-right-radius: 4px;
+		border-top-right-radius: 4px;
+		-webkit-border-top-left-radius: 4px;
+		border-top-left-radius: 4px;
+		background-clip: padding-box;
+		margin-bottom: -1px;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.selected a,
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.current a {
+		background: none;
+		color: #141412;
+		outline: none;
+	}
+	#main #buddypress #subnav ul {
+		border-bottom: 0;
+	}
+}
+
+/**
+*----------------------------------------------------------
+* @section 2.2 - Pagination
+*----------------------------------------------------------
+*/
+#buddypress div.pagination {
+	box-shadow: none;
+	min-height: 0;
+}
+
+#buddypress div.pagination:before,
+#buddypress div.pagination:after {
+	height: 0;
+	width: 0;
+}
+
+#buddypress div.pagination .pag-count {
+	margin-left: 0;
+}
+
+#buddypress div.pagination .pagination-links {
+	margin-right: 0;
+}
+
+#buddypress div.pagination .pagination-links span,
+#buddypress div.pagination .pagination-links a {
+	height: auto;
+	line-height: 1;
+	padding: 5px;
+}
+
+#buddypress div.pagination .pagination-links .prev,
+#buddypress div.pagination .pagination-links .next {
+	background-color: transparent;
+	color: inherit;
+	overflow: visible;
+	width: auto;
+}
+
+#buddypress div.pagination .pagination-links .prev:before,
+#buddypress div.pagination .pagination-links .next:before {
+	display: none;
+}
+
+#buddypress div.pagination .pagination-links .prev {
+	left: auto;
+	position: static;
+}
+
+#buddypress div.pagination .pagination-links .next {
+	position: static;
+	right: auto;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+#buddypress .item-list .activity-header,
+#buddypress .item-list .activity-meta {
+	font-family: "Source Sans Pro", Helvetica, sans-serif;
+}
+
+#buddypress .activity-meta .button:focus,
+#buddypress .activity-meta .button:hover {
+	background: inherit;
+	color: #000;
+}
+
+#buddypress .action .generic-button a:focus,
+#buddypress .action .generic-button a:hover {
+	background: inherit;
+	color: #000;
+}
+
+#buddypress ul.item-list li {
+	overflow: hidden !important;
+}
+
+#buddypress ul.item-list li .item-avatar {
+	margin-bottom: 10px;
+	text-align: center;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item-avatar {
+		margin-bottom: 0;
+	}
+}
+
+#buddypress ul.item-list li .item-avatar a {
+	border-bottom: 0;
+}
+
+#buddypress ul.item-list li .item-avatar img.avatar {
+	display: inline-block;
+	float: none;
+	margin-bottom: 10px;
+}
+
+@media screen and (min-width: 44.375em) {
+	#buddypress ul.item-list li .item-avatar img.avatar {
+		display: block;
+		float: left;
+	}
+}
+
+#buddypress ul.item-list li .item {
+	overflow: hidden;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress ul.item-list li .item {
+		margin-left: 15%;
+	}
+}
+
+#buddypress ul.item-list li .item span.activity {
+	font-style: italic;
+}
+
+#buddypress ul.item-list li .item .item-desc {
+	margin-left: 0;
+	width: 94%;
+}
+
+#buddypress ul.item-list li .item .item-title {
+	font-size: 18px;
+	font-size: 1.125rem;
+	line-height: 1.2;
+	text-align: center;
+	width: 100%;
+}
+
+@media screen and (min-width: 44.375em) {
+	#buddypress ul.item-list li .item .item-title {
+		text-align: left;
+	}
+}
+
+#buddypress ul.item-list li .item .item-title .update {
+	display: block;
+	font-size: 12px;
+	font-size: 0.75rem;
+	padding: 10px 0;
+	text-align: left;
+}
+
+@media screen and (min-width: 61.5625em) {
+	#buddypress ul.item-list li .item .item-title .update {
+		font-size: 14px;
+		font-size: 0.875rem;
+	}
+}
+
+@media screen and (min-width: 61.5625em) {
+	#buddypress ul.item-list li .item-avatar,
+	#buddypress ul.item-list li .item,
+	#buddypress ul.item-list li .action {
+		float: left;
+	}
+	#buddypress ul.item-list li .item {
+		left: 5%;
+		margin-left: 0;
+		position: relative;
+		width: 55%;
+	}
+	#buddypress ul.item-list li .item .item-title {
+		font-size: 22px;
+		font-size: 1.375rem;
+	}
+}
+
+#buddypress ul.item-list li div.action {
+	clear: left;
+	float: none;
+	margin-bottom: -20px;
+	margin-left: 0;
+	padding: 20px 0 5px;
+	position: relative;
+	text-align: left;
+	top: 0;
+}
+
+@media screen and (min-width: 61.5625em) {
+	#buddypress ul.item-list li div.action {
+		clear: none;
+		float: right;
+		margin-bottom: 0;
+		padding: 0;
+	}
+}
+
+#buddypress ul.item-list li div.action div {
+	display: inline-block;
+	margin: 10px 0;
+	width: 100%;
+}
+
+#buddypress ul.item-list li div.action div a {
+	display: block;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li div.action div {
+		margin: 0 10px 10px 0;
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 61.5625em) {
+	#buddypress ul.item-list li div.action div {
+		clear: right;
+		float: right;
+		margin: 0 0 10px 0;
+	}
+}
+
+#buddypress ul.item-list li div.action .meta {
+	font-style: italic;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.1 - Activity
+*----------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------
+* @section 4.1.1 - Activity Whats New
+*-----------------------------------------------------
+*/
+#buddypress form#whats-new-form p.activity-greeting {
+	line-height: 1.4;
+}
+
+@media screen and (max-width: 46.25em) {
+	#buddypress form#whats-new-form #whats-new-content {
+		clear: left;
+		margin: 10px 0 20px;
+		padding: 10px 0 0;
+	}
+}
+
+#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box {
+	border: 1px solid rgba(212, 208, 186, 0.5);
+	float: left;
+	line-height: 1.5em;
+	margin-top: 12px;
+	padding-left: 0.2em;
+	width: 100%;
+}
+
+#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box select {
+	background: none;
+	border: 0;
+	float: right;
+	margin: 0;
+	min-height: 1.5em;
+	padding-left: 0.4em;
+}
+
+@media screen and (min-width: 30em) {
+	#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box {
+		width: auto;
+	}
+	#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-submit {
+		float: right;
+	}
+}
+
+#buddypress #item-body form#whats-new-form {
+	margin: 40px 0;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.2 - Activity Listing
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream li {
+	padding: 25px 0 15px;
+}
+
+#buddypress #activity-stream li .activity-avatar {
+	float: none;
+	text-align: center;
+}
+
+#buddypress #activity-stream li .activity-avatar a {
+	display: inline-block;
+}
+
+#buddypress #activity-stream li .activity-avatar a img.avatar {
+	display: inline;
+	float: none;
+	height: 60px;
+	margin-bottom: 20px;
+	margin-left: 0;
+	width: 60px;
+}
+
+#buddypress #activity-stream li .activity-content {
+	margin-left: 0;
+}
+
+#buddypress #activity-stream li .activity-content .activity-header {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress #activity-stream li .activity-avatar {
+		float: left;
+		margin-right: 10px;
+		text-align: left;
+	}
+	#buddypress #activity-stream li .activity-avatar a {
+		border-bottom: 0;
+	}
+	#buddypress #activity-stream li .activity-content {
+		margin: 0;
+		overflow: hidden;
+	}
+	#buddypress #activity-stream li .activity-content .activity-header {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+#buddypress #activity-stream li.mini .activity-avatar a img.avatar {
+	height: 30px;
+	margin-left: 15px;
+	width: 30px;
+}
+
+#buddypress #activity-stream li.mini .activity-content .activity-header {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+#buddypress #activity-stream .activity-content {
+	margin-top: -12px;
+}
+
+#buddypress #activity-stream .activity-content .activity-header {
+	line-height: inherit;
+	margin-right: 0;
+}
+
+#buddypress #activity-stream .activity-content .activity-header p {
+	background-color: #f7f7f7;
+	border: 1px solid rgba(234, 234, 234, 0.6);
+	margin-top: 0;
+	padding: 0 0.2em;
+}
+
+#buddypress #activity-stream .activity-content .activity-header img.avatar {
+	display: inline-block;
+	margin-bottom: 0 !important;
+}
+
+#buddypress #activity-stream .activity-content .activity-meta a {
+	display: block;
+	margin-bottom: 5px;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #activity-stream .activity-content .activity-meta a {
+		display: inline-block;
+		margin-bottom: 0;
+	}
+}
+
+#buddypress #activity-stream .load-more {
+	background: #f7f7f7;
+	border: 1px solid transparent;
+	padding: 10px;
+}
+
+#buddypress #activity-stream .load-more:focus,
+#buddypress #activity-stream .load-more:hover {
+	background: #f4f4f4;
+	border: 1px solid rgba(159, 209, 226, 0.3);
+}
+
+#buddypress #activity-stream .load-more:focus a,
+#buddypress #activity-stream .load-more:hover a {
+	font-style: italic;
+}
+
+#buddypress #activity-stream .load-more a {
+	display: block;
+}
+
+/* Single activity view  - activity permalink */
+.activity-permalink #buddypress #activity-stream li.activity-item {
+	padding: 20px;
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+	font-size: 16px;
+	font-size: 1rem;
+	margin-bottom: 40px;
+}
+
+@media screen and (min-width: 46.25em) {
+	.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+		font-size: 20px;
+		font-size: 1.25rem;
+	}
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header p {
+	padding: 20px;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.3 - Activity Comments
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream .activity-comments {
+	border-left: 1px solid #eaeaea;
+	margin-top: 5px;
+}
+
+#buddypress #activity-stream .activity-comments ul {
+	background: rgba(247, 247, 247, 0.6);
+	margin: 15px 0 0 2px;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form input[type="submit"],
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	color: rgba(20, 20, 18, 0.8);
+	display: inline-block;
+	font-family: inherit;
+	font-size: 12px;
+	font-size: 0.75rem;
+	font-weight: normal;
+	line-height: 1.2;
+	padding: 4px 10px;
+	text-transform: lowercase;
+	width: 100px;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	border: 1px solid rgba(212, 208, 186, 0.7);
+	text-align: center;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:focus,
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:hover {
+	background: #ededed;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.2 - Members Loop
+*----------------------------------------------------------
+*/
+@media screen and (min-width: 61.5625em) {
+	#buddypress #members-list li .item-avatar,
+	#buddypress #members-list li .item {
+		float: left;
+	}
+	#buddypress #members-list li .action {
+		float: right;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 5.0 - Directories
+*-------------------------------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------------
+* @subsection Registration
+*-----------------------------------------------------------
+*/
+#buddypress #signup_form.standard-form #profile-details-section,
+#buddypress #signup_form.standard-form #basic-details-section,
+#buddypress #signup_form.standard-form #blog-details-section {
+	float: none;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #signup_form.standard-form #profile-details-section,
+	#buddypress #signup_form.standard-form #basic-details-section,
+	#buddypress #signup_form.standard-form #blog-details-section {
+		width: 48%;
+	}
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #signup_form.standard-form #profile-details-section {
+		float: right;
+	}
+	#buddypress #signup_form.standard-form #basic-details-section {
+		float: left;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens: User Account & Single Group Screens
+*-------------------------------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------------
+* @subsection 6.1 - Item Header Global
+*-----------------------------------------------------------
+*/
+.bp-user #buddypress #item-header-content #item-meta,
+.single-item.groups #buddypress #item-header-content #item-meta {
+	font-size: 14px;
+	font-size: 0.875rem;
+	text-align: left;
+}
+
+.bp-user #buddypress #item-header-content #item-meta p,
+.single-item.groups #buddypress #item-header-content #item-meta p {
+	margin-bottom: 0.5em;
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user main header.entry-header,
+	.single-item.groups main header.entry-header {
+		padding-bottom: 1rem;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user h1,
+	.bp-user #item-header-content,
+	.single-item.groups h1,
+	.single-item.groups #item-header-content {
+		text-align: center;
+	}
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #item-header .generic-button,
+	.single-item.groups #buddypress #item-header .generic-button {
+		float: none;
+		margin: 1.5em 0 0 0;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress h1,
+	.single-item.groups #buddypress h1 {
+		margin-bottom: 0;
+	}
+	.bp-user #buddypress #item-header-avatar img.avatar,
+	.single-item.groups #buddypress #item-header-avatar img.avatar {
+		margin-right: 0;
+	}
+	.bp-user #buddypress #item-header-content,
+	.single-item.groups #buddypress #item-header-content {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.1 - item-header Groups
+*
+* Group Specific Item Header
+*-----------------------------------------------------
+*/
+@media screen and (max-width: 46.25em) {
+	.single-item.groups #buddypress #item-header #item-meta {
+		margin-bottom: 20px;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header {
+		display: flex;
+		flex-direction: column;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		order: 1;
+		text-align: center;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a {
+		border-bottom: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a img {
+		display: inline-block;
+		float: none;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		order: 2;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions {
+		order: 3;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions h3 {
+		border-bottom: 1px solid #eaeaea;
+		text-align: center;
+	}
+}
+
+.single-item.groups #buddypress div#item-header {
+	padding-bottom: 40px;
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions {
+	margin: 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		border-left: 1px solid #eaeaea;
+		clear: none;
+		float: right;
+		padding-left: 0.2em;
+		width: 30%;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		width: 40%;
+	}
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions ul {
+	margin-top: 0;
+	padding-left: 0;
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions h3 {
+	font-size: 14px;
+	font-size: 0.875rem;
+	padding: 0.2em;
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header div#item-actions h3 {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header #item-header-avatar,
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		float: left;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		width: 21%;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		margin-left: 4%;
+		width: 40%;
+	}
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		float: right;
+		width: 28%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.2 - Item Header User Accounts
+*
+* User Accounts Specific Item Header
+*-----------------------------------------------------
+*/
+.bp-user #buddypress #item-header {
+	padding: 20px 0;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar {
+	text-align: center;
+	width: 100%;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar img.avatar,
+.bp-user #buddypress #item-header #item-header-avatar a {
+	border-bottom: 0;
+	display: inline-block;
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #item-header #item-header-avatar {
+		float: left;
+		width: 20%;
+	}
+	.bp-user #buddypress #item-header #item-header-avatar a {
+		float: left;
+	}
+	.bp-user #buddypress #item-header #item-header-content {
+		float: right;
+		margin-right: 5%;
+		width: 69%;
+	}
+}
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*-----------------------------------------------------------
+*/
+/**
+*----------------------------------------------------
+* @subsection 6.2.1 - Item Body Groups
+*
+* Groups specific item body rules -  screens
+*----------------------------------------------------
+*/
+/**
+*-----------------------------------------
+* @subsection 6.2.1.1 - Group Management
+*-----------------------------------------
+*/
+.groups #group-settings-form h4 {
+	background: #555;
+	color: #fff;
+	padding: 0.2em;
+}
+
+.groups.edit-details #group-settings-form label {
+	margin-bottom: 0;
+	padding: 0.2em;
+	width: 80%;
+}
+
+.groups.edit-details #group-settings-form textarea + p label {
+	background: none;
+	color: inherit;
+	font-size: 14px;
+	font-size: 0.875rem;
+	width: auto;
+}
+
+.groups.edit-details #group-settings-form textarea {
+	height: auto;
+	min-height: 100px;
+	overflow: auto;
+}
+
+.groups.group-settings #group-settings-form div.radio label {
+	border: 1px solid #eaeaea;
+	padding: 0.2em;
+}
+
+.groups.group-settings #group-settings-form div.radio label ul {
+	color: rgba(20, 20, 18, 0.6);
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.groups.group-avatar form > p {
+	margin-top: 20px;
+}
+
+.groups.manage-members #group-settings-form .item-list li {
+	border-bottom: 1px solid #eaeaea;
+}
+
+.groups.manage-members #group-settings-form .item-list li img,
+.groups.manage-members #group-settings-form .item-list li h5 {
+	float: left;
+}
+
+.groups.manage-members #group-settings-form .item-list li img > a,
+.groups.manage-members #group-settings-form .item-list li h5 > a {
+	border-bottom: 0;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small {
+	clear: left;
+	display: block;
+	float: none;
+	margin-top: 10px;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small a {
+	display: inline-block;
+	margin: 5px 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.groups.manage-members #group-settings-form .item-list li span.small a {
+		width: auto;
+	}
+}
+
+.groups.manage-members #group-settings-form .item-list li h5 {
+	margin: 0;
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.2 - Group members
+*-----------------------------------------
+*/
+.groups.group-members #subnav li {
+	width: 100%;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li {
+		background: #fff;
+		padding: 20px 0;
+	}
+}
+
+.groups.group-members #subnav li #search-members-form {
+	float: right;
+	margin: 5px 5px 0 0;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li #search-members-form {
+		margin: 0;
+		width: 100%;
+	}
+	.groups.group-members #subnav li #search-members-form label input[type="text"] {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.2.2 - Item Body User Accounts
+*
+* User Account specific item body rules
+*-----------------------------------------------------
+*/
+.bp-user .entry-title {
+	margin-bottom: 0.5em;
+}
+
+/**
+*--------------------------------------------
+* @subsection 6.2.2.1 - classes, pag, filters
+*--------------------------------------------
+*/
+.bp-user #buddypress table th {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.bp-user #buddypress table td {
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+@media screen and (min-width: 61.5625em) {
+	.bp-user #buddypress table th {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 14px;
+		font-size: 0.875rem;
+	}
+}
+
+@media screen and (min-width: 75em) {
+	.bp-user #buddypress table th {
+		font-size: 18px;
+		font-size: 1.125rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+.bp-user #buddypress .pag-count {
+	font-style: italic;
+}
+
+.bp-user #buddypress .notifications-options-nav {
+	border: 1px solid rgba(212, 208, 186, 0.5);
+	float: left;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.bp-user #buddypress .notifications-options-nav {
+		width: 300px;
+	}
+}
+
+.bp-user #buddypress .notifications-options-nav select,
+.bp-user #buddypress .notifications-options-nav input {
+	border: 0;
+	font-size: 14px;
+	font-size: 0.875rem;
+	outline: 0;
+	padding: 0;
+}
+
+.bp-user #buddypress .notifications-options-nav select {
+	float: left;
+	margin-right: 0;
+	width: 60%;
+}
+
+.bp-user #buddypress .notifications-options-nav input {
+	float: right;
+	font-family: inherit;
+	line-height: 20px;
+	width: 40%;
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.2 - Extended Profiles
+	*-------------------------------------------
+	*/
+.bp-user #buddypress .profile {
+	/* Edit profile */
+}
+
+.bp-user #buddypress .profile .bp-widget h4 {
+	background: #6e6e6e;
+	color: #fff;
+	margin-bottom: 0;
+	padding: 0.4em;
+}
+
+.bp-user #buddypress .profile .bp-widget table {
+	margin-top: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .button-nav:before,
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	content: " ";
+	display: table;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	clear: both;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav {
+	border-bottom: 1px solid #eaeaea;
+	margin-left: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li {
+	float: left;
+	margin-bottom: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li.current {
+	border: 1px solid #eaeaea;
+	border-bottom-color: #fff;
+	margin-bottom: -1px;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav a {
+	background: none;
+	border: 0;
+	font-size: 18px;
+	font-size: 1.125rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-toggle,
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-close,
+.bp-user #buddypress .profile #profile-edit-form .visibility-toggle-link {
+	background: #555;
+	color: #fff;
+	padding: 0.2em 0.5em;
+}
+
+.bp-user #buddypress .profile .bp-avatar #bp-delete-avatar a {
+	font-size: inherit;
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.3 - Groups
+	*-------------------------------------------
+	*/
+@media screen and (min-width: 77.5em) {
+	.bp-user #buddypress #groups-list li .item {
+		left: 5%;
+		width: 50%;
+	}
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.5 - Private Messaging
+	*-------------------------------------------
+	*/
+.bp-user #buddypress {
+	/* Sitewide Notices loop */
+}
+
+.bp-user #buddypress #message-thread a {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress #message-thread #message-subject {
+	background: #6e6e6e;
+	color: #fff;
+	padding: 0.3em 0 0.3em 0.2em;
+}
+
+.bp-user #buddypress #message-thread #message-recipients {
+	font-style: italic;
+}
+
+.bp-user #buddypress #message-thread #message-recipients a.confirm {
+	border: 1px solid #eaeaea;
+	font-style: normal;
+}
+
+.bp-user #buddypress #message-thread .message-metadata {
+	overflow: hidden;
+}
+
+.bp-user #buddypress #message-thread .message-metadata img.avatar {
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #message-thread .message-metadata img.avatar {
+		float: left;
+	}
+}
+
+.bp-user #buddypress #message-thread .message-metadata .message-star-actions {
+	float: right;
+	margin-right: 5px;
+	position: static;
+}
+
+.bp-user #buddypress #message-thread .message-content {
+	background: #f7f7f7;
+	border: 1px solid #eaeaea;
+	margin: 10px 0 0 0;
+	padding: 0.3em;
+}
+
+.bp-user #buddypress #message-thread #send-reply .message-content {
+	background: #fff;
+	border: 0;
+}
+
+.bp-user #buddypress #message-thread .alt {
+	background: #fff;
+}
+
+.bp-user #buddypress #message-threads thead tr {
+	background: #6e6e6e;
+}
+
+.bp-user #buddypress #message-threads tr td {
+	background: #fff;
+	display: inline-block;
+	float: left;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-star,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-bottom-color: #ccc;
+	border-bottom-width: 2px;
+	height: 2.4em;
+	padding-bottom: 0.2em;
+	padding-top: 0.2em;
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #message-threads tr td.thread-star,
+	.bp-user #buddypress #message-threads tr td.thread-options {
+		padding-top: 0;
+	}
+}
+
+.bp-user #buddypress #message-threads tr td.thread-star {
+	vertical-align: middle;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-star .message-action-star {
+	line-height: 1.2;
+}
+
+.bp-user #buddypress #message-threads tr td.bulk-select-check,
+.bp-user #buddypress #message-threads tr td.thread-from {
+	height: 3em;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr td.bulk-select-check,
+	.bp-user #buddypress #message-threads tr td.thread-from {
+		height: 5.2em;
+	}
+}
+
+.bp-user #buddypress #message-threads tr td.thread-from,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-left: 0 !important;
+	width: -webkit-calc(100% - 30px);
+	width: -moz-calc(100% - 30px);
+	width: calc(100% - 30px);
+	margin-left: 0;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info {
+	padding-left: 41px;
+	width: 100%;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info a::after {
+	content: " ‖  " attr(title);
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options {
+	text-align: right;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options a {
+	font-size: 12px;
+	font-size: 0.75rem;
+	line-height: 2.2;
+}
+
+.bp-user #buddypress #message-threads tr span.from {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads tr span.activity {
+	display: block;
+	float: right;
+	line-height: 2;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr span.activity {
+		clear: both;
+		font-size: 11px;
+		font-size: 0.6875rem;
+		width: 100%;
+	}
+}
+
+.bp-user #buddypress #message-threads tr.unread td {
+	background: #dce5ff;
+	border-color: #d4d0ba;
+}
+
+.bp-user #buddypress #message-threads th {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all {
+	border-bottom: 0;
+	display: inline-block;
+	text-align: left;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all,
+.bp-user #buddypress #message-threads td.bulk-select-check,
+.bp-user #buddypress #message-threads td.thread-star {
+	border-right: 0;
+	width: 30px;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td {
+	width: 100%;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td strong {
+	background: #6e6e6e;
+	color: #fff;
+	display: block;
+	margin-bottom: 0.4em;
+	padding-left: 0.2em;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td a {
+	display: inline-block;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:nth-child(2) strong {
+	margin: -8px -8px 8px;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child + td + td {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:first-child + td + td span {
+	line-height: 1;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child {
+	border-bottom-color: #b7b7b7;
+	line-height: 1;
+	text-align: right;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child a:last-child {
+	line-height: 0;
+	margin-left: 0.7em;
+	text-indent: -999em;
+}
+
+.bp-user #buddypress #message-threads.sitewide-notices td:last-child a:last-child::after {
+	content: attr(title);
+	display: block;
+	line-height: initial;
+	text-indent: 0;
+}
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+.bp-user #buddypress #settings-form > p {
+	font-size: 20px;
+	font-size: 1.25rem;
+	margin: 20px 0 10px;
+}
+
+.bp-user #buddypress table.notification-settings td.yes,
+.bp-user #buddypress table.notification-settings td.no {
+	vertical-align: middle;
+}
+
+.bp-user #buddypress table.profile-settings {
+	width: 100%;
+}
+
+.bp-user #buddypress table.profile-settings th.field-group-name,
+.bp-user #buddypress table.profile-settings td.field-name {
+	width: 50%;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress table.profile-settings th.field-group-name,
+	.bp-user #buddypress table.profile-settings td.field-name {
+		width: 70%;
+	}
+}
+
+.bp-user #buddypress table.profile-settings th.title,
+.bp-user #buddypress table.profile-settings td.field-visibility {
+	width: 30%;
+}
+
+.bp-user #buddypress table.profile-settings td.field-visibility select {
+	width: 100%;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+#main #buddypress .standard-form li {
+	float: none;
+}
+
+#main #buddypress .standard-form input[type='text'],
+#main #buddypress .standard-form input[type='email'],
+#main #buddypress .standard-form input[type='password'],
+#main #buddypress .standard-form textarea {
+	width: 100%;
+}
+
+#buddypress div.activity-comments form .ac-textarea {
+	background: #f7f7f7;
+	border: 1px solid rgba(212, 208, 186, 0.5);
+}
+
+#buddypress div.activity-comments form .ac-textarea textarea {
+	background: none;
+	border: 0;
+}
+
+#buddypress .standard-form input[type="text"],
+#buddypress .standard-form input[type="email"],
+#buddypress .standard-form input[type="password"],
+#buddypress .standard-form button,
+#buddypress .standard-form select,
+#buddypress .standard-form textarea {
+	border-color: rgba(212, 208, 186, 0.5);
+	border-width: 1px;
+}
+
+#buddypress #signup_form.standard-form div.submit {
+	float: none;
+}
+
+#buddypress #signup_form.standard-form div.submit input {
+	margin-right: 0;
+}
+
+#buddypress div.dir-search,
+#buddypress div.message-search,
+#buddypress li.groups-members-search {
+	float: none;
+	margin: 10px 0;
+}
+
+#buddypress div.dir-search form,
+#buddypress div.message-search form,
+#buddypress li.groups-members-search form {
+	border: 1px solid rgba(212, 208, 186, 0.6);
+	overflow: hidden;
+}
+
+#buddypress div.dir-search form label,
+#buddypress div.message-search form label,
+#buddypress li.groups-members-search form label {
+	float: left;
+	width: 70%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	float: left;
+	margin: 0;
+	width: 100%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='text'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	font-size: 14px;
+	font-size: 0.875rem;
+	border: 0;
+	line-height: inherit;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	border-right: 1px solid rgba(212, 208, 186, 0.6);
+	padding: 0.2em 0 0.2em 0.2em;
+}
+
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	float: right;
+	font-weight: normal;
+	padding: 0.2em 1em;
+	text-align: center;
+	text-transform: none;
+	width: 30%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div.dir-search,
+	#buddypress div.message-search,
+	#buddypress li.groups-members-search {
+		float: right;
+		margin-bottom: 5px !important;
+	}
+	#buddypress div.dir-search form label,
+	#buddypress div.dir-search form input[type='text'],
+	#buddypress div.dir-search form input[type='submit'],
+	#buddypress div.message-search form label,
+	#buddypress div.message-search form input[type='text'],
+	#buddypress div.message-search form input[type='submit'],
+	#buddypress li.groups-members-search form label,
+	#buddypress li.groups-members-search form input[type='text'],
+	#buddypress li.groups-members-search form input[type='submit'] {
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 75em) {
+	#buddypress .dir-search form input[type='text'],
+	#buddypress .message-search form input[type='text'] {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+	#buddypress .dir-search form input[type='submit'],
+	#buddypress .message-search form input[type='submit'] {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables - General
+*-------------------------------------------------------------------------------
+*/
+#buddypress table {
+	font-size: 14px;
+	font-size: 0.875rem;
+	margin: 20px 0;
+}
+
+#buddypress table tr th {
+	background: #6e6e6e;
+	border-color: #b7b7b7;
+	color: #fff;
+}
+
+#buddypress table p {
+	margin-bottom: 0.5em;
+}
+
+@media screen and (min-width: 55em) {
+	#buddypress table {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+/*__ User Account tables __*/
+#buddypress .notifications th,
+#buddypress .messages-notices th {
+	width: 30%;
+}
+
+#buddypress .notifications th.bulk-select-all,
+#buddypress .messages-notices th.bulk-select-all {
+	text-align: center;
+	width: 10%;
+}
+
+#buddypress .notifications .bulk-select-check,
+#buddypress .notifications .thread-star,
+#buddypress .messages-notices .bulk-select-check,
+#buddypress .messages-notices .thread-star {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions,
+#buddypress .notifications td.thread-options,
+#buddypress .messages-notices .notification-actions,
+#buddypress .messages-notices td.thread-options {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions a,
+#buddypress .notifications td.thread-options a,
+#buddypress .messages-notices .notification-actions a,
+#buddypress .messages-notices td.thread-options a {
+	display: inline-block;
+	margin: 0;
+	padding: 0;
+}
+
+#buddypress .notifications td .button,
+#buddypress .messages-notices td .button {
+	border: 0;
+	display: block;
+	padding: 0;
+	text-align: center;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+#buddypress div#message p {
+	font-size: 18px;
+	font-size: 1.125rem;
+	font-weight: bold;
+}
+
+#buddypress div#message.info p {
+	background: #c6e4ee;
+	border: 1px solid #78bed6;
+	color: #1e5264;
+}
+
+#buddypress div#message.updated p {
+	background: #dee6b2;
+	border: 1px solid #becc66;
+	color: #454d19;
+}
+
+.delete-group #buddypress div#message.info p {
+	background: #db7e7e;
+	border: 1px solid #be3535;
+	color: #1f0808;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+#buddypress {
+	/* Auto Complete background */
+}
+
+#buddypress .acfb-holder li.friend-tab {
+	background: #9fd1e2;
+	border: inherit;
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentysixteen.scss
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentysixteen.scss	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentysixteen.scss	(revision 10410)
@@ -0,0 +1,2352 @@
+
+// Stylesheet Guidence Notes
+// Table of content represents a guide to sections of the sheet.
+// Note that not all sections may be described in the body of the
+// rulesets only those that require styles for a given theme.
+// Sub sections might not exist but can be added as required
+// as per BP Lists - 'activity' section.
+
+// We follow the dictats of the parent theme in respect of media queries,
+// font sizing etc. Media queries are used in a modular sense (OOCSS)
+// rather than described as blocks to the end of the sheet, this aids ease
+// of managing media queries, scalability & flexibility .
+
+// 2016 Media Queries / Breakpoints.
+// @media screen and (min-width: 44.375em)
+// @media screen and (min-width: 61.5625em)
+// @media screen and (min-width: 75em)
+
+
+// In addition BP will use the em breakpoints as  referenced below to provide
+// a series of finer breakpoints, these are be used sparingly though.
+// @media screen and (max-width: 22em) // Additional for xtra small mobile only
+// @media screen and (max-width: 30em) // Additional small only
+// @media screen and (min-width: 38.75em)
+// @media screen and (min-width: 46.25em)
+// @media screen and (min-width: 55em)
+// @media screen and (min-width: 87.6875em)
+
+// Mixins and Variables
+
+// simplify remembering/writing media queries
+// written as:
+// @include medium-up {
+//  body {property: value;}
+// }
+//
+
+@mixin small-up {
+	@media screen and (min-width: 44.375em) {
+		@content;
+	}
+}
+
+@mixin medium-up {
+	@media screen and (min-width: 61.5625em) {
+		@content;
+	}
+}
+
+@mixin large-up {
+	@media screen and (min-width: 75em) {
+		@content;
+	}
+}
+
+
+// Common spacing values
+$spacing-val-lg: 40px;
+$spacing-val-md: 20px;
+$spacing-val-sm: 10px;
+$spacing-val-xs: 5px;
+
+// A simple mixin to handle font-sizing
+// 2016 uses a basic pixel font size approach, we'll stick with rem/px
+@mixin font-size($font-size: 16) {
+	$rem-font-value: ($font-size / 16);
+	font-size: ($font-size * 1px);
+	font-size: $rem-font-value + rem;
+}
+
+// To allow mixins to pass VendorPrefix scss-lint tests we disable lint-test
+// for specific blocks / rulesets
+// until such time as exemption lists can be built for the scss-lint.yml?
+// Any vendor prefix properties / mixins need to be in this block
+
+// scss-lint:disable VendorPrefix
+
+// Border border-radius mixins
+@mixin border-radius($radius) {
+	-webkit-border-radius: $radius;
+	-moz-border-radius: $radius;
+	-ms-border-radius: $radius;
+	border-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-top-radius($radius) {
+	-webkit-border-top-right-radius: $radius;
+	border-top-right-radius: $radius;
+	-webkit-border-top-left-radius: $radius;
+	border-top-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-right-radius($radius) {
+	-webkit-border-bottom-right-radius: $radius;
+	border-bottom-right-radius: $radius;
+	-webkit-border-top-right-radius: $radius;
+	border-top-right-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-bottom-radius($radius) {
+	-webkit-border-bottom-right-radius: $radius;
+	border-bottom-right-radius: $radius;
+	-webkit-border-bottom-left-radius: $radius;
+	border-bottom-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-left-radius($radius) {
+	-webkit-border-bottom-left-radius: $radius;
+	border-bottom-left-radius: $radius;
+	-webkit-border-top-left-radius: $radius;
+	border-top-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+// Box sizing
+@mixin box-model($box-model) {
+	-webkit-box-sizing: $box-model;
+	-moz-box-sizing: $box-model;
+	box-sizing: $box-model;
+}
+
+// Calc
+@mixin calc($property, $expression) {
+	#{$property}: -webkit-calc(#{$expression});
+	#{$property}: -moz-calc(#{$expression});
+	#{$property}: calc(#{$expression});
+}
+
+// Re-enable the VendorPrefix lint test
+// scss-lint:enable VendorPrefix
+
+// BP message boxes
+@mixin message-box($background, $text-color: null) {
+
+	@if $text-color {
+		// if a param was passed through
+		$text-color: $text-color;
+	} @else {
+		$text-color: darken($background, 50%);
+	}
+
+	background: lighten($background, 10%);
+	border: 1px solid darken($background, 10%);
+	color: $text-color;
+}
+
+// 2016 font family
+@mixin default-font-sans() {
+	font-family: "Source Sans Pro", Helvetica, sans-serif;
+}
+
+// Variables: color definitions
+$body-text:          #141412;
+$content-background: #fff;
+$off-white:          lighten(#f9f8ee, 0.7);
+$cream-background:   #f7f5e7;
+$light-background:   #f7f7f7;
+$medium-background:  #ccc;
+$dark-background:    #555;
+$border-color:       #d4d0ba;    // border color is varied using rgba
+$border-light:       #eaeaea; // BP dividers
+$border-med:         #ddd;
+$background-hover:   #db572f; // 2016 link background hover
+$bp-button-hover:    #ededed; // this is the default BP button hover background
+$notice-error:       #c85a6e;
+$notice-warning:     #d05656;
+$notice-info:        #9fd1e2;
+$notice-update:      #ced98c;
+$stripe-odd:         #ebf0ff;
+$stripe-even:        #dbe5ff;
+$unread:             #dce5ff;
+
+/*--------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentysixteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentysixteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the rulesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+----------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+----------------------------------------------------------------
+1.0 Theme Structural Elements & overrides
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 Whats New Activity
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Register, activation
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - item-header: Groups
+		6.1.2 - item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1 - Management settings screens
+			6.2.1.2 - Group members list
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading, Widgets, General classes
+--------------------------------------------------------------*/
+
+/**
+*-------------------------------------------------------------------------------
+* @section 1.0 - Theme - Structural Elements. overrides, Globals
+*-------------------------------------------------------------------------------
+*/
+
+
+
+.buddypress {
+	// Remove any empty clearing elements, CSS provides clearing.
+	div.clear {display: none;}
+
+	// Ensure no text decoration or box shadow causing underlined effect.
+	#page a {
+		box-shadow: none;
+		text-decoration: none !important;
+	}
+
+	.entry-title {
+		text-align: center;
+
+		@include medium-up {
+			text-align: left;
+		}
+	}
+
+	// The breakpoint for main & sidebar could cut in earlier, elements wrap to blocks/columns
+	// but margins & padding still in effect for sidebar style.css L:2781.
+
+	@include small-up {
+		#primary {
+			float: none;
+			margin: 0;
+			width: auto;
+
+			.entry-header {
+				margin: 0;
+			}
+
+			.entry-content,
+			.content-bottom-widgets {
+				margin-left: 7.6923%;
+				margin-right: 7.6923%;
+			}
+		}
+
+		.sidebar {
+			float: none;
+			margin-left: 0;
+			padding: 0 7.6923%;
+			width: auto;
+		}
+	}
+
+	@include medium-up {
+		#primary {
+			float: left;
+			margin-right: -100%;
+			width: 70%;
+
+			.entry-content,
+			.content-bottom-widgets {
+				margin-left: 0;
+				margin-right: 0;
+			}
+		}
+
+		.sidebar {
+			float: left;
+			margin-left: 75%;
+			padding: 0;
+			width: 25%;
+		}
+
+	}
+
+} // .buddypress (body class)
+
+// 2016 defines the class 'no-sidebar' if the primary sidebar is empty/inactive in terms
+// of widgets; if this is in effect on a site then adjust the BP screens for full width.
+
+.buddypress.no-sidebar {
+
+	#primary {
+		float: none;
+		margin: 0;
+		width: auto;
+	}
+
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+
+
+
+/**
+*----------------------------------------------------------
+* @section 2.1 - Navs Object Nav / Sub Nav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+
+// We have a need to override BP specifity
+// so some rulesets sets will appear over weighted in selectors
+
+#buddypress {
+
+	// active/current states all navs
+	div.item-list-tabs {
+		ul {
+			li.selected {
+				a {
+					background: inherit;
+					opacity: 1;
+				}
+			}
+		}
+	}
+	// global nav styles
+	div.item-list-tabs {
+		ul {
+			background-color: $cream-background;
+			border-bottom: 1px solid rgba($border-light, 0.9);
+			border-top: 1px solid rgba($border-light, 0.9);
+			overflow: hidden;
+			padding: 0;
+
+			li {
+				a {
+					span {border-radius: 25%;}
+				}
+			}
+		}
+	} // close item-list-tabs
+
+} // close #buddypress
+
+// .bp-user #object-nav @ small screen as a dropdown
+
+.bp-user {
+
+	#buddypress {
+
+		@media screen and (max-width: 38.75em) {
+
+			#object-nav {
+				background: $cream-background;
+				border: 1px solid $border-light;
+				overflow: visible;
+				padding: $spacing-val-sm;
+
+				ul {
+					border: 0;
+					height: 0;
+					// all latest vendor releases handle transitions, so no prefixes used.
+					transition: height 0.3s ease-in-out 0.7s;
+					visibility: hidden;
+				}
+
+				&:before {
+					content: "Menu \021E9";
+					display: inline;
+					opacity: 0.7;
+				}
+
+				&:hover,
+				&:focus {
+					&:before {content: "Menu \021E7";}
+
+					ul {
+						height: 320px;
+						opacity: 1;
+						overflow-y: auto;
+						visibility: visible;
+					}
+				}
+			}
+
+			#subnav {
+				clear: both;
+			}
+
+		} // close @media
+	}
+}
+
+	/*__ Horizontal menus __*/
+#buddypress {
+
+	#object-nav {
+
+		ul {
+			overflow: hidden;
+
+			li {
+				float: none;
+			}
+
+			@media screen and (max-width: 38.75em) {
+				li:not(:last-child) {
+					border-bottom: 1px solid $border-light;
+				}
+			}
+
+			li:not(.selected) {
+				a {opacity: 0.7;}
+
+				@media screen and (max-width: 38.75em) {
+					&:focus,
+					&:hover {
+						background: darken($cream-background, 2%);
+					}
+				}
+			}
+
+			@media screen and (min-width: 38.75em) {
+				li {
+					float: left;
+				}
+			}
+		}
+	}
+
+	div#subnav.item-list-tabs {
+		margin-top: 0;
+
+		ul {
+			background-color: $light-background;
+			border-bottom: 0;
+			margin-top: $spacing-val-xs;
+			padding: 0;
+
+			li.last {
+				background: $content-background;
+				margin-top: 0;
+				padding: $spacing-val-xs $spacing-val-xs $spacing-val-xs 0;
+				width: 100%;
+
+				select,
+				select:focus {
+					background: transparent;
+					border: 0;
+					outline: 0;
+				}
+
+				select,
+				label,
+				option {
+					@include font-size(14);
+				}
+
+				select {
+					font-style: italic;
+				}
+			}
+
+			@media screen and (min-width: 38.75em) {
+				li.last {
+					text-align: right;
+				}
+			}
+		} // close ul
+	} // close #subnav
+
+} // close #buddypress
+
+
+	/*__ Vertical menu User Account / Group single screens __*/
+
+// This block contains rules to re-factor the item-body structural element
+// to sit alongside the vert menu
+// 2016 right sidebar is displayed if the widget sidebar has widgets;
+// we'll only display object nav as a vert menu if sidebar not present.
+
+@media screen and (min-width: 55em) {
+
+	body.no-sidebar {
+		#buddypress {
+
+			#item-header,
+			#item-body {
+				background: $content-background;
+			}
+
+			#object-nav {
+				border-right: 1px solid $border-med;
+				float: left;
+				margin-right: -1px;
+				width: 200px;
+
+				ul {
+					background: none;
+					border-bottom: 0;
+					padding: 0;
+
+					li {
+						float: none;
+						overflow: hidden;
+
+						span {
+							background: $content-background;
+							border-radius: 10%;
+							float: right;
+							margin-right: 2px;
+						}
+					}
+				}
+			}
+
+			#item-body {
+				border-left: 1px solid $border-med;
+				overflow: hidden;
+				padding: 0 0 0 $spacing-val-md;
+				width: auto;
+
+				#subnav {
+					margin: 0 0 0 (-$spacing-val-md);
+
+					ul {margin-top: 0;}
+				}
+			}
+
+		} // close  #buddypress
+	} // close body:not(.sidebar)
+} // close @media
+
+
+// Tabbed Nav styling
+// Primarilly used for group create screens & ? directory nav.
+
+@media screen and (min-width: 46.25em) {
+	#main {
+		#buddypress {
+
+			#group-create-tabs.item-list-tabs {
+
+				ul:before,
+				ul:after {
+					content: " ";
+					display: table;
+				}
+
+				ul:after {
+					clear: both;
+				}
+
+				ul {
+					background: none;
+					border: 0;
+					border-bottom: 1px solid $border-med;
+					overflow: visible;
+					padding-bottom: 0;
+
+					li {
+						float: left;
+						width: auto;
+					}
+
+					li.selected,
+					li.current {
+						border: 1px solid $border-med;
+						border-bottom-color: $content-background;
+						@include border-top-radius(4px);
+						margin-bottom: -1px;
+
+						a {
+							background: none;
+							color: $body-text;
+							outline: none;
+						}
+					}
+				}
+
+			} // close .item-list-tabs
+
+			#subnav {
+				ul {
+					border-bottom: 0;
+				}
+
+			} // close #subnav
+		} // close #buddypress
+	}
+} // close @media
+
+/**
+*----------------------------------------------------------
+* @section 2.2 - Pagination
+*----------------------------------------------------------
+*/
+
+#buddypress {
+
+	div.pagination {
+		box-shadow: none;
+		min-height: 0;
+
+		&:before,
+		&:after {
+			height: 0;
+			width: 0;
+		}
+
+		.pag-count {margin-left: 0;}
+		.pagination-links {
+			margin-right: 0;
+
+			span,
+			a {
+				height: auto;
+				line-height: 1;
+				padding: 5px;
+			}
+
+			.prev,
+			.next {
+				background-color: transparent;
+				color: inherit;
+				overflow: visible;
+				width: auto;
+				&:before {display: none;}
+			}
+
+			.prev {
+				left: auto;
+				position: static;
+			}
+
+			.next {
+				position: static;
+				right: auto;
+			}
+		} // close .pagination-links
+	} // close .pagination
+} // close #buddypress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+
+// Adjusts Meta items and various elements to match 2016 font-family
+#buddypress {
+	.item-list {
+		.activity-header,
+		.activity-meta {
+			@include default-font-sans();
+		}
+	}
+}
+
+#buddypress {
+	.activity-meta {
+		.button {
+			&:focus,
+			&:hover {
+				background: inherit;
+				color: #000;
+			}
+		}
+	}
+
+	.action {
+		.generic-button {
+			a {
+				&:focus,
+				&:hover {
+					background: inherit;
+					color: #000;
+				}
+			}
+		}
+	}
+} // close #buddypress
+
+// Trying to position the action div absolute has bad consequences where
+// aditional items are displayed, floating is an alternative that
+// allows natural flow expansion.
+// This block:
+// * Manages li items, Stacks them small screen, floated left at wider screens.
+// * Adjusts display of action buttons at small screen mobile up.
+// Adjusts Meta items to match 2016 font-family
+
+#buddypress {
+	ul.item-list {
+		li {
+			overflow: hidden !important;
+
+			// Undo BP floats to center avatars initially mobile up
+			.item-avatar {
+				margin-bottom: $spacing-val-sm;
+				@media screen and (min-width: 38.75em) {
+					margin-bottom: 0;
+				}
+				text-align: center;
+
+				a {border-bottom: 0;}
+
+				img.avatar {
+					display: inline-block;
+					float: none;
+					margin-bottom: $spacing-val-sm;
+
+					// Quite early on we need to move avatars to the left(right)
+					@include small-up {
+						display: block;
+						float: left;
+					}
+				}
+			}
+
+			.item {
+				overflow: hidden;
+				@media screen and (min-width: 46.25em) {
+					margin-left: 15%;
+				}
+
+				span.activity {
+					font-style: italic;
+				}
+
+				.item-desc {
+					margin-left: 0;
+					width: 94%;
+				}
+
+				.item-title {
+					@include font-size(18);
+					line-height: 1.2;
+					text-align: center;
+
+					@include small-up {
+						text-align: left;
+					}
+
+					width: 100%;
+
+					.update {
+						display: block;
+						@include font-size(12);
+
+						@include medium-up {
+							@include font-size(14);
+						}
+
+						padding: $spacing-val-sm 0;
+						text-align: left;
+					}
+				}
+
+			}
+
+			@include medium-up {
+				.item-avatar,
+				.item,
+				.action {
+					float: left;
+				}
+
+				.item {
+					left: 5%;
+					margin-left: 0;
+					position: relative;
+					width: 55%;
+
+					.item-title {
+						@include font-size(22)
+					}
+				}
+
+			}// close medium-up
+
+			div.action {
+				clear: left;
+				float: none;
+				margin-bottom: -$spacing-val-md;
+				// remove BP margin left
+				margin-left: 0;
+				padding: $spacing-val-md 0 $spacing-val-xs;
+				// push the actions to the right
+
+				@include medium-up {
+					clear: none;
+					float: right;
+					margin-bottom: 0;
+					padding: 0;
+				}
+
+				position: relative;
+				text-align: left;
+				top: 0;
+
+				div {
+					display: inline-block;
+					margin: $spacing-val-sm 0;
+					width: 100%;
+
+					a {
+						display: block;
+						width: 100%;
+					}
+					// Provisionally lets keep the 'action' div
+					//position but inline-block the buttons
+
+					@media screen and (min-width: 38.75em) {
+						// keep buttons to a row small tablet up
+						margin: 0 $spacing-val-sm $spacing-val-sm 0;
+						width: auto;
+					} // close @media
+
+					@include medium-up {
+						// take buttons to block to stack them when floated right
+						clear: right;
+						float: right;
+						margin: 0 0 $spacing-val-sm 0;
+					} // close @media
+
+				} // close div
+
+				.meta {
+					font-style: italic;
+				}
+
+			} // close .action
+
+		} // close li
+	} // close .item-list
+} // close #buddypress
+
+/**
+*----------------------------------------------------------
+* @section 4.1 - Activity
+*----------------------------------------------------------
+*/
+
+/**
+*-----------------------------------------------------
+* @section 4.1.1 - Activity Whats New
+*-----------------------------------------------------
+*/
+
+#buddypress {
+	form#whats-new-form {
+
+		// Line-height issue inherited from BP rules, override.
+		p.activity-greeting {
+			line-height: 1.4;
+		}
+
+		// corrective measure for clipped elements due to JS inline styling
+
+		@media screen and (max-width: 46.25em) {
+			#whats-new-content {
+				clear: left;
+				margin: $spacing-val-sm 0 $spacing-val-md;
+				padding: $spacing-val-sm 0 0;
+			}
+		}
+
+		#whats-new-content.active {
+			#whats-new-options[style] {
+				#whats-new-post-in-box {
+					border: 1px solid rgba($border-color, 0.5);
+					float: left;
+					line-height: 1.5em;
+					margin-top: 12px;
+					padding-left: 0.2em;
+					width: 100%;
+
+					select {
+						background: none;
+						border: 0;
+						float: right;
+						margin: 0;
+						min-height: 1.5em;
+						padding-left: 0.4em;
+					}
+				}
+
+				@media screen and (min-width: 30em) {
+					#whats-new-post-in-box {
+						width: auto;
+					}
+
+					#whats-new-submit {
+						float: right;
+					}
+				}
+
+			} // close #whats-new-options
+		} // close #whats-new-content.active
+
+	} // close #whats-new-form
+
+	// User account form requires matching bp default specificity
+	#item-body {
+		form#whats-new-form {
+			margin: $spacing-val-lg 0;
+		}
+	}
+} // close #buddypress
+
+/**
+*-----------------------------------------------------
+* @section 4.1.2 - Activity Listing
+*-----------------------------------------------------
+*/
+
+#buddypress {
+
+	#activity-stream {
+
+		// re-size activity avatars and stack small screen
+		// refactor main elements positioning medium up.
+		li {
+			padding: 25px 0 15px;
+
+			.activity-avatar {
+				float: none;
+				text-align: center;
+
+				a {
+					display: inline-block;
+
+					img.avatar {
+						display: inline;
+						float: none;
+						height: 60px;
+						margin-bottom: $spacing-val-md;
+						margin-left: 0;
+						width: 60px;
+					}
+				}
+			} // close .activity-avatar
+
+			.activity-content {
+				margin-left: 0;
+
+				.activity-header {
+					@include font-size(14);
+				}
+			}
+
+			@media screen and (min-width: 46.25em) {
+
+				.activity-avatar {
+					float: left;
+					margin-right: $spacing-val-sm;
+					text-align: left;
+					a {border-bottom: 0;}
+				}
+
+				.activity-content {
+					margin: 0;
+					overflow: hidden;
+
+					.activity-header {
+						@include font-size(16);
+					}
+				}
+
+			} // close @media
+
+		} // close li
+
+		li.mini {
+			.activity-avatar {
+				a {
+					img.avatar {
+						height: 30px;
+						margin-left: 15px;
+						width: 30px;
+					}
+				}
+			}
+
+			.activity-content {
+				.activity-header {
+					@include font-size(14);
+				}
+			}
+		}
+
+		.activity-content {
+			margin-top: -12px;
+
+			.activity-header {
+				line-height: inherit;
+				margin-right: 0;
+
+				p {
+					background-color: $light-background;
+					border: 1px solid rgba($border-light, 0.6);
+					margin-top: 0;
+					padding: 0 0.2em;
+				}
+
+				img.avatar {
+					display: inline-block;
+					margin-bottom: 0 !important; // have to match to BP
+				}
+			} // close .activity-header
+
+			.activity-meta {
+				a {
+					display: block;
+					margin-bottom: $spacing-val-xs;
+					@media screen and (min-width: 38.75em) {
+						display: inline-block;
+						margin-bottom: 0;
+					}
+				}
+			}
+
+		} // close .activity-content
+
+		.load-more {
+			background: $light-background;
+			border: 1px solid transparent;
+			padding: $spacing-val-sm;
+
+			&:focus,
+			&:hover {
+				background: darken($light-background, 1%);
+				border: 1px solid rgba(#9fd1e2, 0.3);
+				a { font-style: italic; }
+			}
+
+			a {
+				display: block;
+			}
+		}
+
+	} // close #activity-stream
+} // close #buddypress
+
+/* Single activity view  - activity permalink */
+
+.activity-permalink {
+	#buddypress {
+		#activity-stream {
+
+			li.activity-item {
+				padding: $spacing-val-md;
+			}
+
+			li.mini {
+				.activity-header {
+					@include font-size(16);
+					@media screen and (min-width: 46.25em) {
+						@include font-size(20);
+					}
+					margin-bottom: $spacing-val-lg;
+					p {padding: $spacing-val-md;}
+				}
+			}
+		}
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.3 - Activity Comments
+*-----------------------------------------------------
+*/
+#buddypress {
+
+	#activity-stream {
+		.activity-comments {
+			border-left: 1px solid $border-light;
+			margin-top: 5px;
+
+			ul {
+				background: rgba($light-background, 0.6);
+				margin: 15px 0 0 2px;
+			}
+
+			.ac-form {
+
+				input[type="submit"],
+				.ac-reply-cancel {
+					color: rgba($body-text, 0.8);
+					display: inline-block;
+					font-family: inherit;
+					@include font-size(12);
+					font-weight: normal;
+					line-height: 1.2;
+					padding: 4px 10px;
+					text-transform: lowercase;
+					width: 100px;
+				}
+
+				.ac-reply-cancel {
+					border: 1px solid rgba($border-color, 0.7);
+					text-align: center;
+
+					&:focus,
+					&:hover {
+						background: $bp-button-hover;
+					}
+				}
+
+			} // close .ac-form
+
+		} // close .activity-comments
+	} // close .activity
+
+} // close #buddypress
+
+/**
+*----------------------------------------------------------
+* @section 4.2 - Members Loop
+*----------------------------------------------------------
+*/
+
+#buddypress {
+	#members-list {
+
+		@include medium-up {
+			li {
+				.item-avatar,
+				.item {
+					float: left;
+				}
+
+				.action {
+					float: right;
+				}
+			}
+		}
+
+	} // close #members-list
+} //close #BuddyPress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 5.0 - Directories
+*-------------------------------------------------------------------------------
+*/
+
+/**
+*-----------------------------------------------------------
+* @subsection Registration
+*-----------------------------------------------------------
+*/
+
+#buddypress {
+	#signup_form.standard-form {
+
+		#profile-details-section,
+		#basic-details-section,
+		#blog-details-section {
+			float: none;
+			width: 100%;
+
+			@media screen and (min-width: 38.75em) {
+				width: 48%;
+			}
+		}
+
+		@media screen and (min-width: 38.75em) {
+			#profile-details-section {float: right;}
+			#basic-details-section {float: left;}
+		}
+
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens: User Account & Single Group Screens
+*-------------------------------------------------------------------------------
+*/
+
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.1 - Item Header Global
+*-----------------------------------------------------------
+*/
+
+.bp-user,
+.single-item.groups {
+	#buddypress {
+		#item-header-content {
+			#item-meta {
+				@include font-size(14);
+				text-align: left;
+
+				p {margin-bottom: 0.5em;}
+			}
+		}
+	}
+
+	@media screen and (max-width: 46.25em) {
+		main {
+			header.entry-header {
+				padding-bottom: 1rem;
+			}
+		}
+	}
+
+	@media screen and (max-width: 38.75em) {
+		h1,
+		#item-header-content {
+			text-align: center;
+		}
+	}
+
+	#buddypress {
+		@media screen and (max-width: 46.25em) {
+			#item-header {
+				.generic-button {
+					float: none;
+					margin: 1.5em 0 0 0;
+				}
+			}
+		}
+
+		@media screen and (max-width: 38.75em) {
+			h1 {
+				margin-bottom: 0;
+			}
+
+			#item-header-avatar {
+				img.avatar {
+					margin-right: 0;
+				}
+			}
+
+			#item-header-content {
+				width: 100%;
+			}
+		}
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.1 - item-header Groups
+*
+* Group Specific Item Header
+*-----------------------------------------------------
+*/
+
+.single-item.groups {
+	#buddypress {
+		@media screen and (max-width: 46.25em) {
+			#item-header {
+				#item-meta {
+					margin-bottom: $spacing-val-md;
+				}
+			}
+		}
+
+		// Move visual flow of avatar & item-actions at narrow width,
+		// avatar first after group title
+		@media screen and (max-width: 38.75em) {
+			div#item-header {
+				display: flex;
+				flex-direction: column;
+
+				#item-header-avatar {
+					order: 1;
+					text-align: center;
+
+					a {
+						border-bottom: 0;
+
+						img {
+							display: inline-block;
+							float: none;
+						}
+					}
+				}
+
+				#item-header-content {
+					order: 2;
+				}
+
+				#item-actions {
+					order: 3;
+
+					h3 {
+						border-bottom: 1px solid $border-light;
+						text-align: center;
+					}
+				}
+			}
+		} // close @media
+
+		div#item-header {
+			padding-bottom: $spacing-val-lg;
+
+			div#item-actions {
+				margin: 0;
+				width: 100%;
+
+				@media screen and (min-width: 38.75em) {
+					border-left: 1px solid $border-light;
+					clear: none;
+					float: right;
+					padding-left: 0.2em;
+					width: 30%;
+				} // close @media
+
+				@media screen and (min-width: 46.25em) {
+					width: 40%;
+				}
+
+				ul {
+					margin-top: 0;
+					padding-left: 0;
+				}
+
+				h3 {
+					//background: $dark-background;
+					//color: $content-background;
+					@include font-size(14);
+
+					@media screen and (min-width: 46.25em) {
+						@include font-size(16);
+					}
+					padding: 0.2em;
+				}
+			} // close item-actions
+
+			@media screen and (min-width: 46.25em) {
+
+				#item-header-avatar,
+				#item-header-content {
+					float: left;
+				}
+
+				#item-header-avatar {
+					width: 21%;
+				}
+
+				#item-header-content {
+					margin-left: 4%;
+					width: 40%;
+				}
+
+				div#item-actions {
+					float: right;
+					width: 28%;
+				}
+
+			} // close @media
+
+		} // close #item-header
+	} // close #buddypress
+} // close .groups
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.2 - Item Header User Accounts
+*
+* User Accounts Specific Item Header
+*-----------------------------------------------------
+*/
+
+.bp-user {
+	#buddypress {
+		#item-header {
+			padding: $spacing-val-md 0;
+
+			#item-header-avatar {
+				text-align: center;
+				width: 100%;
+
+				img.avatar,
+				a {
+					border-bottom: 0;
+					display: inline-block;
+					float: none;
+				}
+			}
+
+			@media screen and (min-width: 46.25em) {
+				#item-header-avatar {
+					float: left;
+					width: 20%;
+
+					a {
+						float: left;
+					}
+				}
+
+				#item-header-content {
+					float: right;
+					margin-right: 5%;
+					width: 69%;
+				}
+
+			} // close @media
+
+		} // close #item-header
+
+	} // close #buddypress
+} // close .bp-user
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*-----------------------------------------------------------
+*/
+
+/**
+*----------------------------------------------------
+* @subsection 6.2.1 - Item Body Groups
+*
+* Groups specific item body rules -  screens
+*----------------------------------------------------
+*/
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.1 - Group Management
+*-----------------------------------------
+*/
+
+// headings settings screens & general global settings styles
+.groups {
+	#group-settings-form {
+		h4 {
+			background: $dark-background;
+			color: $content-background;
+			padding: 0.2em;
+		}
+	}
+}
+
+.groups.edit-details {
+	#group-settings-form {
+
+		label {
+			margin-bottom: 0;
+			padding: 0.2em;
+			width: 80%;
+		}
+
+		textarea + p label {
+			background: none;
+			color: inherit;
+			@include font-size(14);
+			width: auto;
+		}
+
+		textarea {
+			height: auto;
+			min-height: 100px;
+			overflow: auto;
+		}
+	}
+} // close .groups.edit-details
+
+.groups.group-settings {
+	#group-settings-form {
+
+		div.radio label {
+			border: 1px solid $border-light;
+			padding: 0.2em;
+
+			ul {
+				color: rgba($body-text, 0.6);
+				@include font-size(14);
+			}
+		}
+
+	}
+} // close .groups.group-settings
+
+.groups.group-avatar {
+
+	form > p {
+		margin-top: $spacing-val-md;
+	}
+
+}
+
+.groups.manage-members {
+	#group-settings-form {
+		.item-list {
+			li {
+				border-bottom: 1px solid $border-light;
+
+				img,
+				h5 {
+					float: left;
+
+					> a {border-bottom: 0;}
+				}
+
+				span.small {
+					clear: left;
+					display: block;
+					float: none;
+					margin-top: $spacing-val-sm;
+
+					a {
+						display: inline-block;
+						margin: $spacing-val-xs 0;
+						width: 100%;
+
+						@media screen and (min-width: 38.75em) {
+							width: auto;
+						}
+					}
+				}
+
+				h5 {
+					margin: 0;
+				}
+
+			}
+		}
+	}
+} // close .groups.manage-members
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.2 - Group members
+*-----------------------------------------
+*/
+
+// Massage the members search for groups nav specifically.
+.groups.group-members {
+	#subnav {
+		li {
+			@media screen and (max-width: 38.75em) {
+				background: $content-background;
+				padding: $spacing-val-md 0;
+			}
+			width: 100%;
+
+			#search-members-form {
+				float: right;
+
+				@media screen and (max-width: 38.75em) {
+					margin: 0;
+					width: 100%;
+
+					label {
+						input[type="text"] {width: 100%;}
+					}
+				}
+
+				margin: $spacing-val-xs $spacing-val-xs 0 0;
+			}
+		}
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.2.2 - Item Body User Accounts
+*
+* User Account specific item body rules
+*-----------------------------------------------------
+*/
+
+.bp-user {
+	.entry-title {margin-bottom: 0.5em;}
+}
+
+/**
+*--------------------------------------------
+* @subsection 6.2.2.1 - classes, pag, filters
+*--------------------------------------------
+*/
+.bp-user {
+
+	#buddypress {
+
+		table {
+			th {@include font-size(14)}
+			td {@include font-size(12)}
+
+			@include medium-up {
+				th {@include font-size(16)}
+				td {@include font-size(14)}
+			}
+
+			@include large-up {
+				th {@include font-size(18)}
+				td {@include font-size(16)}
+			}
+		}
+
+		.pag-count {font-style: italic;}
+
+		.notifications-options-nav {
+			border: 1px solid rgba($border-color, 0.5);
+			float: left;
+			width: 100%;
+			@media screen and (min-width: 38.75em) {
+				width: 300px;
+			}
+
+			select,
+			input {
+				border: 0;
+				@include font-size(14);
+				outline: 0;
+				padding: 0;
+			}
+
+			select {
+				float: left;
+				margin-right: 0;
+				width: 60%;
+			}
+
+			input {
+				float: right;
+				font-family: inherit;
+				line-height: 20px;
+				width: 40%;
+			}
+
+		} // close .notifications-options-nav
+
+	} //close #buddypress
+} // close .bp-user
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.2 - Extended Profiles
+	*-------------------------------------------
+	*/
+
+.bp-user {
+
+	#buddypress {
+
+		.profile {
+			.bp-widget {
+				h4 {
+					background: lighten($dark-background, 10%);
+					color: #fff;
+					margin-bottom: 0;
+					padding: 0.4em;
+				}
+
+				table {margin-top: 0;}
+			}
+			/* Edit profile */
+
+			#profile-edit-form {
+				.button-nav:before,
+				.button-nav:after {
+					content: " ";
+					display: table;
+				}
+
+				.button-nav:after {
+					clear: both;
+				}
+
+				ul.button-nav {
+					border-bottom: 1px solid $border-light;
+					margin-left: 0;
+
+					li {
+						float: left;
+						margin-bottom: 0;
+
+						&.current {
+							border: 1px solid $border-light;
+							border-bottom-color: #fff;
+							margin-bottom: -1px;
+						}
+					} // li
+
+					a {
+						background: none;
+						border: 0;
+						@include font-size(18)
+					}
+				} //.button-nav
+
+				.field-visibility-settings-toggle,
+				.field-visibility-settings {
+					@include font-size(14);
+				}
+
+				.field-visibility-settings-close,
+				.visibility-toggle-link {
+					background: $dark-background;
+					color: #fff;
+					padding: 0.2em 0.5em;
+				}
+			} // close profile form
+
+			.bp-avatar {
+				#bp-delete-avatar {
+					a {font-size: inherit; }
+				}
+			}
+		} // close .profile
+
+	} // close #buddypress
+} // close .bp-user
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.3 - Groups
+	*-------------------------------------------
+	*/
+
+.bp-user {
+
+	#buddypress {
+
+		#groups-list {
+			li {
+				.item {
+					@media screen and (min-width: 77.5em) {
+						left: 5%;
+						width: 50%;
+					}
+				}
+			}
+		}
+
+	} // close #buddypress
+} // close .bp-user
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.5 - Private Messaging
+	*-------------------------------------------
+	*/
+
+.bp-user {
+
+	#buddypress {
+
+		#message-thread {
+		//Single message view
+			a {
+				border-bottom: 0;
+			}
+
+			#message-subject {
+				background: lighten($dark-background, 10%);
+				color: $content-background;
+				padding: 0.3em 0 0.3em 0.2em;
+			}
+
+			#message-recipients {
+				font-style: italic;
+
+				a.confirm {
+					border: 1px solid $border-light;
+					font-style: normal;
+				}
+			}
+
+			.message-metadata {
+				overflow: hidden;
+
+				img.avatar {
+					float: none;
+				}
+
+				@media screen and (min-width: 46.25em) {
+					img.avatar {
+						float: left;
+					}
+				}
+
+				.message-star-actions {
+					float: right;
+					margin-right: $spacing-val-xs;
+					position: static;
+				}
+			} // close .message-meta
+
+			.message-content {
+				background: $light-background;
+				border: 1px solid $border-light;
+				margin: $spacing-val-sm 0 0 0;
+				padding: 0.3em;
+			}
+
+			#send-reply {
+				.message-content {
+					background: $content-background;
+					border: 0;
+				}
+			}
+
+			.alt {
+				background: $content-background;
+			}
+
+		} // close message-thread  Singular view!
+
+		#message-threads {
+
+			thead {
+				tr {
+					background: lighten($dark-background, 10%);
+				}
+			}
+
+			tr {
+				td {
+					background: $content-background;
+					display: inline-block;
+					float: left;
+				}
+
+				td.thread-star,
+				td.thread-options {
+					border-bottom-color: $medium-background;
+					border-bottom-width: 2px;
+					height: 2.4em;
+					padding-bottom: 0.2em;
+					padding-top: 0.2em;
+
+					@media screen and (max-width: 46.25em) {
+						padding-top: 0;
+					}
+				}
+
+				td.thread-star {
+					vertical-align: middle;
+
+					.message-action-star {
+						line-height: 1.2;
+					}
+				}
+
+				td.bulk-select-check,
+				td.thread-from {
+					height: 3em;
+					@media screen and (max-width: 38.75em) {
+						height: 5.2em;
+					}
+				}
+
+				td.thread-from,
+				td.thread-options {
+					border-left: 0 !important;
+					@include calc(width, "100% - 30px");
+					margin-left: 0;
+				}
+
+				td.thread-info {
+					padding-left: 41px;
+					width: 100%;
+
+					a::after {
+						content: " \2016 \00a0"attr(title);
+						@include font-size(12);
+					}
+				}
+
+				td.thread-options {
+					text-align: right;
+
+					a {
+						@include font-size(12);
+						line-height: 2.2;
+					}
+				}
+
+				span.from {
+					display: none;
+				}
+
+				span.activity {
+					display: block;
+					float: right;
+					line-height: 2;
+					@media screen and (max-width: 38.75em) {
+						clear: both;
+						@include font-size(11);
+						width: 100%;
+					}
+				}
+			}
+
+			tr.unread {
+				td {
+					background: $unread;
+					border-color: $border-color;
+				}
+			}
+
+			th {
+				display: none;
+			}
+
+			th.bulk-select-all {
+				border-bottom: 0;
+				display: inline-block;
+				text-align: left;
+			}
+
+			th.bulk-select-all,
+			td.bulk-select-check,
+			td.thread-star {
+				border-right: 0;
+				width: 30px;
+			}
+		}
+
+		/* Sitewide Notices loop */
+		#message-threads.sitewide-notices {
+
+			td {
+				width: 100%;
+
+				strong {
+					background: lighten($dark-background, 10%);
+					color: $content-background;
+					display: block;
+					margin-bottom: 0.4em;
+					padding-left: 0.2em;
+				}
+
+				a {
+					display: inline-block;
+				}
+			}
+
+			td:first-child {
+				display: none;
+			}
+
+			td:nth-child(2) {
+				strong {
+					margin: -8px -8px 8px;
+				}
+			}
+
+			td:first-child + td + td {
+			// notice date
+				border-bottom: 0;
+
+				span {
+					line-height: 1;
+				}
+			}
+
+			td:last-child {
+			// notice actions
+				border-bottom-color: darken($border-light, 20%);
+				line-height: 1;
+				text-align: right;
+
+				a:last-child {
+				// hide the 'x' text
+					line-height: 0;
+					margin-left: 0.7em;
+					text-indent: -999em;
+				}
+
+				a:last-child::after {
+					content: attr(title);
+					display: block;
+					line-height: initial;
+					text-indent: 0;
+				}
+			}
+		}
+
+	} // #buddypress
+} // close .bp-user
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+
+.bp-user {
+
+	#buddypress {
+
+		#settings-form {
+			// 'p' = email notification screen sub heading
+			> p {
+				@include font-size(20);
+				margin: $spacing-val-md 0 $spacing-val-sm;
+			}
+		}
+
+		table.notification-settings {
+			td.yes,
+			td.no {
+				vertical-align: middle;
+			}
+		}
+		// Profile table td widths
+		table.profile-settings {
+			width: 100%;
+
+			th.field-group-name,
+			td.field-name {
+				width: 50%;
+			}
+
+			@media screen and (min-width: 46.25em) {
+				th.field-group-name,
+				td.field-name {
+					width: 70%;
+				}
+			}
+
+			th.title,
+			td.field-visibility {
+				width: 30%;
+			}
+
+			td.field-visibility {
+				select {width: 100%;}
+			}
+		}
+
+	} // close #buddyPress
+
+} // close .bp-user
+
+/**
+*-------------------------------------------------------------------------------
+* @section 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+	// Attempt to reset form control widths
+#main {
+	#buddypress {
+		.standard-form {
+
+			li {float: none;}
+			input[type='text'],
+			input[type='email'],
+			input[type='password'],
+			textarea {
+				width: 100%;
+			}
+		}
+	}
+}
+
+
+#buddypress {
+
+// Match BP form elements to Themes(2016) styling where styles
+// are not inherited or being overriden.
+	div.activity-comments {
+
+		form {
+			.ac-textarea {
+				background: $light-background;
+				border: 1px solid rgba($border-color, 0.5);
+
+				textarea {
+					background: none;
+					border: 0;
+				}
+			}
+		}
+	}
+
+	.standard-form {
+
+		// Keep BP form element borders to 1px rather than 2013 2px
+		input[type="text"],
+		input[type="email"],
+		input[type="password"],
+		button,
+		select,
+		textarea {
+			border-color: rgba($border-color, 0.5);
+			border-width: 1px;
+		}
+
+	} // close .standard-form
+
+	#signup_form.standard-form {
+		div.submit {
+			float: none;
+
+			input {margin-right: 0;}
+		}
+	}
+
+} // close #buddypress
+
+#buddypress {
+	div.dir-search,
+	div.message-search,
+	li.groups-members-search {
+		float: none;
+		margin: $spacing-val-sm 0;
+
+		// Stylise the seach form elements, in part this deals with padding
+		// issues on the submit & sizing issues between bp styles & 2016
+
+		form { // *sigh* only to bludgeon over specified rules
+			border: 1px solid rgba($border-color, 0.6);
+			overflow: hidden;
+
+			label {
+				float: left;
+				width: 70%;
+			}
+
+			input[type='text'] {
+				float: left;
+				margin: 0;
+				width: 100%;
+			}
+
+			input[type='text'],
+			input[type='submit'] {
+				@include font-size(14);
+				border: 0;
+				line-height: inherit;
+			}
+
+			input[type='text'] {
+				border-right: 1px solid rgba($border-color, 0.6);
+				padding: 0.2em 0 0.2em 0.2em;
+			}
+
+			input[type='submit'] {
+				float: right;
+				font-weight: normal;
+				padding: 0.2em 1em;
+				text-align: center;
+				text-transform: none;
+				width: 30%;
+			}
+		} // close form
+
+	} // close .dir-search, .message-search
+
+	// Shift the search parent to the right and allow to shrinkwrap
+
+	@media screen and (min-width: 38.75em) {
+		div.dir-search,
+		div.message-search,
+		li.groups-members-search {
+			float: right;
+			margin-bottom: 5px !important;
+
+			form {
+				label,
+				input[type='text'],
+				input[type='submit'] {
+					width: auto;
+				}
+			}
+		}
+
+	} // close @media
+
+
+	@include large-up {
+
+		.dir-search,
+		.message-search {
+			form {
+				input[type='text'] { @include font-size(16) ;}
+				input[type='submit'] { @include font-size(16) ;}
+			}
+		}
+	}
+
+} // close #buddypress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables - General
+*-------------------------------------------------------------------------------
+*/
+
+// Adjust table font sizes, default too large proportionally
+// This approach will require refinement and perhaps re-location to
+// a more general typography section to manage BP elements grouped under
+// breakpoints. Provide top/bottom margins for tables, lacking in BP styles
+#buddypress {
+	table {
+		@include font-size(14);
+		margin: $spacing-val-md 0;
+
+		tr th {
+			background: lighten($dark-background, 10%);
+			border-color: darken($border-light, 20%);
+			color: $content-background;
+		}
+
+		// Reduce the themes inherited paragraph margins in tables
+		p {margin-bottom: 0.5em;}
+	}
+
+	@media screen and (min-width: 55em) {
+		table {@include font-size(16);}
+	}
+}
+
+/*__ User Account tables __*/
+
+#buddypress {
+
+	// Manage some table cells widths that are disproportionate to their content
+	.notifications,
+	.messages-notices {
+		th {
+			width: 30%;
+
+			&.bulk-select-all {
+				text-align: center;
+				width: 10%;
+			}
+		}
+
+		.bulk-select-check,
+		.thread-star {
+			text-align: center;
+		}
+
+		.notification-actions,
+		td.thread-options {
+			text-align: center;
+
+			a {
+				display: inline-block;
+				margin: 0;
+				padding: 0;
+			}
+		}
+
+		// Notices action buttons, this maybe better moved, temp for now to address
+		// styling issues - this will need styling ideas
+		td {
+			.button {
+				border: 0;
+				display: block;
+				padding: 0;
+				text-align: center;
+			}
+		}
+
+	} // .notifications, .message-notices
+} // #buddypress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+
+// message args ($variable-color, text-color)
+// Leave text-color undefined to automagically set text color
+// to background color darker by 50%
+
+#buddypress {
+	div#message {
+		p {
+			@include font-size(18);
+			font-weight: bold;
+		}
+
+		&.info {
+			p {
+				@include message-box($notice-info);
+			}
+		}
+
+		&.updated {
+			p {
+				@include message-box($notice-update);
+			}
+		}
+
+	} // close #message
+
+} // close  #buddypress
+
+// Without direct classes on our mesages ( 'warning' )
+// we need to use the body classes
+// todo: If this is class .warning is included in main BP styles remove from here
+.delete-group {
+	#buddypress {
+		div#message.info {
+			p {
+				@include message-box($notice-warning);
+			}
+		}
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+
+#buddypress {
+	/* Auto Complete background */
+	.acfb-holder {
+
+		li.friend-tab {
+			background: $notice-info;
+			border: inherit;
+		}
+	}
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentythirteen-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentythirteen-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentythirteen-rtl.css	(revision 10410)
@@ -0,0 +1,1944 @@
+@charset "UTF-8";
+/*--------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentythirteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentyfifteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the rulesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+----------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+----------------------------------------------------------------
+1.0 Theme Structural Elements & overrides
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 Whats New Activity
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Register, activation
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - item-header: Groups
+		6.1.2 - item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1 - Management settings screens
+			6.2.1.2 - Group members list
+			6.2.1.3 - Group send Invites
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading, Widgets, General classes
+--------------------------------------------------------------*/
+/**
+*-------------------------------------------------------------------------------
+* @section 1.0 - Theme - Structural Elements & overrides
+*-------------------------------------------------------------------------------
+*/
+.buddypress div.clear {
+	display: none;
+}
+
+@media (max-width: 999px) {
+	.buddypress #content {
+		padding: 0 20px;
+	}
+}
+
+@media (max-width: 643px) {
+	.buddypress #content {
+		padding: 0;
+	}
+}
+
+.buddypress .entry-header,
+.buddypress .entry-content,
+.buddypress .entry-summary,
+.buddypress .entry-meta {
+	max-width: 890px;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+/**
+*----------------------------------------------------------
+* @section 2.1 - Navs Object Nav / Sub Nav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+#buddypress div.item-list-tabs ul {
+	background-color: #f7f5e7;
+	border-bottom: 1px solid rgba(234, 234, 234, 0.9);
+	border-top: 1px solid rgba(234, 234, 234, 0.9);
+	overflow: hidden;
+	padding: 0;
+}
+
+#buddypress div.item-list-tabs ul li a span {
+	border-radius: 25%;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #object-nav {
+		background: #f7f5e7;
+		border: 1px solid #eaeaea;
+		overflow: visible;
+		padding: 10px;
+	}
+	.bp-user #buddypress #object-nav ul {
+		border: 0;
+		height: 0;
+		transition: height 0.3s ease-in-out 0.7s;
+		visibility: hidden;
+	}
+	.bp-user #buddypress #object-nav:before {
+		content: "Menu \021E9";
+		display: inline;
+		opacity: 0.7;
+	}
+	.bp-user #buddypress #object-nav:hover:before,
+	.bp-user #buddypress #object-nav:focus:before {
+		content: "Menu \021E7";
+	}
+	.bp-user #buddypress #object-nav:hover ul,
+	.bp-user #buddypress #object-nav:focus ul {
+		height: 320px;
+		opacity: 1;
+		overflow-y: auto;
+		visibility: visible;
+	}
+	.bp-user #buddypress #subnav {
+		clear: both;
+	}
+}
+
+/*__ Horizontal menus & small screen __*/
+#buddypress #object-nav ul {
+	overflow: hidden;
+}
+
+#buddypress #object-nav ul li {
+	float: none;
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress #object-nav ul li:focus,
+	#buddypress #object-nav ul li:hover {
+		background: #f4f2df;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress #object-nav ul li:not(:last-child) {
+		border-bottom: 1px solid #eaeaea;
+	}
+}
+
+#buddypress #object-nav ul li:not(.selected) a {
+	opacity: 0.7;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #object-nav ul li {
+		float: right;
+	}
+}
+
+#buddypress div#subnav.item-list-tabs {
+	margin-top: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul {
+	background-color: #f7f7f7;
+	border-bottom: 0;
+	margin-top: 5px;
+	padding: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last {
+	background: #fff;
+	margin-top: 0;
+	padding: 5px 0 5px 5px;
+	width: 100%;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last select:focus {
+	background: transparent;
+	border: 0;
+	outline: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last label,
+#buddypress div#subnav.item-list-tabs ul li.last option {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select {
+	font-style: italic;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div#subnav.item-list-tabs ul li.last {
+		text-align: left;
+	}
+}
+
+#buddypress div.item-list-tabs ul li.selected a,
+#buddypress div.item-list-tabs ul li.current a {
+	background: none;
+	opacity: 1;
+}
+
+/*__ Vertical menu User Account / Group single screens __*/
+@media screen and (min-width: 55em) {
+	body:not(.sidebar) #buddypress #item-header,
+	body:not(.sidebar) #buddypress #item-body {
+		background: #fff;
+	}
+	body:not(.sidebar) #buddypress #object-nav {
+		border-left: 1px solid #ddd;
+		float: right;
+		margin-left: -1px;
+		width: 200px;
+	}
+	body:not(.sidebar) #buddypress #object-nav ul {
+		background: none;
+		border-bottom: 0;
+		padding: 0;
+	}
+	body:not(.sidebar) #buddypress #object-nav ul li {
+		float: none;
+		overflow: hidden;
+	}
+	body:not(.sidebar) #buddypress #object-nav ul li span {
+		background: #fff;
+		border-radius: 10%;
+		float: left;
+		margin-left: 2px;
+	}
+	body:not(.sidebar) #buddypress #item-body {
+		border-right: 1px solid #ddd;
+		overflow: hidden;
+		padding: 0 20px 0 0;
+		width: auto;
+	}
+	body:not(.sidebar) #buddypress #item-body #subnav {
+		margin: 0 -20px 0 0;
+	}
+	body:not(.sidebar) #buddypress #item-body #subnav ul {
+		margin-top: 0;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	#main #buddypress #group-create-tabs.item-list-tabs ul:before,
+	#main #buddypress #group-create-tabs.item-list-tabs ul:after {
+		content: " ";
+		display: table;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul:after {
+		clear: both;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul {
+		background: none;
+		border: 0;
+		border-bottom: 1px solid #ddd;
+		overflow: visible;
+		padding: 0;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li {
+		float: right;
+		width: auto;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.selected,
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.current {
+		border: 1px solid #ddd;
+		border-bottom-color: #fff;
+		-webkit-border-top-left-radius: 4px;
+		border-top-left-radius: 4px;
+		-webkit-border-top-right-radius: 4px;
+		border-top-right-radius: 4px;
+		background-clip: padding-box;
+		margin-bottom: -1px;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.selected a,
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.current a {
+		background: none;
+		color: #141412;
+		outline: none;
+	}
+	#main #buddypress #subnav ul {
+		border-bottom: 0;
+	}
+}
+
+/**
+*----------------------------------------------------------
+* @section 2.2 - Pagination
+*----------------------------------------------------------
+*/
+#buddypress div.pagination {
+	box-shadow: none;
+}
+
+#buddypress div.pagination .pag-count {
+	margin-right: 0;
+}
+
+#buddypress div.pagination .pagination-links {
+	margin-left: 0;
+}
+
+#buddypress div.pagination .pagination-links span,
+#buddypress div.pagination .pagination-links a {
+	height: auto;
+	line-height: 1;
+	padding: 5px;
+}
+
+#buddypress div.pagination .pagination-links .prev,
+#buddypress div.pagination .pagination-links .next {
+	background-color: transparent;
+	color: inherit;
+	overflow: visible;
+	width: auto;
+}
+
+#buddypress div.pagination .pagination-links .prev:before,
+#buddypress div.pagination .pagination-links .next:before {
+	display: none;
+}
+
+#buddypress div.pagination .pagination-links .prev {
+	right: auto;
+	position: static;
+}
+
+#buddypress div.pagination .pagination-links .next {
+	position: static;
+	left: auto;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+#buddypress .item-list .activity-header,
+#buddypress .item-list .activity-meta {
+	font-family: "Source Sans Pro", Helvetica, sans-serif;
+}
+
+#buddypress .activity-meta .button:focus,
+#buddypress .activity-meta .button:hover {
+	background: #e37a5a;
+	color: #f7f5e7;
+}
+
+#buddypress .action .generic-button a:focus,
+#buddypress .action .generic-button a:hover {
+	background: #e37a5a;
+	color: #f7f5e7;
+}
+
+#buddypress ul.item-list li {
+	overflow: hidden !important;
+}
+
+#buddypress ul.item-list li .item-avatar {
+	margin-bottom: 10px;
+	text-align: center;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item-avatar {
+		margin-bottom: 0;
+	}
+}
+
+#buddypress ul.item-list li .item-avatar a {
+	border-bottom: 0;
+}
+
+#buddypress ul.item-list li .item-avatar img.avatar {
+	display: inline-block;
+	float: none;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item-avatar img.avatar {
+		display: block;
+		float: right;
+	}
+}
+
+#buddypress ul.item-list li .item {
+	overflow: hidden;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress ul.item-list li .item {
+		margin-right: 15%;
+	}
+}
+
+#buddypress ul.item-list li .item span.activity {
+	font-style: italic;
+}
+
+#buddypress ul.item-list li .item .item-desc {
+	margin: 0 auto;
+	width: 100%;
+}
+
+#buddypress ul.item-list li .item .item-title {
+	font-size: 18px;
+	font-size: 1.125rem;
+	line-height: 1.2;
+	margin: 0 auto;
+	text-align: center;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item .item-title {
+		text-align: right;
+	}
+}
+
+#buddypress ul.item-list li .item .item-title .update {
+	font-size: 12px;
+	font-size: 0.75rem;
+	display: block;
+	padding: 10px 0;
+	text-align: right;
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li .item .item-title .update {
+		font-size: 14px;
+		font-size: 0.875rem;
+	}
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li .item-avatar,
+	#buddypress ul.item-list li .item,
+	#buddypress ul.item-list li .action {
+		float: right;
+	}
+	#buddypress ul.item-list li .item {
+		right: 5%;
+		margin-right: 0;
+		position: relative;
+		width: 55%;
+	}
+	#buddypress ul.item-list li .item .item-title {
+		font-size: 22px;
+		font-size: 1.375rem;
+	}
+}
+
+#buddypress ul.item-list li div.action {
+	clear: right;
+	float: none;
+	margin-bottom: -20px;
+	margin-right: 0;
+	padding: 20px 0 5px;
+	position: relative;
+	text-align: right;
+	top: 0;
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li div.action {
+		clear: none;
+		float: left;
+		margin-bottom: 0;
+		padding: 0;
+	}
+}
+
+#buddypress ul.item-list li div.action div {
+	display: inline-block;
+	margin: 10px 0;
+	width: 100%;
+}
+
+#buddypress ul.item-list li div.action div a {
+	display: block;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li div.action div {
+		margin: 0 0 10px 10px;
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li div.action div {
+		clear: left;
+		float: left;
+		margin: 0 0 10px 0;
+	}
+}
+
+#buddypress ul.item-list li div.action .meta {
+	font-style: italic;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.1 - Activity
+*----------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------
+* @section 4.1.1 - Activity Whats New
+*-----------------------------------------------------
+*/
+#buddypress form#whats-new-form p.activity-greeting {
+	line-height: 1.4;
+}
+
+@media screen and (max-width: 46.25em) {
+	#buddypress form#whats-new-form #whats-new-content {
+		clear: right;
+		margin: 10px 0 20px;
+		padding: 10px 0 0;
+	}
+}
+
+#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box {
+	border: 1px solid rgba(212, 208, 186, 0.5);
+	float: right;
+	line-height: 1.5em;
+	margin-top: 12px;
+	padding-right: 0.2em;
+	width: 100%;
+}
+
+#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box select {
+	background: none;
+	border: 0;
+	float: left;
+	margin: 0;
+	min-height: 1.5em;
+	padding-right: 0.4em;
+}
+
+@media screen and (min-width: 30em) {
+	#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box {
+		width: auto;
+	}
+	#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-submit {
+		float: left;
+	}
+}
+
+#buddypress #item-body form#whats-new-form {
+	margin: 40px 0;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.2 - Activity Listing
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream li {
+	padding: 25px 0 15px;
+}
+
+#buddypress #activity-stream li .activity-avatar {
+	float: none;
+	text-align: center;
+}
+
+#buddypress #activity-stream li .activity-avatar a {
+	display: inline-block;
+}
+
+#buddypress #activity-stream li .activity-avatar a img.avatar {
+	display: inline;
+	float: none;
+	height: 60px;
+	margin-bottom: 20px;
+	margin-right: 0;
+	width: 60px;
+}
+
+#buddypress #activity-stream li .activity-content {
+	margin-right: 0;
+}
+
+#buddypress #activity-stream li .activity-content .activity-header {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress #activity-stream li .activity-avatar {
+		float: right;
+		margin-left: 10px;
+		text-align: right;
+	}
+	#buddypress #activity-stream li .activity-avatar a {
+		border-bottom: 0;
+	}
+	#buddypress #activity-stream li .activity-content {
+		margin: 0;
+		overflow: hidden;
+	}
+	#buddypress #activity-stream li .activity-content .activity-header {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+#buddypress #activity-stream li.mini .activity-avatar a img.avatar {
+	height: 30px;
+	margin-right: 15px;
+	width: 30px;
+}
+
+#buddypress #activity-stream li.mini .activity-content .activity-header {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+#buddypress #activity-stream .activity-content {
+	margin-top: -12px;
+}
+
+#buddypress #activity-stream .activity-content .activity-header {
+	line-height: inherit;
+	margin-left: 0;
+}
+
+#buddypress #activity-stream .activity-content .activity-header p {
+	background-color: #f7f7f7;
+	border: 1px solid rgba(234, 234, 234, 0.6);
+	margin-top: 0;
+	padding: 0 0.2em;
+}
+
+#buddypress #activity-stream .activity-content .activity-header img.avatar {
+	display: inline-block;
+	margin-bottom: 0 !important;
+}
+
+#buddypress #activity-stream .activity-content .activity-meta a {
+	display: block;
+	margin-bottom: 5px;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #activity-stream .activity-content .activity-meta a {
+		display: inline-block;
+		margin-bottom: 0;
+	}
+}
+
+#buddypress #activity-stream .load-more {
+	background: #f7f7f7;
+	border: 1px solid transparent;
+	padding: 10px;
+}
+
+#buddypress #activity-stream .load-more:focus,
+#buddypress #activity-stream .load-more:hover {
+	background: #f4f4f4;
+	border: 1px solid rgba(159, 209, 226, 0.3);
+}
+
+#buddypress #activity-stream .load-more:focus a,
+#buddypress #activity-stream .load-more:hover a {
+	font-style: italic;
+}
+
+#buddypress #activity-stream .load-more a {
+	display: block;
+}
+
+/* Single activity view  - activity permalink */
+.activity-permalink #buddypress #activity-stream li.activity-item {
+	padding: 20px;
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+	font-size: 16px;
+	font-size: 1rem;
+	margin-bottom: 40px;
+}
+
+@media screen and (min-width: 46.25em) {
+	.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+		font-size: 20px;
+		font-size: 1.25rem;
+	}
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header p {
+	padding: 20px;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.3 - Activity Comments
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream .activity-comments {
+	border-right: 1px solid #eaeaea;
+	margin-top: 5px;
+}
+
+#buddypress #activity-stream .activity-comments ul {
+	background: rgba(247, 247, 247, 0.6);
+	margin: 15px 2px 0 0;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form input[type="submit"],
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	color: rgba(20, 20, 18, 0.8);
+	display: inline-block;
+	font-family: inherit;
+	font-size: 12px;
+	font-size: 0.75rem;
+	font-weight: normal;
+	line-height: 1.2;
+	padding: 4px 10px;
+	text-transform: lowercase;
+	width: 100px;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	border: 1px solid rgba(212, 208, 186, 0.7);
+	text-align: center;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:focus,
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:hover {
+	background: #ededed;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.2 - Members Loop
+*----------------------------------------------------------
+*/
+@media screen and (min-width: 59.6875em) {
+	#buddypress #members-list li .item-avatar,
+	#buddypress #members-list li .item {
+		float: right;
+	}
+	#buddypress #members-list li .action {
+		float: left;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 5.0 - Directories
+*-------------------------------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------------
+* @subsection Registration
+*-----------------------------------------------------------
+*/
+#buddypress #signup_form.standard-form #profile-details-section,
+#buddypress #signup_form.standard-form #basic-details-section,
+#buddypress #signup_form.standard-form #blog-details-section {
+	float: none;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #signup_form.standard-form #profile-details-section,
+	#buddypress #signup_form.standard-form #basic-details-section,
+	#buddypress #signup_form.standard-form #blog-details-section {
+		width: 48%;
+	}
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #signup_form.standard-form #profile-details-section {
+		float: left;
+	}
+	#buddypress #signup_form.standard-form #basic-details-section {
+		float: right;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens: User Account & Single Group Screens
+*-------------------------------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------------
+* @subsection 6.1 - Item Header Global
+*-----------------------------------------------------------
+*/
+.bp-user .entry-header,
+.single-item.groups .entry-header {
+	margin-bottom: 0;
+}
+
+.bp-user .entry-header .entry-title,
+.single-item.groups .entry-header .entry-title {
+	color: #8c8c7f;
+	font-size: 32px;
+	font-size: 2rem;
+	margin-bottom: 10px;
+	text-align: center;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user .entry-header .entry-title,
+	.single-item.groups .entry-header .entry-title {
+		text-align: right;
+	}
+}
+
+.bp-user #buddypress #item-header-content #item-meta,
+.single-item.groups #buddypress #item-header-content #item-meta {
+	font-size: 14px;
+	font-size: 0.875rem;
+	text-align: right;
+}
+
+.bp-user #buddypress #item-header-content #item-meta p,
+.single-item.groups #buddypress #item-header-content #item-meta p {
+	margin-bottom: 0.5em;
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user main header.entry-header,
+	.single-item.groups main header.entry-header {
+		padding-bottom: 1rem;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user h1,
+	.bp-user #item-header-content,
+	.single-item.groups h1,
+	.single-item.groups #item-header-content {
+		text-align: center;
+	}
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #item-header .generic-button,
+	.single-item.groups #buddypress #item-header .generic-button {
+		float: none;
+		margin: 1.5em 0 0 0;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress h1,
+	.single-item.groups #buddypress h1 {
+		margin-bottom: 0;
+	}
+	.bp-user #buddypress #item-header-avatar img.avatar,
+	.single-item.groups #buddypress #item-header-avatar img.avatar {
+		margin-left: 0;
+	}
+	.bp-user #buddypress #item-header-content,
+	.single-item.groups #buddypress #item-header-content {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.1 - item-header Groups
+*
+* Group Specific Item Header
+*-----------------------------------------------------
+*/
+@media screen and (max-width: 46.25em) {
+	.single-item.groups #buddypress #item-header #item-meta {
+		margin-bottom: 20px;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header {
+		display: flex;
+		flex-direction: column;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		order: 1;
+		text-align: center;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a {
+		border-bottom: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a img {
+		display: inline-block;
+		float: none;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		order: 2;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions {
+		order: 3;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions h3 {
+		border-bottom: 1px solid #eaeaea;
+		text-align: center;
+	}
+}
+
+.single-item.groups #buddypress div#item-header {
+	padding-bottom: 40px;
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions {
+	margin: 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		border-right: 1px solid #eaeaea;
+		clear: none;
+		float: left;
+		padding-right: 0.2em;
+		width: 30%;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		width: 40%;
+	}
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions ul {
+	margin-top: 0;
+	padding-right: 0;
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions h3 {
+	font-size: 14px;
+	font-size: 0.875rem;
+	padding: 0.2em;
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header div#item-actions h3 {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header #item-header-avatar,
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		float: right;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		width: 21%;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		margin-right: 4%;
+		width: 40%;
+	}
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		clear: both;
+		float: left;
+		width: 28%;
+	}
+	.single-item.groups #buddypress div#item-header div#item-actions h3 {
+		font-size: 14px;
+		font-size: 0.875rem;
+	}
+}
+
+.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
+	border: 0;
+	float: none;
+	width: auto;
+}
+
+@media screen and (max-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
+		clear: both !important;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header #item-header-cover-image #item-header-content {
+		padding-right: 10px;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-cover-image #item-header-content #item-buttons {
+		margin-top: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
+		border-right: 1px solid #eaeaea;
+		clear: none;
+		float: left;
+		margin-top: 230px !important;
+		padding-top: 0 !important;
+		width: 20%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.2 - Item Header User Accounts
+*
+* User Accounts Specific Item Header
+*-----------------------------------------------------
+*/
+.bp-user #buddypress #item-header {
+	padding: 20px 0;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar {
+	text-align: center;
+	width: 100%;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar img.avatar,
+.bp-user #buddypress #item-header #item-header-avatar a {
+	border-bottom: 0;
+	display: inline-block;
+	float: none;
+}
+
+.bp-user #buddypress #item-header #item-header-content .user-nicename,
+.bp-user #buddypress #item-header #item-header-content span.activity {
+	display: block;
+	text-align: center;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #item-header #item-header-avatar {
+		float: right;
+		width: auto;
+	}
+	.bp-user #buddypress #item-header #item-header-avatar a {
+		float: right;
+	}
+	.bp-user #buddypress #item-header #item-header-content {
+		float: none;
+		overflow: hidden;
+	}
+	.bp-user #buddypress #item-header #item-header-content .user-nicename,
+	.bp-user #buddypress #item-header #item-header-content span.activity {
+		text-align: right;
+	}
+	.bp-user #buddypress #item-header #item-header-content #item-meta {
+		margin-top: 0;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #item-header-cover-image #item-header-content .user-nicename {
+		margin-bottom: 20px;
+	}
+	.bp-user #buddypress #item-header-cover-image #item-header-content #item-buttons {
+		margin: 0;
+	}
+}
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*-----------------------------------------------------------
+*/
+/**
+*----------------------------------------------------
+* @subsection 6.2.1 - Item Body Groups
+*
+* Groups specific item body rules -  screens
+*----------------------------------------------------
+*/
+/**
+*-----------------------------------------
+* @subsection 6.2.1.1 - Group Management
+*-----------------------------------------
+*/
+.groups #group-settings-form h4 {
+	background: #555;
+	color: #fff;
+	padding: 0.2em;
+}
+
+.groups.edit-details #group-settings-form label {
+	margin-bottom: 0;
+	padding: 0.2em;
+	width: 80%;
+}
+
+.groups.edit-details #group-settings-form textarea + p label {
+	background: none;
+	color: inherit;
+	font-size: 14px;
+	font-size: 0.875rem;
+	width: auto;
+}
+
+.groups.edit-details #group-settings-form textarea {
+	height: auto;
+	min-height: 100px;
+	overflow: auto;
+}
+
+.groups.group-settings #group-settings-form div.radio label {
+	border: 1px solid #eaeaea;
+	padding: 0.2em;
+}
+
+.groups.group-settings #group-settings-form div.radio label ul {
+	color: rgba(20, 20, 18, 0.6);
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.groups.group-avatar form > p {
+	margin-top: 20px;
+}
+
+.groups.manage-members #group-settings-form .item-list li {
+	border-bottom: 1px solid #eaeaea;
+}
+
+.groups.manage-members #group-settings-form .item-list li img,
+.groups.manage-members #group-settings-form .item-list li h5 {
+	float: right;
+}
+
+.groups.manage-members #group-settings-form .item-list li img > a,
+.groups.manage-members #group-settings-form .item-list li h5 > a {
+	border-bottom: 0;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small {
+	clear: right;
+	display: block;
+	float: none;
+	margin-top: 10px;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small a {
+	display: inline-block;
+	margin: 5px 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.groups.manage-members #group-settings-form .item-list li span.small a {
+		width: auto;
+	}
+}
+
+.groups.manage-members #group-settings-form .item-list li h5 {
+	margin: 0;
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.2 - Group members
+*-----------------------------------------
+*/
+.groups.group-members #subnav li {
+	width: 100%;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li {
+		background: #fff;
+		padding: 20px 0;
+	}
+}
+
+.groups.group-members #subnav li #search-members-form {
+	float: left;
+	margin: 5px 0 0 5px;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li #search-members-form {
+		margin: 0;
+		width: 100%;
+	}
+	.groups.group-members #subnav li #search-members-form label input[type="text"] {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.3 - Group send Invites
+*-----------------------------------------
+*/
+#buddypress div.invite .left-menu {
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress div.invite .left-menu {
+		float: right;
+	}
+}
+
+#buddypress div.invite #invite-list {
+	border: 1px solid #eaeaea;
+	height: auto;
+	min-height: 3.125em;
+	padding: 0 0.3em 0.5em;
+	width: auto;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress div.invite #invite-list {
+		width: 220px;
+	}
+}
+
+#buddypress div.invite #invite-list ul {
+	padding-right: 0;
+}
+
+#buddypress div.invite .main-column {
+	margin-right: 0;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress div.invite .main-column {
+		margin-right: 235px;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.2.2 - Item Body User Accounts
+*
+* User Account specific item body rules
+*-----------------------------------------------------
+*/
+.bp-user .entry-title {
+	margin-bottom: 0.5em;
+}
+
+/**
+*--------------------------------------------
+* @subsection 6.2.2.1 - classes, pag, filters
+*--------------------------------------------
+*/
+.bp-user #buddypress table th {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.bp-user #buddypress table td {
+	font-size: 13px;
+	font-size: 0.8125rem;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress table th {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 14px;
+		font-size: 0.875rem;
+	}
+}
+
+@media screen and (min-width: 77.5em) {
+	.bp-user #buddypress table th {
+		font-size: 18px;
+		font-size: 1.125rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+.bp-user #buddypress .pag-count {
+	font-style: italic;
+}
+
+.bp-user #buddypress .notifications-options-nav {
+	border: 1px solid rgba(212, 208, 186, 0.5);
+	float: right;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.bp-user #buddypress .notifications-options-nav {
+		width: 300px;
+	}
+}
+
+.bp-user #buddypress .notifications-options-nav select,
+.bp-user #buddypress .notifications-options-nav input {
+	border: 0;
+	font-size: 14px;
+	font-size: 0.875rem;
+	outline: 0;
+	padding: 0;
+}
+
+.bp-user #buddypress .notifications-options-nav select {
+	float: right;
+	margin-left: 0;
+	width: 60%;
+}
+
+.bp-user #buddypress .notifications-options-nav input {
+	float: left;
+	font-family: inherit;
+	line-height: 20px;
+	width: 40%;
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.2 - Extended Profiles
+	*-------------------------------------------
+	*/
+.bp-user #buddypress .profile .bp-widget h4 {
+	background: #6e6e6e;
+	color: #fff;
+	margin-bottom: 0;
+	padding: 0.4em;
+}
+
+.bp-user #buddypress .profile .bp-widget table {
+	margin-top: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .button-nav:before,
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	content: " ";
+	display: table;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	clear: both;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav {
+	border-bottom: 1px solid #eaeaea;
+	margin-right: 0;
+	padding: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li {
+	float: right;
+	margin-bottom: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li.current {
+	border: 1px solid #eaeaea;
+	border-bottom-color: #fff;
+	margin-bottom: -1px;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav a {
+	background: none;
+	border: 0;
+	font-size: 18px;
+	font-size: 1.125rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-toggle,
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-close,
+.bp-user #buddypress .profile #profile-edit-form .visibility-toggle-link {
+	background: #555;
+	color: #fff;
+	padding: 0.2em 0.5em;
+}
+
+.bp-user #buddypress .profile .bp-avatar #bp-delete-avatar a {
+	font-size: inherit;
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.3 - Groups
+	*-------------------------------------------
+	*/
+@media screen and (min-width: 77.5em) {
+	.bp-user #buddypress #groups-list li .item {
+		right: 5%;
+		width: 50%;
+	}
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.5 - Private Messaging
+	*-------------------------------------------
+	*/
+.bp-user #buddypress {
+	/* Auto Complete background */
+	/* Sitewide Notices loop */
+}
+
+.bp-user #buddypress #message-thread a {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress #message-thread #message-subject {
+	background: #6e6e6e;
+	color: #fff;
+	padding: 0.3em 0.2em 0.3em 0;
+}
+
+.bp-user #buddypress #message-thread #message-recipients {
+	font-style: italic;
+}
+
+.bp-user #buddypress #message-thread #message-recipients a.confirm {
+	border: 1px solid #eaeaea;
+	font-style: normal;
+}
+
+.bp-user #buddypress #message-thread .message-metadata {
+	overflow: hidden;
+}
+
+.bp-user #buddypress #message-thread .message-metadata img.avatar {
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #message-thread .message-metadata img.avatar {
+		float: right;
+	}
+}
+
+.bp-user #buddypress #message-thread .message-metadata .message-star-actions {
+	float: left;
+	margin-left: 5px;
+	position: static;
+}
+
+.bp-user #buddypress #message-thread .message-content {
+	background: #f7f7f7;
+	border: 1px solid #eaeaea;
+	margin: 10px 0 0 0;
+	padding: 0.3em;
+}
+
+.bp-user #buddypress #message-thread #send-reply .message-content {
+	background: #fff;
+	border: 0;
+}
+
+.bp-user #buddypress #message-thread .alt {
+	background: #fff;
+}
+
+.bp-user #buddypress #message-threads thead tr {
+	background: #6e6e6e;
+}
+
+.bp-user #buddypress #message-threads tr td {
+	background: #fff;
+	display: inline-block;
+	float: right;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-star,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-bottom-color: #ccc;
+	border-bottom-width: 2px;
+	height: 2.4em;
+	padding-bottom: 0.2em;
+	padding-top: 0.2em;
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #message-threads tr td.thread-star,
+	.bp-user #buddypress #message-threads tr td.thread-options {
+		padding-top: 0;
+	}
+	.bp-user #buddypress #message-threads tr td.thread-star span.icon:before,
+	.bp-user #buddypress #message-threads tr td.thread-options span.icon:before {
+		font-size: 14px;
+	}
+}
+
+.bp-user #buddypress #message-threads tr .thread-checkbox {
+	padding: 0 0.3em 0 0;
+}
+
+.bp-user #buddypress #message-threads tr td.bulk-select-check,
+.bp-user #buddypress #message-threads tr td.thread-from {
+	height: 3em;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr td.bulk-select-check,
+	.bp-user #buddypress #message-threads tr td.thread-from {
+		height: 5.2em;
+	}
+}
+
+.bp-user #buddypress #message-threads tr td.thread-from,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-right: 0 !important;
+	width: -webkit-calc(100% - 30px);
+	width: -moz-calc(100% - 30px);
+	width: calc(100% - 30px);
+	margin-right: 0;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info {
+	padding-right: 41px;
+	width: 100%;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info a::after {
+	content: " –  " attr(title);
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info .thread-excerpt {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options {
+	text-align: left;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options a {
+	font-size: 12px;
+	font-size: 0.75rem;
+	line-height: 2.2;
+}
+
+.bp-user #buddypress #message-threads tr span.from {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads tr span.activity {
+	display: block;
+	float: left;
+	line-height: 2;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr span.activity {
+		clear: both;
+		font-size: 12px;
+		font-size: 0.75rem;
+		width: 100%;
+	}
+}
+
+.bp-user #buddypress #message-threads tr.unread td {
+	background: #dce5ff;
+	border-color: #d4d0ba;
+}
+
+.bp-user #buddypress #message-threads th {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all {
+	border-bottom: 0;
+	display: inline-block;
+	text-align: right;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all,
+.bp-user #buddypress #message-threads td.bulk-select-check,
+.bp-user #buddypress #message-threads td.thread-star {
+	border-left: 0;
+	width: 30px;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab {
+	margin-left: 0;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab a {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab a img {
+	display: inline;
+	height: 20px;
+	margin-left: 0.5em;
+	width: 20px !important;
+}
+
+.bp-user #buddypress .sitewide-notices tr td:first-child {
+	display: none;
+}
+
+.bp-user #buddypress .sitewide-notices td strong {
+	background: #6e6e6e;
+	color: #fff;
+	display: block;
+	padding-right: 0.2em;
+}
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+.bp-user #buddypress #settings-form {
+	padding: 0 10px;
+}
+
+.bp-user #buddypress #settings-form > p {
+	font-size: 20px;
+	font-size: 1.25rem;
+	margin: 20px 0 10px;
+}
+
+.bp-user #buddypress table.notification-settings td.yes,
+.bp-user #buddypress table.notification-settings td.no {
+	vertical-align: middle;
+}
+
+.bp-user #buddypress table.profile-settings {
+	width: 100%;
+}
+
+.bp-user #buddypress table.profile-settings th.field-group-name,
+.bp-user #buddypress table.profile-settings td.field-name {
+	width: 50%;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress table.profile-settings th.field-group-name,
+	.bp-user #buddypress table.profile-settings td.field-name {
+		width: 70%;
+	}
+}
+
+.bp-user #buddypress table.profile-settings th.title,
+.bp-user #buddypress table.profile-settings td.field-visibility {
+	width: 30%;
+}
+
+.bp-user #buddypress table.profile-settings td.field-visibility select {
+	width: 100%;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+#main #buddypress .standard-form li {
+	float: none;
+}
+
+#main #buddypress .standard-form input[type='text'],
+#main #buddypress .standard-form input[type='email'],
+#main #buddypress .standard-form input[type='password'],
+#main #buddypress .standard-form textarea {
+	width: 100%;
+}
+
+#buddypress div.activity-comments form .ac-textarea {
+	background: #f7f7f7;
+	border: 1px solid rgba(212, 208, 186, 0.5);
+}
+
+#buddypress div.activity-comments form .ac-textarea textarea {
+	background: none;
+	border: 0;
+}
+
+#buddypress .standard-form input[type="text"],
+#buddypress .standard-form input[type="email"],
+#buddypress .standard-form input[type="password"],
+#buddypress .standard-form button,
+#buddypress .standard-form select,
+#buddypress .standard-form textarea {
+	border-color: rgba(212, 208, 186, 0.5);
+	border-width: 1px;
+}
+
+#buddypress #signup_form.standard-form div.submit {
+	float: none;
+}
+
+#buddypress #signup_form.standard-form div.submit input {
+	margin-left: 0;
+}
+
+#buddypress div.dir-search,
+#buddypress div.message-search,
+#buddypress li.groups-members-search {
+	float: none;
+	margin: 10px 0;
+}
+
+#buddypress div.dir-search form,
+#buddypress div.message-search form,
+#buddypress li.groups-members-search form {
+	border: 1px solid rgba(212, 208, 186, 0.6);
+	overflow: hidden;
+}
+
+#buddypress div.dir-search form label,
+#buddypress div.message-search form label,
+#buddypress li.groups-members-search form label {
+	float: right;
+	width: 60%;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress div.dir-search form label,
+	#buddypress div.message-search form label,
+	#buddypress li.groups-members-search form label {
+		width: 80%;
+	}
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	float: right;
+	margin: 0;
+	width: 100%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='text'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	font-size: 14px;
+	font-size: 0.875rem;
+	border: 0;
+	line-height: inherit;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	border-left: 1px solid rgba(212, 208, 186, 0.6);
+	padding: 0.2em 0.2em 0.2em 0;
+}
+
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	float: left;
+	font-weight: normal;
+	padding: 0.2em 1em;
+	text-align: center;
+	text-transform: none;
+	width: 40%;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress div.dir-search form input[type='submit'],
+	#buddypress div.message-search form input[type='submit'],
+	#buddypress li.groups-members-search form input[type='submit'] {
+		width: 20%;
+	}
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div.dir-search,
+	#buddypress div.message-search,
+	#buddypress li.groups-members-search {
+		float: left;
+		margin-bottom: 5px !important;
+	}
+	#buddypress div.dir-search form label,
+	#buddypress div.dir-search form input[type='text'],
+	#buddypress div.dir-search form input[type='submit'],
+	#buddypress div.message-search form label,
+	#buddypress div.message-search form input[type='text'],
+	#buddypress div.message-search form input[type='submit'],
+	#buddypress li.groups-members-search form label,
+	#buddypress li.groups-members-search form input[type='text'],
+	#buddypress li.groups-members-search form input[type='submit'] {
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 77.5em) {
+	#buddypress .dir-search form input[type='text'],
+	#buddypress .message-search form input[type='text'] {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+	#buddypress .dir-search form input[type='submit'],
+	#buddypress .message-search form input[type='submit'] {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables - General
+*-------------------------------------------------------------------------------
+*/
+#buddypress table {
+	font-size: 14px;
+	font-size: 0.875rem;
+	margin: 20px 0;
+}
+
+#buddypress table tr th {
+	background: #6e6e6e;
+	border-color: #b7b7b7;
+	color: #fff;
+}
+
+#buddypress table th,
+#buddypress table td {
+	padding: 0.3em;
+}
+
+#buddypress table p {
+	margin-bottom: 0.5em;
+}
+
+@media screen and (min-width: 55em) {
+	#buddypress table {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+/*__ User Account tables __*/
+#buddypress .notifications th,
+#buddypress .messages-notices th {
+	width: 30%;
+}
+
+#buddypress .notifications th.bulk-select-all,
+#buddypress .messages-notices th.bulk-select-all {
+	text-align: center;
+	width: 10%;
+}
+
+#buddypress .notifications .bulk-select-check,
+#buddypress .notifications .thread-star,
+#buddypress .messages-notices .bulk-select-check,
+#buddypress .messages-notices .thread-star {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions,
+#buddypress .notifications td.thread-options,
+#buddypress .messages-notices .notification-actions,
+#buddypress .messages-notices td.thread-options {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions a,
+#buddypress .notifications td.thread-options a,
+#buddypress .messages-notices .notification-actions a,
+#buddypress .messages-notices td.thread-options a {
+	display: inline-block;
+	margin: 0;
+	padding: 0;
+}
+
+#buddypress .notifications td .button,
+#buddypress .messages-notices td .button {
+	border: 0;
+	display: block;
+	padding: 0;
+	text-align: center;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+#buddypress div#message p {
+	font-size: 18px;
+	font-size: 1.125rem;
+	font-weight: bold;
+}
+
+#buddypress div#message.info p {
+	background: #c6e4ee;
+	border: 1px solid #78bed6;
+	color: #1e5264;
+}
+
+#buddypress div#message.updated p {
+	background: #dee6b2;
+	border: 1px solid #becc66;
+	color: #454d19;
+}
+
+.delete-group #buddypress div#message.info p {
+	background: #db7e7e;
+	border: 1px solid #be3535;
+	color: #1f0808;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+#buddypress {
+	/* Auto Complete background */
+}
+
+#buddypress .acfb-holder li.friend-tab {
+	background: #9fd1e2;
+	border: inherit;
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentythirteen.css
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentythirteen.css	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentythirteen.css	(revision 10410)
@@ -0,0 +1,1944 @@
+@charset "UTF-8";
+/*--------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentythirteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentyfifteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the rulesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+----------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+----------------------------------------------------------------
+1.0 Theme Structural Elements & overrides
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 Whats New Activity
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Register, activation
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - item-header: Groups
+		6.1.2 - item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1 - Management settings screens
+			6.2.1.2 - Group members list
+			6.2.1.3 - Group send Invites
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading, Widgets, General classes
+--------------------------------------------------------------*/
+/**
+*-------------------------------------------------------------------------------
+* @section 1.0 - Theme - Structural Elements & overrides
+*-------------------------------------------------------------------------------
+*/
+.buddypress div.clear {
+	display: none;
+}
+
+@media (max-width: 999px) {
+	.buddypress #content {
+		padding: 0 20px;
+	}
+}
+
+@media (max-width: 643px) {
+	.buddypress #content {
+		padding: 0;
+	}
+}
+
+.buddypress .entry-header,
+.buddypress .entry-content,
+.buddypress .entry-summary,
+.buddypress .entry-meta {
+	max-width: 890px;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+/**
+*----------------------------------------------------------
+* @section 2.1 - Navs Object Nav / Sub Nav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+#buddypress div.item-list-tabs ul {
+	background-color: #f7f5e7;
+	border-bottom: 1px solid rgba(234, 234, 234, 0.9);
+	border-top: 1px solid rgba(234, 234, 234, 0.9);
+	overflow: hidden;
+	padding: 0;
+}
+
+#buddypress div.item-list-tabs ul li a span {
+	border-radius: 25%;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #object-nav {
+		background: #f7f5e7;
+		border: 1px solid #eaeaea;
+		overflow: visible;
+		padding: 10px;
+	}
+	.bp-user #buddypress #object-nav ul {
+		border: 0;
+		height: 0;
+		transition: height 0.3s ease-in-out 0.7s;
+		visibility: hidden;
+	}
+	.bp-user #buddypress #object-nav:before {
+		content: "Menu \021E9";
+		display: inline;
+		opacity: 0.7;
+	}
+	.bp-user #buddypress #object-nav:hover:before,
+	.bp-user #buddypress #object-nav:focus:before {
+		content: "Menu \021E7";
+	}
+	.bp-user #buddypress #object-nav:hover ul,
+	.bp-user #buddypress #object-nav:focus ul {
+		height: 320px;
+		opacity: 1;
+		overflow-y: auto;
+		visibility: visible;
+	}
+	.bp-user #buddypress #subnav {
+		clear: both;
+	}
+}
+
+/*__ Horizontal menus & small screen __*/
+#buddypress #object-nav ul {
+	overflow: hidden;
+}
+
+#buddypress #object-nav ul li {
+	float: none;
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress #object-nav ul li:focus,
+	#buddypress #object-nav ul li:hover {
+		background: #f4f2df;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	#buddypress #object-nav ul li:not(:last-child) {
+		border-bottom: 1px solid #eaeaea;
+	}
+}
+
+#buddypress #object-nav ul li:not(.selected) a {
+	opacity: 0.7;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #object-nav ul li {
+		float: left;
+	}
+}
+
+#buddypress div#subnav.item-list-tabs {
+	margin-top: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul {
+	background-color: #f7f7f7;
+	border-bottom: 0;
+	margin-top: 5px;
+	padding: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last {
+	background: #fff;
+	margin-top: 0;
+	padding: 5px 5px 5px 0;
+	width: 100%;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last select:focus {
+	background: transparent;
+	border: 0;
+	outline: 0;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select,
+#buddypress div#subnav.item-list-tabs ul li.last label,
+#buddypress div#subnav.item-list-tabs ul li.last option {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+#buddypress div#subnav.item-list-tabs ul li.last select {
+	font-style: italic;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div#subnav.item-list-tabs ul li.last {
+		text-align: right;
+	}
+}
+
+#buddypress div.item-list-tabs ul li.selected a,
+#buddypress div.item-list-tabs ul li.current a {
+	background: none;
+	opacity: 1;
+}
+
+/*__ Vertical menu User Account / Group single screens __*/
+@media screen and (min-width: 55em) {
+	body:not(.sidebar) #buddypress #item-header,
+	body:not(.sidebar) #buddypress #item-body {
+		background: #fff;
+	}
+	body:not(.sidebar) #buddypress #object-nav {
+		border-right: 1px solid #ddd;
+		float: left;
+		margin-right: -1px;
+		width: 200px;
+	}
+	body:not(.sidebar) #buddypress #object-nav ul {
+		background: none;
+		border-bottom: 0;
+		padding: 0;
+	}
+	body:not(.sidebar) #buddypress #object-nav ul li {
+		float: none;
+		overflow: hidden;
+	}
+	body:not(.sidebar) #buddypress #object-nav ul li span {
+		background: #fff;
+		border-radius: 10%;
+		float: right;
+		margin-right: 2px;
+	}
+	body:not(.sidebar) #buddypress #item-body {
+		border-left: 1px solid #ddd;
+		overflow: hidden;
+		padding: 0 0 0 20px;
+		width: auto;
+	}
+	body:not(.sidebar) #buddypress #item-body #subnav {
+		margin: 0 0 0 -20px;
+	}
+	body:not(.sidebar) #buddypress #item-body #subnav ul {
+		margin-top: 0;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	#main #buddypress #group-create-tabs.item-list-tabs ul:before,
+	#main #buddypress #group-create-tabs.item-list-tabs ul:after {
+		content: " ";
+		display: table;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul:after {
+		clear: both;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul {
+		background: none;
+		border: 0;
+		border-bottom: 1px solid #ddd;
+		overflow: visible;
+		padding: 0;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li {
+		float: left;
+		width: auto;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.selected,
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.current {
+		border: 1px solid #ddd;
+		border-bottom-color: #fff;
+		-webkit-border-top-right-radius: 4px;
+		border-top-right-radius: 4px;
+		-webkit-border-top-left-radius: 4px;
+		border-top-left-radius: 4px;
+		background-clip: padding-box;
+		margin-bottom: -1px;
+	}
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.selected a,
+	#main #buddypress #group-create-tabs.item-list-tabs ul li.current a {
+		background: none;
+		color: #141412;
+		outline: none;
+	}
+	#main #buddypress #subnav ul {
+		border-bottom: 0;
+	}
+}
+
+/**
+*----------------------------------------------------------
+* @section 2.2 - Pagination
+*----------------------------------------------------------
+*/
+#buddypress div.pagination {
+	box-shadow: none;
+}
+
+#buddypress div.pagination .pag-count {
+	margin-left: 0;
+}
+
+#buddypress div.pagination .pagination-links {
+	margin-right: 0;
+}
+
+#buddypress div.pagination .pagination-links span,
+#buddypress div.pagination .pagination-links a {
+	height: auto;
+	line-height: 1;
+	padding: 5px;
+}
+
+#buddypress div.pagination .pagination-links .prev,
+#buddypress div.pagination .pagination-links .next {
+	background-color: transparent;
+	color: inherit;
+	overflow: visible;
+	width: auto;
+}
+
+#buddypress div.pagination .pagination-links .prev:before,
+#buddypress div.pagination .pagination-links .next:before {
+	display: none;
+}
+
+#buddypress div.pagination .pagination-links .prev {
+	left: auto;
+	position: static;
+}
+
+#buddypress div.pagination .pagination-links .next {
+	position: static;
+	right: auto;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+#buddypress .item-list .activity-header,
+#buddypress .item-list .activity-meta {
+	font-family: "Source Sans Pro", Helvetica, sans-serif;
+}
+
+#buddypress .activity-meta .button:focus,
+#buddypress .activity-meta .button:hover {
+	background: #e37a5a;
+	color: #f7f5e7;
+}
+
+#buddypress .action .generic-button a:focus,
+#buddypress .action .generic-button a:hover {
+	background: #e37a5a;
+	color: #f7f5e7;
+}
+
+#buddypress ul.item-list li {
+	overflow: hidden !important;
+}
+
+#buddypress ul.item-list li .item-avatar {
+	margin-bottom: 10px;
+	text-align: center;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item-avatar {
+		margin-bottom: 0;
+	}
+}
+
+#buddypress ul.item-list li .item-avatar a {
+	border-bottom: 0;
+}
+
+#buddypress ul.item-list li .item-avatar img.avatar {
+	display: inline-block;
+	float: none;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item-avatar img.avatar {
+		display: block;
+		float: left;
+	}
+}
+
+#buddypress ul.item-list li .item {
+	overflow: hidden;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress ul.item-list li .item {
+		margin-left: 15%;
+	}
+}
+
+#buddypress ul.item-list li .item span.activity {
+	font-style: italic;
+}
+
+#buddypress ul.item-list li .item .item-desc {
+	margin: 0 auto;
+	width: 100%;
+}
+
+#buddypress ul.item-list li .item .item-title {
+	font-size: 18px;
+	font-size: 1.125rem;
+	line-height: 1.2;
+	margin: 0 auto;
+	text-align: center;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li .item .item-title {
+		text-align: left;
+	}
+}
+
+#buddypress ul.item-list li .item .item-title .update {
+	font-size: 12px;
+	font-size: 0.75rem;
+	display: block;
+	padding: 10px 0;
+	text-align: left;
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li .item .item-title .update {
+		font-size: 14px;
+		font-size: 0.875rem;
+	}
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li .item-avatar,
+	#buddypress ul.item-list li .item,
+	#buddypress ul.item-list li .action {
+		float: left;
+	}
+	#buddypress ul.item-list li .item {
+		left: 5%;
+		margin-left: 0;
+		position: relative;
+		width: 55%;
+	}
+	#buddypress ul.item-list li .item .item-title {
+		font-size: 22px;
+		font-size: 1.375rem;
+	}
+}
+
+#buddypress ul.item-list li div.action {
+	clear: left;
+	float: none;
+	margin-bottom: -20px;
+	margin-left: 0;
+	padding: 20px 0 5px;
+	position: relative;
+	text-align: left;
+	top: 0;
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li div.action {
+		clear: none;
+		float: right;
+		margin-bottom: 0;
+		padding: 0;
+	}
+}
+
+#buddypress ul.item-list li div.action div {
+	display: inline-block;
+	margin: 10px 0;
+	width: 100%;
+}
+
+#buddypress ul.item-list li div.action div a {
+	display: block;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress ul.item-list li div.action div {
+		margin: 0 10px 10px 0;
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 59.6875em) {
+	#buddypress ul.item-list li div.action div {
+		clear: right;
+		float: right;
+		margin: 0 0 10px 0;
+	}
+}
+
+#buddypress ul.item-list li div.action .meta {
+	font-style: italic;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.1 - Activity
+*----------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------
+* @section 4.1.1 - Activity Whats New
+*-----------------------------------------------------
+*/
+#buddypress form#whats-new-form p.activity-greeting {
+	line-height: 1.4;
+}
+
+@media screen and (max-width: 46.25em) {
+	#buddypress form#whats-new-form #whats-new-content {
+		clear: left;
+		margin: 10px 0 20px;
+		padding: 10px 0 0;
+	}
+}
+
+#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box {
+	border: 1px solid rgba(212, 208, 186, 0.5);
+	float: left;
+	line-height: 1.5em;
+	margin-top: 12px;
+	padding-left: 0.2em;
+	width: 100%;
+}
+
+#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box select {
+	background: none;
+	border: 0;
+	float: right;
+	margin: 0;
+	min-height: 1.5em;
+	padding-left: 0.4em;
+}
+
+@media screen and (min-width: 30em) {
+	#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-post-in-box {
+		width: auto;
+	}
+	#buddypress form#whats-new-form #whats-new-content.active #whats-new-options[style] #whats-new-submit {
+		float: right;
+	}
+}
+
+#buddypress #item-body form#whats-new-form {
+	margin: 40px 0;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.2 - Activity Listing
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream li {
+	padding: 25px 0 15px;
+}
+
+#buddypress #activity-stream li .activity-avatar {
+	float: none;
+	text-align: center;
+}
+
+#buddypress #activity-stream li .activity-avatar a {
+	display: inline-block;
+}
+
+#buddypress #activity-stream li .activity-avatar a img.avatar {
+	display: inline;
+	float: none;
+	height: 60px;
+	margin-bottom: 20px;
+	margin-left: 0;
+	width: 60px;
+}
+
+#buddypress #activity-stream li .activity-content {
+	margin-left: 0;
+}
+
+#buddypress #activity-stream li .activity-content .activity-header {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress #activity-stream li .activity-avatar {
+		float: left;
+		margin-right: 10px;
+		text-align: left;
+	}
+	#buddypress #activity-stream li .activity-avatar a {
+		border-bottom: 0;
+	}
+	#buddypress #activity-stream li .activity-content {
+		margin: 0;
+		overflow: hidden;
+	}
+	#buddypress #activity-stream li .activity-content .activity-header {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+#buddypress #activity-stream li.mini .activity-avatar a img.avatar {
+	height: 30px;
+	margin-left: 15px;
+	width: 30px;
+}
+
+#buddypress #activity-stream li.mini .activity-content .activity-header {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+#buddypress #activity-stream .activity-content {
+	margin-top: -12px;
+}
+
+#buddypress #activity-stream .activity-content .activity-header {
+	line-height: inherit;
+	margin-right: 0;
+}
+
+#buddypress #activity-stream .activity-content .activity-header p {
+	background-color: #f7f7f7;
+	border: 1px solid rgba(234, 234, 234, 0.6);
+	margin-top: 0;
+	padding: 0 0.2em;
+}
+
+#buddypress #activity-stream .activity-content .activity-header img.avatar {
+	display: inline-block;
+	margin-bottom: 0 !important;
+}
+
+#buddypress #activity-stream .activity-content .activity-meta a {
+	display: block;
+	margin-bottom: 5px;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #activity-stream .activity-content .activity-meta a {
+		display: inline-block;
+		margin-bottom: 0;
+	}
+}
+
+#buddypress #activity-stream .load-more {
+	background: #f7f7f7;
+	border: 1px solid transparent;
+	padding: 10px;
+}
+
+#buddypress #activity-stream .load-more:focus,
+#buddypress #activity-stream .load-more:hover {
+	background: #f4f4f4;
+	border: 1px solid rgba(159, 209, 226, 0.3);
+}
+
+#buddypress #activity-stream .load-more:focus a,
+#buddypress #activity-stream .load-more:hover a {
+	font-style: italic;
+}
+
+#buddypress #activity-stream .load-more a {
+	display: block;
+}
+
+/* Single activity view  - activity permalink */
+.activity-permalink #buddypress #activity-stream li.activity-item {
+	padding: 20px;
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+	font-size: 16px;
+	font-size: 1rem;
+	margin-bottom: 40px;
+}
+
+@media screen and (min-width: 46.25em) {
+	.activity-permalink #buddypress #activity-stream li.mini .activity-header {
+		font-size: 20px;
+		font-size: 1.25rem;
+	}
+}
+
+.activity-permalink #buddypress #activity-stream li.mini .activity-header p {
+	padding: 20px;
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.3 - Activity Comments
+*-----------------------------------------------------
+*/
+#buddypress #activity-stream .activity-comments {
+	border-left: 1px solid #eaeaea;
+	margin-top: 5px;
+}
+
+#buddypress #activity-stream .activity-comments ul {
+	background: rgba(247, 247, 247, 0.6);
+	margin: 15px 0 0 2px;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form input[type="submit"],
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	color: rgba(20, 20, 18, 0.8);
+	display: inline-block;
+	font-family: inherit;
+	font-size: 12px;
+	font-size: 0.75rem;
+	font-weight: normal;
+	line-height: 1.2;
+	padding: 4px 10px;
+	text-transform: lowercase;
+	width: 100px;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel {
+	border: 1px solid rgba(212, 208, 186, 0.7);
+	text-align: center;
+}
+
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:focus,
+#buddypress #activity-stream .activity-comments .ac-form .ac-reply-cancel:hover {
+	background: #ededed;
+}
+
+/**
+*----------------------------------------------------------
+* @section 4.2 - Members Loop
+*----------------------------------------------------------
+*/
+@media screen and (min-width: 59.6875em) {
+	#buddypress #members-list li .item-avatar,
+	#buddypress #members-list li .item {
+		float: left;
+	}
+	#buddypress #members-list li .action {
+		float: right;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 5.0 - Directories
+*-------------------------------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------------
+* @subsection Registration
+*-----------------------------------------------------------
+*/
+#buddypress #signup_form.standard-form #profile-details-section,
+#buddypress #signup_form.standard-form #basic-details-section,
+#buddypress #signup_form.standard-form #blog-details-section {
+	float: none;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #signup_form.standard-form #profile-details-section,
+	#buddypress #signup_form.standard-form #basic-details-section,
+	#buddypress #signup_form.standard-form #blog-details-section {
+		width: 48%;
+	}
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress #signup_form.standard-form #profile-details-section {
+		float: right;
+	}
+	#buddypress #signup_form.standard-form #basic-details-section {
+		float: left;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens: User Account & Single Group Screens
+*-------------------------------------------------------------------------------
+*/
+/**
+*-----------------------------------------------------------
+* @subsection 6.1 - Item Header Global
+*-----------------------------------------------------------
+*/
+.bp-user .entry-header,
+.single-item.groups .entry-header {
+	margin-bottom: 0;
+}
+
+.bp-user .entry-header .entry-title,
+.single-item.groups .entry-header .entry-title {
+	color: #8c8c7f;
+	font-size: 32px;
+	font-size: 2rem;
+	margin-bottom: 10px;
+	text-align: center;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user .entry-header .entry-title,
+	.single-item.groups .entry-header .entry-title {
+		text-align: left;
+	}
+}
+
+.bp-user #buddypress #item-header-content #item-meta,
+.single-item.groups #buddypress #item-header-content #item-meta {
+	font-size: 14px;
+	font-size: 0.875rem;
+	text-align: left;
+}
+
+.bp-user #buddypress #item-header-content #item-meta p,
+.single-item.groups #buddypress #item-header-content #item-meta p {
+	margin-bottom: 0.5em;
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user main header.entry-header,
+	.single-item.groups main header.entry-header {
+		padding-bottom: 1rem;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user h1,
+	.bp-user #item-header-content,
+	.single-item.groups h1,
+	.single-item.groups #item-header-content {
+		text-align: center;
+	}
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #item-header .generic-button,
+	.single-item.groups #buddypress #item-header .generic-button {
+		float: none;
+		margin: 1.5em 0 0 0;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress h1,
+	.single-item.groups #buddypress h1 {
+		margin-bottom: 0;
+	}
+	.bp-user #buddypress #item-header-avatar img.avatar,
+	.single-item.groups #buddypress #item-header-avatar img.avatar {
+		margin-right: 0;
+	}
+	.bp-user #buddypress #item-header-content,
+	.single-item.groups #buddypress #item-header-content {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.1 - item-header Groups
+*
+* Group Specific Item Header
+*-----------------------------------------------------
+*/
+@media screen and (max-width: 46.25em) {
+	.single-item.groups #buddypress #item-header #item-meta {
+		margin-bottom: 20px;
+	}
+}
+
+@media screen and (max-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header {
+		display: flex;
+		flex-direction: column;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		order: 1;
+		text-align: center;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a {
+		border-bottom: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar a img {
+		display: inline-block;
+		float: none;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		order: 2;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions {
+		order: 3;
+	}
+	.single-item.groups #buddypress div#item-header #item-actions h3 {
+		border-bottom: 1px solid #eaeaea;
+		text-align: center;
+	}
+}
+
+.single-item.groups #buddypress div#item-header {
+	padding-bottom: 40px;
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions {
+	margin: 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		border-left: 1px solid #eaeaea;
+		clear: none;
+		float: right;
+		padding-left: 0.2em;
+		width: 30%;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		width: 40%;
+	}
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions ul {
+	margin-top: 0;
+	padding-left: 0;
+}
+
+.single-item.groups #buddypress div#item-header div#item-actions h3 {
+	font-size: 14px;
+	font-size: 0.875rem;
+	padding: 0.2em;
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header div#item-actions h3 {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header #item-header-avatar,
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		float: left;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-avatar {
+		width: 21%;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-content {
+		margin-left: 4%;
+		width: 40%;
+	}
+	.single-item.groups #buddypress div#item-header div#item-actions {
+		clear: both;
+		float: right;
+		width: 28%;
+	}
+	.single-item.groups #buddypress div#item-header div#item-actions h3 {
+		font-size: 14px;
+		font-size: 0.875rem;
+	}
+}
+
+.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
+	border: 0;
+	float: none;
+	width: auto;
+}
+
+@media screen and (max-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
+		clear: both !important;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.single-item.groups #buddypress div#item-header #item-header-cover-image #item-header-content {
+		padding-left: 10px;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-cover-image #item-header-content #item-buttons {
+		margin-top: 0;
+	}
+	.single-item.groups #buddypress div#item-header #item-header-cover-image #item-actions {
+		border-left: 1px solid #eaeaea;
+		clear: none;
+		float: right;
+		margin-top: 230px !important;
+		padding-top: 0 !important;
+		width: 20%;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.2 - Item Header User Accounts
+*
+* User Accounts Specific Item Header
+*-----------------------------------------------------
+*/
+.bp-user #buddypress #item-header {
+	padding: 20px 0;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar {
+	text-align: center;
+	width: 100%;
+}
+
+.bp-user #buddypress #item-header #item-header-avatar img.avatar,
+.bp-user #buddypress #item-header #item-header-avatar a {
+	border-bottom: 0;
+	display: inline-block;
+	float: none;
+}
+
+.bp-user #buddypress #item-header #item-header-content .user-nicename,
+.bp-user #buddypress #item-header #item-header-content span.activity {
+	display: block;
+	text-align: center;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #item-header #item-header-avatar {
+		float: left;
+		width: auto;
+	}
+	.bp-user #buddypress #item-header #item-header-avatar a {
+		float: left;
+	}
+	.bp-user #buddypress #item-header #item-header-content {
+		float: none;
+		overflow: hidden;
+	}
+	.bp-user #buddypress #item-header #item-header-content .user-nicename,
+	.bp-user #buddypress #item-header #item-header-content span.activity {
+		text-align: left;
+	}
+	.bp-user #buddypress #item-header #item-header-content #item-meta {
+		margin-top: 0;
+	}
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #item-header-cover-image #item-header-content .user-nicename {
+		margin-bottom: 20px;
+	}
+	.bp-user #buddypress #item-header-cover-image #item-header-content #item-buttons {
+		margin: 0;
+	}
+}
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*-----------------------------------------------------------
+*/
+/**
+*----------------------------------------------------
+* @subsection 6.2.1 - Item Body Groups
+*
+* Groups specific item body rules -  screens
+*----------------------------------------------------
+*/
+/**
+*-----------------------------------------
+* @subsection 6.2.1.1 - Group Management
+*-----------------------------------------
+*/
+.groups #group-settings-form h4 {
+	background: #555;
+	color: #fff;
+	padding: 0.2em;
+}
+
+.groups.edit-details #group-settings-form label {
+	margin-bottom: 0;
+	padding: 0.2em;
+	width: 80%;
+}
+
+.groups.edit-details #group-settings-form textarea + p label {
+	background: none;
+	color: inherit;
+	font-size: 14px;
+	font-size: 0.875rem;
+	width: auto;
+}
+
+.groups.edit-details #group-settings-form textarea {
+	height: auto;
+	min-height: 100px;
+	overflow: auto;
+}
+
+.groups.group-settings #group-settings-form div.radio label {
+	border: 1px solid #eaeaea;
+	padding: 0.2em;
+}
+
+.groups.group-settings #group-settings-form div.radio label ul {
+	color: rgba(20, 20, 18, 0.6);
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.groups.group-avatar form > p {
+	margin-top: 20px;
+}
+
+.groups.manage-members #group-settings-form .item-list li {
+	border-bottom: 1px solid #eaeaea;
+}
+
+.groups.manage-members #group-settings-form .item-list li img,
+.groups.manage-members #group-settings-form .item-list li h5 {
+	float: left;
+}
+
+.groups.manage-members #group-settings-form .item-list li img > a,
+.groups.manage-members #group-settings-form .item-list li h5 > a {
+	border-bottom: 0;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small {
+	clear: left;
+	display: block;
+	float: none;
+	margin-top: 10px;
+}
+
+.groups.manage-members #group-settings-form .item-list li span.small a {
+	display: inline-block;
+	margin: 5px 0;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.groups.manage-members #group-settings-form .item-list li span.small a {
+		width: auto;
+	}
+}
+
+.groups.manage-members #group-settings-form .item-list li h5 {
+	margin: 0;
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.2 - Group members
+*-----------------------------------------
+*/
+.groups.group-members #subnav li {
+	width: 100%;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li {
+		background: #fff;
+		padding: 20px 0;
+	}
+}
+
+.groups.group-members #subnav li #search-members-form {
+	float: right;
+	margin: 5px 5px 0 0;
+}
+
+@media screen and (max-width: 38.75em) {
+	.groups.group-members #subnav li #search-members-form {
+		margin: 0;
+		width: 100%;
+	}
+	.groups.group-members #subnav li #search-members-form label input[type="text"] {
+		width: 100%;
+	}
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.3 - Group send Invites
+*-----------------------------------------
+*/
+#buddypress div.invite .left-menu {
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress div.invite .left-menu {
+		float: left;
+	}
+}
+
+#buddypress div.invite #invite-list {
+	border: 1px solid #eaeaea;
+	height: auto;
+	min-height: 3.125em;
+	padding: 0 0.3em 0.5em;
+	width: auto;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress div.invite #invite-list {
+		width: 220px;
+	}
+}
+
+#buddypress div.invite #invite-list ul {
+	padding-left: 0;
+}
+
+#buddypress div.invite .main-column {
+	margin-left: 0;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress div.invite .main-column {
+		margin-left: 235px;
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.2.2 - Item Body User Accounts
+*
+* User Account specific item body rules
+*-----------------------------------------------------
+*/
+.bp-user .entry-title {
+	margin-bottom: 0.5em;
+}
+
+/**
+*--------------------------------------------
+* @subsection 6.2.2.1 - classes, pag, filters
+*--------------------------------------------
+*/
+.bp-user #buddypress table th {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.bp-user #buddypress table td {
+	font-size: 13px;
+	font-size: 0.8125rem;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress table th {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 14px;
+		font-size: 0.875rem;
+	}
+}
+
+@media screen and (min-width: 77.5em) {
+	.bp-user #buddypress table th {
+		font-size: 18px;
+		font-size: 1.125rem;
+	}
+	.bp-user #buddypress table td {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+.bp-user #buddypress .pag-count {
+	font-style: italic;
+}
+
+.bp-user #buddypress .notifications-options-nav {
+	border: 1px solid rgba(212, 208, 186, 0.5);
+	float: left;
+	width: 100%;
+}
+
+@media screen and (min-width: 38.75em) {
+	.bp-user #buddypress .notifications-options-nav {
+		width: 300px;
+	}
+}
+
+.bp-user #buddypress .notifications-options-nav select,
+.bp-user #buddypress .notifications-options-nav input {
+	border: 0;
+	font-size: 14px;
+	font-size: 0.875rem;
+	outline: 0;
+	padding: 0;
+}
+
+.bp-user #buddypress .notifications-options-nav select {
+	float: left;
+	margin-right: 0;
+	width: 60%;
+}
+
+.bp-user #buddypress .notifications-options-nav input {
+	float: right;
+	font-family: inherit;
+	line-height: 20px;
+	width: 40%;
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.2 - Extended Profiles
+	*-------------------------------------------
+	*/
+.bp-user #buddypress .profile .bp-widget h4 {
+	background: #6e6e6e;
+	color: #fff;
+	margin-bottom: 0;
+	padding: 0.4em;
+}
+
+.bp-user #buddypress .profile .bp-widget table {
+	margin-top: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .button-nav:before,
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	content: " ";
+	display: table;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .button-nav:after {
+	clear: both;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav {
+	border-bottom: 1px solid #eaeaea;
+	margin-left: 0;
+	padding: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li {
+	float: left;
+	margin-bottom: 0;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav li.current {
+	border: 1px solid #eaeaea;
+	border-bottom-color: #fff;
+	margin-bottom: -1px;
+}
+
+.bp-user #buddypress .profile #profile-edit-form ul.button-nav a {
+	background: none;
+	border: 0;
+	font-size: 18px;
+	font-size: 1.125rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-toggle,
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.bp-user #buddypress .profile #profile-edit-form .field-visibility-settings-close,
+.bp-user #buddypress .profile #profile-edit-form .visibility-toggle-link {
+	background: #555;
+	color: #fff;
+	padding: 0.2em 0.5em;
+}
+
+.bp-user #buddypress .profile .bp-avatar #bp-delete-avatar a {
+	font-size: inherit;
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.3 - Groups
+	*-------------------------------------------
+	*/
+@media screen and (min-width: 77.5em) {
+	.bp-user #buddypress #groups-list li .item {
+		left: 5%;
+		width: 50%;
+	}
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.5 - Private Messaging
+	*-------------------------------------------
+	*/
+.bp-user #buddypress {
+	/* Auto Complete background */
+	/* Sitewide Notices loop */
+}
+
+.bp-user #buddypress #message-thread a {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress #message-thread #message-subject {
+	background: #6e6e6e;
+	color: #fff;
+	padding: 0.3em 0 0.3em 0.2em;
+}
+
+.bp-user #buddypress #message-thread #message-recipients {
+	font-style: italic;
+}
+
+.bp-user #buddypress #message-thread #message-recipients a.confirm {
+	border: 1px solid #eaeaea;
+	font-style: normal;
+}
+
+.bp-user #buddypress #message-thread .message-metadata {
+	overflow: hidden;
+}
+
+.bp-user #buddypress #message-thread .message-metadata img.avatar {
+	float: none;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress #message-thread .message-metadata img.avatar {
+		float: left;
+	}
+}
+
+.bp-user #buddypress #message-thread .message-metadata .message-star-actions {
+	float: right;
+	margin-right: 5px;
+	position: static;
+}
+
+.bp-user #buddypress #message-thread .message-content {
+	background: #f7f7f7;
+	border: 1px solid #eaeaea;
+	margin: 10px 0 0 0;
+	padding: 0.3em;
+}
+
+.bp-user #buddypress #message-thread #send-reply .message-content {
+	background: #fff;
+	border: 0;
+}
+
+.bp-user #buddypress #message-thread .alt {
+	background: #fff;
+}
+
+.bp-user #buddypress #message-threads thead tr {
+	background: #6e6e6e;
+}
+
+.bp-user #buddypress #message-threads tr td {
+	background: #fff;
+	display: inline-block;
+	float: left;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-star,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-bottom-color: #ccc;
+	border-bottom-width: 2px;
+	height: 2.4em;
+	padding-bottom: 0.2em;
+	padding-top: 0.2em;
+}
+
+@media screen and (max-width: 46.25em) {
+	.bp-user #buddypress #message-threads tr td.thread-star,
+	.bp-user #buddypress #message-threads tr td.thread-options {
+		padding-top: 0;
+	}
+	.bp-user #buddypress #message-threads tr td.thread-star span.icon:before,
+	.bp-user #buddypress #message-threads tr td.thread-options span.icon:before {
+		font-size: 14px;
+	}
+}
+
+.bp-user #buddypress #message-threads tr .thread-checkbox {
+	padding: 0 0 0 0.3em;
+}
+
+.bp-user #buddypress #message-threads tr td.bulk-select-check,
+.bp-user #buddypress #message-threads tr td.thread-from {
+	height: 3em;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr td.bulk-select-check,
+	.bp-user #buddypress #message-threads tr td.thread-from {
+		height: 5.2em;
+	}
+}
+
+.bp-user #buddypress #message-threads tr td.thread-from,
+.bp-user #buddypress #message-threads tr td.thread-options {
+	border-left: 0 !important;
+	width: -webkit-calc(100% - 30px);
+	width: -moz-calc(100% - 30px);
+	width: calc(100% - 30px);
+	margin-left: 0;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info {
+	padding-left: 41px;
+	width: 100%;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info a::after {
+	content: " –  " attr(title);
+	font-size: 12px;
+	font-size: 0.75rem;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-info .thread-excerpt {
+	font-size: 14px;
+	font-size: 0.875rem;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options {
+	text-align: right;
+}
+
+.bp-user #buddypress #message-threads tr td.thread-options a {
+	font-size: 12px;
+	font-size: 0.75rem;
+	line-height: 2.2;
+}
+
+.bp-user #buddypress #message-threads tr span.from {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads tr span.activity {
+	display: block;
+	float: right;
+	line-height: 2;
+}
+
+@media screen and (max-width: 38.75em) {
+	.bp-user #buddypress #message-threads tr span.activity {
+		clear: both;
+		font-size: 12px;
+		font-size: 0.75rem;
+		width: 100%;
+	}
+}
+
+.bp-user #buddypress #message-threads tr.unread td {
+	background: #dce5ff;
+	border-color: #d4d0ba;
+}
+
+.bp-user #buddypress #message-threads th {
+	display: none;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all {
+	border-bottom: 0;
+	display: inline-block;
+	text-align: left;
+}
+
+.bp-user #buddypress #message-threads th.bulk-select-all,
+.bp-user #buddypress #message-threads td.bulk-select-check,
+.bp-user #buddypress #message-threads td.thread-star {
+	border-right: 0;
+	width: 30px;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab {
+	margin-right: 0;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab a {
+	border-bottom: 0;
+}
+
+.bp-user #buddypress .acfb-holder li.friend-tab a img {
+	display: inline;
+	height: 20px;
+	margin-right: 0.5em;
+	width: 20px !important;
+}
+
+.bp-user #buddypress .sitewide-notices tr td:first-child {
+	display: none;
+}
+
+.bp-user #buddypress .sitewide-notices td strong {
+	background: #6e6e6e;
+	color: #fff;
+	display: block;
+	padding-left: 0.2em;
+}
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+.bp-user #buddypress #settings-form {
+	padding: 0 10px;
+}
+
+.bp-user #buddypress #settings-form > p {
+	font-size: 20px;
+	font-size: 1.25rem;
+	margin: 20px 0 10px;
+}
+
+.bp-user #buddypress table.notification-settings td.yes,
+.bp-user #buddypress table.notification-settings td.no {
+	vertical-align: middle;
+}
+
+.bp-user #buddypress table.profile-settings {
+	width: 100%;
+}
+
+.bp-user #buddypress table.profile-settings th.field-group-name,
+.bp-user #buddypress table.profile-settings td.field-name {
+	width: 50%;
+}
+
+@media screen and (min-width: 46.25em) {
+	.bp-user #buddypress table.profile-settings th.field-group-name,
+	.bp-user #buddypress table.profile-settings td.field-name {
+		width: 70%;
+	}
+}
+
+.bp-user #buddypress table.profile-settings th.title,
+.bp-user #buddypress table.profile-settings td.field-visibility {
+	width: 30%;
+}
+
+.bp-user #buddypress table.profile-settings td.field-visibility select {
+	width: 100%;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+#main #buddypress .standard-form li {
+	float: none;
+}
+
+#main #buddypress .standard-form input[type='text'],
+#main #buddypress .standard-form input[type='email'],
+#main #buddypress .standard-form input[type='password'],
+#main #buddypress .standard-form textarea {
+	width: 100%;
+}
+
+#buddypress div.activity-comments form .ac-textarea {
+	background: #f7f7f7;
+	border: 1px solid rgba(212, 208, 186, 0.5);
+}
+
+#buddypress div.activity-comments form .ac-textarea textarea {
+	background: none;
+	border: 0;
+}
+
+#buddypress .standard-form input[type="text"],
+#buddypress .standard-form input[type="email"],
+#buddypress .standard-form input[type="password"],
+#buddypress .standard-form button,
+#buddypress .standard-form select,
+#buddypress .standard-form textarea {
+	border-color: rgba(212, 208, 186, 0.5);
+	border-width: 1px;
+}
+
+#buddypress #signup_form.standard-form div.submit {
+	float: none;
+}
+
+#buddypress #signup_form.standard-form div.submit input {
+	margin-right: 0;
+}
+
+#buddypress div.dir-search,
+#buddypress div.message-search,
+#buddypress li.groups-members-search {
+	float: none;
+	margin: 10px 0;
+}
+
+#buddypress div.dir-search form,
+#buddypress div.message-search form,
+#buddypress li.groups-members-search form {
+	border: 1px solid rgba(212, 208, 186, 0.6);
+	overflow: hidden;
+}
+
+#buddypress div.dir-search form label,
+#buddypress div.message-search form label,
+#buddypress li.groups-members-search form label {
+	float: left;
+	width: 60%;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress div.dir-search form label,
+	#buddypress div.message-search form label,
+	#buddypress li.groups-members-search form label {
+		width: 80%;
+	}
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	float: left;
+	margin: 0;
+	width: 100%;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='text'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	font-size: 14px;
+	font-size: 0.875rem;
+	border: 0;
+	line-height: inherit;
+}
+
+#buddypress div.dir-search form input[type='text'],
+#buddypress div.message-search form input[type='text'],
+#buddypress li.groups-members-search form input[type='text'] {
+	border-right: 1px solid rgba(212, 208, 186, 0.6);
+	padding: 0.2em 0 0.2em 0.2em;
+}
+
+#buddypress div.dir-search form input[type='submit'],
+#buddypress div.message-search form input[type='submit'],
+#buddypress li.groups-members-search form input[type='submit'] {
+	float: right;
+	font-weight: normal;
+	padding: 0.2em 1em;
+	text-align: center;
+	text-transform: none;
+	width: 40%;
+}
+
+@media screen and (min-width: 46.25em) {
+	#buddypress div.dir-search form input[type='submit'],
+	#buddypress div.message-search form input[type='submit'],
+	#buddypress li.groups-members-search form input[type='submit'] {
+		width: 20%;
+	}
+}
+
+@media screen and (min-width: 38.75em) {
+	#buddypress div.dir-search,
+	#buddypress div.message-search,
+	#buddypress li.groups-members-search {
+		float: right;
+		margin-bottom: 5px !important;
+	}
+	#buddypress div.dir-search form label,
+	#buddypress div.dir-search form input[type='text'],
+	#buddypress div.dir-search form input[type='submit'],
+	#buddypress div.message-search form label,
+	#buddypress div.message-search form input[type='text'],
+	#buddypress div.message-search form input[type='submit'],
+	#buddypress li.groups-members-search form label,
+	#buddypress li.groups-members-search form input[type='text'],
+	#buddypress li.groups-members-search form input[type='submit'] {
+		width: auto;
+	}
+}
+
+@media screen and (min-width: 77.5em) {
+	#buddypress .dir-search form input[type='text'],
+	#buddypress .message-search form input[type='text'] {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+	#buddypress .dir-search form input[type='submit'],
+	#buddypress .message-search form input[type='submit'] {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables - General
+*-------------------------------------------------------------------------------
+*/
+#buddypress table {
+	font-size: 14px;
+	font-size: 0.875rem;
+	margin: 20px 0;
+}
+
+#buddypress table tr th {
+	background: #6e6e6e;
+	border-color: #b7b7b7;
+	color: #fff;
+}
+
+#buddypress table th,
+#buddypress table td {
+	padding: 0.3em;
+}
+
+#buddypress table p {
+	margin-bottom: 0.5em;
+}
+
+@media screen and (min-width: 55em) {
+	#buddypress table {
+		font-size: 16px;
+		font-size: 1rem;
+	}
+}
+
+/*__ User Account tables __*/
+#buddypress .notifications th,
+#buddypress .messages-notices th {
+	width: 30%;
+}
+
+#buddypress .notifications th.bulk-select-all,
+#buddypress .messages-notices th.bulk-select-all {
+	text-align: center;
+	width: 10%;
+}
+
+#buddypress .notifications .bulk-select-check,
+#buddypress .notifications .thread-star,
+#buddypress .messages-notices .bulk-select-check,
+#buddypress .messages-notices .thread-star {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions,
+#buddypress .notifications td.thread-options,
+#buddypress .messages-notices .notification-actions,
+#buddypress .messages-notices td.thread-options {
+	text-align: center;
+}
+
+#buddypress .notifications .notification-actions a,
+#buddypress .notifications td.thread-options a,
+#buddypress .messages-notices .notification-actions a,
+#buddypress .messages-notices td.thread-options a {
+	display: inline-block;
+	margin: 0;
+	padding: 0;
+}
+
+#buddypress .notifications td .button,
+#buddypress .messages-notices td .button {
+	border: 0;
+	display: block;
+	padding: 0;
+	text-align: center;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+#buddypress div#message p {
+	font-size: 18px;
+	font-size: 1.125rem;
+	font-weight: bold;
+}
+
+#buddypress div#message.info p {
+	background: #c6e4ee;
+	border: 1px solid #78bed6;
+	color: #1e5264;
+}
+
+#buddypress div#message.updated p {
+	background: #dee6b2;
+	border: 1px solid #becc66;
+	color: #454d19;
+}
+
+.delete-group #buddypress div#message.info p {
+	background: #db7e7e;
+	border: 1px solid #be3535;
+	color: #1f0808;
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+#buddypress {
+	/* Auto Complete background */
+}
+
+#buddypress .acfb-holder li.friend-tab {
+	background: #9fd1e2;
+	border: inherit;
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/css/twentythirteen.scss
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/css/twentythirteen.scss	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/css/twentythirteen.scss	(revision 10410)
@@ -0,0 +1,2375 @@
+// Stylesheet Guidence Notes
+// Table of content represents a guide to sections of the sheet.
+// Note that not all sections may be described in the body of the
+// rulesets only those that require styles for a given theme.
+// Sub sections might not exist but can be added as required
+// as per BP Lists - 'activity' section.
+
+// We follow the dictats of the parent theme in respect of media queries,
+// font sizing etc. Media queries are used in a modular sense (OOCSS)
+// rather than described as blocks to the end of the sheet, this aids ease
+// of managing media queries, scalability & flexibility .
+
+// Twentythirteen Media Queries / Breakpoints.
+// @media (max-width: 1599px)
+// @media (max-width: 1069px)
+// @media (max-width: 999px)
+// @media (max-width: 767px)
+// @media (max-width: 643px)
+// @media (max-width: 359px)
+
+// These differ from other WP themes in using fewer breakpoints
+// and in using max-width rather than min-width
+// In addition BP will use the em breakpoints as used for twentyfifteen referenced below.
+// @media screen and (max-width: 22em) // Additional for xtra small mobile only
+// @media screen and (max-width: 30em) // Additional small only
+// @media screen and (min-width: 38.75em)
+// @media screen and (min-width: 46.25em)
+// @media screen and (min-width: 55em)
+// @media screen and (min-width: 59.6875em)
+// @media screen and (min-width: 77.5em)
+// @media screen and (min-width: 87.6875em)
+
+// Mixins and Variables
+
+// Common margin values
+$spacing-val-lg: 40px;
+$spacing-val-md: 20px;
+$spacing-val-sm: 10px;
+$spacing-val-xs: 5px;
+
+// A simple mixin to handle font-sizing
+// 2013 uses a basic pixel font size approach, we'll stick with rem/px
+@mixin font-size($font-size: 16) {
+	$rem-font-value: ($font-size / 16);
+	font-size: ($font-size * 1px);
+	font-size: $rem-font-value + rem;
+}
+
+// To allow mixins to pass VendorPrefix scss-lint tests we disable lint-test
+// for specific blocks / rulesets
+// until such time as exemption lists can be built for the scss-lint.yml?
+// Any vendor prefix properties / mixins need to be in this block
+
+// scss-lint:disable VendorPrefix
+
+// Border border-radius mixins
+@mixin border-radius($radius) {
+	-webkit-border-radius: $radius;
+	-moz-border-radius: $radius;
+	-ms-border-radius: $radius;
+	border-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-top-radius($radius) {
+	-webkit-border-top-right-radius: $radius;
+	border-top-right-radius: $radius;
+	-webkit-border-top-left-radius: $radius;
+	border-top-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-right-radius($radius) {
+	-webkit-border-bottom-right-radius: $radius;
+	border-bottom-right-radius: $radius;
+	-webkit-border-top-right-radius: $radius;
+	border-top-right-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-bottom-radius($radius) {
+	-webkit-border-bottom-right-radius: $radius;
+	border-bottom-right-radius: $radius;
+	-webkit-border-bottom-left-radius: $radius;
+	border-bottom-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+@mixin border-left-radius($radius) {
+	-webkit-border-bottom-left-radius: $radius;
+	border-bottom-left-radius: $radius;
+	-webkit-border-top-left-radius: $radius;
+	border-top-left-radius: $radius;
+	background-clip: padding-box;
+}
+
+// Box sizing
+@mixin box-model($box-model) {
+	-webkit-box-sizing: $box-model;
+	-moz-box-sizing: $box-model;
+	box-sizing: $box-model;
+}
+
+// Calc
+@mixin calc($property, $expression) {
+	#{$property}: -webkit-calc(#{$expression});
+	#{$property}: -moz-calc(#{$expression});
+	#{$property}: calc(#{$expression});
+}
+
+// Re-enable the VendorPrefix lint test
+// scss-lint:enable VendorPrefix
+
+// BP message boxes
+@mixin message-box($background, $text-color: null) {
+
+	@if $text-color {
+		// if a param was passed through
+		$text-color: $text-color;
+	} @else {
+		$text-color: darken($background, 50%);
+	}
+
+	background: lighten($background, 10%);
+	border: 1px solid darken($background, 10%);
+	color: $text-color;
+}
+
+// 2013 font family
+@mixin default-font-sans() {
+	font-family: "Source Sans Pro", Helvetica, sans-serif;
+}
+
+// Variables: color definitions
+$body-text:          #141412;
+$light-body:         lighten(#141412, 45%);
+$content-background: #fff;
+$off-white:          lighten(#f9f8ee, 10%);
+$cream-background:   #f7f5e7;
+$light-background:   #f7f7f7;
+$medium-background:  #ccc;
+$dark-background:    #555;
+$border-color:       #d4d0ba;    // border color is varied using rgba
+$border-light:       #eaeaea; // BP dividers
+$border-med:         #ddd;
+$background-hover:   #db572f; // 2013 link background hover
+$bp-button-hover:    #ededed; // this is the default BP button hover background
+$notice-error:       #c85a6e;
+$notice-warning:     #d05656;
+$notice-info:        #9fd1e2;
+$notice-update:      #ced98c;
+$stripe-odd:         #ebf0ff;
+$stripe-even:        #dbe5ff;
+$unread:             #dce5ff;
+
+/*--------------------------------------------------------------
+
+This is the BuddyPress companion stylesheet for
+the WordPress Twentythirteen theme.
+
+This sheet supports the primary BuddyPress styles in buddypress.css
+
+If you are running as a child theme of twentyfifteen this stylesheet will be
+loaded by default. If you want to modify the styles the normal BP Theme Compat
+hierarchy works and you can copy the file to buddypress/css/ or community/css/
+in your child themes root & either start over or modify the rulesets.
+
+If you do not require the styles you can dequeue them from the themes functions file.
+
+Please see this codex article for more information:
+http://codex.buddypress.org/themes/buddypress-companion-stylesheets/
+
+----------------------------------------------------------------
+>>> TABLE OF CONTENTS:
+----------------------------------------------------------------
+1.0 Theme Structural Elements & overrides
+2.0 - Navigation - General
+	2.1 - Navs - Object Nav / Sub Nav (item-list-tabs)
+	2.2 - Pagination
+3.0 - Images
+4.0 - BP Lists / Loops Generic
+	4.1 - Activity Loop
+		4.1.1 Whats New Activity
+		4.1.2 - Activity Listing
+		4.1.3 - Activity Comments
+	4.2 - Members Loop
+	4.3 - Groups Loop
+	4.4 - Blogs Loop
+5.0 - Directories - Members, Groups, Blogs, Register, activation
+6.0 - Single Item screens: User Account & Single Group Screens
+	6.1 - Item Headers: Global
+		6.1.1 - item-header: Groups
+		6.1.2 - item-header: User Accounts
+	6.2 - Item Body: Global
+		6.2.1 - item-body: Groups
+			6.2.1.1 - Management settings screens
+			6.2.1.2 - Group members list
+			6.2.1.3 - Group send Invites
+		6.2.2 - item-body: User Accounts
+			6.2.2.1 - classes, pag, filters
+			6.2.2.2 - Extended Profiles
+			6.2.2.3 - Groups
+			6.2.2.4 - friends
+			6.2.2.5 - Private Messaging Threads
+			6.2.2.6 - Settings
+7.0 - Forms  -  General
+8.0 - Tables -  General
+9.0 - Error / Success Messages
+10.0 - Ajax Loading, Widgets, General classes
+--------------------------------------------------------------*/
+
+/**
+*-------------------------------------------------------------------------------
+* @section 1.0 - Theme - Structural Elements & overrides
+*-------------------------------------------------------------------------------
+*/
+
+// As BP screens have specific content lets adjust the themes primary elements
+// to maximise the screen real estate for BP pages.
+
+.buddypress {
+	div.clear {display: none;}
+
+	// re-factor main themes primary elements widths for BP screens
+	@media (max-width: 999px) {
+		#content {padding: 0 $spacing-val-md;}
+	}
+	@media (max-width: 643px) {
+		#content {padding: 0;}
+	}
+
+	.entry-header,
+	.entry-content,
+	.entry-summary,
+	.entry-meta {
+		max-width: 890px;
+	}
+
+} // .buddypress (body class)
+
+/**
+*-------------------------------------------------------------------------------
+* @section 2.0 - Navigation - General
+*-------------------------------------------------------------------------------
+*/
+
+
+
+/**
+*----------------------------------------------------------
+* @section 2.1 - Navs Object Nav / Sub Nav (item-list-tabs)
+*
+* The main navigational elements for all BP screens
+*----------------------------------------------------------
+*/
+
+// We have a need to override BP specifity
+// so some rulesets sets will appear over weighted in selectors
+
+#buddypress {
+
+	// Global Nav Styles
+	div.item-list-tabs {
+		ul {
+			background-color: $cream-background;
+			border-bottom: 1px solid rgba($border-light, 0.9);
+			border-top: 1px solid rgba($border-light, 0.9);
+			overflow: hidden;
+			padding: 0;
+
+			li {
+				a {
+					span {border-radius: 25%;}
+				}
+			}
+		}
+	}
+}
+// .bp-user #object-nav @ small screen as a dropdown
+
+.bp-user {
+	#buddypress {
+		@media screen and (max-width: 38.75em) {
+
+			#object-nav {
+				background: $cream-background;
+				border: 1px solid $border-light;
+				overflow: visible;
+				padding: $spacing-val-sm;
+
+				ul {
+					border: 0;
+					height: 0;
+					// all latest vendor releases handle transitions, so no prefixes used.
+					transition: height 0.3s ease-in-out 0.7s;
+					visibility: hidden;
+				}
+
+				&:before {
+					content: "Menu \021E9";
+					display: inline;
+					opacity: 0.7;
+				}
+
+				&:hover,
+				&:focus {
+					&:before {content: "Menu \021E7";}
+
+					ul {
+						height: 320px;
+						opacity: 1;
+						overflow-y: auto;
+						visibility: visible;
+					}
+				}
+			}
+
+			#subnav {clear: both;}
+
+		} // close @media
+	}
+}
+	/*__ Horizontal menus & small screen __*/
+
+#buddypress {
+
+	#object-nav {
+
+		ul {
+			overflow: hidden;
+
+			li {
+				@media screen and (max-width: 38.75em) {
+					&:focus,
+					&:hover {
+						background: darken($cream-background, 2%);
+					}
+				}
+
+				float: none;
+			}
+
+			@media screen and (max-width: 38.75em) {
+				li:not(:last-child) {
+					border-bottom: 1px solid $border-light;
+				}
+			}
+
+			li:not(.selected) {
+				a {opacity: 0.7;}
+			}
+
+			@media screen and (min-width: 38.75em) {
+				li {
+					float: left;
+				}
+			}
+		}
+	}
+
+	div#subnav.item-list-tabs {
+		margin-top: 0;
+
+		ul {
+			background-color: $light-background;
+			border-bottom: 0;
+			margin-top: $spacing-val-xs;
+			padding: 0;
+
+			li.last {
+				background: $content-background;
+				margin-top: 0;
+				padding: $spacing-val-xs $spacing-val-xs $spacing-val-xs 0;
+				width: 100%;
+
+				select,
+				select:focus {
+					background: transparent;
+					border: 0;
+					outline: 0;
+				}
+
+				select,
+				label,
+				option {
+					@include font-size(14);
+				}
+
+				select {
+					font-style: italic;
+				}
+			}
+
+			@media screen and (min-width: 38.75em) {
+				li.last {
+					text-align: right;
+				}
+			}
+		} // close ul
+	} // close #subnav
+
+	// active/current states all navs
+	div.item-list-tabs {
+		ul {
+			li.selected,
+			li.current {
+				a {
+					background: none;
+					opacity: 1;
+				}
+			}
+		}
+	}
+
+} // close #buddypress
+
+	/*__ Vertical menu User Account / Group single screens __*/
+
+// This block contains rules to re-factor the item-body structural element
+// to sit alongside the vert menu
+// As 2013 has an inner sidebar which if active can narrow screen too far
+// for a columner nav we'll only use these rules if no sidebar 'body:not(.sidebar)'.
+
+@media screen and (min-width: 55em) {
+
+	.bp-user #buddypress,
+	.single-item.groups #buddypress {
+	//	background: $light-background;
+	}
+
+	body:not(.sidebar) {
+		#buddypress {
+
+			#item-header,
+			#item-body {
+				background: $content-background;
+			}
+
+			#object-nav {
+				border-right: 1px solid $border-med;
+				float: left;
+				margin-right: -1px;
+				width: 200px;
+
+				ul {
+					background: none;
+					border-bottom: 0;
+					padding: 0;
+
+					li {
+						float: none;
+						overflow: hidden;
+
+						span {
+							background: $content-background;
+							border-radius: 10%;
+							float: right;
+							margin-right: 2px;
+						}
+					}
+				}
+			}
+
+			#item-body {
+				border-left: 1px solid $border-med;
+				overflow: hidden;
+				padding: 0 0 0 $spacing-val-md;
+				width: auto;
+
+				#subnav {
+					margin: 0 0 0 (-$spacing-val-md);
+
+					ul {margin-top: 0;}
+				}
+			}
+
+		} // close  #buddypress
+	} // close body:not(.sidebar)
+} // close @media
+
+
+// Tabbed Nav styling
+// Primarilly used for group create screens & ? directory nav.
+
+@media screen and (min-width: 46.25em) {
+	#main {
+		#buddypress {
+
+			#group-create-tabs.item-list-tabs {
+
+				ul:before,
+				ul:after {
+					content: " ";
+					display: table;
+				}
+
+				ul:after {
+					clear: both;
+				}
+
+				ul {
+					background: none;
+					border: 0;
+					border-bottom: 1px solid $border-med;
+					overflow: visible;
+					padding: 0;
+
+					li {
+						float: left;
+						width: auto;
+					}
+
+					li.selected,
+					li.current {
+						border: 1px solid $border-med;
+						border-bottom-color: $content-background;
+						@include border-top-radius(4px);
+						margin-bottom: -1px;
+
+						a {
+							background: none;
+							color: $body-text;
+							outline: none;
+						}
+					}
+				}
+
+			} // close .item-list-tabs
+
+			#subnav {
+				ul {
+					border-bottom: 0;
+				}
+
+			} // close #subnav
+		} // close #buddypress
+	}
+} // close @media
+
+/**
+*----------------------------------------------------------
+* @section 2.2 - Pagination
+*----------------------------------------------------------
+*/
+
+#buddypress {
+	div.pagination {
+		box-shadow: none;
+		.pag-count {margin-left: 0;}
+		.pagination-links {
+			margin-right: 0;
+
+			span,
+			a {
+				height: auto;
+				line-height: 1;
+				padding: 5px;
+			}
+
+			.prev,
+			.next {
+				background-color: transparent;
+				color: inherit;
+				overflow: visible;
+				width: auto;
+				&:before {display: none;}
+			}
+
+			.prev {
+				left: auto;
+				position: static;
+			}
+
+			.next {
+				position: static;
+				right: auto;
+			}
+		} // close .pagination-links
+	} // close .pagination
+} // close #buddypress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 4.0 - BP Lists / Loops Generic
+*-------------------------------------------------------------------------------
+*/
+
+// Adjusts Meta items and various elements to match 2013 font-family
+#buddypress {
+	.item-list {
+		.activity-header,
+		.activity-meta {
+			@include default-font-sans();
+		}
+	}
+}
+
+#buddypress {
+	.activity-meta {
+		.button {
+			&:focus,
+			&:hover {
+				background: lighten($background-hover, 10%);
+				color: $cream-background;
+			}
+		}
+	}
+
+	.action {
+		.generic-button {
+			a {
+				&:focus,
+				&:hover {
+					background: lighten($background-hover, 10%);
+					color: $cream-background;
+				}
+			}
+		}
+	}
+} // close #buddypress
+
+// Trying to position the action div absolute has bad consequences where
+// aditional items are displayed, floating is an alternative that
+// allows natural flow expansion.
+// This block:
+// * Manages li items, Stacks them small screen, floated left at wider screens.
+// * Adjusts display of action buttons at small screen mobile up.
+// Adjusts Meta items to match 2013 font-family
+
+#buddypress {
+	ul.item-list {
+		li {
+			overflow: hidden !important;
+
+			// Undo BP floats to center avatars initially mobile up
+			.item-avatar {
+				margin-bottom: $spacing-val-sm;
+				@media screen and (min-width: 38.75em) {
+					margin-bottom: 0;
+				}
+				text-align: center;
+
+				a {border-bottom: 0;}
+
+				img.avatar {
+					display: inline-block;
+					float: none;
+
+					// Quite early on we need to move avatars to the left(right)
+					@media screen and (min-width: 38.75em) {
+						display: block;
+						float: left;
+					}
+				}
+			}
+
+			.item {
+				overflow: hidden;
+				@media screen and (min-width: 46.25em) {
+					margin-left: 15%;
+				}
+
+				span.activity {
+					font-style: italic;
+				}
+
+				.item-desc {
+					margin: 0 auto;
+					width: 100%;
+				}
+
+				.item-title {
+					@include font-size(18);
+					line-height: 1.2;
+					margin: 0 auto;
+					text-align: center;
+
+					@media screen and (min-width: 38.75em) {
+						text-align: left;
+					}
+					width: 100%;
+
+					.update {
+						@include font-size(12);
+						display: block;
+
+						@media screen and (min-width: 59.6875em) {
+							@include font-size(14);
+						}
+						padding: $spacing-val-sm 0;
+						text-align: left;
+					}
+
+				} // .item-title
+
+			}
+
+			@media screen and (min-width: 59.6875em) {
+				.item-avatar,
+				.item,
+				.action {
+					float: left;
+				}
+
+				.item {
+					left: 5%;
+					margin-left: 0;
+					position: relative;
+					width: 55%;
+
+					.item-title {
+						@include font-size(22)
+					}
+				}
+
+			}// close @media
+
+			div.action {
+				clear: left;
+				float: none;
+				margin-bottom: -$spacing-val-md;
+				// remove BP margin left
+				margin-left: 0;
+				padding: $spacing-val-md 0 $spacing-val-xs;
+				// push the actions to the right
+
+				@media screen and (min-width: 59.6875em) {
+					clear: none;
+					float: right;
+					margin-bottom: 0;
+					padding: 0;
+				}
+
+				position: relative;
+				text-align: left;
+				top: 0;
+
+				div {
+					display: inline-block;
+					margin: $spacing-val-sm 0;
+					width: 100%;
+
+					a {
+						display: block;
+						width: 100%;
+					}
+					// Provisionally lets keep the 'action' div
+					//position but inline-block the buttons
+
+					@media screen and (min-width: 38.75em) {
+						// keep buttons to a row small tablet up
+						margin: 0 $spacing-val-sm $spacing-val-sm 0;
+						width: auto;
+					} // close @media
+
+					@media screen and (min-width: 59.6875em) {
+						// take buttons to block to stack them when floated right
+						clear: right;
+						float: right;
+						margin: 0 0 $spacing-val-sm 0;
+					} // close @media
+
+				} // close div
+
+				.meta {
+					font-style: italic;
+				}
+
+			} // close .action
+
+		} // close li
+	} // close .item-list
+} // close #buddypress
+
+/**
+*----------------------------------------------------------
+* @section 4.1 - Activity
+*----------------------------------------------------------
+*/
+
+/**
+*-----------------------------------------------------
+* @section 4.1.1 - Activity Whats New
+*-----------------------------------------------------
+*/
+
+#buddypress {
+	form#whats-new-form {
+
+		// Line-height issue inherited from BP rules, override.
+		p.activity-greeting {
+			line-height: 1.4;
+		}
+
+		// corrective measure for clipped elements due to JS inline styling
+
+		@media screen and (max-width: 46.25em) {
+			#whats-new-content {
+				clear: left;
+				margin: $spacing-val-sm 0 $spacing-val-md;
+				padding: $spacing-val-sm 0 0;
+			}
+		}
+
+		#whats-new-content.active {
+			#whats-new-options[style] {
+				#whats-new-post-in-box {
+					border: 1px solid rgba($border-color, 0.5);
+					float: left;
+					line-height: 1.5em;
+					margin-top: 12px;
+					padding-left: 0.2em;
+					width: 100%;
+
+					select {
+						background: none;
+						border: 0;
+						float: right;
+						margin: 0;
+						min-height: 1.5em;
+						padding-left: 0.4em;
+					}
+				}
+
+				@media screen and (min-width: 30em) {
+					#whats-new-post-in-box {
+						width: auto;
+					}
+
+					#whats-new-submit {
+						float: right;
+					}
+				}
+
+			} // close #whats-new-options
+		} // close #whats-new-content.active
+
+	} // close #whats-new-form
+
+	// User account form requires matching bp default specificity
+	#item-body {
+		form#whats-new-form {
+			margin: $spacing-val-lg 0;
+		}
+	}
+} // close #buddypress
+
+/**
+*-----------------------------------------------------
+* @section 4.1.2 - Activity Listing
+*-----------------------------------------------------
+*/
+
+#buddypress {
+
+	#activity-stream {
+
+		// re-size activity avatars and stack small screen
+		// refactor main elements positioning medium up.
+		li {
+			padding: 25px 0 15px;
+
+			.activity-avatar {
+				float: none;
+				text-align: center;
+
+				a {
+					display: inline-block;
+
+					img.avatar {
+						display: inline;
+						float: none;
+						height: 60px;
+						margin-bottom: $spacing-val-md;
+						margin-left: 0;
+						width: 60px;
+					}
+				}
+			} // close .activity-avatar
+
+			.activity-content {
+				margin-left: 0;
+
+				.activity-header {
+					@include font-size(14);
+				}
+			}
+
+			@media screen and (min-width: 46.25em) {
+
+				.activity-avatar {
+					float: left;
+					margin-right: $spacing-val-sm;
+					text-align: left;
+					a {border-bottom: 0;}
+				}
+
+				.activity-content {
+					margin: 0;
+					overflow: hidden;
+
+					.activity-header {
+						@include font-size(16);
+					}
+				}
+
+			} // close @media
+
+		} // close li
+
+		li.mini {
+			.activity-avatar {
+				a {
+					img.avatar {
+						height: 30px;
+						margin-left: 15px;
+						width: 30px;
+					}
+				}
+			}
+
+			.activity-content {
+				.activity-header {
+					@include font-size(14);
+				}
+			}
+		}
+
+		.activity-content {
+			margin-top: -12px;
+
+			.activity-header {
+				line-height: inherit;
+				margin-right: 0;
+
+				p {
+					background-color: $light-background;
+					border: 1px solid rgba($border-light, 0.6);
+					margin-top: 0;
+					padding: 0 0.2em;
+				}
+
+				img.avatar {
+					display: inline-block;
+					margin-bottom: 0 !important; // have to match to BP
+				}
+			} // close .activity-header
+
+			.activity-meta {
+				a {
+					display: block;
+					margin-bottom: $spacing-val-xs;
+					@media screen and (min-width: 38.75em) {
+						display: inline-block;
+						margin-bottom: 0;
+					}
+				}
+			}
+
+		} // close .activity-content
+
+		.load-more {
+			background: $light-background;
+			border: 1px solid transparent;
+			padding: $spacing-val-sm;
+
+			&:focus,
+			&:hover {
+				background: darken($light-background, 1%);
+				border: 1px solid rgba(#9fd1e2, 0.3);
+				a { font-style: italic; }
+			}
+
+			a {
+				display: block;
+			}
+		}
+
+	} // close #activity-stream
+} // close #buddypress
+
+/* Single activity view  - activity permalink */
+
+.activity-permalink {
+	#buddypress {
+		#activity-stream {
+
+			li.activity-item {
+				padding: $spacing-val-md;
+			}
+
+			li.mini {
+				.activity-header {
+					@include font-size(16);
+					@media screen and (min-width: 46.25em) {
+						@include font-size(20);
+					}
+					margin-bottom: $spacing-val-lg;
+					p {padding: $spacing-val-md;}
+				}
+			}
+		}
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @section 4.1.3 - Activity Comments
+*-----------------------------------------------------
+*/
+#buddypress {
+
+	#activity-stream {
+		.activity-comments {
+			border-left: 1px solid $border-light;
+			margin-top: 5px;
+
+			ul {
+				background: rgba($light-background, 0.6);
+				margin: 15px 0 0 2px;
+			}
+
+			.ac-form {
+
+				input[type="submit"],
+				.ac-reply-cancel {
+					color: rgba($body-text, 0.8);
+					display: inline-block;
+					font-family: inherit;
+					@include font-size(12);
+					font-weight: normal;
+					line-height: 1.2;
+					padding: 4px 10px;
+					text-transform: lowercase;
+					width: 100px;
+				}
+
+				.ac-reply-cancel {
+					border: 1px solid rgba($border-color, 0.7);
+					text-align: center;
+
+					&:focus,
+					&:hover {
+						background: $bp-button-hover;
+					}
+				}
+
+			} // close .ac-form
+
+		} // close .activity-comments
+	} // close .activity
+
+} // close #buddypress
+
+/**
+*----------------------------------------------------------
+* @section 4.2 - Members Loop
+*----------------------------------------------------------
+*/
+
+#buddypress {
+	#members-list {
+
+		@media screen and (min-width: 59.6875em) {
+			li {
+				.item-avatar,
+				.item {
+					float: left;
+				}
+
+				.action {
+					float: right;
+				}
+			}
+		}
+
+	} // close #members-list
+} //close #BuddyPress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 5.0 - Directories
+*-------------------------------------------------------------------------------
+*/
+
+/**
+*-----------------------------------------------------------
+* @subsection Registration
+*-----------------------------------------------------------
+*/
+
+#buddypress {
+	#signup_form.standard-form {
+
+		#profile-details-section,
+		#basic-details-section,
+		#blog-details-section {
+			float: none;
+			width: 100%;
+
+			@media screen and (min-width: 38.75em) {
+				width: 48%;
+			}
+		}
+
+		@media screen and (min-width: 38.75em) {
+			#profile-details-section {float: right;}
+			#basic-details-section {float: left;}
+		}
+
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 6.0 - Single Item screens: User Account & Single Group Screens
+*-------------------------------------------------------------------------------
+*/
+
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.1 - Item Header Global
+*-----------------------------------------------------------
+*/
+
+.bp-user,
+.single-item.groups {
+
+	.entry-header {
+		margin-bottom: 0;
+
+		.entry-title {
+			color: $light-body;
+			@include font-size(32);
+			margin-bottom: $spacing-val-sm;
+			text-align: center;
+			@media screen and (min-width: 46.25em) {
+				text-align: left;
+			}
+		}
+	}
+
+	#buddypress {
+		#item-header-content {
+			#item-meta {
+				@include font-size(14);
+				text-align: left;
+
+				p {margin-bottom: 0.5em;}
+			}
+		}
+	}
+
+	@media screen and (max-width: 46.25em) {
+		main {
+			header.entry-header {
+				padding-bottom: 1rem;
+			}
+		}
+	}
+
+	@media screen and (max-width: 38.75em) {
+		h1,
+		#item-header-content {
+			text-align: center;
+		}
+	}
+
+	#buddypress {
+		@media screen and (max-width: 46.25em) {
+			#item-header {
+				.generic-button {
+					float: none;
+					margin: 1.5em 0 0 0;
+				}
+			}
+		}
+
+		@media screen and (max-width: 38.75em) {
+			h1 {
+				margin-bottom: 0;
+			}
+
+			#item-header-avatar {
+				img.avatar {
+					margin-right: 0;
+				}
+			}
+
+			#item-header-content {
+				width: 100%;
+			}
+		}
+	}
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.1 - item-header Groups
+*
+* Group Specific Item Header
+*-----------------------------------------------------
+*/
+
+.single-item.groups {
+	#buddypress {
+		@media screen and (max-width: 46.25em) {
+			#item-header {
+				#item-meta {
+					margin-bottom: $spacing-val-md;
+				}
+			}
+		}
+
+		// Move visual flow of avatar & item-actions at narrow width,
+		// avatar first after group title
+		@media screen and (max-width: 38.75em) {
+			div#item-header {
+				display: flex;
+				flex-direction: column;
+
+				#item-header-avatar {
+					order: 1;
+					text-align: center;
+
+					a {
+						border-bottom: 0;
+
+						img {
+							display: inline-block;
+							float: none;
+						}
+					}
+				}
+
+				#item-header-content {
+					order: 2;
+				}
+
+				#item-actions {
+					order: 3;
+
+					h3 {
+						border-bottom: 1px solid $border-light;
+						text-align: center;
+					}
+				}
+			}
+		} // close @media
+
+		div#item-header {
+			padding-bottom: $spacing-val-lg;
+
+			div#item-actions {
+				margin: 0;
+				width: 100%;
+
+				@media screen and (min-width: 38.75em) {
+					border-left: 1px solid $border-light;
+					clear: none;
+					float: right;
+					padding-left: 0.2em;
+					width: 30%;
+				} // close @media
+
+				@media screen and (min-width: 46.25em) {
+					width: 40%;
+				}
+
+				ul {
+					margin-top: 0;
+					padding-left: 0;
+				}
+
+				h3 {
+					@include font-size(14);
+
+					@media screen and (min-width: 46.25em) {
+						@include font-size(16);
+					}
+					padding: 0.2em;
+				}
+			} // close item-actions
+
+			@media screen and (min-width: 46.25em) {
+
+				#item-header-avatar,
+				#item-header-content {
+					float: left;
+				}
+
+				#item-header-avatar {
+					width: 21%;
+				}
+
+				#item-header-content {
+					margin-left: 4%;
+					width: 40%;
+				}
+
+				div#item-actions {
+					clear: both;
+					float: right;
+					width: 28%;
+
+					h3 {
+						@include font-size(14);
+					}
+				}
+
+			} // close @media
+
+			#item-header-cover-image {
+	//			#item-header-content {
+	//			}
+
+				#item-actions {
+					border: 0;
+					@media screen and (max-width: 46.25em) {
+						clear: both !important;
+					}
+					float: none;
+					width: auto;
+				}
+
+				@media screen and (min-width: 46.25em) {
+					#item-header-content {
+						padding-left: $spacing-val-sm;
+
+						#item-buttons {
+							margin-top: 0;
+						}
+					}
+
+					#item-actions {
+						border-left: 1px solid $border-light;
+						clear: none;
+						float: right;
+						margin-top: 230px !important;
+						padding-top: 0 !important;
+						width: 20%;
+					}
+				}
+			}
+
+		} // close #item-header
+	} // close #buddypress
+} // close .groups
+
+/**
+*-----------------------------------------------------
+* @subsection 6.1.2 - Item Header User Accounts
+*
+* User Accounts Specific Item Header
+*-----------------------------------------------------
+*/
+
+.bp-user {
+
+	#buddypress {
+		#item-header {
+			padding: $spacing-val-md 0;
+
+			#item-header-avatar {
+				text-align: center;
+				width: 100%;
+
+				img.avatar,
+				a {
+					border-bottom: 0;
+					display: inline-block;
+					float: none;
+				}
+			}
+
+			#item-header-content {
+				.user-nicename,
+				span.activity {
+					display: block;
+					text-align: center;
+				}
+			}
+
+			@media screen and (min-width: 46.25em) {
+				#item-header-avatar {
+					float: left;
+					width: auto;
+
+					a {
+						float: left;
+					}
+				}
+
+				#item-header-content {
+					float: none;
+					overflow: hidden;
+
+					.user-nicename,
+					span.activity {
+						text-align: left;
+					}
+
+					#item-meta {
+						margin-top: 0;
+					}
+				}
+
+			} // close @media
+
+		} // close #item-header
+
+		#item-header-cover-image {
+
+			#item-header-content {
+				@media screen and (min-width: 46.25em) {
+					.user-nicename {
+						margin-bottom: $spacing-val-md;
+					}
+
+					#item-buttons {
+						margin: 0;
+					}
+				}
+			}
+		}
+
+	} // close #buddypress
+} // close .bp-user
+
+/**
+*-----------------------------------------------------------
+* @subsection 6.2 - Item Body: Global
+*-----------------------------------------------------------
+*/
+
+/**
+*----------------------------------------------------
+* @subsection 6.2.1 - Item Body Groups
+*
+* Groups specific item body rules -  screens
+*----------------------------------------------------
+*/
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.1 - Group Management
+*-----------------------------------------
+*/
+
+// headings settings screens & general global settings styles
+.groups {
+	#group-settings-form {
+		h4 {
+			background: $dark-background;
+			color: $content-background;
+			padding: 0.2em;
+		}
+	}
+}
+
+.groups.edit-details {
+	#group-settings-form {
+
+		label {
+			margin-bottom: 0;
+			padding: 0.2em;
+			width: 80%;
+		}
+
+		textarea + p label {
+			background: none;
+			color: inherit;
+			@include font-size(14);
+			width: auto;
+		}
+
+		textarea {
+			height: auto;
+			min-height: 100px;
+			overflow: auto;
+		}
+	}
+} // close .groups.edit-details
+
+.groups.group-settings {
+	#group-settings-form {
+
+		div.radio label {
+			border: 1px solid $border-light;
+			padding: 0.2em;
+
+			ul {
+				color: rgba($body-text, 0.6);
+				@include font-size(14);
+			}
+		}
+
+	}
+} // close .groups.group-settings
+
+.groups.group-avatar {
+
+	form > p {
+		margin-top: $spacing-val-md;
+	}
+
+}
+
+.groups.manage-members {
+	#group-settings-form {
+		.item-list {
+			li {
+				border-bottom: 1px solid $border-light;
+
+				img,
+				h5 {
+					float: left;
+
+					> a {border-bottom: 0;}
+				}
+
+				span.small {
+					clear: left;
+					display: block;
+					float: none;
+					margin-top: $spacing-val-sm;
+
+					a {
+						display: inline-block;
+						margin: $spacing-val-xs 0;
+						width: 100%;
+
+						@media screen and (min-width: 38.75em) {
+							width: auto;
+						}
+					}
+				}
+
+				h5 {
+					margin: 0;
+				}
+
+			}
+		}
+	}
+} // close .groups.manage-members
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.2 - Group members
+*-----------------------------------------
+*/
+
+// Massage the members search for groups nav specifically.
+.groups.group-members {
+	#subnav {
+		li {
+			@media screen and (max-width: 38.75em) {
+				background: $content-background;
+				padding: $spacing-val-md 0;
+			}
+			width: 100%;
+
+			#search-members-form {
+				float: right;
+
+				@media screen and (max-width: 38.75em) {
+					margin: 0;
+					width: 100%;
+
+					label {
+						input[type="text"] {width: 100%;}
+					}
+				}
+
+				margin: $spacing-val-xs $spacing-val-xs 0 0;
+			}
+		}
+	}
+}
+
+/**
+*-----------------------------------------
+* @subsection 6.2.1.3 - Group send Invites
+*-----------------------------------------
+*/
+#buddypress {
+
+	div.invite {
+		.left-menu {
+			float: none;
+			@media screen and (min-width: 46.25em) {
+				float: left;
+			}
+		}
+
+		#invite-list {
+			border: 1px solid $border-light;
+			height: auto;
+			min-height: 3.125em;
+			padding: 0 0.3em 0.5em;
+			width: auto;
+			@media screen and (min-width: 46.25em) {
+				width: 220px;
+			}
+
+			ul {
+				padding-left: 0;
+			}
+		}
+
+		.main-column {
+			margin-left: 0;
+			@media screen and (min-width: 46.25em) {
+				margin-left: 235px;
+			}
+		}
+
+	} // close div.invite
+}
+
+/**
+*-----------------------------------------------------
+* @subsection 6.2.2 - Item Body User Accounts
+*
+* User Account specific item body rules
+*-----------------------------------------------------
+*/
+
+.bp-user {
+	.entry-title {margin-bottom: 0.5em;}
+}
+
+/**
+*--------------------------------------------
+* @subsection 6.2.2.1 - classes, pag, filters
+*--------------------------------------------
+*/
+
+.bp-user {
+
+	#buddypress {
+
+		table {
+			th {@include font-size(14)}
+			td {@include font-size(13)}
+
+			@media screen and (min-width: 46.25em) {
+				th {@include font-size(16)}
+				td {@include font-size(14)}
+			}
+
+			@media screen and (min-width: 77.5em) {
+				th {@include font-size(18)}
+				td {@include font-size(16)}
+			}
+		}
+
+		.pag-count {font-style: italic;}
+
+		.notifications-options-nav {
+			border: 1px solid rgba($border-color, 0.5);
+			float: left;
+			width: 100%;
+			@media screen and (min-width: 38.75em) {
+				width: 300px;
+			}
+
+			select,
+			input {
+				border: 0;
+				@include font-size(14);
+				outline: 0;
+				padding: 0;
+			}
+
+			select {
+				float: left;
+				margin-right: 0;
+				width: 60%;
+			}
+
+			input {
+				float: right;
+				font-family: inherit;
+				line-height: 20px;
+				width: 40%;
+			}
+
+		} // close .notifications-options-nav
+	} // close #buddypress
+} // close .bp-user
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.2 - Extended Profiles
+	*-------------------------------------------
+	*/
+
+.bp-user {
+
+	#buddypress {
+
+		.profile {
+			.bp-widget {
+				h4 {
+					background: lighten($dark-background, 10%);
+					color: #fff;
+					margin-bottom: 0;
+					padding: 0.4em;
+				}
+
+				table {margin-top: 0;}
+			}
+
+			#profile-edit-form {
+
+				.button-nav:before,
+				.button-nav:after {
+					content: " ";
+					display: table;
+				}
+
+				.button-nav:after {
+					clear: both;
+				}
+
+				ul.button-nav {
+					border-bottom: 1px solid $border-light;
+					margin-left: 0;
+					padding: 0;
+
+					li {
+						float: left;
+						margin-bottom: 0;
+
+						&.current {
+							border: 1px solid $border-light;
+							border-bottom-color: #fff;
+							margin-bottom: -1px;
+						}
+					} // li
+
+					a {
+						background: none;
+						border: 0;
+						@include font-size(18)
+					}
+				} //.button-nav
+
+				.field-visibility-settings-toggle,
+				.field-visibility-settings {
+					@include font-size(14);
+				}
+
+				.field-visibility-settings-close,
+				.visibility-toggle-link {
+					background: $dark-background;
+					color: #fff;
+					padding: 0.2em 0.5em;
+				}
+			} // close profile form
+
+			.bp-avatar {
+				#bp-delete-avatar {
+					a {font-size: inherit; }
+				}
+			}
+		} // close .profile
+	} // close #buddyPress
+} // close .bp-user
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.3 - Groups
+	*-------------------------------------------
+	*/
+
+.bp-user {
+
+	#buddypress {
+
+		#groups-list {
+			li {
+				.item {
+					@media screen and (min-width: 77.5em) {
+						left: 5%;
+						width: 50%;
+					}
+				}
+			}
+		}
+	}
+}
+
+/**
+	*-------------------------------------------
+	* @subsection 6.2.2.5 - Private Messaging
+	*-------------------------------------------
+	*/
+
+.bp-user {
+
+	#buddypress {
+
+		#message-thread {
+		//Single message view
+			a {
+				border-bottom: 0;
+			}
+
+			#message-subject {
+				background: lighten($dark-background, 10%);
+				color: $content-background;
+				padding: 0.3em 0 0.3em 0.2em;
+			}
+
+			#message-recipients {
+				font-style: italic;
+
+				a.confirm {
+					border: 1px solid $border-light;
+					font-style: normal;
+				}
+			}
+
+			.message-metadata {
+				overflow: hidden;
+
+				img.avatar {
+					float: none;
+				}
+
+				@media screen and (min-width: 46.25em) {
+					img.avatar {
+						float: left;
+					}
+				}
+
+				.message-star-actions {
+					float: right;
+					margin-right: $spacing-val-xs;
+					position: static;
+				}
+			} // close .message-meta
+
+			.message-content {
+				background: $light-background;
+				border: 1px solid $border-light;
+				margin: $spacing-val-sm 0 0 0;
+				padding: 0.3em;
+			}
+
+			#send-reply {
+				.message-content {
+					background: $content-background;
+					border: 0;
+				}
+			}
+
+			.alt {
+				background: $content-background;
+			}
+
+		} // close message-thread  Singular view!
+
+		#message-threads {
+			thead {
+				tr {
+					background: lighten($dark-background, 10%);
+				}
+			}
+
+			tr {
+				td {
+					background: $content-background;
+					display: inline-block;
+					float: left;
+				}
+
+				td.thread-star,
+				td.thread-options {
+					border-bottom-color: $medium-background;
+					border-bottom-width: 2px;
+					height: 2.4em;
+					padding-bottom: 0.2em;
+					padding-top: 0.2em;
+
+					@media screen and (max-width: 46.25em) {
+						padding-top: 0;
+						span.icon:before {font-size: 14px;}
+					}
+				}
+
+				.thread-checkbox {
+					padding: 0 0 0 0.3em;
+				}
+
+				td.bulk-select-check,
+				td.thread-from {
+					height: 3em;
+					@media screen and (max-width: 38.75em) {
+						height: 5.2em;
+					}
+				}
+
+				td.thread-from,
+				td.thread-options {
+					border-left: 0 !important;
+					@include calc(width, "100% - 30px");
+					margin-left: 0;
+				}
+
+				td.thread-info {
+					padding-left: 41px;
+					width: 100%;
+
+					a::after {
+						content: " \2013 \00a0"attr(title);
+						@include font-size(12);
+					}
+
+					.thread-excerpt {
+						@include font-size(14);
+					}
+				}
+
+				td.thread-options {
+					text-align: right;
+
+					a {
+						@include font-size(12);
+						line-height: 2.2;
+					}
+				}
+
+				span.from {
+					display: none;
+				}
+
+				span.activity {
+					display: block;
+					float: right;
+					line-height: 2;
+					@media screen and (max-width: 38.75em) {
+						clear: both;
+						@include font-size(12);
+						width: 100%;
+					}
+				}
+			}
+
+			tr.unread {
+				td {
+					background: $unread;
+					border-color: $border-color;
+				}
+			}
+
+			th {
+				display: none;
+			}
+
+			th.bulk-select-all {
+				border-bottom: 0;
+				display: inline-block;
+				text-align: left;
+			}
+
+			th.bulk-select-all,
+			td.bulk-select-check,
+			td.thread-star {
+				border-right: 0;
+				width: 30px;
+			}
+		}
+
+	/* Auto Complete background */
+		.acfb-holder {
+
+			li.friend-tab {
+				margin-right: 0;
+
+				a {
+					border-bottom: 0;
+
+					img {
+						// this is the tiny user avatar
+						display: inline;
+						height: 20px;
+						margin-right: 0.5em;
+						width: 20px !important; // override inline style
+					}
+				}
+			}
+		}
+
+		/* Sitewide Notices loop */
+		.sitewide-notices {
+			tr {
+				td:first-child {
+					display: none;
+				}
+			}
+
+			td {
+				strong {
+					background: lighten($dark-background, 10%);
+					color: $content-background;
+					display: block;
+					padding-left: 0.2em;
+				}
+			}
+		}
+	} // close #buddypress
+} // close .bp-user
+
+/**
+	*------------------------------
+	* @subsection 6.2.2.6 - Settings
+	*------------------------------
+	*/
+
+.bp-user {
+
+	#buddypress {
+
+		#settings-form {
+			padding: 0 $spacing-val-sm;
+		// 'p' = email notification screen sub heading
+			> p {
+				@include font-size(20);
+				margin: $spacing-val-md 0 $spacing-val-sm;
+			}
+		}
+
+		table.notification-settings {
+			td.yes,
+			td.no {
+				vertical-align: middle;
+			}
+		}
+		// Profile table td widths
+		table.profile-settings {
+			width: 100%;
+
+			th.field-group-name,
+			td.field-name {
+				width: 50%;
+			}
+
+			@media screen and (min-width: 46.25em) {
+				th.field-group-name,
+				td.field-name {
+					width: 70%;
+				}
+			}
+
+			th.title,
+			td.field-visibility {
+				width: 30%;
+			}
+
+			td.field-visibility {
+				select {width: 100%;}
+			}
+		}
+
+	} // close #buddyPress
+
+} // close .bp-user
+
+/**
+*-------------------------------------------------------------------------------
+* @section 7.0 - Forms  - General
+*-------------------------------------------------------------------------------
+*/
+	// Attempt to reset form control widths
+#main {
+	#buddypress {
+		.standard-form {
+
+			li {float: none;}
+			input[type='text'],
+			input[type='email'],
+			input[type='password'],
+			textarea {
+				width: 100%;
+			}
+		}
+	}
+}
+
+
+#buddypress {
+
+// Match BP form elements to Themes(2013) styling where styles
+// are not inherited or being overriden.
+	div.activity-comments {
+
+		form {
+			.ac-textarea {
+				background: $light-background;
+				border: 1px solid rgba($border-color, 0.5);
+
+				textarea {
+					background: none;
+					border: 0;
+				}
+			}
+		}
+	}
+
+	.standard-form {
+
+		// Keep BP form element borders to 1px rather than 2013 2px
+		input[type="text"],
+		input[type="email"],
+		input[type="password"],
+		button,
+		select,
+		textarea {
+			border-color: rgba($border-color, 0.5);
+			border-width: 1px;
+		}
+
+	} // close .standard-form
+
+	#signup_form.standard-form {
+		div.submit {
+			float: none;
+
+			input {margin-right: 0;}
+		}
+	}
+
+} // close #buddypress
+
+#buddypress {
+	div.dir-search,
+	div.message-search,
+	li.groups-members-search {
+		float: none;
+		margin: $spacing-val-sm 0;
+
+		// Stylise the seach form elements, in part this deals with padding
+		// issues on the submit & sizing issues between bp styles & twentythirteen
+
+		form { // *sigh* only to bludgeon over specified rules
+			border: 1px solid rgba($border-color, 0.6);
+			overflow: hidden;
+
+			label {
+				float: left;
+				width: 60%;
+				@media screen and (min-width: 46.25em) {
+					width: 80%;
+				}
+			}
+
+			input[type='text'] {
+				float: left;
+				margin: 0;
+				width: 100%;
+			}
+
+			input[type='text'],
+			input[type='submit'] {
+				@include font-size(14);
+				border: 0;
+				line-height: inherit;
+			}
+
+			input[type='text'] {
+				border-right: 1px solid rgba($border-color, 0.6);
+				padding: 0.2em 0 0.2em 0.2em;
+			}
+
+			input[type='submit'] {
+				float: right;
+				font-weight: normal;
+				padding: 0.2em 1em;
+				text-align: center;
+				text-transform: none;
+				width: 40%;
+
+				@media screen and (min-width: 46.25em) {
+					width: 20%;
+				}
+
+			}
+		} // close form
+
+	} // close .dir-search, .message-search
+
+	// Shift the search parent to the right and allow to shrinkwrap
+	@media screen and (min-width: 38.75em) {
+		div.dir-search,
+		div.message-search,
+		li.groups-members-search {
+			float: right;
+			margin-bottom: 5px !important;
+
+			form {
+				label,
+				input[type='text'],
+				input[type='submit'] {
+					width: auto;
+				}
+			}
+		}
+
+	} // close @media
+
+
+	@media screen and (min-width: 77.5em) {
+
+		.dir-search,
+		.message-search {
+			form {
+				input[type='text'] { @include font-size(16) ;}
+				input[type='submit'] { @include font-size(16) ;}
+			}
+		}
+	}
+
+} // close #buddypress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 8.0 - Tables - General
+*-------------------------------------------------------------------------------
+*/
+
+// Adjust table font sizes, default too large proportionally
+// This approach will require refinement and perhaps re-location to
+// a more general typography section to manage BP elements grouped under
+// breakpoints. Provide top/bottom margins for tables, lacking in BP styles
+
+#buddypress {
+
+	table {
+		@include font-size(14);
+		margin: $spacing-val-md 0;
+
+		tr th {
+			background: lighten($dark-background, 10%);
+			border-color: darken($border-light, 20%);
+			color: $content-background;
+		}
+
+		// twentythirteen removes left padding, replace!
+		th,
+		td {
+			padding: 0.3em;
+		}
+
+		// Reduce the themes inherited paragraph margins in tables
+		p {margin-bottom: 0.5em;}
+	}
+
+	@media screen and (min-width: 55em) {
+		table {@include font-size(16);}
+	}
+}
+
+/*__ User Account tables __*/
+
+#buddypress {
+
+	// Manage some table cells widths that are disproportionate to their content
+	.notifications,
+	.messages-notices {
+		th {
+			width: 30%;
+
+			&.bulk-select-all {
+				text-align: center;
+				width: 10%;
+			}
+		}
+
+		.bulk-select-check,
+		.thread-star {
+			text-align: center;
+		}
+
+		.notification-actions,
+		td.thread-options {
+			text-align: center;
+
+			a {
+				display: inline-block;
+				margin: 0;
+				padding: 0;
+			}
+		}
+
+		// Notices action buttons, this maybe better moved, temp for now to address
+		// styling issues - this will need styling ideas
+		td {
+			.button {
+				border: 0;
+				display: block;
+				padding: 0;
+				text-align: center;
+			}
+		}
+
+	} // .notifications, .message-notices
+} // #buddypress
+
+/**
+*-------------------------------------------------------------------------------
+* @section 9.0 - Error / Success messages
+*-------------------------------------------------------------------------------
+*/
+
+// message args ($variable-color, text-color)
+// Leave text-color undefined to automagically set text color
+// to background color darker by 50%
+
+#buddypress {
+	div#message {
+		p {
+			@include font-size(18);
+			font-weight: bold;
+		}
+
+		&.info {
+			p {
+				@include message-box($notice-info);
+			}
+		}
+
+		&.updated {
+			p {
+				@include message-box($notice-update);
+			}
+		}
+
+	} // close #message
+
+} // close  #buddypress
+
+// Without direct classes on our mesages ( 'warning' )
+// we need to use the body classes
+// todo: If this is class .warning is included in main BP styles remove from here
+.delete-group {
+	#buddypress {
+		div#message.info {
+			p {
+				@include message-box($notice-warning);
+			}
+		}
+	}
+}
+
+/**
+*-------------------------------------------------------------------------------
+* @section 10.0 - Ajax Loading, Widgets, General classes
+*-------------------------------------------------------------------------------
+*/
+
+#buddypress {
+	/* Auto Complete background */
+	.acfb-holder {
+
+		li.friend-tab {
+			background: $notice-info;
+			border: inherit;
+	 }
+	}
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/js/buddypress.js
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/js/buddypress.js	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/js/buddypress.js	(revision 10410)
@@ -0,0 +1,2125 @@
+/* jshint undef: false, unused:false */
+// AJAX Functions
+var jq = jQuery;
+
+// Global variable to prevent multiple AJAX requests
+var bp_ajax_request = null;
+
+// Global variables to temporarily store newest activities
+var newest_activities = '';
+var activity_last_recorded  = 0;
+
+jq(document).ready( function() {
+	/**** Page Load Actions *******************************************************/
+
+	/* Hide Forums Post Form */
+	if ( '-1' === window.location.search.indexOf('new') && jq('div.forums').length ) {
+		jq('#new-topic-post').hide();
+	} else {
+		jq('#new-topic-post').show();
+	}
+
+	/* Activity filter and scope set */
+	bp_init_activity();
+
+	var objects  = [ 'members', 'groups', 'blogs', 'forums', 'group_members' ],
+		$whats_new = jq('#whats-new');
+
+	/* Object filter and scope set. */
+	bp_init_objects( objects );
+
+	/* @mention Compose Scrolling */
+	if ( $whats_new.length && bp_get_querystring('r') ) {
+		var $member_nicename = $whats_new.val();
+
+		jq('#whats-new-options').slideDown();
+
+		$whats_new.animate({
+			height:'3.8em'
+		});
+
+		jq.scrollTo( $whats_new, 500, {
+			offset:-125,
+			easing:'swing'
+		} );
+
+		$whats_new.val('').focus().val( $member_nicename );
+	} else {
+		jq('#whats-new-options').hide();
+	}
+
+	/**** Activity Posting ********************************************************/
+
+	/* Textarea focus */
+	$whats_new.focus( function(){
+		jq( '#whats-new-options' ).slideDown();
+
+		jq( this ).animate({
+			height:'3.8em'
+		});
+
+		jq('#aw-whats-new-submit').prop('disabled', false);
+
+		jq( this ).parent().addClass( 'active' );
+		jq( '#whats-new-content' ).addClass( 'active' );
+
+		var $whats_new_form = jq('form#whats-new-form'),
+			$activity_all = jq( '#activity-all' );
+
+		if ( $whats_new_form.hasClass('submitted') ) {
+			$whats_new_form.removeClass('submitted');
+		}
+
+		// Return to the 'All Members' tab and 'Everything' filter,
+		// to avoid inconsistencies with the heartbeat integration
+		if ( $activity_all.length  ) {
+			if ( ! $activity_all.hasClass( 'selected' ) ) {
+				// reset to everything
+				jq( '#activity-filter-select select' ).val( '-1' );
+				$activity_all.children( 'a' ).trigger( 'click' );
+			} else if ( '-1' !== jq( '#activity-filter-select select' ).val() ) {
+				jq( '#activity-filter-select select' ).val( '-1' );
+				jq( '#activity-filter-select select' ).trigger( 'change' );
+			}
+		}
+	});
+
+	/* For the "What's New" form, do the following on focusout. */
+	jq( '#whats-new-form' ).on( 'focusout', function( e ) {
+		var elem = jq( this );
+
+		// Let child hover actions passthrough.
+		// This allows click events to go through without focusout.
+		setTimeout( function () {
+			if ( ! elem.find(':hover').length ) {
+				// Do not slide up if textarea has content.
+				if ( '' !== $whats_new.val() ) {
+					return;
+				}
+
+				$whats_new.animate({
+					height:'2.2em'
+				});
+
+				jq( '#whats-new-options' ).slideUp();
+
+				jq('#aw-whats-new-submit').prop( 'disabled', true );
+
+				jq( '#whats-new-content' ).removeClass( 'active' );
+				$whats_new.parent().removeClass( 'active' );
+			}
+		}, 0 );
+	} );
+
+	/* New posts */
+	jq('#aw-whats-new-submit').on( 'click', function() {
+		var last_date_recorded = 0,
+			button = jq(this),
+			form   = button.closest('form#whats-new-form'),
+			inputs = {}, post_data;
+
+		// Get all inputs and organize them into an object {name: value}
+		jq.each( form.serializeArray(), function( key, input ) {
+			// Only include public extra data
+			if ( '_' !== input.name.substr( 0, 1 ) && 'whats-new' !== input.name.substr( 0, 9 ) ) {
+				if ( ! inputs[ input.name ] ) {
+					inputs[ input.name ] = input.value;
+				} else {
+					// Checkboxes/dropdown list can have multiple selected value
+					if ( ! jq.isArray( inputs[ input.name ] ) ) {
+						inputs[ input.name ] = new Array( inputs[ input.name ], input.value );
+					} else {
+						inputs[ input.name ].push( input.value );
+					}
+				}
+			}
+		} );
+
+		form.find( '*' ).each( function() {
+			if ( jq.nodeName( this, 'textarea' ) || jq.nodeName( this, 'input' ) ) {
+				jq(this).prop( 'disabled', true );
+			}
+		} );
+
+		/* Remove any errors */
+		jq('div.error').remove();
+		button.addClass('loading');
+		button.prop('disabled', true);
+		form.addClass('submitted');
+
+		/* Default POST values */
+		object = '';
+		item_id = jq('#whats-new-post-in').val();
+		content = jq('#whats-new').val();
+		firstrow = jq( '#buddypress ul.activity-list li' ).first();
+		activity_row = firstrow;
+		timestamp = null;
+
+		// Checks if at least one activity exists
+		if ( firstrow.length ) {
+
+			if ( activity_row.hasClass( 'load-newest' ) ) {
+				activity_row = firstrow.next();
+			}
+
+			timestamp = activity_row.prop( 'class' ).match( /date-recorded-([0-9]+)/ );
+		}
+
+		if ( timestamp ) {
+			last_date_recorded = timestamp[1];
+		}
+
+		/* Set object for non-profile posts */
+		if ( item_id > 0 ) {
+			object = jq('#whats-new-post-object').val();
+		}
+
+		post_data = jq.extend( {
+			action: 'post_update',
+			'cookie': bp_get_cookies(),
+			'_wpnonce_post_update': jq('#_wpnonce_post_update').val(),
+			'content': content,
+			'object': object,
+			'item_id': item_id,
+			'since': last_date_recorded,
+			'_bp_as_nonce': jq('#_bp_as_nonce').val() || ''
+		}, inputs );
+
+		jq.post( ajaxurl, post_data, function( response ) {
+			form.find( '*' ).each( function() {
+				if ( jq.nodeName( this, 'textarea' ) || jq.nodeName( this, 'input' ) ) {
+					jq(this).prop( 'disabled', false );
+				}
+			});
+
+			/* Check for errors and append if found. */
+			if ( response[0] + response[1] === '-1' ) {
+				form.prepend( response.substr( 2, response.length ) );
+				jq( '#' + form.attr('id') + ' div.error').hide().fadeIn( 200 );
+			} else {
+				if ( 0 === jq('ul.activity-list').length ) {
+					jq('div.error').slideUp(100).remove();
+					jq('#message').slideUp(100).remove();
+					jq('div.activity').append( '<ul id="activity-stream" class="activity-list item-list">' );
+				}
+
+				if ( firstrow.hasClass( 'load-newest' ) ) {
+					firstrow.remove();
+				}
+
+				jq('#activity-stream').prepend(response);
+
+				if ( ! last_date_recorded ) {
+					jq('#activity-stream li:first').addClass('new-update just-posted');
+				}
+
+				if ( 0 !== jq('#latest-update').length ) {
+					var l   = jq('#activity-stream li.new-update .activity-content .activity-inner p').html(),
+						v     = jq('#activity-stream li.new-update .activity-content .activity-header p a.view').attr('href'),
+						ltext = jq('#activity-stream li.new-update .activity-content .activity-inner p').text(),
+						u     = '';
+
+					if ( ltext !== '' ) {
+						u = l + ' ';
+					}
+
+					u += '<a href="' + v + '" rel="nofollow">' + BP_DTheme.view + '</a>';
+
+					jq('#latest-update').slideUp(300,function(){
+						jq('#latest-update').html( u );
+						jq('#latest-update').slideDown(300);
+					});
+				}
+
+				jq('li.new-update').hide().slideDown( 300 );
+				jq('li.new-update').removeClass( 'new-update' );
+				jq('#whats-new').val('');
+				form.get(0).reset();
+
+				// reset vars to get newest activities
+				newest_activities = '';
+				activity_last_recorded  = 0;
+			}
+
+			jq('#whats-new-options').slideUp();
+			jq('#whats-new-form textarea').animate({
+				height:'2.2em'
+			});
+			jq('#aw-whats-new-submit').prop('disabled', true).removeClass('loading');
+			jq( '#whats-new-content' ).removeClass( 'active' );
+		});
+
+		return false;
+	});
+
+	/* List tabs event delegation */
+	jq('div.activity-type-tabs').on( 'click', function(event) {
+		var target = jq(event.target).parent(),
+			scope, filter;
+
+		if ( event.target.nodeName === 'STRONG' || event.target.nodeName === 'SPAN' ) {
+			target = target.parent();
+		} else if ( event.target.nodeName !== 'A' ) {
+			return false;
+		}
+
+		/* Reset the page */
+		jq.cookie( 'bp-activity-oldestpage', 1, {
+			path: '/'
+		} );
+
+		/* Activity Stream Tabs */
+		scope  = target.attr('id').substr( 9, target.attr('id').length );
+		filter = jq('#activity-filter-select select').val();
+
+		if ( scope === 'mentions' ) {
+			jq( '#' + target.attr('id') + ' a strong' ).remove();
+		}
+
+		bp_activity_request(scope, filter);
+
+		return false;
+	});
+
+	/* Activity filter select */
+	jq('#activity-filter-select select').change( function() {
+		var selected_tab = jq( 'div.activity-type-tabs li.selected' ),
+			filter = jq(this).val(),
+			scope;
+
+		if ( !selected_tab.length ) {
+			scope = null;
+		} else {
+			scope = selected_tab.attr('id').substr( 9, selected_tab.attr('id').length );
+		}
+
+		bp_activity_request(scope, filter);
+
+		return false;
+	});
+
+	/* Stream event delegation */
+	jq('div.activity').on( 'click', function(event) {
+		var target = jq(event.target),
+			type, parent, parent_id,
+			li, id, link_href, nonce, timestamp,
+			oldest_page, just_posted;
+
+		/* Favoriting activity stream items */
+		if ( target.hasClass('fav') || target.hasClass('unfav') ) {
+			type      = target.hasClass('fav') ? 'fav' : 'unfav';
+			parent    = target.closest('.activity-item');
+			parent_id = parent.attr('id').substr( 9, parent.attr('id').length );
+
+			target.addClass('loading');
+
+			jq.post( ajaxurl, {
+				action: 'activity_mark_' + type,
+				'cookie': bp_get_cookies(),
+				'id': parent_id
+			},
+			function(response) {
+				target.removeClass('loading');
+
+				target.fadeOut( 200, function() {
+					jq(this).html(response);
+					jq(this).attr('title', 'fav' === type ? BP_DTheme.remove_fav : BP_DTheme.mark_as_fav);
+					jq(this).fadeIn(200);
+				});
+
+				if ( 'fav' === type ) {
+					if ( !jq('.item-list-tabs #activity-favs-personal-li').length ) {
+						if ( !jq('.item-list-tabs #activity-favorites').length ) {
+							jq('.item-list-tabs ul #activity-mentions').before( '<li id="activity-favorites"><a href="#">' + BP_DTheme.my_favs + ' <span>0</span></a></li>');
+						}
+
+						jq('.item-list-tabs ul #activity-favorites span').html( Number( jq('.item-list-tabs ul #activity-favorites span').html() ) + 1 );
+					}
+
+					target.removeClass('fav');
+					target.addClass('unfav');
+
+				} else {
+					target.removeClass('unfav');
+					target.addClass('fav');
+
+					jq('.item-list-tabs ul #activity-favorites span').html( Number( jq('.item-list-tabs ul #activity-favorites span').html() ) - 1 );
+
+					if ( !Number( jq('.item-list-tabs ul #activity-favorites span').html() ) ) {
+						if ( jq('.item-list-tabs ul #activity-favorites').hasClass('selected') ) {
+							bp_activity_request( null, null );
+						}
+
+						jq('.item-list-tabs ul #activity-favorites').remove();
+					}
+				}
+
+				if ( 'activity-favorites' === jq( '.item-list-tabs li.selected').attr('id') ) {
+					target.closest( '.activity-item' ).slideUp( 100 );
+				}
+			});
+
+			return false;
+		}
+
+		/* Delete activity stream items */
+		if ( target.hasClass('delete-activity') ) {
+			li        = target.parents('div.activity ul li');
+			id        = li.attr('id').substr( 9, li.attr('id').length );
+			link_href = target.attr('href');
+			nonce     = link_href.split('_wpnonce=');
+			timestamp = li.prop( 'class' ).match( /date-recorded-([0-9]+)/ );
+			nonce     = nonce[1];
+
+			target.addClass('loading');
+
+			jq.post( ajaxurl, {
+				action: 'delete_activity',
+				'cookie': bp_get_cookies(),
+				'id': id,
+				'_wpnonce': nonce
+			},
+			function(response) {
+
+				if ( response[0] + response[1] === '-1' ) {
+					li.prepend( response.substr( 2, response.length ) );
+					li.children('#message').hide().fadeIn(300);
+				} else {
+					li.slideUp(300);
+
+					// reset vars to get newest activities
+					if ( timestamp && activity_last_recorded === timestamp[1] ) {
+						newest_activities = '';
+						activity_last_recorded  = 0;
+					}
+				}
+			});
+
+			return false;
+		}
+
+		// Spam activity stream items
+		if ( target.hasClass( 'spam-activity' ) ) {
+			li        = target.parents( 'div.activity ul li' );
+			timestamp = li.prop( 'class' ).match( /date-recorded-([0-9]+)/ );
+			target.addClass( 'loading' );
+
+			jq.post( ajaxurl, {
+				action: 'bp_spam_activity',
+				'cookie': encodeURIComponent( document.cookie ),
+				'id': li.attr( 'id' ).substr( 9, li.attr( 'id' ).length ),
+				'_wpnonce': target.attr( 'href' ).split( '_wpnonce=' )[1]
+			},
+
+			function(response) {
+				if ( response[0] + response[1] === '-1' ) {
+					li.prepend( response.substr( 2, response.length ) );
+					li.children( '#message' ).hide().fadeIn(300);
+				} else {
+					li.slideUp( 300 );
+					// reset vars to get newest activities
+					if ( timestamp && activity_last_recorded === timestamp[1] ) {
+						newest_activities = '';
+						activity_last_recorded  = 0;
+					}
+				}
+			});
+
+			return false;
+		}
+
+		/* Load more updates at the end of the page */
+		if ( target.parent().hasClass('load-more') ) {
+			if ( bp_ajax_request ) {
+				bp_ajax_request.abort();
+			}
+
+			jq('#buddypress li.load-more').addClass('loading');
+
+			if ( null === jq.cookie('bp-activity-oldestpage') ) {
+				jq.cookie('bp-activity-oldestpage', 1, {
+					path: '/'
+				} );
+			}
+
+			oldest_page = ( jq.cookie('bp-activity-oldestpage') * 1 ) + 1;
+			just_posted = [];
+
+			jq('.activity-list li.just-posted').each( function(){
+				just_posted.push( jq(this).attr('id').replace( 'activity-','' ) );
+			});
+
+			load_more_args = {
+				action: 'activity_get_older_updates',
+				'cookie': bp_get_cookies(),
+				'page': oldest_page,
+				'exclude_just_posted': just_posted.join(',')
+			};
+
+			load_more_search = bp_get_querystring('s');
+
+			if ( load_more_search ) {
+				load_more_args.search_terms = load_more_search;
+			}
+
+			bp_ajax_request = jq.post( ajaxurl, load_more_args,
+			function(response)
+			{
+				jq('#buddypress li.load-more').removeClass('loading');
+				jq.cookie( 'bp-activity-oldestpage', oldest_page, {
+					path: '/'
+				} );
+				jq('#buddypress ul.activity-list').append(response.contents);
+
+				target.parent().hide();
+			}, 'json' );
+
+			return false;
+		}
+
+		/* Load newest updates at the top of the list */
+		if ( target.parent().hasClass('load-newest') ) {
+
+			event.preventDefault();
+
+			target.parent().hide();
+
+			/**
+			 * If a plugin is updating the recorded_date of an activity
+			 * it will be loaded as a new one. We need to look in the
+			 * stream and eventually remove similar ids to avoid "double".
+			 */
+			activity_html = jq.parseHTML( newest_activities );
+
+			jq.each( activity_html, function( i, el ){
+				if( 'LI' === el.nodeName && jq(el).hasClass( 'just-posted' ) ) {
+					if( jq( '#' + jq(el).attr( 'id' ) ).length ) {
+						jq( '#' + jq(el).attr( 'id' ) ).remove();
+					}
+				}
+			} );
+
+			// Now the stream is cleaned, prepend newest
+			jq( '#buddypress ul.activity-list' ).prepend( newest_activities );
+
+			// reset the newest activities now they're displayed
+			newest_activities = '';
+		}
+	});
+
+	// Activity "Read More" links
+	jq('div.activity').on('click', '.activity-read-more a', function(event) {
+		var target = jq(event.target),
+			link_id = target.parent().attr('id').split('-'),
+			a_id    = link_id[3],
+			type    = link_id[0], /* activity or acomment */
+			inner_class, a_inner;
+
+		inner_class = type === 'acomment' ? 'acomment-content' : 'activity-inner';
+		a_inner = jq('#' + type + '-' + a_id + ' .' + inner_class + ':first' );
+		jq(target).addClass('loading');
+
+		jq.post( ajaxurl, {
+			action: 'get_single_activity_content',
+			'activity_id': a_id
+		},
+		function(response) {
+			jq(a_inner).slideUp(300).html(response).slideDown(300);
+		});
+
+		return false;
+	});
+
+	/**** Activity Comments *******************************************************/
+
+	/* Hide all activity comment forms */
+	jq('form.ac-form').hide();
+
+	/* Hide excess comments */
+	if ( jq('.activity-comments').length ) {
+		bp_legacy_theme_hide_comments();
+	}
+
+	/* Activity list event delegation */
+	jq('div.activity').on( 'click', function(event) {
+		var target = jq(event.target),
+			id, ids, a_id, c_id, form,
+			form_parent, form_id,
+			tmp_id, comment_id, comment,
+			ajaxdata,
+			ak_nonce,
+			show_all_a, new_count,
+			link_href, comment_li, nonce;
+
+		/* Comment / comment reply links */
+		if ( target.hasClass('acomment-reply') || target.parent().hasClass('acomment-reply') ) {
+			if ( target.parent().hasClass('acomment-reply') ) {
+				target = target.parent();
+			}
+
+			id  = target.attr('id');
+			ids = id.split('-');
+
+			a_id = ids[2];
+			c_id = target.attr('href').substr( 10, target.attr('href').length );
+			form = jq( '#ac-form-' + a_id );
+
+			form.css( 'display', 'none' );
+			form.removeClass('root');
+			jq('.ac-form').hide();
+
+			/* Hide any error messages */
+			form.children('div').each( function() {
+				if ( jq(this).hasClass( 'error' ) ) {
+					jq(this).hide();
+				}
+			});
+
+			if ( ids[1] !== 'comment' ) {
+				jq('#acomment-' + c_id).append( form );
+			} else {
+				jq('#activity-' + a_id + ' .activity-comments').append( form );
+			}
+
+			if ( form.parent().hasClass( 'activity-comments' ) ) {
+				form.addClass('root');
+			}
+
+			form.slideDown( 200 );
+			jq.scrollTo( form, 500, {
+				offset:-100,
+				easing:'swing'
+			} );
+			jq('#ac-form-' + ids[2] + ' textarea').focus();
+
+			return false;
+		}
+
+		/* Activity comment posting */
+		if ( target.attr('name') === 'ac_form_submit' ) {
+			form = target.parents( 'form' );
+			form_parent = form.parent();
+			form_id = form.attr('id').split('-');
+
+			if ( !form_parent.hasClass('activity-comments') ) {
+				tmp_id = form_parent.attr('id').split('-');
+				comment_id = tmp_id[1];
+			} else {
+				comment_id = form_id[2];
+			}
+
+			content = jq( '#' + form.attr('id') + ' textarea' );
+
+			/* Hide any error messages */
+			jq( '#' + form.attr('id') + ' div.error').hide();
+			target.addClass('loading').prop('disabled', true);
+			content.addClass('loading').prop('disabled', true);
+
+			ajaxdata = {
+				action: 'new_activity_comment',
+				'cookie': bp_get_cookies(),
+				'_wpnonce_new_activity_comment': jq('#_wpnonce_new_activity_comment').val(),
+				'comment_id': comment_id,
+				'form_id': form_id[2],
+				'content': content.val()
+			};
+
+			// Akismet
+			ak_nonce = jq('#_bp_as_nonce_' + comment_id).val();
+			if ( ak_nonce ) {
+				ajaxdata['_bp_as_nonce_' + comment_id] = ak_nonce;
+			}
+
+			jq.post( ajaxurl, ajaxdata, function(response) {
+				target.removeClass('loading');
+				content.removeClass('loading');
+
+				/* Check for errors and append if found. */
+				if ( response[0] + response[1] === '-1' ) {
+					form.append( jq( response.substr( 2, response.length ) ).hide().fadeIn( 200 ) );
+				} else {
+					var activity_comments = form.parent();
+					form.fadeOut( 200, function() {
+						if ( 0 === activity_comments.children('ul').length ) {
+							if ( activity_comments.hasClass('activity-comments') ) {
+								activity_comments.prepend('<ul></ul>');
+							} else {
+								activity_comments.append('<ul></ul>');
+							}
+						}
+
+						/* Preceding whitespace breaks output with jQuery 1.9.0 */
+						var the_comment = jq.trim( response );
+
+						activity_comments.children('ul').append( jq( the_comment ).hide().fadeIn( 200 ) );
+						form.children('textarea').val('');
+						activity_comments.parent().addClass('has-comments');
+					} );
+					jq( '#' + form.attr('id') + ' textarea').val('');
+
+					/* Increase the "Reply (X)" button count */
+					jq('#activity-' + form_id[2] + ' a.acomment-reply span').html( Number( jq('#activity-' + form_id[2] + ' a.acomment-reply span').html() ) + 1 );
+
+					// Increment the 'Show all x comments' string, if present
+					show_all_a = activity_comments.find('.show-all').find('a');
+					if ( show_all_a ) {
+						new_count = jq('li#activity-' + form_id[2] + ' a.acomment-reply span').html();
+						show_all_a.html( BP_DTheme.show_x_comments.replace( '%d', new_count ) );
+					}
+				}
+
+				jq(target).prop('disabled', false);
+				jq(content).prop('disabled', false);
+			});
+
+			return false;
+		}
+
+		/* Deleting an activity comment */
+		if ( target.hasClass('acomment-delete') ) {
+			link_href = target.attr('href');
+			comment_li = target.parent().parent();
+			form = comment_li.parents('div.activity-comments').children('form');
+
+			nonce = link_href.split('_wpnonce=');
+			nonce = nonce[1];
+
+			comment_id = link_href.split('cid=');
+			comment_id = comment_id[1].split('&');
+			comment_id = comment_id[0];
+
+			target.addClass('loading');
+
+			/* Remove any error messages */
+			jq('.activity-comments ul .error').remove();
+
+			/* Reset the form position */
+			comment_li.parents('.activity-comments').append(form);
+
+			jq.post( ajaxurl, {
+				action: 'delete_activity_comment',
+				'cookie': bp_get_cookies(),
+				'_wpnonce': nonce,
+				'id': comment_id
+			},
+			function(response) {
+				/* Check for errors and append if found. */
+				if ( response[0] + response[1] === '-1' ) {
+					comment_li.prepend( jq( response.substr( 2, response.length ) ).hide().fadeIn( 200 ) );
+				} else {
+					var children  = jq( '#' + comment_li.attr('id') + ' ul' ).children('li'),
+						child_count = 0,
+						count_span, new_count, show_all_a;
+
+					jq(children).each( function() {
+						if ( !jq(this).is(':hidden') ) {
+							child_count++;
+						}
+					});
+					comment_li.fadeOut(200, function() {
+						comment_li.remove();
+					});
+
+					/* Decrease the "Reply (X)" button count */
+					count_span = jq('#' + comment_li.parents('#activity-stream > li').attr('id') + ' a.acomment-reply span');
+					new_count = count_span.html() - ( 1 + child_count );
+					count_span.html(new_count);
+
+					// Change the 'Show all x comments' text
+					show_all_a = comment_li.siblings('.show-all').find('a');
+					if ( show_all_a ) {
+						show_all_a.html( BP_DTheme.show_x_comments.replace( '%d', new_count ) );
+					}
+
+					/* If that was the last comment for the item, remove the has-comments class to clean up the styling */
+					if ( 0 === new_count ) {
+						jq(comment_li.parents('#activity-stream > li')).removeClass('has-comments');
+					}
+				}
+			});
+
+			return false;
+		}
+
+		// Spam an activity stream comment
+		if ( target.hasClass( 'spam-activity-comment' ) ) {
+			link_href  = target.attr( 'href' );
+			comment_li = target.parent().parent();
+
+			target.addClass('loading');
+
+			// Remove any error messages
+			jq( '.activity-comments ul div.error' ).remove();
+
+			// Reset the form position
+			comment_li.parents( '.activity-comments' ).append( comment_li.parents( '.activity-comments' ).children( 'form' ) );
+
+			jq.post( ajaxurl, {
+				action: 'bp_spam_activity_comment',
+				'cookie': encodeURIComponent( document.cookie ),
+				'_wpnonce': link_href.split( '_wpnonce=' )[1],
+				'id': link_href.split( 'cid=' )[1].split( '&' )[0]
+			},
+
+			function ( response ) {
+				// Check for errors and append if found.
+				if ( response[0] + response[1] === '-1' ) {
+					comment_li.prepend( jq( response.substr( 2, response.length ) ).hide().fadeIn( 200 ) );
+
+				} else {
+					var children  = jq( '#' + comment_li.attr( 'id' ) + ' ul' ).children( 'li' ),
+						child_count = 0,
+						parent_li;
+
+					jq(children).each( function() {
+						if ( !jq( this ).is( ':hidden' ) ) {
+							child_count++;
+						}
+					});
+					comment_li.fadeOut( 200 );
+
+					// Decrease the "Reply (X)" button count
+					parent_li = comment_li.parents( '#activity-stream > li' );
+					jq( '#' + parent_li.attr( 'id' ) + ' a.acomment-reply span' ).html( jq( '#' + parent_li.attr( 'id' ) + ' a.acomment-reply span' ).html() - ( 1 + child_count ) );
+				}
+			});
+
+			return false;
+		}
+
+		/* Showing hidden comments - pause for half a second */
+		if ( target.parent().hasClass('show-all') ) {
+			target.parent().addClass('loading');
+
+			setTimeout( function() {
+				target.parent().parent().children('li').fadeIn(200, function() {
+					target.parent().remove();
+				});
+			}, 600 );
+
+			return false;
+		}
+
+		// Canceling an activity comment
+		if ( target.hasClass( 'ac-reply-cancel' ) ) {
+			jq(target).closest('.ac-form').slideUp( 200 );
+			return false;
+		}
+	});
+
+	/* Escape Key Press for cancelling comment forms */
+	jq(document).keydown( function(e) {
+		e = e || window.event;
+		if (e.target) {
+			element = e.target;
+		} else if (e.srcElement) {
+			element = e.srcElement;
+		}
+
+		if( element.nodeType === 3) {
+			element = element.parentNode;
+		}
+
+		if( e.ctrlKey === true || e.altKey === true || e.metaKey === true ) {
+			return;
+		}
+
+		var keyCode = (e.keyCode) ? e.keyCode : e.which;
+
+		if ( keyCode === 27 ) {
+			if (element.tagName === 'TEXTAREA') {
+				if ( jq(element).hasClass('ac-input') ) {
+					jq(element).parent().parent().parent().slideUp( 200 );
+				}
+			}
+		}
+	});
+
+	/**** Directory Search ****************************************************/
+
+	/* The search form on all directory pages */
+	jq( '.dir-search, .groups-members-search' ).on( 'click', function(event) {
+		if ( jq(this).hasClass('no-ajax') ) {
+			return;
+		}
+
+		var target = jq(event.target),
+			css_id, object, template;
+
+		if ( target.attr('type') === 'submit' ) {
+			css_id = jq('.item-list-tabs li.selected').attr('id').split( '-' );
+			object = css_id[0];
+			template = null;
+
+			// The Group Members page specifies its own template
+			if ( event.currentTarget.className === 'groups-members-search' ) {
+				object = 'group_members';
+				template = 'groups/single/members';
+			}
+
+			bp_filter_request( object, jq.cookie('bp-' + object + '-filter'), jq.cookie('bp-' + object + '-scope') , 'div.' + object, target.parent().children('label').children('input').val(), 1, jq.cookie('bp-' + object + '-extras'), null, template );
+
+			return false;
+		}
+	});
+
+	/**** Tabs and Filters ****************************************************/
+
+	/* When a navigation tab is clicked - e.g. | All Groups | My Groups | */
+	jq('div.item-list-tabs').on( 'click', function(event) {
+		if ( jq(this).hasClass('no-ajax')  || jq( event.target ).hasClass('no-ajax') )  {
+			return;
+		}
+
+		var targetElem = ( event.target.nodeName === 'SPAN' ) ? event.target.parentNode : event.target,
+			target       = jq( targetElem ).parent(),
+			css_id, object, scope, filter, search_terms;
+
+		if ( 'LI' === target[0].nodeName && !target.hasClass( 'last' ) ) {
+			css_id = target.attr('id').split( '-' );
+			object = css_id[0];
+
+			if ( 'activity' === object ) {
+				return false;
+			}
+
+			scope = css_id[1];
+			filter = jq('#' + object + '-order-select select').val();
+			search_terms = jq('#' + object + '_search').val();
+
+			bp_filter_request( object, filter, scope, 'div.' + object, search_terms, 1, jq.cookie('bp-' + object + '-extras') );
+
+			return false;
+		}
+	});
+
+	/* When the filter select box is changed re-query */
+	jq('li.filter select').change( function() {
+		var el,
+			css_id, object, scope, filter, search_terms, template,
+			$gm_search;
+
+		if ( jq('.item-list-tabs li.selected').length ) {
+			el = jq('.item-list-tabs li.selected');
+		} else {
+			el = jq(this);
+		}
+
+		css_id = el.attr('id').split('-');
+		object = css_id[0];
+		scope = css_id[1];
+		filter = jq(this).val();
+		search_terms = false;
+		template = null;
+
+		if ( jq('.dir-search input').length ) {
+			search_terms = jq('.dir-search input').val();
+		}
+
+		// The Group Members page has a different selector for its
+		// search terms box
+		$gm_search = jq( '.groups-members-search input' );
+		if ( $gm_search.length ) {
+			search_terms = $gm_search.val();
+			object = 'members';
+			scope = 'groups';
+		}
+
+		// On the Groups Members page, we specify a template
+		if ( 'members' === object && 'groups' === scope ) {
+			object = 'group_members';
+			template = 'groups/single/members';
+		}
+
+		if ( 'friends' === object ) {
+			object = 'members';
+		}
+
+		bp_filter_request( object, filter, scope, 'div.' + object, search_terms, 1, jq.cookie('bp-' + object + '-extras'), null, template );
+
+		return false;
+	});
+
+	/* All pagination links run through this function */
+	jq('#buddypress').on( 'click', function(event) {
+		var target = jq(event.target),
+			el,
+			css_id, object, search_terms, pagination_id, template,
+			page_number,
+			$gm_search,
+			caller;
+
+		if ( target.hasClass('button') ) {
+			return true;
+		}
+
+		if ( target.parent().parent().hasClass('pagination') && !target.parent().parent().hasClass('no-ajax') ) {
+			if ( target.hasClass('dots') || target.hasClass('current') ) {
+				return false;
+			}
+
+			if ( jq('.item-list-tabs li.selected').length ) {
+				el = jq('.item-list-tabs li.selected');
+			} else {
+				el = jq('li.filter select');
+			}
+
+			css_id = el.attr('id').split( '-' );
+			object = css_id[0];
+			search_terms = false;
+			pagination_id = jq(target).closest('.pagination-links').attr('id');
+			template = null;
+
+			// Search terms
+			if ( jq('div.dir-search input').length ) {
+				search_terms =  jq('.dir-search input');
+
+				if ( ! search_terms.val() && bp_get_querystring( search_terms.attr( 'name' ) ) ) {
+					search_terms = jq('.dir-search input').prop('placeholder');
+				} else {
+					search_terms = search_terms.val();
+				}
+			}
+
+			// Page number
+			if ( jq(target).hasClass('next') || jq(target).hasClass('prev') ) {
+				page_number = jq('.pagination span.current').html();
+			} else {
+				page_number = jq(target).html();
+			}
+
+			// Remove any non-numeric characters from page number text (commas, etc.)
+			page_number = Number( page_number.replace(/\D/g,'') );
+
+			if ( jq(target).hasClass('next') ) {
+				page_number++;
+			} else if ( jq(target).hasClass('prev') ) {
+				page_number--;
+			}
+
+			// The Group Members page has a different selector for
+			// its search terms box
+			$gm_search = jq( '.groups-members-search input' );
+			if ( $gm_search.length ) {
+				search_terms = $gm_search.val();
+				object = 'members';
+			}
+
+			// On the Groups Members page, we specify a template
+			if ( 'members' === object && 'groups' === css_id[1] ) {
+				object = 'group_members';
+				template = 'groups/single/members';
+			}
+
+			// On the Admin > Requests page, we need to reset the object,
+			// since "admin" isn't specific enough
+			if ( 'admin' === object && jq( 'body' ).hasClass( 'membership-requests' ) ) {
+				object = 'requests';
+			}
+
+			if ( pagination_id.indexOf( 'pag-bottom' ) !== -1 ) {
+				caller = 'pag-bottom';
+			} else {
+				caller = null;
+			}
+
+			bp_filter_request( object, jq.cookie('bp-' + object + '-filter'), jq.cookie('bp-' + object + '-scope'), 'div.' + object, search_terms, page_number, jq.cookie('bp-' + object + '-extras'), caller, template );
+
+			return false;
+		}
+
+	});
+
+	/**** New Forum Directory Post **************************************/
+
+	/* Hit the "New Topic" button on the forums directory page */
+	jq('a.show-hide-new').on( 'click', function() {
+		if ( !jq('#new-topic-post').length ) {
+			return false;
+		}
+
+		if ( jq('#new-topic-post').is(':visible') ) {
+			jq('#new-topic-post').slideUp(200);
+		} else {
+			jq('#new-topic-post').slideDown(200, function() {
+				jq('#topic_title').focus();
+			} );
+		}
+
+		return false;
+	});
+
+	/* Cancel the posting of a new forum topic */
+	jq('#submit_topic_cancel').on( 'click', function() {
+		if ( !jq('#new-topic-post').length ) {
+			return false;
+		}
+
+		jq('#new-topic-post').slideUp(200);
+		return false;
+	});
+
+	/* Clicking a forum tag */
+	jq('#forum-directory-tags a').on( 'click', function() {
+		bp_filter_request( 'forums', 'tags', jq.cookie('bp-forums-scope'), 'div.forums', jq(this).html().replace( /&nbsp;/g, '-' ), 1, jq.cookie('bp-forums-extras') );
+		return false;
+	});
+
+	/** Invite Friends Interface ****************************************/
+
+	/* Select a user from the list of friends and add them to the invite list */
+	jq('#send-invite-form').on( 'click', '#invite-list input', function() {
+		// invites-loop template contains a div with the .invite class
+		// We use the existence of this div to check for old- vs new-
+		// style templates.
+		var invites_new_template = jq( '#send-invite-form > .invite' ).length,
+			friend_id, friend_action;
+
+		jq('.ajax-loader').toggle();
+
+		// Dim the form until the response arrives
+		if ( invites_new_template ) {
+			jq( this ).parents( 'ul' ).find( 'input' ).prop( 'disabled', true );
+		}
+
+		friend_id = jq(this).val();
+
+		if ( jq(this).prop('checked') === true ) {
+			friend_action = 'invite';
+		} else {
+			friend_action = 'uninvite';
+		}
+
+		if ( ! invites_new_template ) {
+			jq( '.item-list-tabs li.selected' ).addClass( 'loading' );
+		}
+
+		jq.post( ajaxurl, {
+			action: 'groups_invite_user',
+			'friend_action': friend_action,
+			'cookie': bp_get_cookies(),
+			'_wpnonce': jq('#_wpnonce_invite_uninvite_user').val(),
+			'friend_id': friend_id,
+			'group_id': jq('#group_id').val()
+		},
+		function(response)
+		{
+			if ( jq('#message') ) {
+				jq('#message').hide();
+			}
+
+			if ( invites_new_template ) {
+				// With new-style templates, we refresh the
+				// entire list
+				bp_filter_request( 'invite', 'bp-invite-filter', 'bp-invite-scope', 'div.invite', false, 1, '', '', '' );
+			} else {
+				// Old-style templates manipulate only the
+				// single invitation element
+				jq('.ajax-loader').toggle();
+
+				if ( friend_action === 'invite' ) {
+					jq('#friend-list').append(response);
+				} else if ( friend_action === 'uninvite' ) {
+					jq('#friend-list li#uid-' + friend_id).remove();
+				}
+
+				jq('.item-list-tabs li.selected').removeClass('loading');
+			}
+		});
+	});
+
+	/* Remove a user from the list of users to invite to a group */
+	jq('#send-invite-form').on('click', 'a.remove', function() {
+		// invites-loop template contains a div with the .invite class
+		// We use the existence of this div to check for old- vs new-
+		// style templates.
+		var invites_new_template = jq('#send-invite-form > .invite').length,
+			friend_id = jq(this).attr('id');
+
+		jq('.ajax-loader').toggle();
+
+		friend_id = friend_id.split('-');
+		friend_id = friend_id[1];
+
+		jq.post( ajaxurl, {
+			action: 'groups_invite_user',
+			'friend_action': 'uninvite',
+			'cookie': bp_get_cookies(),
+			'_wpnonce': jq('#_wpnonce_invite_uninvite_user').val(),
+			'friend_id': friend_id,
+			'group_id': jq('#group_id').val()
+		},
+		function(response)
+		{
+			if ( invites_new_template ) {
+				// With new-style templates, we refresh the
+				// entire list
+				bp_filter_request( 'invite', 'bp-invite-filter', 'bp-invite-scope', 'div.invite', false, 1, '', '', '' );
+			} else {
+				// Old-style templates manipulate only the
+				// single invitation element
+				jq('.ajax-loader').toggle();
+				jq('#friend-list #uid-' + friend_id).remove();
+				jq('#invite-list #f-' + friend_id).prop('checked', false);
+			}
+		});
+
+		return false;
+	});
+
+	/** Profile Visibility Settings *********************************/
+	jq( '.visibility-toggle-link' ).on( 'click', function( event ) {
+		event.preventDefault();
+
+		jq( this ).parent().hide().addClass( 'field-visibility-settings-hide' )
+			.siblings( '.field-visibility-settings' ).show().addClass( 'field-visibility-settings-open' );
+	} );
+
+	jq( '.field-visibility-settings-close' ).on( 'click', function( event ) {
+		event.preventDefault();
+
+		var settings_div = jq( this ).parent(),
+			vis_setting_text = settings_div.find( 'input:checked' ).parent().text();
+
+		settings_div.hide().removeClass( 'field-visibility-settings-open' )
+			.siblings( '.field-visibility-settings-toggle' )
+				.children( '.current-visibility-level' ).text( vis_setting_text ).end()
+			.show().removeClass( 'field-visibility-settings-hide' );
+	} );
+
+	jq('#profile-edit-form input:not(:submit), #profile-edit-form textarea, #profile-edit-form select, #signup_form input:not(:submit), #signup_form textarea, #signup_form select').change( function() {
+		var shouldconfirm = true;
+
+		jq('#profile-edit-form input:submit, #signup_form input:submit').on( 'click', function() {
+			shouldconfirm = false;
+		});
+
+		window.onbeforeunload = function(e) {
+			if ( shouldconfirm ) {
+				return BP_DTheme.unsaved_changes;
+			}
+		};
+	});
+
+	/** Friendship Requests **************************************/
+
+	/* Accept and Reject friendship request buttons */
+	jq('#friend-list a.accept, #friend-list a.reject').on( 'click', function() {
+		var button   = jq(this),
+			li         = jq(this).parents('#friend-list li'),
+			action_div = jq(this).parents('li div.action'),
+			id         = li.attr('id').substr( 11, li.attr('id').length ),
+			link_href  = button.attr('href'),
+			nonce      = link_href.split('_wpnonce=')[1],
+			action;
+
+		if ( jq(this).hasClass('accepted') || jq(this).hasClass('rejected') ) {
+			return false;
+		}
+
+		if ( jq(this).hasClass('accept') ) {
+			action = 'accept_friendship';
+			action_div.children('a.reject').css( 'visibility', 'hidden' );
+		} else {
+			action = 'reject_friendship';
+			action_div.children('a.accept').css( 'visibility', 'hidden' );
+		}
+
+		button.addClass('loading');
+
+		jq.post( ajaxurl, {
+			action: action,
+			'cookie': bp_get_cookies(),
+			'id': id,
+			'_wpnonce': nonce
+		},
+		function(response) {
+			button.removeClass('loading');
+
+			if ( response[0] + response[1] === '-1' ) {
+				li.prepend( response.substr( 2, response.length ) );
+				li.children('#message').hide().fadeIn(200);
+			} else {
+				button.fadeOut( 100, function() {
+					if ( jq(this).hasClass('accept') ) {
+						action_div.children('a.reject').hide();
+						jq(this).html( BP_DTheme.accepted ).contents().unwrap();
+					} else {
+						action_div.children('a.accept').hide();
+						jq(this).html( BP_DTheme.rejected ).contents().unwrap();
+					}
+				});
+			}
+		});
+
+		return false;
+	});
+
+	/* Add / Remove friendship buttons */
+	jq( '#members-dir-list, #members-group-list, #item-header' ).on('click', '.friendship-button a', function() {
+		jq(this).parent().addClass('loading');
+		var fid   = jq(this).attr('id'),
+			nonce   = jq(this).attr('href'),
+			thelink = jq(this);
+
+		fid = fid.split('-');
+		fid = fid[1];
+
+		nonce = nonce.split('?_wpnonce=');
+		nonce = nonce[1].split('&');
+		nonce = nonce[0];
+
+		jq.post( ajaxurl, {
+			action: 'addremove_friend',
+			'cookie': bp_get_cookies(),
+			'fid': fid,
+			'_wpnonce': nonce
+		},
+		function(response)
+		{
+			var action  = thelink.attr('rel');
+				parentdiv = thelink.parent();
+
+			if ( action === 'add' ) {
+				jq(parentdiv).fadeOut(200,
+					function() {
+						parentdiv.removeClass('add_friend');
+						parentdiv.removeClass('loading');
+						parentdiv.addClass('pending_friend');
+						parentdiv.fadeIn(200).html(response);
+					}
+					);
+
+			} else if ( action === 'remove' ) {
+				jq(parentdiv).fadeOut(200,
+					function() {
+						parentdiv.removeClass('remove_friend');
+						parentdiv.removeClass('loading');
+						parentdiv.addClass('add');
+						parentdiv.fadeIn(200).html(response);
+					}
+					);
+			}
+		});
+		return false;
+	} );
+
+	/** Group Join / Leave Buttons **************************************/
+
+	// Confirmation when clicking Leave Group in group headers
+	jq('#buddypress').on('click', '.group-button .leave-group', function() {
+		if ( false === confirm( BP_DTheme.leave_group_confirm ) ) {
+			return false;
+		}
+	});
+
+	jq('#groups-dir-list').on('click', '.group-button a', function() {
+		var gid   = jq(this).parent().attr('id'),
+			nonce   = jq(this).attr('href'),
+			thelink = jq(this);
+
+		gid = gid.split('-');
+		gid = gid[1];
+
+		nonce = nonce.split('?_wpnonce=');
+		nonce = nonce[1].split('&');
+		nonce = nonce[0];
+
+		// Leave Group confirmation within directories - must intercept
+		// AJAX request
+		if ( thelink.hasClass( 'leave-group' ) && false === confirm( BP_DTheme.leave_group_confirm ) ) {
+			return false;
+		}
+
+		jq.post( ajaxurl, {
+			action: 'joinleave_group',
+			'cookie': bp_get_cookies(),
+			'gid': gid,
+			'_wpnonce': nonce
+		},
+		function(response)
+		{
+			var parentdiv = thelink.parent();
+
+			// user groups page
+			if ( ! jq('body.directory').length ) {
+				window.location.reload();
+
+			// groups directory
+			} else {
+				jq(parentdiv).fadeOut(200,
+					function() {
+						parentdiv.fadeIn(200).html(response);
+
+						var mygroups = jq('#groups-personal span'),
+							add        = 1;
+
+						if( thelink.hasClass( 'leave-group' ) ) {
+							// hidden groups slide up
+							if ( parentdiv.hasClass( 'hidden' ) ) {
+								parentdiv.closest('li').slideUp( 200 );
+							}
+
+							add = 0;
+						} else if ( thelink.hasClass( 'request-membership' ) ) {
+							add = false;
+						}
+
+						// change the "My Groups" value
+						if ( mygroups.length && add !== false ) {
+							if ( add ) {
+								mygroups.text( ( mygroups.text() >> 0 ) + 1 );
+							} else {
+								mygroups.text( ( mygroups.text() >> 0 ) - 1 );
+							}
+						}
+
+					}
+				);
+			}
+		});
+		return false;
+	} );
+
+	/** Button disabling ************************************************/
+
+	jq('#buddypress').on( 'click', '.pending', function() {
+		return false;
+	});
+
+	/** Registration ***********************************************/
+
+	if ( jq('body').hasClass('register') ) {
+		var blog_checked = jq('#signup_with_blog');
+
+		// hide "Blog Details" block if not checked by default
+		if ( ! blog_checked.prop('checked') ) {
+			jq('#blog-details').toggle();
+		}
+
+		// toggle "Blog Details" block whenever checkbox is checked
+		blog_checked.change(function() {
+			jq('#blog-details').toggle();
+		});
+	}
+
+	/** Private Messaging ******************************************/
+
+	/** Message search */
+	jq('.message-search').on( 'click', function(event) {
+		if ( jq(this).hasClass('no-ajax') ) {
+			return;
+		}
+
+		var target = jq(event.target),
+			object;
+
+		if ( target.attr('type') === 'submit' || target.attr('type') === 'button' ) {
+			object = 'messages';
+
+			bp_filter_request(
+				object,
+				jq.cookie('bp-' + object + '-filter'),
+				jq.cookie('bp-' + object + '-scope'),
+				'div.' + object, jq('#messages_search').val(),
+				1,
+				jq.cookie('bp-' + object + '-extras')
+			);
+
+			return false;
+		}
+	});
+
+	/* AJAX send reply functionality */
+	jq('#send_reply_button').click(
+		function() {
+			var order = jq('#messages_order').val() || 'ASC',
+				offset  = jq('#message-recipients').offset(),
+				button  = jq('#send_reply_button');
+
+			jq(button).addClass('loading');
+
+			jq.post( ajaxurl, {
+				action: 'messages_send_reply',
+				'cookie': bp_get_cookies(),
+				'_wpnonce': jq('#send_message_nonce').val(),
+
+				'content': jq('#message_content').val(),
+				'send_to': jq('#send_to').val(),
+				'subject': jq('#subject').val(),
+				'thread_id': jq('#thread_id').val()
+			},
+			function(response)
+			{
+				if ( response[0] + response[1] === '-1' ) {
+					jq('#send-reply').prepend( response.substr( 2, response.length ) );
+				} else {
+					jq('#send-reply #message').remove();
+					jq('#message_content').val('');
+
+					if ( 'ASC' === order ) {
+						jq('#send-reply').before( response );
+					} else {
+						jq('#message-recipients').after( response );
+						jq(window).scrollTop(offset.top);
+					}
+
+					jq('.new-message').hide().slideDown( 200, function() {
+						jq('.new-message').removeClass('new-message');
+					});
+				}
+				jq(button).removeClass('loading');
+			});
+
+			return false;
+		}
+	);
+
+	/* Marking private messages as read and unread */
+	jq('#mark_as_read, #mark_as_unread').click(function() {
+		var checkboxes_tosend = '',
+			checkboxes = jq('#message-threads tr td input[type="checkbox"]'),
+			currentClass, newClass, unreadCount, inboxCount, unreadCountDisplay, action,
+			inboxcount, thread_count;
+
+		if ( 'mark_as_unread' === jq(this).attr('id') ) {
+			currentClass = 'read';
+			newClass = 'unread';
+			unreadCount = 1;
+			inboxCount = 0;
+			unreadCountDisplay = 'inline';
+			action = 'messages_markunread';
+		} else {
+			currentClass = 'unread';
+			newClass = 'read';
+			unreadCount = 0;
+			inboxCount = 1;
+			unreadCountDisplay = 'none';
+			action = 'messages_markread';
+		}
+
+		checkboxes.each( function(i) {
+			if(jq(this).is(':checked')) {
+				if ( jq('#m-' + jq(this).attr('value')).hasClass(currentClass) ) {
+					checkboxes_tosend += jq(this).attr('value');
+					jq('#m-' + jq(this).attr('value')).removeClass(currentClass);
+					jq('#m-' + jq(this).attr('value')).addClass(newClass);
+					thread_count = jq('#m-' + jq(this).attr('value') + ' td span.unread-count').html();
+
+					jq('#m-' + jq(this).attr('value') + ' td span.unread-count').html(unreadCount);
+					jq('#m-' + jq(this).attr('value') + ' td span.unread-count').css('display', unreadCountDisplay);
+
+					inboxcount = jq('tr.unread').length;
+
+					jq('#user-messages span').html( inboxcount );
+
+					if ( i !== checkboxes.length - 1 ) {
+						checkboxes_tosend += ',';
+					}
+				}
+			}
+		});
+		jq.post( ajaxurl, {
+			action: action,
+			'thread_ids': checkboxes_tosend
+		});
+		return false;
+	});
+
+	/* Selecting unread and read messages in inbox */
+	jq( 'body.messages #item-body div.messages' ).on( 'change', '#message-type-select', function() {
+		var selection   = this.value,
+			checkboxes    = jq( 'td input[type="checkbox"]' ),
+			checked_value = 'checked';
+
+		checkboxes.each( function(i) {
+			checkboxes[i].checked = '';
+		});
+
+		switch ( selection ) {
+			case 'unread':
+				checkboxes = jq('tr.unread td input[type="checkbox"]');
+				break;
+			case 'read':
+				checkboxes = jq('tr.read td input[type="checkbox"]');
+				break;
+			case '':
+				checked_value = '';
+				break;
+		}
+
+		checkboxes.each( function(i) {
+			checkboxes[i].checked = checked_value;
+		});
+	});
+
+	/* Bulk delete messages */
+	jq( 'body.messages #item-body div.messages' ).on( 'click', '.messages-options-nav a', function() {
+		if ( -1 === jq.inArray( this.id, Array( 'delete_sentbox_messages', 'delete_inbox_messages' ) ) ) {
+			return;
+		}
+
+		checkboxes_tosend = '';
+		checkboxes = jq('#message-threads tr td input[type="checkbox"]');
+
+		jq('#message').remove();
+		jq(this).addClass('loading');
+
+		jq(checkboxes).each( function(i) {
+			if( jq(this).is(':checked') ) {
+				checkboxes_tosend += jq(this).attr('value') + ',';
+			}
+		});
+
+		if ( '' === checkboxes_tosend ) {
+			jq(this).removeClass('loading');
+			return false;
+		}
+
+		jq.post( ajaxurl, {
+			action: 'messages_delete',
+			'thread_ids': checkboxes_tosend
+		}, function(response) {
+			if ( response[0] + response[1] === '-1' ) {
+				jq('#message-threads').prepend( response.substr( 2, response.length ) );
+			} else {
+				jq('#message-threads').before( '<div id="message" class="updated"><p>' + response + '</p></div>' );
+
+				jq(checkboxes).each( function(i) {
+					if( jq(this).is(':checked') ) {
+						// We need to uncheck because message is only hidden
+						// Otherwise, AJAX will be fired again with same data
+						jq(this).attr( 'checked', false );
+						jq(this).parent().parent().fadeOut(150);
+					}
+				});
+			}
+
+			jq('#message').hide().slideDown(150);
+			jq('#delete_inbox_messages, #delete_sentbox_messages').removeClass('loading');
+		});
+
+		return false;
+	});
+
+	/* Selecting/Deselecting all messages */
+	jq('#select-all-messages').click(function(event) {
+		if( this.checked ) {
+			jq('.message-check').each(function() {
+				this.checked = true;
+			});
+		} else {
+			jq('.message-check').each(function() {
+				this.checked = false;
+			});
+		}
+	});
+
+	/* Make sure a 'Bulk Action' is selected before submitting the messages bulk action form */
+	jq('#messages-bulk-manage').attr('disabled', 'disabled');
+
+	/* Remove the disabled attribute from the messages form submit button when bulk action has a value */
+	jq('#messages-select').on('change', function(){
+		jq('#messages-bulk-manage').attr('disabled', jq(this).val().length <= 0);
+	});
+
+	/* Star action function */
+	starAction = function() {
+		var link = jq(this);
+
+		jq.post( ajaxurl, {
+			action: 'messages_star',
+			'message_id': link.data('message-id'),
+			'star_status': link.data('star-status'),
+			'nonce': link.data('star-nonce'),
+			'bulk': link.data('star-bulk')
+		},
+		function(response) {
+			if ( 1 === parseInt( response, 10 ) ) {
+				if ( 'unstar' === link.data('star-status') ) {
+					link.data('star-status', 'star');
+					link.removeClass('message-action-unstar').addClass('message-action-star');
+					link.find('.bp-screen-reader-text').text( BP_PM_Star.strings.text_star );
+
+					if ( 1 === BP_PM_Star.is_single_thread ) {
+						link.prop('title', BP_PM_Star.strings.title_star );
+					} else {
+						link.prop('title', BP_PM_Star.strings.title_star_thread );
+					}
+
+				} else {
+					link.data('star-status', 'unstar');
+					link.removeClass('message-action-star').addClass('message-action-unstar');
+					link.find('.bp-screen-reader-text').text(BP_PM_Star.strings.text_unstar);
+
+					if ( 1 === BP_PM_Star.is_single_thread ) {
+						link.prop('title', BP_PM_Star.strings.title_unstar );
+					} else {
+						link.prop('title', BP_PM_Star.strings.title_unstar_thread );
+					}
+				}
+			}
+		});
+		return false;
+	};
+
+	/* Star actions */
+	jq('#message-threads').on('click', 'td.thread-star a', starAction );
+	jq('#message-thread').on('click', '.message-star-actions a', starAction );
+
+	/* Star bulk manage - Show only the valid action based on the starred item. */
+	jq('#message-threads td.bulk-select-check :checkbox').on('change', function() {
+		var box = jq(this),
+			star = box.closest('tr').find('.thread-star a');
+
+		if ( box.prop('checked') ) {
+			if( 'unstar' === star.data('star-status') ) {
+				BP_PM_Star.star_counter++;
+			} else {
+				BP_PM_Star.unstar_counter++;
+			}
+		} else {
+			if( 'unstar' === star.data('star-status') ) {
+				BP_PM_Star.star_counter--;
+			} else {
+				BP_PM_Star.unstar_counter--;
+			}
+		}
+
+		if ( BP_PM_Star.star_counter > 0 && parseInt( BP_PM_Star.unstar_counter, 10 ) === 0 ) {
+			jq('option[value="star"]').hide();
+		} else {
+			jq('option[value="star"]').show();
+		}
+
+		if ( BP_PM_Star.unstar_counter > 0 && parseInt( BP_PM_Star.star_counter, 10 ) === 0 ) {
+			jq('option[value="unstar"]').hide();
+		} else {
+			jq('option[value="unstar"]').show();
+		}
+	});
+
+	/** Notifications **********************************************/
+
+	/* Selecting/Deselecting all notifications */
+	jq('#select-all-notifications').click(function(event) {
+		if( this.checked ) {
+			jq('.notification-check').each(function() {
+				this.checked = true;
+			});
+		} else {
+			jq('.notification-check').each(function() {
+				this.checked = false;
+			});
+		}
+	});
+
+	/* Make sure a 'Bulk Action' is selected before submitting the form */
+	jq('#notification-bulk-manage').attr('disabled', 'disabled');
+
+	/* Remove the disabled attribute from the form submit button when bulk action has a value */
+	jq('#notification-select').on('change', function(){
+		jq('#notification-bulk-manage').attr('disabled', jq(this).val().length <= 0);
+	});
+
+	/* Close site wide notices in the sidebar */
+	jq('#close-notice').on( 'click', function() {
+		jq(this).addClass('loading');
+		jq('#sidebar div.error').remove();
+
+		jq.post( ajaxurl, {
+			action: 'messages_close_notice',
+			'notice_id': jq('.notice').attr('rel').substr( 2, jq('.notice').attr('rel').length )
+		},
+		function(response) {
+			jq('#close-notice').removeClass('loading');
+
+			if ( response[0] + response[1] === '-1' ) {
+				jq('.notice').prepend( response.substr( 2, response.length ) );
+				jq( '#sidebar div.error').hide().fadeIn( 200 );
+			} else {
+				jq('.notice').slideUp( 100 );
+			}
+		});
+		return false;
+	});
+
+	/* Toolbar & wp_list_pages JavaScript IE6 hover class */
+	jq('#wp-admin-bar ul.main-nav li, #nav li').mouseover( function() {
+		jq(this).addClass('sfhover');
+	});
+
+	jq('#wp-admin-bar ul.main-nav li, #nav li').mouseout( function() {
+		jq(this).removeClass('sfhover');
+	});
+
+	/* Clear BP cookies on logout */
+	jq('#wp-admin-bar-logout, a.logout').on( 'click', function() {
+		jq.removeCookie('bp-activity-scope', {
+			path: '/'
+		});
+		jq.removeCookie('bp-activity-filter', {
+			path: '/'
+		});
+		jq.removeCookie('bp-activity-oldestpage', {
+			path: '/'
+		});
+
+		var objects = [ 'members', 'groups', 'blogs', 'forums' ];
+		jq(objects).each( function(i) {
+			jq.removeCookie('bp-' + objects[i] + '-scope', {
+				path: '/'
+			} );
+			jq.removeCookie('bp-' + objects[i] + '-filter', {
+				path: '/'
+			} );
+			jq.removeCookie('bp-' + objects[i] + '-extras', {
+				path: '/'
+			} );
+		});
+	});
+
+	/* if js is enabled then replace the no-js class by a js one */
+	if( jq('body').hasClass('no-js') ) {
+		jq('body').attr('class', jq('body').attr('class').replace( /no-js/,'js' ) );
+	}
+
+	/** Activity HeartBeat ************************************************/
+
+	// Set the interval and the namespace event
+	if ( typeof wp !== 'undefined' && typeof wp.heartbeat !== 'undefined' && typeof BP_DTheme.pulse !== 'undefined' ) {
+
+		wp.heartbeat.interval( Number( BP_DTheme.pulse ) );
+
+		jq.fn.extend({
+			'heartbeat-send': function() {
+				return this.bind( 'heartbeat-send.buddypress' );
+			}
+		});
+	}
+
+	// Set the last id to request after
+	var first_item_recorded = 0;
+	jq( document ).on( 'heartbeat-send.buddypress', function( e, data ) {
+
+		first_item_recorded = 0;
+
+		// First row is default latest activity id
+		if ( jq( '#buddypress ul.activity-list li' ).first().prop( 'id' ) ) {
+			// getting the timestamp
+			timestamp = jq( '#buddypress ul.activity-list li' ).first().prop( 'class' ).match( /date-recorded-([0-9]+)/ );
+
+			if ( timestamp ) {
+				first_item_recorded = timestamp[1];
+			}
+		}
+
+		if ( 0 === activity_last_recorded || Number( first_item_recorded ) > activity_last_recorded ) {
+			activity_last_recorded = Number( first_item_recorded );
+		}
+
+		data.bp_activity_last_recorded = activity_last_recorded;
+
+		last_recorded_search = bp_get_querystring('s');
+
+		if ( last_recorded_search ) {
+			data.bp_activity_last_recorded_search_terms = last_recorded_search;
+		}
+	});
+
+	// Increment newest_activities and activity_last_recorded if data has been returned
+	jq( document ).on( 'heartbeat-tick', function( e, data ) {
+
+		// Only proceed if we have newest activities
+		if ( ! data.bp_activity_newest_activities ) {
+			return;
+		}
+
+		newest_activities = data.bp_activity_newest_activities.activities + newest_activities;
+		activity_last_recorded  = Number( data.bp_activity_newest_activities.last_recorded );
+
+		if ( jq( '#buddypress ul.activity-list li' ).first().hasClass( 'load-newest' ) ) {
+			return;
+		}
+
+		jq( '#buddypress ul.activity-list' ).prepend( '<li class="load-newest"><a href="#newest">' + BP_DTheme.newest + '</a></li>' );
+	});
+});
+
+/* Setup activity scope and filter based on the current cookie settings. */
+function bp_init_activity() {
+	/* Reset the page */
+	jq.cookie( 'bp-activity-oldestpage', 1, {
+		path: '/'
+	} );
+
+	if ( undefined !== jq.cookie('bp-activity-filter') && jq('#activity-filter-select').length ) {
+		jq('#activity-filter-select select option[value="' + jq.cookie('bp-activity-filter') + '"]').prop( 'selected', true );
+	}
+
+	/* Activity Tab Set */
+	if ( undefined !== jq.cookie('bp-activity-scope') && jq('.activity-type-tabs').length ) {
+		jq('.activity-type-tabs li').each( function() {
+			jq(this).removeClass('selected');
+		});
+		jq('#activity-' + jq.cookie('bp-activity-scope') + ', .item-list-tabs li.current').addClass('selected');
+	}
+}
+
+/* Setup object scope and filter based on the current cookie settings for the object. */
+function bp_init_objects(objects) {
+	jq(objects).each( function(i) {
+		if ( undefined !== jq.cookie('bp-' + objects[i] + '-filter') && jq('#' + objects[i] + '-order-select select').length ) {
+			jq('#' + objects[i] + '-order-select select option[value="' + jq.cookie('bp-' + objects[i] + '-filter') + '"]').prop( 'selected', true );
+		}
+
+		if ( undefined !== jq.cookie('bp-' + objects[i] + '-scope') && jq('div.' + objects[i]).length ) {
+			jq('.item-list-tabs li').each( function() {
+				jq(this).removeClass('selected');
+			});
+			jq('#' + objects[i] + '-' + jq.cookie('bp-' + objects[i] + '-scope') + ', #object-nav li.current').addClass('selected');
+		}
+	});
+}
+
+/* Filter the current content list (groups/members/blogs/topics) */
+function bp_filter_request( object, filter, scope, target, search_terms, page, extras, caller, template ) {
+	if ( 'activity' === object ) {
+		return false;
+	}
+
+	if ( null === scope ) {
+		scope = 'all';
+	}
+
+	/* Save the settings we want to remain persistent to a cookie */
+	jq.cookie( 'bp-' + object + '-scope', scope, {
+		path: '/'
+	} );
+	jq.cookie( 'bp-' + object + '-filter', filter, {
+		path: '/'
+	} );
+	jq.cookie( 'bp-' + object + '-extras', extras, {
+		path: '/'
+	} );
+
+	/* Set the correct selected nav and filter */
+	jq('.item-list-tabs li').each( function() {
+		jq(this).removeClass('selected');
+	});
+	jq('#' + object + '-' + scope + ', #object-nav li.current').addClass('selected');
+	jq('.item-list-tabs li.selected').addClass('loading');
+	jq('.item-list-tabs select option[value="' + filter + '"]').prop( 'selected', true );
+
+	if ( 'friends' === object || 'group_members' === object ) {
+		object = 'members';
+	}
+
+	if ( bp_ajax_request ) {
+		bp_ajax_request.abort();
+	}
+
+	bp_ajax_request = jq.post( ajaxurl, {
+		action: object + '_filter',
+		'cookie': bp_get_cookies(),
+		'object': object,
+		'filter': filter,
+		'search_terms': search_terms,
+		'scope': scope,
+		'page': page,
+		'extras': extras,
+		'template': template
+	},
+	function(response)
+	{
+		/* animate to top if called from bottom pagination */
+		if ( caller === 'pag-bottom' && jq('#subnav').length ) {
+			var top = jq('#subnav').parent();
+			jq('html,body').animate({scrollTop: top.offset().top}, 'slow', function() {
+				jq(target).fadeOut( 100, function() {
+					jq(this).html(response);
+					jq(this).fadeIn(100);
+				});
+			});
+
+		} else {
+			jq(target).fadeOut( 100, function() {
+				jq(this).html(response);
+				jq(this).fadeIn(100);
+			});
+		}
+
+		jq('.item-list-tabs li.selected').removeClass('loading');
+	});
+}
+
+/* Activity Loop Requesting */
+function bp_activity_request(scope, filter) {
+	/* Save the type and filter to a session cookie */
+	if ( null !== scope ) {
+		jq.cookie( 'bp-activity-scope', scope, {
+			path: '/'
+		} );
+	}
+	if ( null !== filter ) {
+		jq.cookie( 'bp-activity-filter', filter, {
+			path: '/'
+		} );
+	}
+	jq.cookie( 'bp-activity-oldestpage', 1, {
+		path: '/'
+	} );
+
+	/* Remove selected and loading classes from tabs */
+	jq('.item-list-tabs li').each( function() {
+		jq(this).removeClass('selected loading');
+	});
+	/* Set the correct selected nav and filter */
+	jq('#activity-' + scope + ', .item-list-tabs li.current').addClass('selected');
+	jq('#object-nav.item-list-tabs li.selected, div.activity-type-tabs li.selected').addClass('loading');
+	jq('#activity-filter-select select option[value="' + filter + '"]').prop( 'selected', true );
+
+	/* Reload the activity stream based on the selection */
+	jq('.widget_bp_activity_widget h2 span.ajax-loader').show();
+
+	if ( bp_ajax_request ) {
+		bp_ajax_request.abort();
+	}
+
+	bp_ajax_request = jq.post( ajaxurl, {
+		action: 'activity_widget_filter',
+		'cookie': bp_get_cookies(),
+		'_wpnonce_activity_filter': jq('#_wpnonce_activity_filter').val(),
+		'scope': scope,
+		'filter': filter
+	},
+	function(response)
+	{
+		jq('.widget_bp_activity_widget h2 span.ajax-loader').hide();
+
+		jq('div.activity').fadeOut( 100, function() {
+			jq(this).html(response.contents);
+			jq(this).fadeIn(100);
+
+			/* Selectively hide comments */
+			bp_legacy_theme_hide_comments();
+		});
+
+		/* Update the feed link */
+		if ( undefined !== response.feed_url ) {
+			jq('.directory #subnav li.feed a, .home-page #subnav li.feed a').attr('href', response.feed_url);
+		}
+
+		jq('.item-list-tabs li.selected').removeClass('loading');
+
+	}, 'json' );
+}
+
+/* Hide long lists of activity comments, only show the latest five root comments. */
+function bp_legacy_theme_hide_comments() {
+	var comments_divs = jq('div.activity-comments'),
+		parent_li, comment_lis, comment_count;
+
+	if ( !comments_divs.length ) {
+		return false;
+	}
+
+	comments_divs.each( function() {
+		if ( jq(this).children('ul').children('li').length < 5 ) {
+			return;
+		}
+
+		comments_div = jq(this);
+		parent_li = comments_div.parents('#activity-stream > li');
+		comment_lis = jq(this).children('ul').children('li');
+		comment_count = ' ';
+
+		if ( jq('#' + parent_li.attr('id') + ' a.acomment-reply span').length ) {
+			comment_count = jq('#' + parent_li.attr('id') + ' a.acomment-reply span').html();
+		}
+
+		comment_lis.each( function(i) {
+			/* Show the latest 5 root comments */
+			if ( i < comment_lis.length - 5 ) {
+				jq(this).addClass('hidden');
+				jq(this).toggle();
+
+				if ( !i ) {
+					jq(this).before( '<li class="show-all"><a href="#' + parent_li.attr('id') + '/show-all/" title="' + BP_DTheme.show_all_comments + '">' + BP_DTheme.show_x_comments.replace( '%d', comment_count ) + '</a></li>' );
+				}
+			}
+		});
+
+	});
+}
+
+/* Helper Functions */
+
+function checkAll() {
+	var checkboxes = document.getElementsByTagName('input'),
+		i;
+
+	for(i=0; i<checkboxes.length; i++) {
+		if(checkboxes[i].type === 'checkbox') {
+			if($('check_all').checked === '') {
+				checkboxes[i].checked = '';
+			}
+			else {
+				checkboxes[i].checked = 'checked';
+			}
+		}
+	}
+}
+
+/**
+ * Deselects any select options or input options for the specified field element.
+ *
+ * @param {String} container HTML ID of the field
+ * @since 1.2.0
+ */
+function clear( container ) {
+	container = document.getElementById( container );
+	if ( ! container ) {
+		return;
+	}
+
+	var radioButtons = container.getElementsByTagName( 'INPUT' ),
+		options = container.getElementsByTagName( 'OPTION' ),
+		i       = 0;
+
+	if ( radioButtons ) {
+		for ( i = 0; i < radioButtons.length; i++ ) {
+			radioButtons[i].checked = '';
+		}
+	}
+
+	if ( options ) {
+		for ( i = 0; i < options.length; i++ ) {
+			options[i].selected = false;
+		}
+	}
+}
+
+/* Returns a querystring of BP cookies (cookies beginning with 'bp-') */
+function bp_get_cookies() {
+	var allCookies = document.cookie.split(';'),  // get all cookies and split into an array
+		bpCookies      = {},
+		cookiePrefix   = 'bp-',
+		i, cookie, delimiter, name, value;
+
+	// loop through cookies
+	for (i = 0; i < allCookies.length; i++) {
+		cookie    = allCookies[i];
+		delimiter = cookie.indexOf('=');
+		name      = jq.trim( unescape( cookie.slice(0, delimiter) ) );
+		value     = unescape( cookie.slice(delimiter + 1) );
+
+		// if BP cookie, store it
+		if ( name.indexOf(cookiePrefix) === 0 ) {
+			bpCookies[name] = value;
+		}
+	}
+
+	// returns BP cookies as querystring
+	return encodeURIComponent( jq.param(bpCookies) );
+}
Index: /tags/2.4.3/src/bp-templates/bp-legacy/js/password-verify.js
===================================================================
--- /tags/2.4.3/src/bp-templates/bp-legacy/js/password-verify.js	(revision 10410)
+++ /tags/2.4.3/src/bp-templates/bp-legacy/js/password-verify.js	(revision 10410)
@@ -0,0 +1,43 @@
+/* jshint undef: false */
+/* Password Verify */
+( function( $ ){
+	function check_pass_strength() {
+		var pass1 = $( '.password-entry' ).val(),
+		    pass2 = $( '.password-entry-confirm' ).val(),
+		    strength;
+
+		// Reset classes and result text
+		$( '#pass-strength-result' ).removeClass( 'short bad good strong' );
+		if ( ! pass1 ) {
+			$( '#pass-strength-result' ).html( pwsL10n.empty );
+			return;
+		}
+
+		strength = wp.passwordStrength.meter( pass1, wp.passwordStrength.userInputBlacklist(), pass2 );
+
+		switch ( strength ) {
+			case 2:
+				$( '#pass-strength-result' ).addClass( 'bad' ).html( pwsL10n.bad );
+				break;
+			case 3:
+				$( '#pass-strength-result' ).addClass( 'good' ).html( pwsL10n.good );
+				break;
+			case 4:
+				$( '#pass-strength-result' ).addClass( 'strong' ).html( pwsL10n.strong );
+				break;
+			case 5:
+				$( '#pass-strength-result' ).addClass( 'short' ).html( pwsL10n.mismatch );
+				break;
+			default:
+				$( '#pass-strength-result' ).addClass( 'short' ).html( pwsL10n['short'] );
+				break;
+		}
+	}
+
+	// Bind check_pass_strength to keyup events in the password fields
+	$( document ).ready( function() {
+		$( '.password-entry' ).val( '' ).keyup( check_pass_strength );
+		$( '.password-entry-confirm' ).val( '' ).keyup( check_pass_strength );
+	});
+
+} )( jQuery );
Index: /tags/2.4.3/src/bp-xprofile/admin/css/admin-rtl.css
===================================================================
--- /tags/2.4.3/src/bp-xprofile/admin/css/admin-rtl.css	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/admin/css/admin-rtl.css	(revision 10410)
@@ -0,0 +1,286 @@
+/* Profile field group main admin page */
+@-o-keyframes tab-throb {
+    from { box-shadow: 0 0 10px #1e8cbe; }
+    50%  { box-shadow: 0 0 0    #1e8cbe; }
+    to   { box-shadow: 0 0 10px #1e8cbe; }
+}
+@-webkit-keyframes tab-throb {
+    from { box-shadow: 0 0 10px #1e8cbe; }
+    50%  { box-shadow: 0 0 0    #1e8cbe; }
+    to   { box-shadow: 0 0 10px #1e8cbe; }
+}
+
+@-moz-keyframes tab-throb {
+    from { box-shadow: 0 0 10px #1e8cbe; }
+    50%  { box-shadow: 0 0 0    #1e8cbe; }
+    to   { box-shadow: 0 0 10px #1e8cbe; }
+}
+
+#profile-field-form {
+	margin-top: 12px;
+}
+
+#tabs {
+	position: relative;
+	}
+	p.nofields {
+		margin: 20px 0 0;
+	}
+
+	/* Field group tabs */
+	ul#field-group-tabs {
+		float: right;
+		padding: 0 15px 0 0;
+		margin: 0;
+		display: none;
+	}
+		ul#field-group-tabs li {
+			float: right;
+			margin-left: 8px;
+			margin-bottom: -1px;
+		}
+		ul#field-group-tabs li.ui-state-hover a.ui-tab {
+			color: #d54e21;
+		}
+		ul#field-group-tabs li.ui-state-acceptable:not(.ui-state-active) a.ui-tab {
+			-webkit-animation: tab-throb 2s infinite;
+			-moz-animation: tab-throb 2s infinite;
+			-o-animation: tab-throb 2s infinite;
+			animation: tab-throb 2s infinite;
+		}
+		ul#field-group-tabs li.drop-candidate.ui-state-acceptable:not(.ui-state-active) a.ui-tab {
+			-webkit-box-shadow: 0 0 10px green;
+			-moz-box-shadow: 0 0 10px green;
+			-o-box-shadow: 0 0 10px green;
+			box-shadow: 0 0 10px green;
+			-webkit-animation: none;
+			-moz-animation: none;
+			-o-animation: none;
+			animation: none;
+			color: #000;
+		}
+		ul#field-group-tabs li a.ui-tab {
+			font-size: 14px;
+			display: block;
+			margin-top: 3px;
+			padding: 5px 10px 6px;
+			border: 1px solid #e5e5e5;
+			background-color: #f5f5f5;
+			text-decoration: none;
+			color: #000;
+			outline: none;
+		}
+
+		/* Selected tab */
+		ul#field-group-tabs li.ui-state-active a.ui-tab {
+			background-color: #fff;
+			margin-top: 0;
+			padding: 7px 10px 7px;
+			border-bottom: 1px solid transparent;
+			color: #000;
+		}
+
+	/* Toolbar */
+	.tab-toolbar {
+		clear: right;
+		background: #fff;
+		border-width: 1px;
+		border-color: #e5e5e5;
+		border-style: solid;
+		-webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.04);
+		box-shadow: 0 1px 1px rgba(0,0,0,0.04);
+		padding: 10px;
+		margin-bottom: 10px;
+	}
+
+	.bp-option a.delete,
+	.field-wrapper a.deletion,
+	.tab-toolbar a.deletion {
+		color: #f00;
+		border-bottom: 1px solid #f00;
+		text-decoration: none;
+		padding: 0;
+	}
+
+	div.delete-button {
+		float: left;
+		line-height: 28px;
+	}
+
+	div.field-group {
+		clear: right;
+	}
+
+	.field-group {
+		cursor: default;
+		border: 1px solid #e5e5e5;
+		background: #fff;
+		-webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.04);
+		box-shadow: 0 1px 1px rgba(0,0,0,0.04);
+		margin-top: 10px;
+		padding: 0 20px 20px;
+	}
+	.field-group .xprofile-field {
+		position: relative;
+	}
+	.field-group fieldset {
+		position: relative;
+		border: 1px solid #ddd;
+		margin: 20px 0 0;
+		cursor: move;
+		background: #fafafa;
+		padding: 10px 0 0 ;
+	}
+		.field-group fieldset legend {
+			position: absolute;
+			top: 10px;
+			right: 10px;
+			padding: 0;
+		}
+	fieldset.radio div div label,
+	fieldset.checkbox div label {
+		margin-left: 20px;
+	}
+
+	fieldset.clear-value {
+		margin-right: 10px;
+	}
+	fieldset div.field-wrapper {
+		padding: 25px 10px 10px;
+	}
+	.field-group div.actions {
+		float: none;
+		border-top: 1px solid #ddd;
+		margin: 10px 0 0;
+		padding-top: 10px;
+	}
+	.field-group div.actions a,
+	.field-group div.actions button,
+	.field-group div.actions input {
+		float: none;
+	}
+	.field-group fieldset:hover {
+		border-color: #999;
+	}
+	.field-group fieldset:hover div.actions {
+		display: block;
+	}
+
+	.bp-options-box .ui-sortable-placeholder,
+	#field-group-tabs .ui-sortable-placeholder,
+	.field-group fieldset.ui-sortable-placeholder {
+		border: 1px dashed #999;
+		background-color: #fff;
+		visibility: visible !important;
+	}
+
+	#field-group-tabs .ui-sortable-placeholder {
+		background: transparent;
+		border-bottom: none;
+		margin: -1px 0 -1px 6px;
+	}
+
+	ul.forTab {
+		list-style: none;
+		padding: 0;
+		margin: 0 1em 0 0;
+	}
+		ul.forTab li {
+			margin: 0 0 1em 0;
+		}
+			ul.forTab li label {
+				display: block;
+			}
+
+			ul.forTab li input {
+				font-size: 1.4em;
+			}
+
+	p.success { background: green;}
+	p.err {
+		border-top: 2px solid red;
+		border-bottom: 2px solid red;
+		color: red;
+		padding: 5px 0;
+		width: 40%;
+	}
+
+	span.desc, span.signup-description {
+		display: block;
+		font-size: 11px;
+		color: #555;
+	}
+
+ul.multi-checkbox {
+	margin: 0 0 0 5px;
+	padding: .5em .9em;
+	height: 10em;
+	overflow: auto;
+	list-style: none;
+	border: solid 1px #ccc;
+	width: 90%;
+}
+
+ul.multi-checkbox li {
+	padding: 0;
+	margin: 0;
+}
+
+div.bp-option {
+	background: #fafafa;
+	border: 1px solid #dfdfdf;
+	margin: 10px 0;
+	padding: 10px 5px;
+}
+
+div.bp-option:hover {
+	border: 1px solid #999;
+}
+
+.bp-option-icon {
+	font: normal 20px/1 'dashicons';
+	speak: none;
+	display: inline-block;
+	padding: 0 2px 0 0;
+	top: 5px;
+	position: relative;
+	vertical-align: top;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+	text-decoration: none !important;
+	color: #888;
+}
+
+div.bp-option .grabber:before {
+	content: "\f329";
+}
+
+th a {
+	background: #fff;
+	padding: 2px 5px;
+	top: -2px;
+}
+
+textarea#description,
+textarea#group_description {
+	border: 1px solid #ddd;
+	width: 100%;
+}
+
+#bp-xprofile-add-field .titlewrap,
+#bp-xprofile-add-field-group .titlewrap {
+	margin-bottom: 20px;
+}
+#bp-xprofile-add-field #post-body-content {
+	margin-bottom: 0;
+}
+
+span.member-type-none-notice,
+p.member-type-none-notice {
+	color: #f00;
+}
+
+.hide {
+	display: none;
+	visibility: hidden;
+}
Index: /tags/2.4.3/src/bp-xprofile/admin/css/admin.css
===================================================================
--- /tags/2.4.3/src/bp-xprofile/admin/css/admin.css	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/admin/css/admin.css	(revision 10410)
@@ -0,0 +1,286 @@
+/* Profile field group main admin page */
+@-o-keyframes tab-throb {
+    from { box-shadow: 0 0 10px #1e8cbe; }
+    50%  { box-shadow: 0 0 0    #1e8cbe; }
+    to   { box-shadow: 0 0 10px #1e8cbe; }
+}
+@-webkit-keyframes tab-throb {
+    from { box-shadow: 0 0 10px #1e8cbe; }
+    50%  { box-shadow: 0 0 0    #1e8cbe; }
+    to   { box-shadow: 0 0 10px #1e8cbe; }
+}
+
+@-moz-keyframes tab-throb {
+    from { box-shadow: 0 0 10px #1e8cbe; }
+    50%  { box-shadow: 0 0 0    #1e8cbe; }
+    to   { box-shadow: 0 0 10px #1e8cbe; }
+}
+
+#profile-field-form {
+	margin-top: 12px;
+}
+
+#tabs {
+	position: relative;
+	}
+	p.nofields {
+		margin: 20px 0 0;
+	}
+
+	/* Field group tabs */
+	ul#field-group-tabs {
+		float: left;
+		padding: 0 0 0 15px;
+		margin: 0;
+		display: none;
+	}
+		ul#field-group-tabs li {
+			float: left;
+			margin-right: 8px;
+			margin-bottom: -1px;
+		}
+		ul#field-group-tabs li.ui-state-hover a.ui-tab {
+			color: #d54e21;
+		}
+		ul#field-group-tabs li.ui-state-acceptable:not(.ui-state-active) a.ui-tab {
+			-webkit-animation: tab-throb 2s infinite;
+			-moz-animation: tab-throb 2s infinite;
+			-o-animation: tab-throb 2s infinite;
+			animation: tab-throb 2s infinite;
+		}
+		ul#field-group-tabs li.drop-candidate.ui-state-acceptable:not(.ui-state-active) a.ui-tab {
+			-webkit-box-shadow: 0 0 10px green;
+			-moz-box-shadow: 0 0 10px green;
+			-o-box-shadow: 0 0 10px green;
+			box-shadow: 0 0 10px green;
+			-webkit-animation: none;
+			-moz-animation: none;
+			-o-animation: none;
+			animation: none;
+			color: #000;
+		}
+		ul#field-group-tabs li a.ui-tab {
+			font-size: 14px;
+			display: block;
+			margin-top: 3px;
+			padding: 5px 10px 6px;
+			border: 1px solid #e5e5e5;
+			background-color: #f5f5f5;
+			text-decoration: none;
+			color: #000;
+			outline: none;
+		}
+
+		/* Selected tab */
+		ul#field-group-tabs li.ui-state-active a.ui-tab {
+			background-color: #fff;
+			margin-top: 0;
+			padding: 7px 10px 7px;
+			border-bottom: 1px solid transparent;
+			color: #000;
+		}
+
+	/* Toolbar */
+	.tab-toolbar {
+		clear: left;
+		background: #fff;
+		border-width: 1px;
+		border-color: #e5e5e5;
+		border-style: solid;
+		-webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.04);
+		box-shadow: 0 1px 1px rgba(0,0,0,0.04);
+		padding: 10px;
+		margin-bottom: 10px;
+	}
+
+	.bp-option a.delete,
+	.field-wrapper a.deletion,
+	.tab-toolbar a.deletion {
+		color: #f00;
+		border-bottom: 1px solid #f00;
+		text-decoration: none;
+		padding: 0;
+	}
+
+	div.delete-button {
+		float: right;
+		line-height: 28px;
+	}
+
+	div.field-group {
+		clear: left;
+	}
+
+	.field-group {
+		cursor: default;
+		border: 1px solid #e5e5e5;
+		background: #fff;
+		-webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.04);
+		box-shadow: 0 1px 1px rgba(0,0,0,0.04);
+		margin-top: 10px;
+		padding: 0 20px 20px;
+	}
+	.field-group .xprofile-field {
+		position: relative;
+	}
+	.field-group fieldset {
+		position: relative;
+		border: 1px solid #ddd;
+		margin: 20px 0 0;
+		cursor: move;
+		background: #fafafa;
+		padding: 10px 0 0 ;
+	}
+		.field-group fieldset legend {
+			position: absolute;
+			top: 10px;
+			left: 10px;
+			padding: 0;
+		}
+	fieldset.radio div div label,
+	fieldset.checkbox div label {
+		margin-right: 20px;
+	}
+
+	fieldset.clear-value {
+		margin-left: 10px;
+	}
+	fieldset div.field-wrapper {
+		padding: 25px 10px 10px;
+	}
+	.field-group div.actions {
+		float: none;
+		border-top: 1px solid #ddd;
+		margin: 10px 0 0;
+		padding-top: 10px;
+	}
+	.field-group div.actions a,
+	.field-group div.actions button,
+	.field-group div.actions input {
+		float: none;
+	}
+	.field-group fieldset:hover {
+		border-color: #999;
+	}
+	.field-group fieldset:hover div.actions {
+		display: block;
+	}
+
+	.bp-options-box .ui-sortable-placeholder,
+	#field-group-tabs .ui-sortable-placeholder,
+	.field-group fieldset.ui-sortable-placeholder {
+		border: 1px dashed #999;
+		background-color: #fff;
+		visibility: visible !important;
+	}
+
+	#field-group-tabs .ui-sortable-placeholder {
+		background: transparent;
+		border-bottom: none;
+		margin: -1px 6px -1px 0;
+	}
+
+	ul.forTab {
+		list-style: none;
+		padding: 0;
+		margin: 0 0 0 1em;
+	}
+		ul.forTab li {
+			margin: 0 0 1em 0;
+		}
+			ul.forTab li label {
+				display: block;
+			}
+
+			ul.forTab li input {
+				font-size: 1.4em;
+			}
+
+	p.success { background: green;}
+	p.err {
+		border-top: 2px solid red;
+		border-bottom: 2px solid red;
+		color: red;
+		padding: 5px 0;
+		width: 40%;
+	}
+
+	span.desc, span.signup-description {
+		display: block;
+		font-size: 11px;
+		color: #555;
+	}
+
+ul.multi-checkbox {
+	margin: 0 5px 0 0;
+	padding: .5em .9em;
+	height: 10em;
+	overflow: auto;
+	list-style: none;
+	border: solid 1px #ccc;
+	width: 90%;
+}
+
+ul.multi-checkbox li {
+	padding: 0;
+	margin: 0;
+}
+
+div.bp-option {
+	background: #fafafa;
+	border: 1px solid #dfdfdf;
+	margin: 10px 0;
+	padding: 10px 5px;
+}
+
+div.bp-option:hover {
+	border: 1px solid #999;
+}
+
+.bp-option-icon {
+	font: normal 20px/1 'dashicons';
+	speak: none;
+	display: inline-block;
+	padding: 0 0 0 2px;
+	top: 5px;
+	position: relative;
+	vertical-align: top;
+	-webkit-font-smoothing: antialiased;
+	-moz-osx-font-smoothing: grayscale;
+	text-decoration: none !important;
+	color: #888;
+}
+
+div.bp-option .grabber:before {
+	content: "\f329";
+}
+
+th a {
+	background: #fff;
+	padding: 2px 5px;
+	top: -2px;
+}
+
+textarea#description,
+textarea#group_description {
+	border: 1px solid #ddd;
+	width: 100%;
+}
+
+#bp-xprofile-add-field .titlewrap,
+#bp-xprofile-add-field-group .titlewrap {
+	margin-bottom: 20px;
+}
+#bp-xprofile-add-field #post-body-content {
+	margin-bottom: 0;
+}
+
+span.member-type-none-notice,
+p.member-type-none-notice {
+	color: #f00;
+}
+
+.hide {
+	display: none;
+	visibility: hidden;
+}
Index: /tags/2.4.3/src/bp-xprofile/admin/js/admin.js
===================================================================
--- /tags/2.4.3/src/bp-xprofile/admin/js/admin.js	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/admin/js/admin.js	(revision 10410)
@@ -0,0 +1,316 @@
+/* exported add_option, show_options, hide, fixHelper */
+/* jshint scripturl: true */
+/* global XProfileAdmin, ajaxurl */
+
+/**
+ * Add option for the forWhat type.
+ *
+ * @param {string} forWhat Value of the field to show options for
+ */
+function add_option(forWhat) {
+	var holder       = document.getElementById(forWhat + '_more'),
+		theId        = document.getElementById(forWhat + '_option_number').value,
+		newDiv       = document.createElement( 'div' ),
+		grabber      = document.createElement( 'span' ),
+		newOption    = document.createElement( 'input' ),
+		label        = document.createElement( 'label' ),
+		isDefault    = document.createElement( 'input' ),
+		txt1         = document.createTextNode( 'Default Value' ),
+		toDeleteText = document.createTextNode( 'Delete' ),
+		toDeleteWrap = document.createElement( 'div' ),
+		toDelete     = document.createElement( 'a' );
+
+	newDiv.setAttribute('id', forWhat + '_div' + theId);
+	newDiv.setAttribute('class', 'bp-option sortable');
+
+	grabber.setAttribute( 'class', 'bp-option-icon grabber');
+
+	newOption.setAttribute( 'type', 'text' );
+	newOption.setAttribute( 'name', forWhat + '_option[' + theId + ']' );
+	newOption.setAttribute( 'id', forWhat + '_option' + theId );
+
+	if ( forWhat === 'checkbox' || forWhat === 'multiselectbox' ) {
+		isDefault.setAttribute( 'type', 'checkbox' );
+		isDefault.setAttribute( 'name', 'isDefault_' + forWhat + '_option[' + theId + ']' );
+	} else {
+		isDefault.setAttribute( 'type', 'radio' );
+		isDefault.setAttribute( 'name', 'isDefault_' + forWhat + '_option' );
+	}
+
+	isDefault.setAttribute( 'value', theId );
+
+	toDelete.setAttribute( 'href', 'javascript:hide("' + forWhat + '_div' + theId + '")' );
+	toDelete.setAttribute( 'class', 'delete' );
+	toDelete.appendChild( toDeleteText );
+
+	toDeleteWrap.setAttribute( 'class', 'delete-button' );
+	toDeleteWrap.appendChild( toDelete );
+
+	label.appendChild( document.createTextNode( ' ' ) );
+	label.appendChild( isDefault );
+	label.appendChild( document.createTextNode( ' ' ) );
+	label.appendChild( txt1 );
+	label.appendChild( document.createTextNode( ' ' ) );
+
+	newDiv.appendChild( grabber );
+	newDiv.appendChild( document.createTextNode( ' ' ) );
+	newDiv.appendChild( newOption );
+	newDiv.appendChild( label );
+	newDiv.appendChild( toDeleteWrap );
+	holder.appendChild( newDiv );
+
+	// re-initialize the sortable ui
+	enableSortableFieldOptions( forWhat );
+
+	// set focus on newly created element
+	document.getElementById(forWhat + '_option' + theId).focus();
+
+	theId++;
+
+	document.getElementById(forWhat + '_option_number').value = theId;
+}
+
+/**
+ * Hide all "options" sections, and show the options section for the forWhat type.
+ *
+ * @param {string} forWhat Value of the field to show options for
+ */
+function show_options( forWhat ) {
+	for ( var i = 0; i < XProfileAdmin.supports_options_field_types.length; i++ ) {
+		document.getElementById( XProfileAdmin.supports_options_field_types[i] ).style.display = 'none';
+	}
+
+	if ( XProfileAdmin.supports_options_field_types.indexOf( forWhat ) >= 0 ) {
+		document.getElementById( forWhat ).style.display = '';
+	}
+}
+
+function hide( id ) {
+	if ( !document.getElementById( id ) ) {
+		return false;
+	}
+
+	document.getElementById( id ).style.display = 'none';
+	// the field id is [fieldtype]option[iterator] and not [fieldtype]div[iterator]
+	var field_id = id.replace( 'div', 'option' );
+	document.getElementById( field_id ).value = '';
+}
+
+/**
+ * @summary Toggles "no member type" notice.
+ *
+ * @since 2.4.0
+ */
+function toggle_no_member_type_notice() {
+	var $member_type_checkboxes = jQuery( 'input.member-type-selector' );
+
+	// No checkboxes? Nothing to do.
+	if ( ! $member_type_checkboxes.length ) {
+		return;
+	}
+
+	var has_checked = false;
+	$member_type_checkboxes.each( function() {
+		if ( jQuery( this ).is( ':checked' ) ) {
+			has_checked = true;
+			return false;
+		}
+	} );
+
+	if ( has_checked ) {
+		jQuery( 'p.member-type-none-notice' ).addClass( 'hide' );
+	} else {
+		jQuery( 'p.member-type-none-notice' ).removeClass( 'hide' );
+	}
+}
+
+var fixHelper = function(e, ui) {
+	ui.children().each(function() {
+		jQuery(this).width( jQuery(this).width() );
+	});
+	return ui;
+};
+
+function enableSortableFieldOptions() {
+	jQuery( '.bp-options-box' ).sortable( {
+		cursor: 'move',
+		items: 'div.sortable',
+		tolerance: 'intersect',
+		axis: 'y'
+	});
+
+	jQuery( '.sortable, .sortable span' ).css( 'cursor', 'move' );
+}
+
+function destroySortableFieldOptions() {
+	jQuery( '.bp-options-box' ).sortable( 'destroy' );
+	jQuery( '.sortable, .sortable span' ).css( 'cursor', 'default' );
+}
+
+function titleHint( id ) {
+	id = id || 'title';
+
+	var title = jQuery('#' + id), titleprompt = jQuery('#' + id + '-prompt-text');
+
+	if ( '' === title.val() ) {
+		titleprompt.removeClass('screen-reader-text');
+	} else {
+		titleprompt.addClass('screen-reader-text');
+	}
+
+	titleprompt.click(function(){
+		jQuery(this).addClass('screen-reader-text');
+		title.focus();
+	});
+
+	title.blur(function(){
+		if ( '' === this.value ) {
+			titleprompt.removeClass('screen-reader-text');
+		}
+	}).focus(function(){
+		titleprompt.addClass('screen-reader-text');
+	}).keydown(function(e){
+		titleprompt.addClass('screen-reader-text');
+		jQuery(this).unbind(e);
+	});
+}
+
+jQuery( document ).ready( function() {
+
+	// Set focus in Field Title, if we're on the right page
+	jQuery( '#bp-xprofile-add-field #title' ).focus();
+
+	// Set up the notice that shows when no member types are selected for a field.
+	toggle_no_member_type_notice();
+	jQuery( 'input.member-type-selector' ).on( 'change', function() {
+		toggle_no_member_type_notice();
+	} );
+
+	// Set up deleting options ajax
+	jQuery( 'a.ajax-option-delete' ).on( 'click', function() {
+		var theId = this.id.split( '-' );
+		theId = theId[1];
+
+		jQuery.post( ajaxurl, {
+			action: 'xprofile_delete_option',
+			'cookie': encodeURIComponent( document.cookie ),
+			'_wpnonce': jQuery('input#_wpnonce').val(),
+			'option_id': theId
+		},
+		function() {} );
+	} );
+
+	// Set up the sort order change actions
+	jQuery( '[id^="sort_order_"]' ).change(function() {
+		if ( jQuery( this ).val() !== 'custom' ) {
+			destroySortableFieldOptions();
+		} else {
+			enableSortableFieldOptions( jQuery('#fieldtype :selected').val() );
+		}
+	});
+
+	// Show object if JS is enabled
+	jQuery( 'ul#field-group-tabs' ).show();
+
+	// Allow reordering of field group tabs
+	jQuery( 'ul#field-group-tabs' ).sortable( {
+		cursor: 'move',
+		axis: 'x',
+		opacity: 1,
+		items: 'li',
+		tolerance: 'intersect',
+
+		update: function() {
+			jQuery.post( ajaxurl, {
+				action: 'xprofile_reorder_groups',
+				'cookie': encodeURIComponent( document.cookie ),
+				'_wpnonce_reorder_groups': jQuery( 'input#_wpnonce_reorder_groups' ).val(),
+				'group_order': jQuery( this ).sortable( 'serialize' )
+			},
+			function() {} );
+		}
+	}).disableSelection();
+
+	// Allow reordering of fields within groups
+	jQuery( 'fieldset.field-group' ).sortable({
+		cursor: 'move',
+		opacity: 0.7,
+		items: 'fieldset',
+		tolerance: 'pointer',
+
+		update: function() {
+			jQuery.post( ajaxurl, {
+				action: 'xprofile_reorder_fields',
+				'cookie': encodeURIComponent(document.cookie),
+				'_wpnonce_reorder_fields': jQuery( 'input#_wpnonce_reorder_fields' ).val(),
+				'field_order': jQuery(this).sortable( 'serialize' ),
+				'field_group_id': jQuery(this).attr( 'id' )
+			},
+			function() {} );
+		}
+	})
+
+	// Disallow text selection
+	.disableSelection();
+
+	// Allow reordering of field options
+	enableSortableFieldOptions( jQuery('#fieldtype :selected').val() );
+
+	// Handle title placeholder text the WordPress way
+	titleHint( 'title' );
+
+	// tabs init with a custom tab template and an "add" callback filling in the content
+	var $tab_items,
+		$tabs = jQuery( '#tabs' ).tabs();
+
+	set_tab_items( $tabs );
+
+	function set_tab_items( $tabs ) {
+		$tab_items = jQuery( 'ul:first li', $tabs ).droppable({
+			accept: '.connectedSortable fieldset',
+			hoverClass: 'ui-state-hover',
+			activeClass: 'ui-state-acceptable',
+			touch: 'pointer',
+			tolerance: 'pointer',
+
+			// When field is dropped on tab
+			drop: function( ev, ui ) {
+				var $item = jQuery(this), // The tab
+					$list = jQuery( $item.find( 'a' ).attr( 'href' ) ).find( '.connectedSortable' ); // The tab body
+
+				// Remove helper class
+				jQuery($item).removeClass( 'drop-candidate' );
+
+				// Hide field, change selected tab, and show new placement
+				ui.draggable.hide( 'slow', function() {
+
+					// Select new tab as current
+					$tabs.tabs( 'option', 'active', $tab_items.index( $item ) );
+
+					// Show new placement
+					jQuery(this).appendTo($list).show( 'slow' ).animate( {opacity: '1'}, 500 );
+
+					// Refresh $list variable
+					$list = jQuery( $item.find( 'a' ).attr( 'href' ) ).find( '.connectedSortable' );
+					jQuery($list).find( 'p.nofields' ).hide( 'slow' );
+
+					// Ajax update field locations and orders
+					jQuery.post( ajaxurl, {
+						action: 'xprofile_reorder_fields',
+						'cookie': encodeURIComponent(document.cookie),
+						'_wpnonce_reorder_fields': jQuery( 'input#_wpnonce_reorder_fields' ).val(),
+						'field_order': jQuery( $list ).sortable( 'serialize' ),
+						'field_group_id': jQuery( $list ).attr( 'id' )
+					},
+					function() {} );
+				});
+			},
+			over: function() {
+				jQuery(this).addClass( 'drop-candidate' );
+			},
+			out: function() {
+				jQuery(this).removeClass( 'drop-candidate' );
+			}
+		});
+	}
+});
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-actions.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-actions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-actions.php	(revision 10410)
@@ -0,0 +1,130 @@
+<?php
+/**
+ * BuddyPress XProfile Actions.
+ *
+ * Action functions are exactly the same as screen functions, however they do not
+ * have a template screen associated with them. Usually they will send the user
+ * back to the default screen after execution.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileActions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * This function runs when an action is set for a screen:
+ * example.com/members/andy/profile/change-avatar/ [delete-avatar]
+ *
+ * The function will delete the active avatar for a user.
+ *
+ * @uses bp_core_delete_avatar() Deletes the active avatar for the logged in user.
+ * @uses add_action() Runs a specific function for an action when it fires.
+ */
+function xprofile_action_delete_avatar() {
+
+	if ( ! bp_is_user_change_avatar() || ! bp_is_action_variable( 'delete-avatar', 0 ) ) {
+		return false;
+	}
+
+	// Check the nonce.
+	check_admin_referer( 'bp_delete_avatar_link' );
+
+	if ( ! bp_is_my_profile() && ! bp_current_user_can( 'bp_moderate' ) ) {
+		return false;
+	}
+
+	if ( bp_core_delete_existing_avatar( array( 'item_id' => bp_displayed_user_id() ) ) ) {
+		bp_core_add_message( __( 'Your profile photo was deleted successfully!', 'buddypress' ) );
+	} else {
+		bp_core_add_message( __( 'There was a problem deleting your profile photo. Please try again.', 'buddypress' ), 'error' );
+	}
+
+	bp_core_redirect( wp_get_referer() );
+}
+add_action( 'bp_actions', 'xprofile_action_delete_avatar' );
+
+/**
+ * Handles the saving of xprofile field visibilities.
+ *
+ * @since 1.9.0
+ */
+function bp_xprofile_action_settings() {
+
+	// Bail if not a POST action.
+	if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) ) {
+		return;
+	}
+
+	// Bail if no submit action.
+	if ( ! isset( $_POST['xprofile-settings-submit'] ) ) {
+		return;
+	}
+
+	// Bail if not in settings.
+	if ( ! bp_is_user_settings_profile() ) {
+		return;
+	}
+
+	// 404 if there are any additional action variables attached
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	// Nonce check.
+	check_admin_referer( 'bp_xprofile_settings' );
+
+	/**
+	 * Fires before saving xprofile field visibilities.
+	 *
+	 * @since 2.0.0
+	 */
+	do_action( 'bp_xprofile_settings_before_save' );
+
+	/** Save ******************************************************************/
+
+	// Only save if there are field ID's being posted.
+	if ( ! empty( $_POST['field_ids'] ) ) {
+
+		// Get the POST'ed field ID's.
+		$posted_field_ids = explode( ',', $_POST['field_ids'] );
+
+		// Backward compatibility: a bug in BP 2.0 caused only a single
+		// group's field IDs to be submitted. Look for values submitted
+		// in the POST request that may not appear in 'field_ids', and
+		// add them to the list of IDs to save.
+		foreach ( $_POST as $posted_key => $posted_value ) {
+			preg_match( '/^field_([0-9]+)_visibility$/', $posted_key, $matches );
+			if ( ! empty( $matches[1] ) && ! in_array( $matches[1], $posted_field_ids ) ) {
+				$posted_field_ids[] = $matches[1];
+			}
+		}
+
+		// Save the visibility settings.
+		foreach ( $posted_field_ids as $field_id ) {
+
+			$visibility_level = 'public';
+
+			if ( !empty( $_POST['field_' . $field_id . '_visibility'] ) ) {
+				$visibility_level = $_POST['field_' . $field_id . '_visibility'];
+			}
+
+			xprofile_set_field_visibility_level( $field_id, bp_displayed_user_id(), $visibility_level );
+		}
+	}
+
+	/** Other *****************************************************************/
+
+	/**
+	 * Fires after saving xprofile field visibilities.
+	 *
+	 * @since 2.0.0
+	 */
+	do_action( 'bp_xprofile_settings_after_save' );
+
+	// Redirect to the root domain.
+	bp_core_redirect( bp_displayed_user_domain() . bp_get_settings_slug() . '/profile' );
+}
+add_action( 'bp_actions', 'bp_xprofile_action_settings' );
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-activity.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-activity.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-activity.php	(revision 10410)
@@ -0,0 +1,353 @@
+<?php
+/**
+ * BuddyPress XProfile Activity & Notification Functions.
+ *
+ * These functions handle the recording, deleting and formatting of activity
+ * items and notifications for the user and for this specific component.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileActivity
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Register the activity actions for the Extended Profile component.
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_activity_set_action() To setup the individual actions.
+ */
+function xprofile_register_activity_actions() {
+
+	// Register the activity stream actions for this component.
+	bp_activity_set_action(
+		// Older avatar activity items use 'profile' for component. See r4273.
+		'profile',
+		'new_avatar',
+		__( 'Member changed profile picture', 'buddypress' ),
+		'bp_xprofile_format_activity_action_new_avatar',
+		__( 'Updated Profile Photos', 'buddypress' )
+	);
+
+	bp_activity_set_action(
+		buddypress()->profile->id,
+		'updated_profile',
+		__( 'Updated Profile', 'buddypress' ),
+		'bp_xprofile_format_activity_action_updated_profile',
+		__( 'Profile Updates', 'buddypress' ),
+		array( 'activity' )
+	);
+
+	/**
+	 * Fires after the registration of default activity actions for Extended Profile component.
+	 *
+	 * @since 1.1.0
+	 */
+	do_action( 'xprofile_register_activity_actions' );
+}
+add_action( 'bp_register_activity_actions', 'xprofile_register_activity_actions' );
+
+/**
+ * Format 'new_avatar' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity object.
+ *
+ * @return string
+ */
+function bp_xprofile_format_activity_action_new_avatar( $action, $activity ) {
+	$userlink = bp_core_get_userlink( $activity->user_id );
+	$action   = sprintf( __( '%s changed their profile picture', 'buddypress' ), $userlink );
+
+	// Legacy filter - pass $user_id instead of $activity.
+	if ( has_filter( 'bp_xprofile_new_avatar_action' ) ) {
+		$action = apply_filters( 'bp_xprofile_new_avatar_action', $action, $activity->user_id );
+	}
+
+	/**
+	 * Filters the formatted 'new_avatar' activity stream action.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $action   Formatted action for activity stream.
+	 * @param object $activity Activity object.
+	 */
+	return apply_filters( 'bp_xprofile_format_activity_action_new_avatar', $action, $activity );
+}
+
+/**
+ * Format 'updated_profile' activity actions.
+ *
+ * @since 2.0.0
+ *
+ * @param string $action   Static activity action.
+ * @param object $activity Activity object.
+ *
+ * @return string
+ */
+function bp_xprofile_format_activity_action_updated_profile( $action, $activity ) {
+
+	// Note for translators: The natural phrasing in English, "Joe updated
+	// his profile", requires that we know Joe's gender, which we don't. If
+	// your language doesn't have this restriction, feel free to use a more
+	// natural translation.
+	$profile_link = trailingslashit( bp_core_get_user_domain( $activity->user_id ) . bp_get_profile_slug() );
+	$action	      = sprintf( __( "%s's profile was updated", 'buddypress' ), '<a href="' . $profile_link . '">' . bp_core_get_user_displayname( $activity->user_id ) . '</a>' );
+
+	/**
+	 * Filters the formatted 'updated_profile' activity stream action.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $action   Formatted action for activity stream.
+	 * @param object $activity Activity object.
+	 */
+	return apply_filters( 'bp_xprofile_format_activity_action_updated_profile', $action, $activity );
+}
+
+/**
+ * Records activity for the logged in user within the profile component so that
+ * it will show in the users activity stream (if installed).
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_activity_add() Adds an entry to the activity component tables for a specific activity.
+ *
+ * @param array|string $args String containing all variables used after bp_parse_args() call.
+ *
+ * @return array
+ */
+function xprofile_record_activity( $args = '' ) {
+
+	// Bail if activity component is not active.
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	// Parse the arguments.
+	$r = bp_parse_args( $args, array(
+		'user_id'           => bp_loggedin_user_id(),
+		'action'            => '',
+		'content'           => '',
+		'primary_link'      => '',
+		'component'         => buddypress()->profile->id,
+		'type'              => false,
+		'item_id'           => false,
+		'secondary_item_id' => false,
+		'recorded_time'     => bp_core_current_time(),
+		'hide_sitewide'     => false
+	) );
+
+	return bp_activity_add( $r );
+}
+
+/**
+ * Deletes activity for a user within the profile component so that it will be
+ * removed from the users activity stream and sitewide stream (if installed).
+ *
+ * @since 1.0.0
+ *
+ * @uses bp_activity_delete() Deletes an entry to the activity component tables
+ *                            for a specific activity.
+ *
+ * @param array|string $args Containing all variables used after bp_parse_args() call.
+ *
+ * @return bool
+ */
+function xprofile_delete_activity( $args = '' ) {
+
+	// Bail if activity component is not active.
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	// Parse the arguments.
+	$r = bp_parse_args( $args, array(
+		'component' => buddypress()->profile->id
+	), 'xprofile_delete_activity' );
+
+	// Delete the activity item.
+	bp_activity_delete_by_item_id( $r );
+}
+
+/**
+ * Register an activity action for the Extended Profiles component.
+ *
+ * @since 1.0.0
+ *
+ * @param string $key Key.
+ * @param string $value Value.
+ *
+ * @return bool True if success, false on failure.
+ */
+function xprofile_register_activity_action( $key, $value ) {
+
+	// Bail if activity component is not active.
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	/**
+	 * Filters the return value of bp_activity_set_action.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool   $value Whether or not an action was successfully registered.
+	 * @param string $key   Key used for the registered action.
+	 * @param string $value Value used for the registered action.
+	 */
+	return apply_filters( 'xprofile_register_activity_action', bp_activity_set_action( buddypress()->profile->id, $key, $value ), $key, $value );
+}
+
+/**
+ * Adds an activity stream item when a user has uploaded a new avatar.
+ *
+ * @since 1.0.0
+ * @since 2.3.4 Add new parameter to get the user id the avatar was set for.
+ *
+ * @uses bp_activity_add() Adds an entry to the activity component tables for a
+ *                         specific activity
+ *
+ * @param int $user_id The user id the avatar was set for.
+ *
+ * @return bool
+ */
+function bp_xprofile_new_avatar_activity( $user_id = 0 ) {
+
+	// Bail if activity component is not active.
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	if ( empty( $user_id ) ) {
+		$user_id = bp_displayed_user_id();
+	}
+
+	/**
+	 * Filters the user ID when a user has uploaded a new avatar.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param int $user_id ID of the user the avatar was set for.
+	 */
+	$user_id = apply_filters( 'bp_xprofile_new_avatar_user_id', $user_id );
+
+	// Add the activity.
+	bp_activity_add( array(
+		'user_id'   => $user_id,
+		'component' => 'profile',
+		'type'      => 'new_avatar'
+	) );
+}
+add_action( 'xprofile_avatar_uploaded', 'bp_xprofile_new_avatar_activity' );
+
+/**
+ * Add an activity item when a user has updated his profile.
+ *
+ * @since 2.0.0
+ *
+ * @param int   $user_id    ID of the user who has updated his profile.
+ * @param array $field_ids  IDs of the fields submitted.
+ * @param bool  $errors     True if validation or saving errors occurred, otherwise false.
+ * @param array $old_values Pre-save xprofile field values and visibility levels.
+ * @param array $new_values Post-save xprofile field values and visibility levels.
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_xprofile_updated_profile_activity( $user_id, $field_ids = array(), $errors = false, $old_values = array(), $new_values = array() ) {
+
+	// If there were errors, don't post.
+	if ( ! empty( $errors ) ) {
+		return false;
+	}
+
+	// Bail if activity component is not active.
+	if ( ! bp_is_active( 'activity' ) ) {
+		return false;
+	}
+
+	// Don't post if there have been no changes, or if the changes are
+	// related solely to non-public fields.
+	$public_changes = false;
+	foreach ( $new_values as $field_id => $new_value ) {
+		$old_value = isset( $old_values[ $field_id ] ) ? $old_values[ $field_id ] : '';
+
+		// Don't register changes to private fields.
+		if ( empty( $new_value['visibility'] ) || ( 'public' !== $new_value['visibility'] ) ) {
+			continue;
+		}
+
+		// Don't register if there have been no changes.
+		if ( $new_value === $old_value ) {
+			continue;
+		}
+
+		// Looks like we have public changes - no need to keep checking.
+		$public_changes = true;
+		break;
+	}
+
+	// Bail if no public changes.
+	if ( empty( $public_changes ) ) {
+		return false;
+	}
+
+	// Throttle to one activity of this type per 2 hours.
+	$existing = bp_activity_get( array(
+		'max'    => 1,
+		'filter' => array(
+			'user_id' => $user_id,
+			'object'  => buddypress()->profile->id,
+			'action'  => 'updated_profile',
+		),
+	) );
+
+	// Default throttle time is 2 hours. Filter to change (in seconds).
+	if ( ! empty( $existing['activities'] ) ) {
+
+		/**
+		 * Filters the throttle time, in seconds, used to prevent excessive activity posting.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param int $value Throttle time, in seconds.
+		 */
+		$throttle_period = apply_filters( 'bp_xprofile_updated_profile_activity_throttle_time', HOUR_IN_SECONDS * 2 );
+		$then            = strtotime( $existing['activities'][0]->date_recorded );
+		$now             = strtotime( bp_core_current_time() );
+
+		// Bail if throttled.
+		if ( ( $now - $then ) < $throttle_period ) {
+			return false;
+		}
+	}
+
+	// If we've reached this point, assemble and post the activity item.
+	$profile_link = trailingslashit( bp_core_get_user_domain( $user_id ) . bp_get_profile_slug() );
+
+	return (bool) xprofile_record_activity( array(
+		'user_id'      => $user_id,
+		'primary_link' => $profile_link,
+		'component'    => buddypress()->profile->id,
+		'type'         => 'updated_profile',
+	) );
+}
+add_action( 'xprofile_updated_profile', 'bp_xprofile_updated_profile_activity', 10, 5 );
+
+/**
+ * Add filters for xprofile activity types to Show dropdowns.
+ *
+ * @since 2.0.0
+ * @todo Mark as deprecated
+ */
+function xprofile_activity_filter_options() {
+	?>
+
+	<option value="updated_profile"><?php _e( 'Profile Updates', 'buddypress' ) ?></option>
+
+	<?php
+}
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-admin.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-admin.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-admin.php	(revision 10410)
@@ -0,0 +1,1136 @@
+<?php
+/**
+ * BuddyPress XProfile Admin.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileAdmin
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Creates the administration interface menus and checks to see if the DB
+ * tables are set up.
+ *
+ * @uses bp_current_user_can() returns true if the current user is a site admin, false if not.
+ * @uses add_users_page() Adds a submenu tab to a top level tab in the admin area.
+ *
+ * @return bool
+ */
+function xprofile_add_admin_menu() {
+
+	// Bail if current user cannot moderate community.
+	if ( ! bp_current_user_can( 'bp_moderate' ) ) {
+		return false;
+	}
+
+	add_users_page( _x( 'Profile Fields', 'xProfile admin page title', 'buddypress' ), _x( 'Profile Fields', 'Admin Users menu', 'buddypress' ), 'manage_options', 'bp-profile-setup', 'xprofile_admin' );
+}
+add_action( bp_core_admin_hook(), 'xprofile_add_admin_menu' );
+
+/**
+ * Handles all actions for the admin area for creating, editing and deleting
+ * profile groups and fields.
+ *
+ * @param string $message Message to display.
+ * @param string $type    Type of action to be displayed.
+ */
+function xprofile_admin( $message = '', $type = 'error' ) {
+
+	if ( isset( $_GET['mode'] ) && isset( $_GET['group_id'] ) && 'add_field' == $_GET['mode'] ) {
+		xprofile_admin_manage_field( $_GET['group_id'] );
+
+	} elseif ( isset( $_GET['mode'] ) && isset( $_GET['group_id'] ) && isset( $_GET['field_id'] ) && 'edit_field' == $_GET['mode'] ) {
+		xprofile_admin_manage_field( $_GET['group_id'], $_GET['field_id'] );
+
+	} elseif ( isset( $_GET['mode'] ) && isset( $_GET['field_id'] ) && 'delete_field' == $_GET['mode'] ) {
+		xprofile_admin_delete_field( $_GET['field_id'], 'field');
+
+	} elseif ( isset( $_GET['mode'] ) && isset( $_GET['option_id'] ) && 'delete_option' == $_GET['mode'] ) {
+		xprofile_admin_delete_field( $_GET['option_id'], 'option' );
+
+	} elseif ( isset( $_GET['mode'] ) && 'add_group' == $_GET['mode'] ) {
+		xprofile_admin_manage_group();
+
+	} elseif ( isset( $_GET['mode'] ) && isset( $_GET['group_id'] ) && 'delete_group' == $_GET['mode'] ) {
+		xprofile_admin_delete_group( $_GET['group_id'] );
+
+	} elseif ( isset( $_GET['mode'] ) && isset( $_GET['group_id'] ) && 'edit_group' == $_GET['mode'] ) {
+		xprofile_admin_manage_group( $_GET['group_id'] );
+
+	} else {
+		xprofile_admin_screen( $message, $type );
+	}
+}
+
+/**
+ * Output the main XProfile management screen
+ *
+ * @since 2.3.0
+ *
+ * @param string $message Feedback message.
+ * @param string $type    Feedback type.
+ *
+ * @todo Improve error message output
+ */
+function xprofile_admin_screen( $message = '', $type = 'error' ) {
+
+	// Validate type.
+	$type = preg_replace( '|[^a-z]|i', '', $type );
+
+	// Get all of the profile groups & fields.
+	$groups = bp_xprofile_get_groups( array(
+		'fetch_fields' => true
+	) ); ?>
+
+	<div class="wrap">
+
+		<h2>
+			<?php _ex( 'Profile Fields', 'Settings page header', 'buddypress'); ?>
+			<a id="add_group" class="add-new-h2" href="users.php?page=bp-profile-setup&amp;mode=add_group"><?php _e( 'Add New Field Group', 'buddypress' ); ?></a>
+		</h2>
+
+		<form action="" id="profile-field-form" method="post">
+
+			<?php
+
+			wp_nonce_field( 'bp_reorder_fields', '_wpnonce_reorder_fields'        );
+			wp_nonce_field( 'bp_reorder_groups', '_wpnonce_reorder_groups', false );
+
+			if ( !empty( $message ) ) :
+				$type = ( $type == 'error' ) ? 'error' : 'updated'; ?>
+
+				<div id="message" class="<?php echo $type; ?> fade">
+					<p><?php echo esc_html( $message ); ?></p>
+				</div>
+
+			<?php endif; ?>
+
+			<div id="tabs">
+				<ul id="field-group-tabs">
+
+					<?php if ( !empty( $groups ) ) : foreach ( $groups as $group ) : ?>
+
+						<li id="group_<?php echo esc_attr( $group->id ); ?>">
+							<a href="#tabs-<?php echo esc_attr( $group->id ); ?>" class="ui-tab">
+								<?php
+								/** This filter is documented in bp-xprofile/bp-xprofile-template.php */
+								echo esc_html( apply_filters( 'bp_get_the_profile_group_name', $group->name ) );
+								?>
+
+								<?php if ( !$group->can_delete ) : ?>
+									<?php _e( '(Primary)', 'buddypress'); ?>
+								<?php endif; ?>
+
+							</a>
+						</li>
+
+					<?php endforeach; endif; ?>
+
+				</ul>
+
+				<?php if ( !empty( $groups ) ) : foreach ( $groups as $group ) : ?>
+
+					<noscript>
+						<h3><?php
+						/** This filter is documented in bp-xprofile/bp-xprofile-template.php */
+						echo esc_html( apply_filters( 'bp_get_the_profile_group_name', $group->name ) );
+						?></h3>
+					</noscript>
+
+					<div id="tabs-<?php echo esc_attr( $group->id ); ?>" class="tab-wrapper">
+						<div class="tab-toolbar">
+							<div class="tab-toolbar-left">
+								<a class="button-primary" href="users.php?page=bp-profile-setup&amp;group_id=<?php echo esc_attr( $group->id ); ?>&amp;mode=add_field"><?php _e( 'Add New Field', 'buddypress' ); ?></a>
+								<a class="button edit" href="users.php?page=bp-profile-setup&amp;mode=edit_group&amp;group_id=<?php echo esc_attr( $group->id ); ?>"><?php _e( 'Edit Group', 'buddypress' ); ?></a>
+
+								<?php if ( $group->can_delete ) : ?>
+
+									<div class="delete-button">
+										<a class="confirm submitdelete deletion ajax-option-delete" href="users.php?page=bp-profile-setup&amp;mode=delete_group&amp;group_id=<?php echo esc_attr( $group->id ); ?>"><?php _e( 'Delete Group', 'buddypress' ); ?></a>
+									</div>
+
+								<?php endif; ?>
+
+								<?php
+
+								/**
+								 * Fires at end of action buttons in xprofile management admin.
+								 *
+								 * @since 2.2.0
+								 *
+								 * @param BP_XProfile_Group $group BP_XProfile_Group object
+								 *                                 for the current group.
+								 */
+								do_action( 'xprofile_admin_group_action', $group ); ?>
+
+							</div>
+						</div>
+
+						<?php if ( ! empty( $group->description ) ) : ?>
+
+							<p><?php
+							/** This filter is documented in bp-xprofile/bp-xprofile-template.php */
+							echo esc_html( apply_filters( 'bp_get_the_profile_group_description', $group->description ) );
+							?></p>
+
+						<?php endif; ?>
+
+						<fieldset id="<?php echo esc_attr( $group->id ); ?>" class="connectedSortable field-group">
+							<legend class="screen-reader-text"><?php
+							/** This filter is documented in bp-xprofile/bp-xprofile-template.php */
+							printf( esc_html__( 'Fields for "%s" Group', 'buddypress' ), apply_filters( 'bp_get_the_profile_group_name', $group->name ) );
+							?></legend>
+
+							<?php
+
+							if ( !empty( $group->fields ) ) :
+								foreach ( $group->fields as $field ) {
+
+									// Load the field.
+									$field = xprofile_get_field( $field->id );
+
+									$class = '';
+									if ( empty( $field->can_delete ) ) {
+										$class = ' core nodrag';
+									}
+
+									/**
+									 * This function handles the WYSIWYG profile field
+									 * display for the xprofile admin setup screen.
+									 */
+									xprofile_admin_field( $field, $group, $class );
+
+								} // end for
+
+							else : // !$group->fields ?>
+
+								<p class="nodrag nofields"><?php _e( 'There are no fields in this group.', 'buddypress' ); ?></p>
+
+							<?php endif; // End $group->fields. ?>
+
+						</fieldset>
+
+						<?php if ( empty( $group->can_delete ) ) : ?>
+
+							<p><?php esc_html_e( '* Fields in this group appear on the signup page.', 'buddypress' ); ?></p>
+
+						<?php endif; ?>
+
+					</div>
+
+				<?php endforeach; else : ?>
+
+					<div id="message" class="error"><p><?php _e( 'You have no groups.', 'buddypress' ); ?></p></div>
+					<p><a href="users.php?page=bp-profile-setup&amp;mode=add_group"><?php _e( 'Add New Group', 'buddypress' ); ?></a></p>
+
+				<?php endif; ?>
+
+			</div>
+		</form>
+	</div>
+
+<?php
+}
+
+/**
+ * Handles the adding or editing of groups.
+ *
+ * @param int|null $group_id Group ID to manage.
+ */
+function xprofile_admin_manage_group( $group_id = null ) {
+	global $message, $type;
+
+	// Get the field group.
+	$group = new BP_XProfile_Group( $group_id );
+
+	// Updating.
+	if ( isset( $_POST['save_group'] ) ) {
+
+		// Validate $_POSTed data.
+		if ( BP_XProfile_Group::admin_validate() ) {
+
+			// Set the group name.
+			$group->name = $_POST['group_name'];
+
+			// Set the group description.
+			if ( ! empty( $_POST['group_description'] ) ) {
+				$group->description = $_POST['group_description'];
+			} else {
+				$group->description = '';
+			}
+
+			// Attempt to save the field group.
+			if ( false === $group->save() ) {
+				$message = __( 'There was an error saving the group. Please try again.', 'buddypress' );
+				$type    = 'error';
+
+			// Save successful.
+			} else {
+				$message = __( 'The group was saved successfully.', 'buddypress' );
+				$type    = 'success';
+
+				// @todo remove these old options
+				if ( 1 == $group_id ) {
+					bp_update_option( 'bp-xprofile-base-group-name', $group->name );
+				}
+
+				/**
+				 * Fires at the end of the group adding/saving process, if successful.
+				 *
+				 * @since 1.0.0
+				 *
+				 * @param BP_XProfile_Group $group Current BP_XProfile_Group object.
+				 */
+				do_action( 'xprofile_groups_saved_group', $group );
+			}
+
+			unset( $_GET['mode'] );
+			xprofile_admin( $message, $type );
+
+		} else {
+			$group->render_admin_form( $message );
+		}
+	} else {
+		$group->render_admin_form();
+	}
+}
+
+/**
+ * Handles the deletion of profile data groups.
+ *
+ * @param int $group_id ID of the group to delete.
+ */
+function xprofile_admin_delete_group( $group_id ) {
+	global $message, $type;
+
+	$group = new BP_XProfile_Group( $group_id );
+
+	if ( !$group->delete() ) {
+		$message = __( 'There was an error deleting the group. Please try again.', 'buddypress' );
+		$type    = 'error';
+	} else {
+		$message = __( 'The group was deleted successfully.', 'buddypress' );
+		$type    = 'success';
+
+		/**
+		 * Fires at the end of group deletion process, if successful.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_XProfile_Group $group Current BP_XProfile_Group object.
+		 */
+		do_action( 'xprofile_groups_deleted_group', $group );
+	}
+
+	unset( $_GET['mode'] );
+	xprofile_admin( $message, $type );
+}
+
+/**
+ * Handles the adding or editing of profile field data for a user.
+ *
+ * @param int      $group_id ID of the group.
+ * @param int|null $field_id ID of the field being managed.
+ */
+function xprofile_admin_manage_field( $group_id, $field_id = null ) {
+	global $wpdb, $message, $groups;
+
+	$bp = buddypress();
+
+	if ( is_null( $field_id ) ) {
+		$field = new BP_XProfile_Field();
+	} else {
+		$field = xprofile_get_field( $field_id );
+	}
+
+	$field->group_id = $group_id;
+
+	if ( isset( $_POST['saveField'] ) ) {
+		if ( BP_XProfile_Field::admin_validate() ) {
+			$field->is_required = $_POST['required'];
+			$field->type        = $_POST['fieldtype'];
+			$field->name        = $_POST['title'];
+
+			if ( ! empty( $_POST['description'] ) ) {
+				$field->description = $_POST['description'];
+			} else {
+				$field->description = '';
+			}
+
+			if ( ! empty( $_POST["sort_order_{$field->type}"] ) ) {
+				$field->order_by = $_POST["sort_order_{$field->type}"];
+			}
+
+			$field->field_order = $wpdb->get_var( $wpdb->prepare( "SELECT field_order FROM {$bp->profile->table_name_fields} WHERE id = %d", $field_id ) );
+			if ( empty( $field->field_order ) || is_wp_error( $field->field_order ) ) {
+				$field->field_order = (int) $wpdb->get_var( $wpdb->prepare( "SELECT max(field_order) FROM {$bp->profile->table_name_fields} WHERE group_id = %d", $group_id ) );
+				$field->field_order++;
+			}
+
+			// For new profile fields, set the $field_id. For existing profile
+			// fields, this will overwrite $field_id with the same value.
+			$field_id = $field->save();
+
+			if ( empty( $field_id ) ) {
+				$message = __( 'There was an error saving the field. Please try again.', 'buddypress' );
+				$type    = 'error';
+			} else {
+				$message = __( 'The field was saved successfully.', 'buddypress' );
+				$type    = 'success';
+
+				// @todo remove these old options
+				if ( 1 == $field_id ) {
+					bp_update_option( 'bp-xprofile-fullname-field-name', $field->name );
+				}
+
+				// Set member types.
+				if ( isset( $_POST['has-member-types'] ) ) {
+					$member_types = array();
+					if ( isset( $_POST['member-types'] ) ) {
+						$member_types = stripslashes_deep( $_POST['member-types'] );
+					}
+
+					$field->set_member_types( $member_types );
+				}
+
+				// Validate default visibility.
+				if ( ! empty( $_POST['default-visibility'] ) && in_array( $_POST['default-visibility'], wp_list_pluck( bp_xprofile_get_visibility_levels(), 'id' ) ) ) {
+					bp_xprofile_update_field_meta( $field_id, 'default_visibility', $_POST['default-visibility'] );
+				}
+
+				// Validate custom visibility.
+				if ( ! empty( $_POST['allow-custom-visibility'] ) && in_array( $_POST['allow-custom-visibility'], array( 'allowed', 'disabled' ) ) ) {
+					bp_xprofile_update_field_meta( $field_id, 'allow_custom_visibility', $_POST['allow-custom-visibility'] );
+				}
+
+				// Validate signup.
+				if ( ! empty( $_POST['signup-position'] ) ) {
+					bp_xprofile_update_field_meta( $field_id, 'signup_position', (int) $_POST['signup-position'] );
+				} else {
+					bp_xprofile_delete_meta( $field_id, 'field', 'signup_position' );
+				}
+
+				/**
+				 * Fires at the end of the process to save a field for a user, if successful.
+				 *
+				 * @since 1.0.0
+				 *
+				 * @param BP_XProfile_Field $field Current BP_XProfile_Field object.
+				 */
+				do_action( 'xprofile_fields_saved_field', $field );
+
+				$groups = bp_xprofile_get_groups();
+			}
+
+			unset( $_GET['mode'] );
+
+			xprofile_admin( $message, $type );
+
+		} else {
+			$field->render_admin_form( $message );
+		}
+	} else {
+		$field->render_admin_form();
+	}
+}
+
+/**
+ * Handles the deletion of a profile field (or field option).
+ *
+ * @since 1.0.0
+ * @global string $message The feedback message to show.
+ * @global $type The type of feedback message to show.
+ *
+ * @param int    $field_id    The field to delete.
+ * @param string $field_type  The type of field being deleted.
+ * @param bool   $delete_data Should the field data be deleted too.
+ */
+function xprofile_admin_delete_field( $field_id, $field_type = 'field', $delete_data = false ) {
+	global $message, $type;
+
+	// Switch type to 'option' if type is not 'field'.
+	// @todo trust this param.
+	$field_type  = ( 'field' == $field_type ) ? __( 'field', 'buddypress' ) : __( 'option', 'buddypress' );
+	$field       = xprofile_get_field( $field_id );
+
+	if ( !$field->delete( (bool) $delete_data ) ) {
+		$message = sprintf( __( 'There was an error deleting the %s. Please try again.', 'buddypress' ), $field_type );
+		$type    = 'error';
+	} else {
+		$message = sprintf( __( 'The %s was deleted successfully!', 'buddypress' ), $field_type );
+		$type    = 'success';
+
+		/**
+		 * Fires at the end of the field deletion process, if successful.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_XProfile_Field $field Current BP_XProfile_Field object.
+		 */
+		do_action( 'xprofile_fields_deleted_field', $field );
+	}
+
+	unset( $_GET['mode'] );
+	xprofile_admin( $message, $type );
+}
+
+/**
+ * Handles the ajax reordering of fields within a group.
+ */
+function xprofile_ajax_reorder_fields() {
+
+	// Check the nonce.
+	check_admin_referer( 'bp_reorder_fields', '_wpnonce_reorder_fields' );
+
+	if ( empty( $_POST['field_order'] ) ) {
+		return false;
+	}
+
+	parse_str( $_POST['field_order'], $order );
+
+	$field_group_id = $_POST['field_group_id'];
+
+	foreach ( (array) $order['draggable_field'] as $position => $field_id ) {
+		xprofile_update_field_position( (int) $field_id, (int) $position, (int) $field_group_id );
+	}
+}
+add_action( 'wp_ajax_xprofile_reorder_fields', 'xprofile_ajax_reorder_fields' );
+
+/**
+ * Handles the reordering of field groups.
+ */
+function xprofile_ajax_reorder_field_groups() {
+
+	// Check the nonce.
+	check_admin_referer( 'bp_reorder_groups', '_wpnonce_reorder_groups' );
+
+	if ( empty( $_POST['group_order'] ) ) {
+		return false;
+	}
+
+	parse_str( $_POST['group_order'], $order );
+
+	foreach ( (array) $order['group'] as $position => $field_group_id ) {
+		xprofile_update_field_group_position( (int) $field_group_id, (int) $position );
+	}
+}
+add_action( 'wp_ajax_xprofile_reorder_groups', 'xprofile_ajax_reorder_field_groups' );
+
+/**
+ * Handles the WYSIWYG display of each profile field on the edit screen.
+ *
+ * @param object $admin_field Admin field.
+ * @param object $admin_group Admin group object.
+ * @param string $class       Classes to append to output.
+ */
+function xprofile_admin_field( $admin_field, $admin_group, $class = '' ) {
+	global $field;
+
+	$field = $admin_field; ?>
+
+	<fieldset id="draggable_field_<?php echo esc_attr( $field->id ); ?>"class="sortable<?php echo ' ' . $field->type; if ( !empty( $class ) ) echo ' ' . $class; ?>">
+		<legend>
+			<span>
+				<?php bp_the_profile_field_name(); ?>
+
+				<?php if ( empty( $field->can_delete )                                    ) : ?><?php esc_html_e( '(Primary)',  'buddypress' ); endif; ?>
+				<?php bp_the_profile_field_required_label(); ?>
+				<?php if ( bp_xprofile_get_meta( $field->id, 'field', 'signup_position' ) ) : ?><?php esc_html_e( '(Sign-up)',  'buddypress' ); endif; ?>
+				<?php if ( bp_get_member_types() ) : echo $field->get_member_type_label(); endif; ?>
+
+				<?php
+
+				/**
+				 * Fires at end of legend above the name field in base xprofile group.
+				 *
+				 * @since 2.2.0
+				 *
+				 * @param BP_XProfile_Field $field Current BP_XProfile_Field
+				 *                                 object being rendered.
+				 */
+				do_action( 'xprofile_admin_field_name_legend', $field ); ?>
+			</span>
+		</legend>
+		<div class="field-wrapper">
+
+			<?php
+			if ( in_array( $field->type, array_keys( bp_xprofile_get_field_types() ) ) ) {
+				$field_type = bp_xprofile_create_field_type( $field->type );
+				$field_type->admin_field_html();
+			} else {
+
+				/**
+				 * Fires after the input if the current field is not in default field types.
+				 *
+				 * @since 1.5.0
+				 *
+				 * @param BP_XProfile_Field $field Current BP_XProfile_Field
+				 *                                 object being rendered.
+				 * @param int               $value Integer 1.
+				 */
+				do_action( 'xprofile_admin_field', $field, 1 );
+			}
+			?>
+
+			<?php if ( $field->description ) : ?>
+
+				<p class="description"><?php echo esc_attr( $field->description ); ?></p>
+
+			<?php endif; ?>
+
+			<div class="actions">
+				<a class="button edit" href="users.php?page=bp-profile-setup&amp;group_id=<?php echo esc_attr( $admin_group->id ); ?>&amp;field_id=<?php echo esc_attr( $field->id ); ?>&amp;mode=edit_field"><?php _e( 'Edit', 'buddypress' ); ?></a>
+
+				<?php if ( $field->can_delete ) : ?>
+
+					<div class="delete-button">
+						<a class="confirm submit-delete deletion" href="users.php?page=bp-profile-setup&amp;field_id=<?php echo esc_attr( $field->id ); ?>&amp;mode=delete_field"><?php _e( 'Delete', 'buddypress' ); ?></a>
+					</div>
+
+				<?php endif; ?>
+
+				<?php
+
+				/**
+				 * Fires at end of field management links in xprofile management admin.
+				 *
+				 * @since 2.2.0
+				 *
+				 * @param BP_XProfile_Group $group BP_XProfile_Group object
+				 *                                 for the current group.
+				 */
+				do_action( 'xprofile_admin_field_action', $field ); ?>
+
+			</div>
+		</div>
+	</fieldset>
+
+<?php
+}
+
+/**
+ * Print <option> elements containing the xprofile field types.
+ *
+ * @since 2.0.0
+ *
+ * @param string $select_field_type The name of the field type that should be selected.
+ *                                  Will defaults to "textbox" if NULL is passed.
+ */
+function bp_xprofile_admin_form_field_types( $select_field_type ) {
+	$categories = array();
+
+	if ( is_null( $select_field_type ) ) {
+		$select_field_type = 'textbox';
+	}
+
+	// Sort each field type into its category.
+	foreach ( bp_xprofile_get_field_types() as $field_name => $field_class ) {
+		$field_type_obj = new $field_class;
+		$the_category   = $field_type_obj->category;
+
+		// Fallback to a catch-all if category not set.
+		if ( ! $the_category ) {
+			$the_category = _x( 'Other', 'xprofile field type category', 'buddypress' );
+		}
+
+		if ( isset( $categories[$the_category] ) ) {
+			$categories[$the_category][] = array( $field_name, $field_type_obj );
+		} else {
+			$categories[$the_category] = array( array( $field_name, $field_type_obj ) );
+		}
+	}
+
+	// Sort the categories alphabetically. ksort()'s SORT_NATURAL is only in PHP >= 5.4 :((.
+	uksort( $categories, 'strnatcmp' );
+
+	// Loop through each category and output form <options>.
+	foreach ( $categories as $category => $fields ) {
+		printf( '<optgroup label="%1$s">', esc_attr( $category ) );  // Already i18n'd in each profile type class.
+
+		// Sort these fields types alphabetically.
+		uasort( $fields, create_function( '$a, $b', 'return strnatcmp( $a[1]->name, $b[1]->name );' ) );
+
+		foreach ( $fields as $field_type_obj ) {
+			$field_name     = $field_type_obj[0];
+			$field_type_obj = $field_type_obj[1];
+
+			printf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $field_name ), selected( $select_field_type, $field_name, false ), esc_html( $field_type_obj->name ) );
+		}
+
+		printf( '</optgroup>' );
+	}
+}
+
+if ( ! class_exists( 'BP_XProfile_User_Admin' ) ) :
+
+/**
+ * Load xProfile Profile admin area.
+ *
+ * @since 2.0.0
+ */
+class BP_XProfile_User_Admin {
+
+	/**
+	 * Setup xProfile User Admin.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @uses buddypress() to get BuddyPress main instance.
+	 */
+	public static function register_xprofile_user_admin() {
+
+		// Bail if not in admin.
+		if ( ! is_admin() ) {
+			return;
+		}
+
+		$bp = buddypress();
+
+		if ( empty( $bp->profile->admin ) ) {
+			$bp->profile->admin = new self;
+		}
+
+		return $bp->profile->admin;
+	}
+
+	/**
+	 * Constructor method.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		$this->setup_actions();
+	}
+
+	/**
+	 * Set admin-related actions and filters.
+	 *
+	 * @since 2.0.0
+	 */
+	private function setup_actions() {
+		// Enqueue scripts.
+		add_action( 'bp_members_admin_enqueue_scripts',  array( $this, 'enqueue_scripts'    ), 10, 1 );
+
+		// Register the metabox in Member's community admin profile.
+		add_action( 'bp_members_admin_xprofile_metabox', array( $this, 'register_metaboxes' ), 10, 3 );
+
+		// Saves the profile actions for user ( avatar, profile fields ).
+		add_action( 'bp_members_admin_update_user',      array( $this, 'user_admin_load'    ), 10, 4 );
+	}
+
+	/**
+	 * Enqueue needed scripts.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param int $screen_id Screen ID being displayed.
+	 */
+	public function enqueue_scripts( $screen_id ) {
+		if ( ( false === strpos( $screen_id, 'users_page_bp-profile-edit' )
+			&& false === strpos( $screen_id, 'profile_page_bp-profile-edit' ) )
+			|| bp_core_get_root_option( 'bp-disable-avatar-uploads' )
+			|| ! buddypress()->avatar->show_avatars
+			|| ! bp_attachments_is_wp_version_supported() ) {
+			return;
+		}
+
+		/**
+		 * Get Thickbox.
+		 *
+		 * We cannot simply use add_thickbox() here as WordPress is not playing
+		 * nice with Thickbox width/height see https://core.trac.wordpress.org/ticket/17249
+		 * Using media-upload might be interesting in the future for the send to editor stuff
+		 * and we make sure the tb_window is wide enougth
+		 */
+		wp_enqueue_style ( 'thickbox' );
+		wp_enqueue_script( 'media-upload' );
+
+		// Get Avatar Uploader.
+		bp_attachments_enqueue_scripts( 'BP_Attachment_Avatar' );
+	}
+
+	/**
+	 * Register the xProfile metabox on Community Profile admin page.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param int    $user_id       ID of the user being edited.
+	 * @param string $screen_id     Screen ID to load the metabox in.
+	 * @param object $stats_metabox Context and priority for the stats metabox.
+	 */
+	public function register_metaboxes( $user_id = 0, $screen_id = '', $stats_metabox = null ) {
+
+		// Set the screen ID if none was passed.
+		if ( empty( $screen_id ) ) {
+			$screen_id = buddypress()->members->admin->user_page;
+		}
+
+		// Setup a new metabox class if none was passed.
+		if ( empty( $stats_metabox ) ) {
+			$stats_metabox = new StdClass();
+		}
+
+		// Moving the Stats Metabox.
+		$stats_metabox->context  = 'side';
+		$stats_metabox->priority = 'low';
+
+		// Each Group of fields will have his own metabox.
+		$profile_args = array(
+			'fetch_fields' => false,
+			'user_id'      => $user_id,
+		);
+
+		if ( ! bp_is_user_spammer( $user_id ) && bp_has_profile( $profile_args ) ) {
+
+			// Loop through field groups and add a metabox for each one.
+			while ( bp_profile_groups() ) : bp_the_profile_group();
+				add_meta_box(
+					'bp_xprofile_user_admin_fields_' . sanitize_key( bp_get_the_profile_group_slug() ),
+					esc_html( bp_get_the_profile_group_name() ),
+					array( $this, 'user_admin_profile_metaboxes' ),
+					$screen_id,
+					'normal',
+					'core',
+					array( 'profile_group_id' => absint( bp_get_the_profile_group_id() ) )
+				);
+			endwhile;
+
+		// If member is already a spammer, show a generic metabox.
+		} else {
+			add_meta_box(
+				'bp_xprofile_user_admin_empty_profile',
+				_x( 'User marked as a spammer', 'xprofile user-admin edit screen', 'buddypress' ),
+				array( $this, 'user_admin_spammer_metabox' ),
+				$screen_id,
+				'normal',
+				'core'
+			);
+		}
+
+		if ( buddypress()->avatar->show_avatars ) {
+			// Avatar Metabox.
+			add_meta_box(
+				'bp_xprofile_user_admin_avatar',
+				_x( 'Profile Photo', 'xprofile user-admin edit screen', 'buddypress' ),
+				array( $this, 'user_admin_avatar_metabox' ),
+				$screen_id,
+				'side',
+				'low'
+			);
+		}
+	}
+
+	/**
+	 * Save the profile fields in Members community profile page.
+	 *
+	 * Loaded before the page is rendered, this function is processing form
+	 * requests.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $doaction    Action being run.
+	 * @param int    $user_id     ID for the user whose profile is being saved.
+	 * @param array  $request     Request being made.
+	 * @param string $redirect_to Where to redirect user to.
+	 */
+	public function user_admin_load( $doaction = '', $user_id = 0, $request = array(), $redirect_to = '' ) {
+
+		// Eventually delete avatar.
+		if ( 'delete_avatar' === $doaction ) {
+
+			check_admin_referer( 'delete_avatar' );
+
+			$redirect_to = remove_query_arg( '_wpnonce', $redirect_to );
+
+			if ( bp_core_delete_existing_avatar( array( 'item_id' => $user_id ) ) ) {
+				$redirect_to = add_query_arg( 'updated', 'avatar', $redirect_to );
+			} else {
+				$redirect_to = add_query_arg( 'error', 'avatar', $redirect_to );
+			}
+
+			bp_core_redirect( $redirect_to );
+
+		// Update profile fields.
+		} elseif ( isset( $_POST['field_ids'] ) ) {
+
+			// Check the nonce.
+			check_admin_referer( 'edit-bp-profile_' . $user_id );
+
+			// Check we have field ID's.
+			if ( empty( $_POST['field_ids'] ) ) {
+				$redirect_to = add_query_arg( 'error', '1', $redirect_to );
+				bp_core_redirect( $redirect_to );
+			}
+
+			/**
+			 * Unlike front-end edit-fields screens, the wp-admin/profile
+			 * displays all groups of fields on a single page, so the list of
+			 * field ids is an array gathering for each group of fields a
+			 * distinct comma separated list of ids.
+			 *
+			 * As a result, before using the wp_parse_id_list() function, we
+			 * must ensure that these ids are "merged" into a single comma
+			 * separated list.
+			 */
+			$merge_ids = join( ',', $_POST['field_ids'] );
+
+			// Explode the posted field IDs into an array so we know which fields have been submitted.
+			$posted_field_ids = wp_parse_id_list( $merge_ids );
+			$is_required      = array();
+
+			// Loop through the posted fields formatting any datebox values then validate the field.
+			foreach ( (array) $posted_field_ids as $field_id ) {
+				if ( ! isset( $_POST['field_' . $field_id ] ) ) {
+					if ( ! empty( $_POST['field_' . $field_id . '_day'] ) && ! empty( $_POST['field_' . $field_id . '_month'] ) && ! empty( $_POST['field_' . $field_id . '_year'] ) ) {
+
+						// Concatenate the values.
+						$date_value =   $_POST['field_' . $field_id . '_day'] . ' ' . $_POST['field_' . $field_id . '_month'] . ' ' . $_POST['field_' . $field_id . '_year'];
+
+						// Turn the concatenated value into a timestamp.
+						$_POST['field_' . $field_id] = date( 'Y-m-d H:i:s', strtotime( $date_value ) );
+					}
+				}
+
+				$is_required[ $field_id ] = xprofile_check_is_required_field( $field_id ) && ! bp_current_user_can( 'bp_moderate' );
+				if ( $is_required[ $field_id ] && empty( $_POST['field_' . $field_id ] ) ) {
+					$redirect_to = add_query_arg( 'error', '2', $redirect_to );
+					bp_core_redirect( $redirect_to );
+				}
+			}
+
+			// Set the errors var.
+			$errors = false;
+
+			// Now we've checked for required fields, let's save the values.
+			foreach ( (array) $posted_field_ids as $field_id ) {
+
+				// Certain types of fields (checkboxes, multiselects) may come
+				// through empty. Save them as an empty array so that they don't
+				// get overwritten by the default on the next edit.
+				$value = isset( $_POST['field_' . $field_id] ) ? $_POST['field_' . $field_id] : '';
+
+				if ( ! xprofile_set_field_data( $field_id, $user_id, $value, $is_required[ $field_id ] ) ) {
+					$errors = true;
+				} else {
+
+					/**
+					 * Fires after the saving of each profile field, if successful.
+					 *
+					 * @since 1.1.0
+					 *
+					 * @param int    $field_id ID of the field being updated.
+					 * @param string $value    Value that was saved to the field.
+					 */
+					do_action( 'xprofile_profile_field_data_updated', $field_id, $value );
+				}
+
+				// Save the visibility level.
+				$visibility_level = ! empty( $_POST['field_' . $field_id . '_visibility'] ) ? $_POST['field_' . $field_id . '_visibility'] : 'public';
+				xprofile_set_field_visibility_level( $field_id, $user_id, $visibility_level );
+			}
+
+			/**
+			 * Fires after all of the profile fields have been saved.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param int   $user_id          ID of the user whose data is being saved.
+			 * @param array $posted_field_ids IDs of the fields that were submitted.
+			 * @param bool  $errors           Whether or not errors occurred during saving.
+			 */
+			do_action( 'xprofile_updated_profile', $user_id, $posted_field_ids, $errors );
+
+			// Set the feedback messages.
+			if ( ! empty( $errors ) ) {
+				$redirect_to = add_query_arg( 'error',   '3', $redirect_to );
+			} else {
+				$redirect_to = add_query_arg( 'updated', '1', $redirect_to );
+			}
+
+			bp_core_redirect( $redirect_to );
+		}
+	}
+
+	/**
+	 * Render the xprofile metabox for Community Profile screen.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param WP_User $user The WP_User object for the user being edited.
+	 * @param array   $args Aray of arguments for metaboxes.
+	 */
+	public function user_admin_profile_metaboxes( $user = null, $args = array() ) {
+
+		// Bail if no user ID.
+		if ( empty( $user->ID ) ) {
+			return;
+		}
+
+		$r = bp_parse_args( $args['args'], array(
+			'profile_group_id' => 0,
+			'user_id'          => $user->ID
+		), 'bp_xprofile_user_admin_profile_loop_args' );
+
+		// We really need these args.
+		if ( empty( $r['profile_group_id'] ) || empty( $r['user_id'] ) ) {
+			return;
+		}
+
+		// Bail if no profile fields are available.
+		if ( ! bp_has_profile( $r ) ) {
+			return;
+		}
+
+		// Loop through profile groups & fields.
+		while ( bp_profile_groups() ) : bp_the_profile_group(); ?>
+
+			<input type="hidden" name="field_ids[]" id="<?php echo esc_attr( 'field_ids_' . bp_get_the_profile_group_slug() ); ?>" value="<?php echo esc_attr( bp_get_the_profile_group_field_ids() ); ?>" />
+
+			<?php if ( bp_get_the_profile_group_description() ) : ?>
+
+				<p class="description"><?php bp_the_profile_group_description(); ?></p>
+
+			<?php endif; ?>
+
+			<?php while ( bp_profile_fields() ) : bp_the_profile_field(); ?>
+
+				<div<?php bp_field_css_class( 'bp-profile-field' ); ?>>
+
+					<?php
+
+					$field_type = bp_xprofile_create_field_type( bp_get_the_profile_field_type() );
+					$field_type->edit_field_html( array( 'user_id' => $r['user_id'] ) );
+
+					if ( bp_get_the_profile_field_description() ) : ?>
+
+						<p class="description"><?php bp_the_profile_field_description(); ?></p>
+
+					<?php endif;
+
+					/**
+					 * Fires before display of visibility form elements for profile metaboxes.
+					 *
+					 * @since 1.7.0
+					 */
+					do_action( 'bp_custom_profile_edit_fields_pre_visibility' );
+
+					$can_change_visibility = bp_current_user_can( 'bp_xprofile_change_field_visibility' ); ?>
+
+					<p class="field-visibility-settings-<?php echo $can_change_visibility ? 'toggle' : 'notoggle'; ?>" id="field-visibility-settings-toggle-<?php bp_the_profile_field_id(); ?>">
+
+						<?php
+						printf(
+							__( 'This field can be seen by: %s', 'buddypress' ),
+							'<span class="current-visibility-level">' . bp_get_the_profile_field_visibility_level_label() . '</span>'
+						);
+						?>
+
+						<?php if ( $can_change_visibility ) : ?>
+
+							<a href="#" class="button visibility-toggle-link"><?php esc_html_e( 'Change', 'buddypress' ); ?></a>
+
+						<?php endif; ?>
+					</p>
+
+					<?php if ( $can_change_visibility ) : ?>
+
+						<div class="field-visibility-settings" id="field-visibility-settings-<?php bp_the_profile_field_id() ?>">
+							<fieldset>
+								<legend><?php _e( 'Who can see this field?', 'buddypress' ); ?></legend>
+
+								<?php bp_profile_visibility_radio_buttons(); ?>
+
+							</fieldset>
+							<a class="button field-visibility-settings-close" href="#"><?php esc_html_e( 'Close', 'buddypress' ); ?></a>
+						</div>
+
+					<?php endif; ?>
+
+					<?php
+
+					/**
+					 * Fires at end of custom profile field items on your xprofile screen tab.
+					 *
+					 * @since 1.1.0
+					 */
+					do_action( 'bp_custom_profile_edit_fields' ); ?>
+
+				</div>
+
+			<?php endwhile; // End bp_profile_fields(). ?>
+
+		<?php endwhile; // End bp_profile_groups.
+	}
+
+	/**
+	 * Render the fallback metabox in case a user has been marked as a spammer.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param WP_User $user The WP_User object for the user being edited.
+	 */
+	public function user_admin_spammer_metabox( $user = null ) {
+	?>
+		<p><?php printf( __( '%s has been marked as a spammer. All BuddyPress data associated with the user has been removed', 'buddypress' ), esc_html( bp_core_get_user_displayname( $user->ID ) ) ) ;?></p>
+	<?php
+	}
+
+	/**
+	 * Render the Avatar metabox to moderate inappropriate images.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param WP_User $user The WP_User object for the user being edited.
+	 */
+	public function user_admin_avatar_metabox( $user = null ) {
+
+		if ( empty( $user->ID ) ) {
+			return;
+		} ?>
+
+		<div class="avatar">
+
+			<?php echo bp_core_fetch_avatar( array(
+				'item_id' => $user->ID,
+				'object'  => 'user',
+				'type'    => 'full',
+				'title'   => $user->display_name
+			) ); ?>
+
+			<?php if ( bp_get_user_has_avatar( $user->ID ) ) :
+
+				$query_args = array(
+					'user_id' => $user->ID,
+					'action'  => 'delete_avatar'
+				);
+
+				if ( ! empty( $_REQUEST['wp_http_referer'] ) ) {
+					$query_args['wp_http_referer'] = urlencode( wp_unslash( $_REQUEST['wp_http_referer'] ) );
+				}
+
+				$community_url = add_query_arg( $query_args, buddypress()->members->admin->edit_profile_url );
+				$delete_link   = wp_nonce_url( $community_url, 'delete_avatar' ); ?>
+
+				<a href="<?php echo esc_url( $delete_link ); ?>" title="<?php esc_attr_e( 'Delete Profile Photo', 'buddypress' ); ?>" class="bp-xprofile-avatar-user-admin"><?php esc_html_e( 'Delete Profile Photo', 'buddypress' ); ?></a>
+
+			<?php endif;
+
+			// Load the Avatar UI templates if user avatar uploads are enabled and current WordPress version is supported.
+			if ( ! bp_core_get_root_option( 'bp-disable-avatar-uploads' ) && bp_attachments_is_wp_version_supported() ) : ?>
+				<a href="#TB_inline?width=800px&height=400px&inlineId=bp-xprofile-avatar-editor" title="<?php esc_attr_e( 'Edit Profile Photo', 'buddypress' );?>" class="thickbox bp-xprofile-avatar-user-edit"><?php esc_html_e( 'Edit Profile Photo', 'buddypress' ); ?></a>
+				<div id="bp-xprofile-avatar-editor" style="display:none;">
+					<?php bp_attachments_get_template_part( 'avatars/index' ); ?>
+				</div>
+			<?php endif; ?>
+
+		</div>
+		<?php
+	}
+
+}
+endif; // End class_exists check.
+
+// Load the xprofile user admin.
+add_action( 'bp_init', array( 'BP_XProfile_User_Admin', 'register_xprofile_user_admin' ), 11 );
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-cache.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-cache.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-cache.php	(revision 10410)
@@ -0,0 +1,311 @@
+<?php
+/**
+ * BuddyPress XProfile Caching Functions.
+ *
+ * Caching functions handle the clearing of cached objects and pages on specific
+ * actions throughout BuddyPress.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileCache
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Determine which xprofile fields do not have cached values for a user.
+ *
+ * @since 2.2.0
+ *
+ * @param int   $user_id   User ID to check.
+ * @param array $field_ids XProfile field IDs.
+ *
+ * @return array
+ */
+function bp_xprofile_get_non_cached_field_ids( $user_id = 0, $field_ids = array() ) {
+	$uncached_fields = array();
+
+	foreach ( $field_ids as $field_id ) {
+		$field_id  = (int) $field_id;
+		$cache_key = "{$user_id}:{$field_id}";
+		if ( false === wp_cache_get( $cache_key, 'bp_xprofile_data' ) ) {
+			$uncached_fields[] = $field_id;
+		}
+	}
+
+	return $uncached_fields;
+}
+
+/**
+ * Slurp up xprofilemeta for a specified set of profile objects.
+ *
+ * We do not use bp_update_meta_cache() for the xprofile component. This is
+ * because the xprofile component has three separate object types (group,
+ * field, and data) and three corresponding cache groups. Using the technique
+ * in bp_update_meta_cache(), pre-fetching would take three separate database
+ * queries. By grouping them together, we can reduce the required queries to
+ * one.
+ *
+ * This function is called within a bp_has_profile() loop.
+ *
+ * @since 2.0.0
+ *
+ * @param array $object_ids Multi-dimensional array of object_ids, keyed by
+ *                          object type ('group', 'field', 'data').
+ *
+ * @return bool
+ */
+function bp_xprofile_update_meta_cache( $object_ids = array() ) {
+	global $wpdb;
+
+	// Bail if no objects.
+	if ( empty( $object_ids ) ) {
+		return false;
+	}
+
+	$bp = buddypress();
+
+	// Define the array where uncached object IDs will be stored.
+	$uncached_object_ids = array(
+		'group',
+		'field',
+		'data'
+	);
+
+	// Define the cache groups for the 3 types of XProfile metadata.
+	$cache_groups = array(
+		'group' => 'xprofile_group_meta',
+		'field' => 'xprofile_field_meta',
+		'data'  => 'xprofile_data_meta',
+	);
+
+	// No reason to query yet.
+	$do_query = false;
+
+	// Loop through object types and look for uncached data.
+	foreach ( $uncached_object_ids as $object_type ) {
+
+		// Skip if empty object type.
+		if ( empty( $object_ids[ $object_type ] ) ) {
+			continue;
+		}
+
+		// Sanitize $object_ids passed to the function.
+		$object_type_ids = wp_parse_id_list( $object_ids[ $object_type ] );
+
+		// Get non-cached IDs for each object type.
+		$uncached_object_ids[ $object_type ] = bp_get_non_cached_ids( $object_type_ids, $cache_groups[ $object_type ] );
+
+		// Set the flag to do the meta query.
+		if ( ! empty( $uncached_object_ids[ $object_type ] ) && ( false === $do_query ) ) {
+			$do_query = true;
+		}
+	}
+
+	// Bail if no uncached items.
+	if ( false === $do_query ) {
+		return;
+	}
+
+	// Setup where conditions for query.
+	$where_sql        = '';
+	$where_conditions = array();
+
+	// Loop through uncached objects and prepare to query for them.
+	foreach ( $uncached_object_ids as $otype => $oids ) {
+
+		// Skip empty object IDs.
+		if ( empty( $oids ) ) {
+			continue;
+		}
+
+		// Compile WHERE query conditions for uncached metadata.
+		$oids_sql           = implode( ',', wp_parse_id_list( $oids ) );
+		$where_conditions[] = $wpdb->prepare( "( object_type = %s AND object_id IN ({$oids_sql}) )", $otype );
+	}
+
+	// Bail if no where conditions.
+	if ( empty( $where_conditions ) ) {
+		return;
+	}
+
+	// Setup the WHERE query part.
+	$where_sql = implode( " OR ", $where_conditions );
+
+	// Attempt to query meta values.
+	$meta_list = $wpdb->get_results( "SELECT object_id, object_type, meta_key, meta_value FROM {$bp->profile->table_name_meta} WHERE {$where_sql}" );
+
+	// Bail if no results found.
+	if ( empty( $meta_list ) || is_wp_error( $meta_list ) ) {
+		return;
+	}
+
+	// Setup empty cache array.
+	$cache = array();
+
+	// Loop through metas.
+	foreach ( $meta_list as $meta ) {
+		$oid    = $meta->object_id;
+		$otype  = $meta->object_type;
+		$okey   = $meta->meta_key;
+		$ovalue = $meta->meta_value;
+
+		// Force subkeys to be array type.
+		if ( ! isset( $cache[ $otype ][ $oid ] ) || ! is_array( $cache[ $otype ][ $oid ] ) ) {
+			$cache[ $otype ][ $oid ] = array();
+		}
+
+		if ( ! isset( $cache[ $otype ][ $oid ][ $okey ] ) || ! is_array( $cache[ $otype ][ $oid ][ $okey ] ) ) {
+			$cache[ $otype ][ $oid ][ $okey ] = array();
+		}
+
+		// Add to the cache array.
+		$cache[ $otype ][ $oid ][ $okey ][] = maybe_unserialize( $ovalue );
+	}
+
+	// Loop through data and cache to the appropriate object.
+	foreach ( $cache as $object_type => $object_caches ) {
+
+		// Determine the cache group for this data.
+		$cache_group = $cache_groups[ $object_type ];
+
+		// Loop through objects and cache appropriately.
+		foreach ( $object_caches as $object_id => $object_cache ) {
+			wp_cache_set( $object_id, $object_cache, $cache_group );
+		}
+	}
+}
+
+/**
+ * Clear cached XProfile field group data.
+ *
+ * @since 2.1.0
+ *
+ * @param object $group_obj Groub object to clear.
+ */
+function xprofile_clear_profile_groups_object_cache( $group_obj ) {
+	wp_cache_delete( 'all',          'bp_xprofile_groups' );
+	wp_cache_delete( $group_obj->id, 'bp_xprofile_groups' );
+}
+add_action( 'xprofile_group_after_delete', 'xprofile_clear_profile_groups_object_cache' );
+add_action( 'xprofile_group_after_save',   'xprofile_clear_profile_groups_object_cache' );
+
+/**
+ * Clear cached XProfile fullname data for user.
+ *
+ * @since 2.1.0
+ *
+ * @param int $user_id ID of user whose fullname cache to delete.
+ */
+function xprofile_clear_profile_data_object_cache( $user_id = 0 ) {
+	wp_cache_delete( 'bp_user_fullname_' . $user_id, 'bp' );
+}
+add_action( 'xprofile_updated_profile', 'xprofile_clear_profile_data_object_cache' );
+
+/**
+ * Clear the fullname cache when field 1 is updated.
+ *
+ * The xprofile_clear_profile_data_object_cache() will make this redundant in most
+ * cases, except where the field is updated directly with xprofile_set_field_data().
+ *
+ * @since 2.0.0
+ *
+ * @param object $data Data object to clear.
+ */
+function xprofile_clear_fullname_cache_on_profile_field_edit( $data ) {
+	if ( 1 == $data->field_id ) {
+		wp_cache_delete( 'bp_user_fullname_' . $data->user_id, 'bp' );
+	}
+}
+add_action( 'xprofile_data_after_save', 'xprofile_clear_fullname_cache_on_profile_field_edit' );
+
+/**
+ * Clear caches when a field object is modified.
+ *
+ * @since 2.0.0
+ *
+ * @param BP_XProfile_Field $field_obj Field object cache to delete.
+ */
+function xprofile_clear_profile_field_object_cache( $field_obj ) {
+
+	// Clear default visibility level cache.
+	wp_cache_delete( 'default_visibility_levels', 'bp_xprofile' );
+
+	// Modified fields can alter parent group status, in particular when
+	// the group goes from empty to non-empty. Bust its cache, as well as
+	// the global 'all' cache.
+	wp_cache_delete( 'all',                'bp_xprofile_groups' );
+	wp_cache_delete( $field_obj->group_id, 'bp_xprofile_groups' );
+}
+add_action( 'xprofile_fields_saved_field',   'xprofile_clear_profile_field_object_cache' );
+add_action( 'xprofile_fields_deleted_field', 'xprofile_clear_profile_field_object_cache' );
+
+/**
+ * Clears member_type cache when a field's member types are updated.
+ *
+ * @since 2.4.0
+ */
+function bp_xprofile_clear_member_type_cache() {
+	wp_cache_delete( 'field_member_types', 'bp_xprofile' );
+}
+add_action( 'bp_xprofile_field_set_member_type', 'bp_xprofile_clear_member_type_cache' );
+
+/**
+ * Clear caches when a user's updates a field data object.
+ *
+ * @since 2.0.0
+ *
+ * @param BP_XProfile_ProfileData $data_obj Field data object to delete.
+ */
+function xprofile_clear_profiledata_object_cache( $data_obj ) {
+	wp_cache_delete( "{$data_obj->user_id}:{$data_obj->field_id}", 'bp_xprofile_data' );
+}
+add_action( 'xprofile_data_after_save',   'xprofile_clear_profiledata_object_cache' );
+add_action( 'xprofile_data_after_delete', 'xprofile_clear_profiledata_object_cache' );
+
+/**
+ * Clear fullname_field_id cache when bp-xprofile-fullname-field-name is updated.
+ *
+ * Note for future developers: Dating from an early version of BuddyPress where
+ * the fullname field (field #1) did not have a title that was editable in the
+ * normal Profile Fields admin interface, we have the bp-xprofile-fullname-field-name
+ * option. In many places throughout BuddyPress, the ID of the fullname field
+ * is queried using this setting. However, this is no longer strictly necessary,
+ * because we essentially hardcode (in the xprofile admin save routine, as well
+ * as the xprofile schema definition) that the fullname field will be 1. The
+ * presence of the non-hardcoded versions (and thus this bit of cache
+ * invalidation) is thus for backward compatibility only.
+ *
+ * @since 2.0.0
+ */
+function xprofile_clear_fullname_field_id_cache() {
+	wp_cache_delete( 'fullname_field_id', 'bp_xprofile' );
+}
+add_action( 'update_option_bp-xprofile-fullname-field-name', 'xprofile_clear_fullname_field_id_cache' );
+
+/**
+ * Clear a field's caches.
+ *
+ * @since 2.4.0
+ *
+ * @param int|BP_XProfile_Field A field ID or a field object.
+ * @param bool False on failure.
+ */
+function bp_xprofile_clear_field_cache( $field ) {
+	if ( is_numeric( $field ) ) {
+		$field_id = (int) $field;
+	} elseif ( $field instanceof BP_XProfile_Field ) {
+		$field_id = (int) $field->id;
+	}
+
+	if ( ! isset( $field_id ) ) {
+		return false;
+	}
+
+	wp_cache_delete( $field_id, 'bp_xprofile_fields' );
+	wp_cache_delete( $field_id, 'xprofile_meta' );
+}
+add_action( 'xprofile_field_after_save', 'bp_xprofile_clear_field_cache' );
+
+// List actions to clear super cached pages on, if super cache is installed.
+add_action( 'xprofile_updated_profile', 'bp_core_clear_cache' );
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-caps.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-caps.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-caps.php	(revision 10410)
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Roles and capabilities logic for the XProfile component.
+ *
+ * @package BuddyPress
+ * @subpackage XPRofileCaps
+ * @since 1.6.0
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Maps XProfile caps to built in WordPress caps.
+ *
+ * @since 1.6.0
+ *
+ * @param array  $caps    Capabilities for meta capability.
+ * @param string $cap     Capability name.
+ * @param int    $user_id User id.
+ * @param mixed  $args    Arguments.
+ *
+ * @uses get_post() To get the post.
+ * @uses get_post_type_object() To get the post type object.
+ * @uses apply_filters() Calls 'bp_map_meta_caps' with caps, cap, user id and args.
+ *
+ * @return array Actual capabilities for meta capability.
+ */
+function bp_xprofile_map_meta_caps( $caps, $cap, $user_id, $args ) {
+	switch ( $cap ) {
+		case 'bp_xprofile_change_field_visibility' :
+			$caps = array( 'exist' ); // Must allow for logged-out users during registration.
+
+			// You may pass args manually: $field_id, $profile_user_id.
+			$field_id        = isset( $args[0] ) ? (int)$args[0] : bp_get_the_profile_field_id();
+			$profile_user_id = isset( $args[1] ) ? (int)$args[1] : bp_displayed_user_id();
+
+			// Visibility on the fullname field is not editable.
+			if ( 1 == $field_id ) {
+				$caps[] = 'do_not_allow';
+				break;
+			}
+
+			// Has the admin disabled visibility modification for this field?
+			if ( 'disabled' == bp_xprofile_get_meta( $field_id, 'field', 'allow_custom_visibility' ) ) {
+				$caps[] = 'do_not_allow';
+				break;
+			}
+
+			// Friends don't let friends edit each other's visibility.
+			if ( $profile_user_id != bp_displayed_user_id() && !bp_current_user_can( 'bp_moderate' ) ) {
+				$caps[] = 'do_not_allow';
+				break;
+			}
+
+			break;
+	}
+
+	/**
+	 * Filters the XProfile caps to built in WordPress caps.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array  $caps    Capabilities for meta capability.
+	 * @param string $cap     Capability name.
+	 * @param int    $user_id User ID being mapped.
+	 * @param mixed  $args    Capability arguments.
+	 */
+	return apply_filters( 'bp_xprofile_map_meta_caps', $caps, $cap, $user_id, $args );
+}
+add_filter( 'bp_map_meta_caps', 'bp_xprofile_map_meta_caps', 10, 4 );
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-classes.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-classes.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-classes.php	(revision 10410)
@@ -0,0 +1,27 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-group.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-profiledata.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-datebox.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-checkbox.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-radiobutton.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-multiselectbox.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-selectbox.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-textarea.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-textbox.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-number.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-url.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-field-type-placeholder.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-meta-query.php';
+require dirname( __FILE__ ) . '/classes/class-bp-xprofile-query.php';
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-cssjs.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-cssjs.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-cssjs.php	(revision 10410)
@@ -0,0 +1,66 @@
+<?php
+/**
+ * BuddyPress XProfile CSS and JS.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileScripts
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Enqueue the CSS for XProfile admin styling.
+ *
+ * @since 1.1.0
+ */
+function xprofile_add_admin_css() {
+	if ( !empty( $_GET['page'] ) && strpos( $_GET['page'], 'bp-profile-setup' ) !== false ) {
+		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+
+		wp_enqueue_style( 'xprofile-admin-css', buddypress()->plugin_url . "bp-xprofile/admin/css/admin{$min}.css", array(), bp_get_version() );
+
+		wp_style_add_data( 'xprofile-admin-css', 'rtl', true );
+		if ( $min ) {
+			wp_style_add_data( 'xprofile-admin-css', 'suffix', $min );
+		}
+	}
+}
+add_action( 'bp_admin_enqueue_scripts', 'xprofile_add_admin_css' );
+
+/**
+ * Enqueue the jQuery libraries for handling drag/drop/sort.
+ *
+ * @since 1.5.0
+ */
+function xprofile_add_admin_js() {
+	if ( !empty( $_GET['page'] ) && strpos( $_GET['page'], 'bp-profile-setup' ) !== false ) {
+		wp_enqueue_script( 'jquery-ui-core'      );
+		wp_enqueue_script( 'jquery-ui-tabs'      );
+		wp_enqueue_script( 'jquery-ui-mouse'     );
+		wp_enqueue_script( 'jquery-ui-draggable' );
+		wp_enqueue_script( 'jquery-ui-droppable' );
+		wp_enqueue_script( 'jquery-ui-sortable'  );
+
+		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+		wp_enqueue_script( 'xprofile-admin-js', buddypress()->plugin_url . "bp-xprofile/admin/js/admin{$min}.js", array( 'jquery', 'jquery-ui-sortable' ), bp_get_version() );
+
+		// Localize strings.
+		// supports_options_field_types is a dynamic list of field
+		// types that support options, for use in showing/hiding the
+		// "please enter options for this field" section.
+		$strings = array(
+			'supports_options_field_types' => array(),
+		);
+
+		foreach ( bp_xprofile_get_field_types() as $field_type => $field_type_class ) {
+			$field = new $field_type_class();
+			if ( $field->supports_options ) {
+				$strings['supports_options_field_types'][] = $field_type;
+			}
+		}
+
+		wp_localize_script( 'xprofile-admin-js', 'XProfileAdmin', $strings );
+	}
+}
+add_action( 'bp_admin_enqueue_scripts', 'xprofile_add_admin_js', 1 );
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-filters.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-filters.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-filters.php	(revision 10410)
@@ -0,0 +1,590 @@
+<?php
+/**
+ * BuddyPress XProfile Filters.
+ *
+ * Apply WordPress defined filters.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileFilters
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+add_filter( 'bp_get_the_profile_group_name',            'wp_filter_kses',       1 );
+add_filter( 'bp_get_the_profile_group_description',     'wp_filter_kses',       1 );
+add_filter( 'bp_get_the_profile_field_value',           'xprofile_filter_kses', 1 );
+add_filter( 'bp_get_the_profile_field_name',            'wp_filter_kses',       1 );
+add_filter( 'bp_get_the_profile_field_edit_value',      'wp_filter_kses',       1 );
+add_filter( 'bp_get_the_profile_field_description',     'wp_filter_kses',       1 );
+
+add_filter( 'bp_get_the_profile_field_value',           'wptexturize'        );
+add_filter( 'bp_get_the_profile_field_value',           'convert_chars'      );
+add_filter( 'bp_get_the_profile_field_value',           'wpautop'            );
+add_filter( 'bp_get_the_profile_field_value',           'force_balance_tags' );
+add_filter( 'bp_get_the_profile_field_value',           'make_clickable'     );
+add_filter( 'bp_get_the_profile_field_value',           'bp_xprofile_escape_field_data', 8, 3 );
+add_filter( 'bp_get_the_profile_field_value',           'convert_smilies', 9 );
+add_filter( 'bp_get_the_profile_field_value',           'xprofile_filter_format_field_value',         1, 2 );
+add_filter( 'bp_get_the_profile_field_value',           'xprofile_filter_format_field_value_by_type', 8, 3 );
+add_filter( 'bp_get_the_profile_field_value',           'xprofile_filter_link_profile_data',          9, 3 );
+
+add_filter( 'bp_get_the_profile_field_edit_value',      'force_balance_tags' );
+add_filter( 'bp_get_the_profile_field_edit_value',      'bp_xprofile_escape_field_data', 10, 3 );
+
+add_filter( 'bp_get_the_profile_group_name',            'stripslashes' );
+add_filter( 'bp_get_the_profile_group_description',     'stripslashes' );
+add_filter( 'bp_get_the_profile_field_value',           'stripslashes' );
+add_filter( 'bp_get_the_profile_field_edit_value',      'stripslashes' );
+add_filter( 'bp_get_the_profile_field_name',            'stripslashes' );
+add_filter( 'bp_get_the_profile_field_description',     'stripslashes' );
+
+add_filter( 'xprofile_get_field_data',                  'xprofile_filter_kses', 1 );
+add_filter( 'xprofile_field_name_before_save',          'wp_filter_kses', 1 );
+add_filter( 'xprofile_field_description_before_save',   'wp_filter_kses', 1 );
+
+add_filter( 'xprofile_get_field_data',                  'force_balance_tags' );
+add_filter( 'xprofile_field_name_before_save',          'force_balance_tags' );
+add_filter( 'xprofile_field_description_before_save',   'force_balance_tags' );
+
+add_filter( 'xprofile_get_field_data',                  'stripslashes' );
+add_filter( 'xprofile_get_field_data',                  'xprofile_filter_format_field_value_by_field_id', 5, 2 );
+
+add_filter( 'bp_xprofile_set_field_data_pre_validate',  'xprofile_filter_pre_validate_value_by_field_type', 10, 3 );
+add_filter( 'xprofile_data_value_before_save',          'xprofile_sanitize_data_value_before_save', 1, 4 );
+add_filter( 'xprofile_filtered_data_value_before_save', 'trim', 2 );
+
+// Save field groups.
+add_filter( 'xprofile_group_name_before_save',        'wp_filter_kses' );
+add_filter( 'xprofile_group_description_before_save', 'wp_filter_kses' );
+
+add_filter( 'xprofile_group_name_before_save',         'stripslashes' );
+add_filter( 'xprofile_group_description_before_save',  'stripslashes' );
+
+// Save fields.
+add_filter( 'xprofile_field_name_before_save',         'wp_filter_kses' );
+add_filter( 'xprofile_field_type_before_save',         'wp_filter_kses' );
+add_filter( 'xprofile_field_description_before_save',  'wp_filter_kses' );
+add_filter( 'xprofile_field_order_by_before_save',     'wp_filter_kses' );
+
+add_filter( 'xprofile_field_is_required_before_save',  'absint' );
+add_filter( 'xprofile_field_field_order_before_save',  'absint' );
+add_filter( 'xprofile_field_option_order_before_save', 'absint' );
+add_filter( 'xprofile_field_can_delete_before_save',   'absint' );
+
+// Save field options.
+add_filter( 'xprofile_field_options_before_save', 'bp_xprofile_sanitize_field_options' );
+add_filter( 'xprofile_field_default_before_save', 'bp_xprofile_sanitize_field_default' );
+
+/**
+ * Sanitize each field option name for saving to the database.
+ *
+ * @since 2.3.0
+ *
+ * @param  mixed $field_options Options to sanitize.
+ *
+ * @return mixed
+ */
+function bp_xprofile_sanitize_field_options( $field_options = '' ) {
+	if ( is_array( $field_options ) ) {
+		return array_map( 'sanitize_text_field', $field_options );
+	} else {
+		return sanitize_text_field( $field_options );
+	}
+}
+
+/**
+ * Sanitize each field option default for saving to the database.
+ *
+ * @since 2.3.0
+ *
+ * @param  mixed $field_default Field defaults to sanitize.
+ *
+ * @return mixed
+ */
+function bp_xprofile_sanitize_field_default( $field_default = '' ) {
+	if ( is_array( $field_default ) ) {
+		return array_map( 'intval', $field_default );
+	} else {
+		return intval( $field_default );
+	}
+}
+
+/**
+ * Run profile field values through kses with filterable allowed tags.
+ *
+ * @param string $content  Content to filter.
+ * @param object $data_obj The BP_XProfile_ProfileData object.
+ *
+ * @return string $content
+ */
+function xprofile_filter_kses( $content, $data_obj = null ) {
+	global $allowedtags;
+
+	$xprofile_allowedtags             = $allowedtags;
+	$xprofile_allowedtags['a']['rel'] = array();
+
+	// If the field supports rich text, we must allow tags that appear in wp_editor().
+	if ( $data_obj instanceof BP_XProfile_ProfileData && bp_xprofile_is_richtext_enabled_for_field( $data_obj->field_id ) ) {
+		$richtext_tags = array(
+			'img'  => array( 'id' => 1, 'class' => 1, 'src' => 1, 'alt' => 1, 'width' => 1, 'height' => 1 ),
+			'ul'   => array( 'id' => 1, 'class' => 1 ),
+			'ol'   => array( 'id' => 1, 'class' => 1 ),
+			'li'   => array( 'id' => 1, 'class' => 1 ),
+			'span' => array( 'style' => 1 ),
+			'p'    => array( 'style' => 1 ),
+		);
+
+		$xprofile_allowedtags = array_merge( $allowedtags, $richtext_tags );
+	}
+
+	/**
+	 * Filters the allowed tags for use within xprofile_filter_kses().
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array                   $xprofile_allowedtags Array of allowed tags for profile field values.
+	 * @param BP_XProfile_ProfileData $data_obj             The BP_XProfile_ProfileData object.
+	 */
+	$xprofile_allowedtags = apply_filters( 'xprofile_allowed_tags', $xprofile_allowedtags, $data_obj );
+	return wp_kses( $content, $xprofile_allowedtags );
+}
+
+/**
+ * Safely runs profile field data through kses and force_balance_tags.
+ *
+ * @param string $field_value Field value being santized.
+ * @param int    $field_id    Field ID being sanitized.
+ * @param bool   $reserialize Whether to reserialize arrays before returning. Defaults to true.
+ * @param object $data_obj    The BP_XProfile_ProfileData object.
+ *
+ * @return string
+ */
+function xprofile_sanitize_data_value_before_save( $field_value, $field_id = 0, $reserialize = true, $data_obj = null ) {
+
+	// Return if empty.
+	if ( empty( $field_value ) ) {
+		return $field_value;
+	}
+
+	// Value might be serialized.
+	$field_value = maybe_unserialize( $field_value );
+
+	// Filter single value.
+	if ( !is_array( $field_value ) ) {
+		$kses_field_value     = xprofile_filter_kses( $field_value, $data_obj );
+		$filtered_field_value = wp_rel_nofollow( force_balance_tags( $kses_field_value ) );
+
+		/**
+		 * Filters the kses-filtered data before saving to database.
+		 *
+		 * @since 1.5.0
+		 *
+		 * @param string                  $filtered_field_value The filtered value.
+		 * @param string                  $field_value          The original value before filtering.
+		 * @param BP_XProfile_ProfileData $data_obj             The BP_XProfile_ProfileData object.
+		 */
+		$filtered_field_value = apply_filters( 'xprofile_filtered_data_value_before_save', $filtered_field_value, $field_value, $data_obj );
+
+	// Filter each array item independently.
+	} else {
+		$filtered_values = array();
+		foreach ( (array) $field_value as $value ) {
+			$kses_field_value       = xprofile_filter_kses( $value, $data_obj );
+			$filtered_value 	= wp_rel_nofollow( force_balance_tags( $kses_field_value ) );
+
+			/** This filter is documented in bp-xprofile/bp-xprofile-filters.php */
+			$filtered_values[] = apply_filters( 'xprofile_filtered_data_value_before_save', $filtered_value, $value, $data_obj );
+
+		}
+
+		if ( !empty( $reserialize ) ) {
+			$filtered_field_value = serialize( $filtered_values );
+		} else {
+			$filtered_field_value = $filtered_values;
+		}
+	}
+
+	return $filtered_field_value;
+}
+
+/**
+ * Runs stripslashes on XProfile fields.
+ *
+ * @since 1.0.0
+ *
+ * @param string $field_value XProfile field_value to be filtered.
+ * @param string $field_type  XProfile field_type to be filtered.
+ *
+ * @return string $field_value Filtered XProfile field_value. False on failure.
+ */
+function xprofile_filter_format_field_value( $field_value, $field_type = '' ) {
+
+	// Valid field values of 0 or '0' get caught by empty(), so we have an extra check for these. See #BP5731.
+ 	if ( ! isset( $field_value ) || empty( $field_value ) && ( '0' !== $field_value ) ) {
+		return false;
+	}
+
+	if ( 'datebox' !== $field_type ) {
+		$field_value = str_replace( ']]>', ']]&gt;', $field_value );
+	}
+
+	return stripslashes( $field_value );
+}
+
+/**
+ * Apply display_filter() filters as defined by BP_XProfile_Field_Type classes, when inside a bp_has_profile() loop.
+ *
+ * @since 2.1.0
+ * @since 2.4.0 Added `$field_id` parameter.
+ *
+ * @param mixed  $field_value Field value.
+ * @param string $field_type  Field type.
+ * @param int    $field_id    Optional. ID of the field.
+ *
+ * @return mixed
+ */
+function xprofile_filter_format_field_value_by_type( $field_value, $field_type = '', $field_id = '' ) {
+	foreach ( bp_xprofile_get_field_types() as $type => $class ) {
+		if ( $type !== $field_type ) {
+			continue;
+		}
+
+		if ( method_exists( $class, 'display_filter' ) ) {
+			$field_value = call_user_func( array( $class, 'display_filter' ), $field_value, $field_id );
+		}
+	}
+
+	return $field_value;
+}
+
+/**
+ * Apply display_filter() filters as defined by the BP_XProfile_Field_Type classes, when fetched
+ * by xprofile_get_field_data().
+ *
+ * @since 2.1.0
+ *
+ * @param mixed $field_value Field value.
+ * @param int   $field_id    Field type.
+ *
+ * @return string
+ */
+function xprofile_filter_format_field_value_by_field_id( $field_value, $field_id ) {
+	$field = xprofile_get_field( $field_id );
+	return xprofile_filter_format_field_value_by_type( $field_value, $field->type, $field_id );
+}
+
+/**
+ * Apply pre_validate_filter() filters as defined by the BP_XProfile_Field_Type classes before validating.
+ *
+ * @since 2.1.0
+ *
+ * @param mixed                  $value          Value passed to the bp_xprofile_set_field_data_pre_validate filter.
+ * @param BP_XProfile_Field      $field          Field object.
+ * @param BP_XProfile_Field_Type $field_type_obj Field type object.
+ *
+ * @return mixed
+ */
+function xprofile_filter_pre_validate_value_by_field_type( $value, $field, $field_type_obj ) {
+	if ( method_exists( $field_type_obj, 'pre_validate_filter' ) ) {
+		$value = call_user_func( array( $field_type_obj, 'pre_validate_filter' ), $value );
+	}
+
+	return $value;
+}
+
+/**
+ * Escape field value for display.
+ *
+ * Most field values are simply run through esc_html(). Those that support rich text (by default, `textarea` only)
+ * are sanitized using kses, which allows a whitelist of HTML tags.
+ *
+ * @since 2.4.0
+ *
+ * @param string $value      Field value.
+ * @param string $field_type Field type.
+ * @param int    $field_id   Field ID.
+ * @return string
+ */
+function bp_xprofile_escape_field_data( $value, $field_type, $field_id ) {
+	if ( bp_xprofile_is_richtext_enabled_for_field( $field_id ) ) {
+		// xprofile_filter_kses() expects a BP_XProfile_ProfileData object.
+		$data_obj = null;
+		if ( bp_is_user() ) {
+			$data_obj = new BP_XProfile_ProfileData( $field_id, bp_displayed_user_id() );
+		}
+
+		$value = xprofile_filter_kses( $value, $data_obj );
+	} else {
+		$value = esc_html( $value );
+	}
+
+	return $value;
+}
+
+/**
+ * Filter an Extended Profile field value, and attempt to make clickable links
+ * to members search results out of them.
+ *
+ * - Not run on datebox field types.
+ * - Not run on values without commas with less than 5 words.
+ * - URL's are made clickable.
+ * - To disable: remove_filter( 'bp_get_the_profile_field_value', 'xprofile_filter_link_profile_data', 9, 2 );
+ *
+ * @since 1.1.0
+ *
+ * @param string $field_value Profile field data value.
+ * @param string $field_type  Profile field type.
+ *
+ * @return string
+ */
+function xprofile_filter_link_profile_data( $field_value, $field_type = 'textbox' ) {
+
+	if ( 'datebox' === $field_type ) {
+		return $field_value;
+	}
+
+	if ( !strpos( $field_value, ',' ) && ( count( explode( ' ', $field_value ) ) > 5 ) ) {
+		return $field_value;
+	}
+
+	$values = explode( ',', $field_value );
+
+	if ( !empty( $values ) ) {
+		foreach ( (array) $values as $value ) {
+			$value = trim( $value );
+
+			// If the value is a URL, skip it and just make it clickable.
+			if ( preg_match( '@(https?://([-\w\.]+)+(:\d+)?(/([\w/_\.]*(\?\S+)?)?)?)@', $value ) ) {
+				$new_values[] = make_clickable( $value );
+
+			// Is not clickable.
+			} else {
+
+				// More than 5 spaces.
+				if ( count( explode( ' ', $value ) ) > 5 ) {
+					$new_values[] = $value;
+
+				// Less than 5 spaces.
+				} else {
+					$query_arg    = bp_core_get_component_search_query_arg( 'members' );
+					$search_url   = add_query_arg( array( $query_arg => urlencode( $value ) ), bp_get_members_directory_permalink() );
+					$new_values[] = '<a href="' . esc_url( $search_url ) . '" rel="nofollow">' . $value . '</a>';
+				}
+			}
+		}
+
+		$values = implode( ', ', $new_values );
+	}
+
+	return $values;
+}
+
+/**
+ * Ensures that BP data appears in comments array.
+ *
+ * This filter loops through the comments return by a normal WordPress request
+ * and swaps out user data with BP xprofile data, where available.
+ *
+ * @param array $comments Comments to filter in.
+ * @param int   $post_id  Post ID the comments are for.
+ *
+ * @return array $comments
+ */
+function xprofile_filter_comments( $comments, $post_id = 0 ) {
+
+	// Locate comment authors with WP accounts.
+	foreach( (array) $comments as $comment ) {
+		if ( $comment->user_id ) {
+			$user_ids[] = $comment->user_id;
+		}
+	}
+
+	// If none are found, just return the comments array.
+	if ( empty( $user_ids ) ) {
+		return $comments;
+	}
+
+	// Pull up the xprofile fullname of each commenter.
+	if ( $fullnames = bp_core_get_user_displaynames( $user_ids ) ) {
+		foreach( (array) $fullnames as $user_id => $user_fullname ) {
+			$users[ $user_id ] = trim( stripslashes( $user_fullname ) );
+		}
+	}
+
+	// Loop through and match xprofile fullname with commenters.
+	foreach( (array) $comments as $i => $comment ) {
+		if ( ! empty( $comment->user_id ) ) {
+			if ( ! empty( $users[ $comment->user_id ] ) ) {
+				$comments[ $i ]->comment_author = $users[ $comment->user_id ];
+			}
+		}
+	}
+
+	return $comments;
+}
+add_filter( 'comments_array', 'xprofile_filter_comments', 10, 2 );
+
+/**
+ * Filter BP_User_Query::populate_extras to override each queries users fullname.
+ *
+ * @since 1.7.0
+ *
+ * @param BP_User_Query $user_query   User query to filter.
+ * @param string        $user_ids_sql SQL statement to use.
+ */
+function bp_xprofile_filter_user_query_populate_extras( BP_User_Query $user_query, $user_ids_sql = '' ) {
+
+	if ( ! bp_is_active( 'xprofile' ) ) {
+		return;
+	}
+
+	$user_id_names = bp_core_get_user_displaynames( $user_query->user_ids );
+
+	// Loop through names and override each user's fullname.
+	foreach ( $user_id_names as $user_id => $user_fullname ) {
+		if ( isset( $user_query->results[ $user_id ] ) ) {
+			$user_query->results[ $user_id ]->fullname = $user_fullname;
+		}
+	}
+}
+add_filter( 'bp_user_query_populate_extras', 'bp_xprofile_filter_user_query_populate_extras', 2, 2 );
+
+/**
+ * Parse 'xprofile_query' argument passed to BP_User_Query.
+ *
+ * @since 2.2.0
+ *
+ * @param BP_User_Query $q User query object.
+ */
+function bp_xprofile_add_xprofile_query_to_user_query( BP_User_Query $q ) {
+
+	// Bail if no `xprofile_query` clause.
+	if ( empty( $q->query_vars['xprofile_query'] ) ) {
+		return;
+	}
+
+	$xprofile_query = new BP_XProfile_Query( $q->query_vars['xprofile_query'] );
+	$sql            = $xprofile_query->get_sql( 'u', $q->uid_name );
+
+	if ( ! empty( $sql['join'] ) ) {
+		$q->uid_clauses['select'] .= $sql['join'];
+		$q->uid_clauses['where'] .= $sql['where'];
+	}
+}
+add_action( 'bp_pre_user_query', 'bp_xprofile_add_xprofile_query_to_user_query' );
+
+/**
+ * Filter meta queries to modify for the xprofile data schema.
+ *
+ * @since 2.0.0
+ *
+ * @access private Do not use.
+ *
+ * @param string $q SQL query.
+ *
+ * @return string
+ */
+function bp_xprofile_filter_meta_query( $q ) {
+	global $wpdb;
+
+	$raw_q = $q;
+
+	/*
+	 * Replace quoted content with __QUOTE__ to avoid false positives.
+	 * This regular expression will match nested quotes.
+	 */
+	$quoted_regex = "/'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'/s";
+	preg_match_all( $quoted_regex, $q, $quoted_matches );
+	$q = preg_replace( $quoted_regex, '__QUOTE__', $q );
+
+	// Get the first word of the command.
+	preg_match( '/^(\S+)/', $q, $first_word_matches );
+
+	if ( empty( $first_word_matches[0] ) ) {
+		return $raw_q;
+	}
+
+	// Get the field type.
+	preg_match( '/xprofile_(group|field|data)_id/', $q, $matches );
+
+	if ( empty( $matches[0] ) || empty( $matches[1] ) ) {
+		return $raw_q;
+	}
+
+	switch ( $first_word_matches[0] ) {
+
+		/**
+		 * SELECT:
+		 * - replace 'xprofile_{fieldtype}_id' with 'object_id'
+		 * - ensure that 'object_id' is aliased to 'xprofile_{fieldtype}_id',
+		 *   because update_meta_cache() needs the column name to parse
+		 *   the query results
+		 * - append the 'object type' WHERE clause
+		 */
+		case 'SELECT' :
+			$q = str_replace(
+				array(
+					$matches[0],
+					'SELECT object_id',
+					'WHERE ',
+				),
+				array(
+					'object_id',
+					'SELECT object_id AS ' . $matches[0],
+					$wpdb->prepare( 'WHERE object_type = %s AND ', $matches[1] ),
+				),
+				$q
+			);
+			break;
+
+		/**
+		 * UPDATE and DELETE:
+		 * - replace 'xprofile_{fieldtype}_id' with 'object_id'
+		 * - append the 'object type' WHERE clause
+		 */
+		case 'UPDATE' :
+		case 'DELETE' :
+			$q = str_replace(
+				array(
+					$matches[0],
+					'WHERE ',
+				),
+				array(
+					'object_id',
+					$wpdb->prepare( 'WHERE object_type = %s AND ', $matches[1] ),
+				),
+				$q
+			);
+			break;
+
+		/**
+		 * UPDATE and DELETE:
+		 * - replace 'xprofile_{fieldtype}_id' with 'object_id'
+		 * - ensure that the object_type field gets filled in
+		 */
+		case 'INSERT' :
+			$q = str_replace(
+				array(
+					'`' . $matches[0] . '`',
+					'VALUES (',
+				),
+				array(
+					'`object_type`,`object_id`',
+					$wpdb->prepare( "VALUES (%s,", $matches[1] ),
+				),
+				$q
+			);
+			break;
+	}
+
+	// Put quoted content back into the string.
+	if ( ! empty( $quoted_matches[0] ) ) {
+		for ( $i = 0; $i < count( $quoted_matches[0] ); $i++ ) {
+			$quote_pos = strpos( $q, '__QUOTE__' );
+			$q = substr_replace( $q, $quoted_matches[0][ $i ], $quote_pos, 9 );
+		}
+	}
+
+	return $q;
+}
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-functions.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-functions.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-functions.php	(revision 10410)
@@ -0,0 +1,1217 @@
+<?php
+/**
+ * BuddyPress XProfile Filters.
+ *
+ * Business functions are where all the magic happens in BuddyPress. They will
+ * handle the actual saving or manipulation of information. Usually they will
+ * hand off to a database class for data access, then return
+ * true or false on success or failure.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileFunctions
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/*** Field Group Management **************************************************/
+
+/**
+ * Fetch a set of field groups, populated with fields and field data.
+ *
+ * Procedural wrapper for BP_XProfile_Group::get() method.
+ *
+ * @since 2.1.0
+ *
+ * @param array $args See {@link BP_XProfile_Group::get()} for description of
+ *                    arguments.
+ *
+ * @return array $groups
+ */
+function bp_xprofile_get_groups( $args = array() ) {
+
+	$groups = BP_XProfile_Group::get( $args );
+
+	/**
+	 * Filters a set of field groups, populated with fields and field data.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param array $groups Array of field groups and field data.
+	 * @param array $args   Array of arguments used to query for groups.
+	 */
+	return apply_filters( 'bp_xprofile_get_groups', $groups, $args );
+}
+
+/**
+ * Insert a new profile field group.
+ *
+ * @since 1.0.0
+ *
+ * @param array|string $args Array of arguments for field group insertion.
+ *
+ * @return boolean
+ */
+function xprofile_insert_field_group( $args = '' ) {
+
+	// Parse the arguments.
+	$r = bp_parse_args( $args, array(
+		'field_group_id' => false,
+		'name'           => false,
+		'description'    => '',
+		'can_delete'     => true
+	), 'xprofile_insert_field_group' );
+
+	// Bail if no group name.
+	if ( empty( $r['name'] ) ) {
+		return false;
+	}
+
+	// Create new field group object, maybe using an existing ID.
+	$field_group              = new BP_XProfile_Group( $r['field_group_id'] );
+	$field_group->name        = $r['name'];
+	$field_group->description = $r['description'];
+	$field_group->can_delete  = $r['can_delete'];
+
+	return $field_group->save();
+}
+
+/**
+ * Get a specific profile field group.
+ *
+ * @since 1.0.0
+ *
+ * @param int $field_group_id Field group ID to fetch.
+ *
+ * @return boolean|BP_XProfile_Group
+ */
+function xprofile_get_field_group( $field_group_id = 0 ) {
+
+	// Try to get a specific field group by ID.
+	$field_group = new BP_XProfile_Group( $field_group_id );
+
+	// Bail if group was not found.
+	if ( empty( $field_group->id ) ) {
+		return false;
+	}
+
+	// Return field group.
+	return $field_group;
+}
+
+/**
+ * Delete a specific profile field group.
+ *
+ * @since 1.0.0
+ *
+ * @param int $field_group_id Field group ID to delete.
+ *
+ * @return boolean
+ */
+function xprofile_delete_field_group( $field_group_id = 0 ) {
+
+	// Try to get a specific field group by ID.
+	$field_group = xprofile_get_field_group( $field_group_id );
+
+	// Bail if group was not found.
+	if ( false === $field_group ) {
+		return false;
+	}
+
+	// Return the results of trying to delete the field group.
+	return $field_group->delete();
+}
+
+/**
+ * Update the position of a specific profile field group.
+ *
+ * @since 1.0.0
+ *
+ * @param int $field_group_id Field group ID to update.
+ * @param int $position       Field group position to update to.
+ *
+ * @return boolean
+ */
+function xprofile_update_field_group_position( $field_group_id = 0, $position = 0 ) {
+	return BP_XProfile_Group::update_position( $field_group_id, $position );
+}
+
+/*** Field Management *********************************************************/
+
+/**
+ * Get details of all xprofile field types.
+ *
+ * @since 2.0.0
+ *
+ * @return array Key/value pairs (field type => class name).
+ */
+function bp_xprofile_get_field_types() {
+	$fields = array(
+		'checkbox'       => 'BP_XProfile_Field_Type_Checkbox',
+		'datebox'        => 'BP_XProfile_Field_Type_Datebox',
+		'multiselectbox' => 'BP_XProfile_Field_Type_Multiselectbox',
+		'number'         => 'BP_XProfile_Field_Type_Number',
+		'url'            => 'BP_XProfile_Field_Type_URL',
+		'radio'          => 'BP_XProfile_Field_Type_Radiobutton',
+		'selectbox'      => 'BP_XProfile_Field_Type_Selectbox',
+		'textarea'       => 'BP_XProfile_Field_Type_Textarea',
+		'textbox'        => 'BP_XProfile_Field_Type_Textbox',
+	);
+
+	/**
+	 * Filters the list of all xprofile field types.
+	 *
+	 * If you've added a custom field type in a plugin, register it with this filter.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $fields Array of field type/class name pairings.
+	 */
+	return apply_filters( 'bp_xprofile_get_field_types', $fields );
+}
+
+/**
+ * Creates the specified field type object; used for validation and templating.
+ *
+ * @since 2.0.0
+ *
+ * @param string $type Type of profile field to create. See {@link bp_xprofile_get_field_types()} for default core values.
+ *
+ * @return object $value If field type unknown, returns BP_XProfile_Field_Type_Textarea.
+ *                       Otherwise returns an instance of the relevant child class of BP_XProfile_Field_Type.
+ */
+function bp_xprofile_create_field_type( $type ) {
+
+	$field = bp_xprofile_get_field_types();
+	$class = isset( $field[$type] ) ? $field[$type] : '';
+
+	/**
+	 * To handle (missing) field types, fallback to a placeholder field object if a type is unknown.
+	 */
+	if ( $class && class_exists( $class ) ) {
+		return new $class;
+	} else {
+		return new BP_XProfile_Field_Type_Placeholder;
+	}
+}
+
+/**
+ * Insert or update an xprofile field.
+ *
+ * @param array|string $args {
+ *     Array of arguments.
+ *     @type int    $field_id          Optional. Pass the ID of an existing field to edit that field.
+ *     @type int    $field_group_id    ID of the associated field group.
+ *     @type int    $parent_id         Optional. ID of the parent field.
+ *     @type string $type              Field type. Checked against a field_types whitelist.
+ *     @type string $name              Name of the new field.
+ *     @type string $description       Optional. Descriptive text for the field.
+ *     @type bool   $is_required       Optional. Whether users must provide a value for the field. Default: false.
+ *     @type bool   $can_delete        Optional. Whether admins can delete this field in the Dashboard interface.
+ *                                     Generally this is false only for the Name field, which is required throughout BP.
+ *                                     Default: true.
+ *     @type string $order_by          Optional. For field types that support options (such as 'radio'), this flag
+ *                                     determines whether the sort order of the options will be 'default'
+ *                                     (order created) or 'custom'.
+ *     @type bool   $is_default_option Optional. For the 'option' field type, setting this value to true means that
+ *                                     it'll be the default value for the parent field when the user has not yet
+ *                                     overridden. Default: true.
+ *     @type int    $option_order      Optional. For the 'option' field type, this determines the order in which the
+ *                                     options appear.
+ * }
+ * @return bool|int False on failure, ID of new field on success.
+ */
+function xprofile_insert_field( $args = '' ) {
+
+	$r = wp_parse_args( $args, array(
+		'field_id'          => null,
+		'field_group_id'    => null,
+		'parent_id'         => null,
+		'type'              => '',
+		'name'              => '',
+		'description'       => '',
+		'is_required'       => false,
+		'can_delete'        => true,
+		'order_by'          => '',
+		'is_default_option' => false,
+		'option_order'      => null,
+		'field_order'       => null,
+	) );
+
+	// Field_group_id is required.
+	if ( empty( $r['field_group_id'] ) ) {
+		return false;
+	}
+
+	// Check this is a non-empty, valid field type.
+	if ( ! in_array( $r['type'], (array) buddypress()->profile->field_types ) ) {
+		return false;
+	}
+
+	// Instantiate a new field object.
+	if ( ! empty( $r['field_id'] ) ) {
+		$field = xprofile_get_field( $r['field_id'] );
+	} else {
+		$field = new BP_XProfile_Field;
+	}
+
+	$field->group_id = $r['field_group_id'];
+	$field->type     = $r['type'];
+
+	// The 'name' field cannot be empty.
+	if ( ! empty( $r['name'] ) ) {
+		$field->name = $r['name'];
+	}
+
+	$field->description       = $r['description'];
+	$field->order_by          = $r['order_by'];
+	$field->parent_id         = (int) $r['parent_id'];
+	$field->field_order       = (int) $r['field_order'];
+	$field->option_order      = (int) $r['option_order'];
+	$field->is_required       = (bool) $r['is_required'];
+	$field->can_delete        = (bool) $r['can_delete'];
+	$field->is_default_option = (bool) $r['is_default_option'];
+
+	return $field->save();
+}
+
+/**
+ * Get a profile field object.
+ *
+ * @param int|object $field ID of the field or object representing field data.
+ * @return BP_XProfile_Field|null Field object if found, otherwise null.
+ */
+function xprofile_get_field( $field ) {
+	if ( $field instanceof BP_XProfile_Field ) {
+		$_field = $field;
+	} elseif ( is_object( $field ) ) {
+		$_field = new BP_XProfile_Field();
+		$_field->fill_data( $field );
+	} else {
+		$_field = BP_XProfile_Field::get_instance( $field );
+	}
+
+	if ( ! $_field ) {
+		return null;
+	}
+
+	return $_field;
+}
+
+function xprofile_delete_field( $field_id ) {
+	$field = new BP_XProfile_Field( $field_id );
+	return $field->delete();
+}
+
+/*** Field Data Management *****************************************************/
+
+
+/**
+ * Fetches profile data for a specific field for the user.
+ *
+ * When the field value is serialized, this function unserializes and filters
+ * each item in the array.
+ *
+ * @uses BP_XProfile_ProfileData::get_value_byid() Fetches the value based on the params passed.
+ *
+ * @param mixed  $field        The ID of the field, or the $name of the field.
+ * @param int    $user_id      The ID of the user.
+ * @param string $multi_format How should array data be returned? 'comma' if you want a
+ *                             comma-separated string; 'array' if you want an array.
+ *
+ * @return mixed The profile field data.
+ */
+function xprofile_get_field_data( $field, $user_id = 0, $multi_format = 'array' ) {
+
+	if ( empty( $user_id ) ) {
+		$user_id = bp_displayed_user_id();
+	}
+
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	if ( is_numeric( $field ) ) {
+		$field_id = $field;
+	} else {
+		$field_id = xprofile_get_field_id_from_name( $field );
+	}
+
+	if ( empty( $field_id ) ) {
+		return false;
+	}
+
+	$values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $field_id, $user_id ) );
+
+	if ( is_array( $values ) ) {
+		$data = array();
+		foreach( (array) $values as $value ) {
+
+			/**
+			 * Filters the field data value for a specific field for the user.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param string $value    Value saved for the field.
+			 * @param int    $field_id ID of the field being displayed.
+			 * @param int    $user_id  ID of the user being displayed.
+			 */
+			$data[] = apply_filters( 'xprofile_get_field_data', $value, $field_id, $user_id );
+		}
+
+		if ( 'comma' == $multi_format ) {
+			$data = implode( ', ', $data );
+		}
+	} else {
+		/** This filter is documented in bp-xprofile/bp-xprofile-functions.php */
+		$data = apply_filters( 'xprofile_get_field_data', $values, $field_id, $user_id );
+	}
+
+	return $data;
+}
+/**
+ * A simple function to set profile data for a specific field for a specific user.
+ *
+ * @uses xprofile_get_field_id_from_name() Gets the ID from the field based on the name.
+ *
+ * @param int|string $field       The ID of the field, or the $name of the field.
+ * @param int        $user_id     The ID of the user.
+ * @param mixed      $value       The value for the field you want to set for the user.
+ * @param bool       $is_required Whether or not the field is required.
+ *
+ * @return bool True on success, false on failure.
+ */
+function xprofile_set_field_data( $field, $user_id, $value, $is_required = false ) {
+
+	if ( is_numeric( $field ) ) {
+		$field_id = $field;
+	} else {
+		$field_id = xprofile_get_field_id_from_name( $field );
+	}
+
+	if ( empty( $field_id ) ) {
+		return false;
+	}
+
+	$field          = xprofile_get_field( $field_id );
+	$field_type     = BP_XProfile_Field::get_type( $field_id );
+	$field_type_obj = bp_xprofile_create_field_type( $field_type );
+
+	/**
+	 * Filter the raw submitted profile field value.
+	 *
+	 * Use this filter to modify the values submitted by users before
+	 * doing field-type-specific validation.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param mixed                  $value          Value passed to xprofile_set_field_data().
+	 * @param BP_XProfile_Field      $field          Field object.
+	 * @param BP_XProfile_Field_Type $field_type_obj Field type object.
+	 */
+	$value = apply_filters( 'bp_xprofile_set_field_data_pre_validate', $value, $field, $field_type_obj );
+
+	// Special-case support for integer 0 for the number field type.
+	if ( $is_required && ! is_integer( $value ) && $value !== '0' && ( empty( $value ) || ! is_array( $value ) && ! strlen( trim( $value ) ) ) ) {
+		return false;
+	}
+
+	/**
+	 * Certain types of fields (checkboxes, multiselects) may come through empty.
+	 * Save as empty array so this isn't overwritten by the default on next edit.
+	 *
+	 * Special-case support for integer 0 for the number field type
+	 */
+	if ( empty( $value ) && ! is_integer( $value ) && $value !== '0' && $field_type_obj->accepts_null_value ) {
+		$value = array();
+	}
+
+	// If the value is empty, then delete any field data that exists, unless the field is of a type
+	// where null values are semantically meaningful.
+	if ( empty( $value ) && ! is_integer( $value ) && $value !== '0' && ! $field_type_obj->accepts_null_value ) {
+		xprofile_delete_field_data( $field_id, $user_id );
+		return true;
+	}
+
+	// For certain fields, only certain parameters are acceptable, so add them to the whitelist.
+	if ( $field_type_obj->supports_options ) {
+		$field_type_obj->set_whitelist_values( wp_list_pluck( $field->get_children(), 'name' ) );
+	}
+
+	// Check the value is in an accepted format for this form field.
+	if ( ! $field_type_obj->is_valid( $value ) ) {
+		return false;
+	}
+
+	$field           = new BP_XProfile_ProfileData();
+	$field->field_id = $field_id;
+	$field->user_id  = $user_id;
+	$field->value    = maybe_serialize( $value );
+
+	return $field->save();
+}
+
+/**
+ * Set the visibility level for this field.
+ *
+ * @param int    $field_id         The ID of the xprofile field.
+ * @param int    $user_id          The ID of the user to whom the data belongs.
+ * @param string $visibility_level What the visibity setting should be.
+ *
+ * @return bool True on success
+ */
+function xprofile_set_field_visibility_level( $field_id = 0, $user_id = 0, $visibility_level = '' ) {
+	if ( empty( $field_id ) || empty( $user_id ) || empty( $visibility_level ) ) {
+		return false;
+	}
+
+	// Check against a whitelist.
+	$allowed_values = bp_xprofile_get_visibility_levels();
+	if ( !array_key_exists( $visibility_level, $allowed_values ) ) {
+		return false;
+	}
+
+	// Stored in an array in usermeta.
+	$current_visibility_levels = bp_get_user_meta( $user_id, 'bp_xprofile_visibility_levels', true );
+
+	if ( !$current_visibility_levels ) {
+		$current_visibility_levels = array();
+	}
+
+	$current_visibility_levels[$field_id] = $visibility_level;
+
+	return bp_update_user_meta( $user_id, 'bp_xprofile_visibility_levels', $current_visibility_levels );
+}
+
+/**
+ * Get the visibility level for a field.
+ *
+ * @since 2.0.0
+ *
+ * @param int $field_id The ID of the xprofile field.
+ * @param int $user_id The ID of the user to whom the data belongs.
+ *
+ * @return string
+ */
+function xprofile_get_field_visibility_level( $field_id = 0, $user_id = 0 ) {
+	$current_level = '';
+
+	if ( empty( $field_id ) || empty( $user_id ) ) {
+		return $current_level;
+	}
+
+	$current_levels = bp_get_user_meta( $user_id, 'bp_xprofile_visibility_levels', true );
+	$current_level  = isset( $current_levels[ $field_id ] ) ? $current_levels[ $field_id ] : '';
+
+	// Use the user's stored level, unless custom visibility is disabled.
+	$field = xprofile_get_field( $field_id );
+	if ( isset( $field->allow_custom_visibility ) && 'disabled' === $field->allow_custom_visibility ) {
+		$current_level = $field->default_visibility;
+	}
+
+	// If we're still empty, it means that overrides are permitted, but the
+	// user has not provided a value. Use the default value.
+	if ( empty( $current_level ) ) {
+		$current_level = $field->default_visibility;
+	}
+
+	return $current_level;
+}
+
+function xprofile_delete_field_data( $field = '', $user_id = 0 ) {
+
+	// Get the field ID.
+	if ( is_numeric( $field ) ) {
+		$field_id = (int) $field;
+	} else {
+		$field_id = xprofile_get_field_id_from_name( $field );
+	}
+
+	// Bail if field or user ID are empty.
+	if ( empty( $field_id ) || empty( $user_id ) ) {
+		return false;
+	}
+
+	// Get the profile field data to delete.
+	$field = new BP_XProfile_ProfileData( $field_id, $user_id );
+
+	// Delete the field data.
+	return $field->delete();
+}
+
+function xprofile_check_is_required_field( $field_id ) {
+	$field  = new BP_XProfile_Field( $field_id );
+	$retval = false;
+
+	if ( isset( $field->is_required ) ) {
+		$retval = $field->is_required;
+	}
+
+	return (bool) $retval;
+}
+
+/**
+ * Returns the ID for the field based on the field name.
+ *
+ * @package BuddyPress Core
+ * @param string $field_name The name of the field to get the ID for.
+ * @return int $field_id on success, false on failure.
+ */
+function xprofile_get_field_id_from_name( $field_name ) {
+	return BP_XProfile_Field::get_id_from_name( $field_name );
+}
+
+/**
+ * Fetches a random piece of profile data for the user.
+ *
+ * @global BuddyPress $bp           The one true BuddyPress instance.
+ * @global object     $wpdb         WordPress DB access object.
+ * @global object     $current_user WordPress global variable containing current logged in user information.
+ * @uses xprofile_format_profile_field() Formats profile field data so it is suitable for display.
+ *
+ * @param int  $user_id          User ID of the user to get random data for.
+ * @param bool $exclude_fullname Optional; whether or not to exclude the full name field as random data.
+ *                               Defaults to true.
+ *
+ * @return string|bool The fetched random data for the user, or false if no data or no match.
+ */
+function xprofile_get_random_profile_data( $user_id, $exclude_fullname = true ) {
+	$field_data = BP_XProfile_ProfileData::get_random( $user_id, $exclude_fullname );
+
+	if ( empty( $field_data ) ) {
+		return false;
+	}
+
+	$field_data[0]->value = xprofile_format_profile_field( $field_data[0]->type, $field_data[0]->value );
+
+	if ( empty( $field_data[0]->value ) ) {
+		return false;
+	}
+
+	/**
+	 * Filters a random piece of profile data for the user.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param array $field_data Array holding random profile data.
+	 */
+	return apply_filters( 'xprofile_get_random_profile_data', $field_data );
+}
+
+/**
+ * Formats a profile field according to its type. [ TODO: Should really be moved to filters ]
+ *
+ * @param string $field_type  The type of field: datebox, selectbox, textbox etc.
+ * @param string $field_value The actual value.
+ *
+ * @return string|bool The formatted value, or false if value is empty.
+ */
+function xprofile_format_profile_field( $field_type, $field_value ) {
+
+	if ( empty( $field_value ) ) {
+		return false;
+	}
+
+	$field_value = bp_unserialize_profile_field( $field_value );
+
+	if ( 'datebox' != $field_type ) {
+		$content = $field_value;
+		$field_value = str_replace( ']]>', ']]&gt;', $content );
+	}
+
+	return xprofile_filter_format_field_value_by_type( stripslashes_deep( $field_value ), $field_type );
+}
+
+function xprofile_update_field_position( $field_id, $position, $field_group_id ) {
+	return BP_XProfile_Field::update_position( $field_id, $position, $field_group_id );
+}
+
+/**
+ * Replace the displayed and logged-in users fullnames with the xprofile name, if required.
+ *
+ * The Members component uses the logged-in user's display_name to set the
+ * value of buddypress()->loggedin_user->fullname. However, in cases where
+ * profile sync is disabled, display_name may diverge from the xprofile
+ * fullname field value, and the xprofile field should take precedence.
+ *
+ * Runs at bp_setup_globals:100 to ensure that all components have loaded their
+ * globals before attempting any overrides.
+ *
+ * @since 2.0.0
+ */
+function xprofile_override_user_fullnames() {
+	// If sync is enabled, the two names will match. No need to continue.
+	if ( ! bp_disable_profile_sync() ) {
+		return;
+	}
+
+	if ( bp_loggedin_user_id() ) {
+		buddypress()->loggedin_user->fullname = bp_core_get_user_displayname( bp_loggedin_user_id() );
+	}
+
+	if ( bp_displayed_user_id() ) {
+		buddypress()->displayed_user->fullname = bp_core_get_user_displayname( bp_displayed_user_id() );
+	}
+}
+add_action( 'bp_setup_globals', 'xprofile_override_user_fullnames', 100 );
+
+/**
+ * Setup the avatar upload directory for a user.
+ *
+ * @since 1.0.0
+ *
+ * @package BuddyPress Core
+ *
+ * @param string $directory The root directory name. Optional.
+ * @param int    $user_id   The user ID. Optional.
+ *
+ * @return array() Array containing the path, URL, and other helpful settings.
+ */
+function xprofile_avatar_upload_dir( $directory = 'avatars', $user_id = 0 ) {
+
+	// Use displayed user if no user ID was passed.
+	if ( empty( $user_id ) ) {
+		$user_id = bp_displayed_user_id();
+	}
+
+	// Failsafe against accidentally nooped $directory parameter.
+	if ( empty( $directory ) ) {
+		$directory = 'avatars';
+	}
+
+	$path      = bp_core_avatar_upload_path() . '/' . $directory. '/' . $user_id;
+	$newbdir   = $path;
+	$newurl    = bp_core_avatar_url() . '/' . $directory. '/' . $user_id;
+	$newburl   = $newurl;
+	$newsubdir = '/' . $directory. '/' . $user_id;
+
+	/**
+	 * Filters the avatar upload directory for a user.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param array $value Array containing the path, URL, and other helpful settings.
+	 */
+	return apply_filters( 'xprofile_avatar_upload_dir', array(
+		'path'    => $path,
+		'url'     => $newurl,
+		'subdir'  => $newsubdir,
+		'basedir' => $newbdir,
+		'baseurl' => $newburl,
+		'error'   => false
+	) );
+}
+
+/**
+ * When search_terms are passed to BP_User_Query, search against xprofile fields.
+ *
+ * @since 2.0.0
+ *
+ * @param array         $sql   Clauses in the user_id SQL query.
+ * @param BP_User_Query $query User query object.
+ *
+ * @return array
+ */
+function bp_xprofile_bp_user_query_search( $sql, BP_User_Query $query ) {
+	global $wpdb;
+
+	if ( empty( $query->query_vars['search_terms'] ) || empty( $sql['where']['search'] ) ) {
+		return $sql;
+	}
+
+	$bp = buddypress();
+
+	$search_terms_clean = bp_esc_like( wp_kses_normalize_entities( $query->query_vars['search_terms'] ) );
+
+	if ( $query->query_vars['search_wildcard'] === 'left' ) {
+		$search_terms_nospace = '%' . $search_terms_clean;
+		$search_terms_space   = '%' . $search_terms_clean . ' %';
+	} elseif ( $query->query_vars['search_wildcard'] === 'right' ) {
+		$search_terms_nospace =        $search_terms_clean . '%';
+		$search_terms_space   = '% ' . $search_terms_clean . '%';
+	} else {
+		$search_terms_nospace = '%' . $search_terms_clean . '%';
+		$search_terms_space   = '%' . $search_terms_clean . '%';
+	}
+
+	// Combine the core search (against wp_users) into a single OR clause
+	// with the xprofile_data search.
+	$search_xprofile = $wpdb->prepare(
+		"u.{$query->uid_name} IN ( SELECT user_id FROM {$bp->profile->table_name_data} WHERE value LIKE %s OR value LIKE %s )",
+		$search_terms_nospace,
+		$search_terms_space
+	);
+
+	$search_core     = $sql['where']['search'];
+	$search_combined = "( {$search_xprofile} OR {$search_core} )";
+	$sql['where']['search'] = $search_combined;
+
+	return $sql;
+}
+add_action( 'bp_user_query_uid_clauses', 'bp_xprofile_bp_user_query_search', 10, 2 );
+
+/**
+ * Syncs Xprofile data to the standard built in WordPress profile data.
+ *
+ * @param int $user_id ID of the user to sync.
+ *
+ * @return bool
+ */
+function xprofile_sync_wp_profile( $user_id = 0 ) {
+
+	// Bail if profile syncing is disabled.
+	if ( bp_disable_profile_sync() ) {
+		return true;
+	}
+
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	if ( empty( $user_id ) ) {
+		return false;
+	}
+
+	$fullname = xprofile_get_field_data( bp_xprofile_fullname_field_id(), $user_id );
+	$space    = strpos( $fullname, ' ' );
+
+	if ( false === $space ) {
+		$firstname = $fullname;
+		$lastname = '';
+	} else {
+		$firstname = substr( $fullname, 0, $space );
+		$lastname = trim( substr( $fullname, $space, strlen( $fullname ) ) );
+	}
+
+	bp_update_user_meta( $user_id, 'nickname',   $fullname  );
+	bp_update_user_meta( $user_id, 'first_name', $firstname );
+	bp_update_user_meta( $user_id, 'last_name',  $lastname  );
+
+	global $wpdb;
+
+	$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->users} SET display_name = %s WHERE ID = %d", $fullname, $user_id ) );
+}
+add_action( 'xprofile_updated_profile', 'xprofile_sync_wp_profile' );
+add_action( 'bp_core_signup_user',      'xprofile_sync_wp_profile' );
+add_action( 'bp_core_activated_user',   'xprofile_sync_wp_profile' );
+
+
+/**
+ * Syncs the standard built in WordPress profile data to XProfile.
+ *
+ * @since 1.2.4
+ *
+ * @param object $errors Array of errors. Passed by reference.
+ * @param bool   $update Whether or not being upated.
+ * @param object $user   User object whose profile is being synced. Passed by reference.
+ */
+function xprofile_sync_bp_profile( &$errors, $update, &$user ) {
+
+	// Bail if profile syncing is disabled.
+	if ( bp_disable_profile_sync() || ! $update || $errors->get_error_codes() ) {
+		return;
+	}
+
+	xprofile_set_field_data( bp_xprofile_fullname_field_id(), $user->ID, $user->display_name );
+}
+add_action( 'user_profile_update_errors', 'xprofile_sync_bp_profile', 10, 3 );
+
+
+/**
+ * When a user is deleted, we need to clean up the database and remove all the
+ * profile data from each table. Also we need to clean anything up in the
+ * usermeta table that this component uses.
+ *
+ * @param int $user_id The ID of the deleted user.
+ */
+function xprofile_remove_data( $user_id ) {
+	BP_XProfile_ProfileData::delete_data_for_user( $user_id );
+}
+add_action( 'wpmu_delete_user',  'xprofile_remove_data' );
+add_action( 'delete_user',       'xprofile_remove_data' );
+add_action( 'bp_make_spam_user', 'xprofile_remove_data' );
+
+/*** XProfile Meta ****************************************************/
+
+/**
+ * Delete a piece of xprofile metadata.
+ *
+ * @param int         $object_id   ID of the object the metadata belongs to.
+ * @param string      $object_type Type of object. 'group', 'field', or 'data'.
+ * @param string|bool $meta_key    Key of the metadata being deleted. If omitted, all
+ *                                 metadata for the object will be deleted.
+ * @param mixed       $meta_value  Optional. If provided, only metadata that matches
+ *                                 the value will be permitted.
+ * @param bool        $delete_all  Optional. If true, delete matching metadata entries
+ *                                 for all objects, ignoring the specified object_id. Otherwise, only
+ *                                 delete matching metadata entries for the specified object.
+ *                                 Default: false.
+ *
+ * @return bool True on success, false on failure.
+ */
+function bp_xprofile_delete_meta( $object_id, $object_type, $meta_key = false, $meta_value = false, $delete_all = false ) {
+	global $wpdb;
+
+	// Sanitize object type.
+	if ( ! in_array( $object_type, array( 'group', 'field', 'data' ) ) ) {
+		return false;
+	}
+
+	// Legacy - if no meta_key is passed, delete all for the item.
+	if ( empty( $meta_key ) ) {
+		$table_key  = 'xprofile_' . $object_type . 'meta';
+		$table_name = $wpdb->{$table_key};
+		$keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$table_name} WHERE object_type = %s AND object_id = %d", $object_type, $object_id ) );
+
+		// Force delete_all to false if deleting all for object.
+		$delete_all = false;
+	} else {
+		$keys = array( $meta_key );
+	}
+
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	add_filter( 'query', 'bp_xprofile_filter_meta_query' );
+
+	$retval = false;
+	foreach ( $keys as $key ) {
+		$retval = delete_metadata( 'xprofile_' . $object_type, $object_id, $key, $meta_value, $delete_all );
+	}
+
+	remove_filter( 'query', 'bp_xprofile_filter_meta_query' );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Get a piece of xprofile metadata.
+ *
+ * Note that the default value of $single is true, unlike in the case of the
+ * underlying get_metadata() function. This is for backward compatibility.
+ *
+ * @param int    $object_id   ID of the object the metadata belongs to.
+ * @param string $object_type Type of object. 'group', 'field', or 'data'.
+ * @param string $meta_key    Key of the metadata being fetched. If omitted, all
+ *                            metadata for the object will be retrieved.
+ * @param bool   $single      Optional. If true, return only the first value of the
+ *                            specified meta_key. This parameter has no effect if meta_key is not
+ *                            specified. Default: true.
+ *
+ * @return mixed Meta value if found. False on failure.
+ */
+function bp_xprofile_get_meta( $object_id, $object_type, $meta_key = '', $single = true ) {
+	// Sanitize object type.
+	if ( ! in_array( $object_type, array( 'group', 'field', 'data' ) ) ) {
+		return false;
+	}
+
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	add_filter( 'query', 'bp_xprofile_filter_meta_query' );
+	$retval = get_metadata( 'xprofile_' . $object_type, $object_id, $meta_key, $single );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+	remove_filter( 'query', 'bp_xprofile_filter_meta_query' );
+
+	return $retval;
+}
+
+/**
+ * Update a piece of xprofile metadata.
+ *
+ * @param int    $object_id   ID of the object the metadata belongs to.
+ * @param string $object_type Type of object. 'group', 'field', or 'data'.
+ * @param string $meta_key    Key of the metadata being updated.
+ * @param mixed  $meta_value  Value of the metadata being updated.
+ * @param mixed  $prev_value  Optional. If specified, only update existing
+ *                            metadata entries with the specified value.
+ *                            Otherwise update all entries.
+ *
+ * @return bool|int Returns false on failure. On successful update of existing
+ *                  metadata, returns true. On successful creation of new metadata,
+ *                  returns the integer ID of the new metadata row.
+ */
+function bp_xprofile_update_meta( $object_id, $object_type, $meta_key, $meta_value, $prev_value = '' ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	add_filter( 'query', 'bp_xprofile_filter_meta_query' );
+	$retval = update_metadata( 'xprofile_' . $object_type, $object_id, $meta_key, $meta_value, $prev_value );
+	remove_filter( 'query', 'bp_xprofile_filter_meta_query' );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+
+	return $retval;
+}
+
+/**
+ * Add a piece of xprofile metadata.
+ *
+ * @since 2.0.0
+ *
+ * @param int    $object_id   ID of the object the metadata belongs to.
+ * @param string $object_type Type of object. 'group', 'field', or 'data'.
+ * @param string $meta_key    Metadata key.
+ * @param mixed  $meta_value  Metadata value.
+ * @param bool   $unique      Optional. Whether to enforce a single metadata value
+ *                            for the given key. If true, and the object already
+ *                            has a value for the key, no change will be made.
+ *                            Default false.
+ *
+ * @return int|bool The meta ID on successful update, false on failure.
+ */
+function bp_xprofile_add_meta( $object_id, $object_type, $meta_key, $meta_value, $unique = false ) {
+	add_filter( 'query', 'bp_filter_metaid_column_name' );
+	add_filter( 'query', 'bp_xprofile_filter_meta_query' );
+	$retval = add_metadata( 'xprofile_' . $object_type , $object_id, $meta_key, $meta_value, $unique );
+	remove_filter( 'query', 'bp_filter_metaid_column_name' );
+	remove_filter( 'query', 'bp_xprofile_filter_meta_query' );
+
+	return $retval;
+}
+
+function bp_xprofile_update_fieldgroup_meta( $field_group_id, $meta_key, $meta_value ) {
+	return bp_xprofile_update_meta( $field_group_id, 'group', $meta_key, $meta_value );
+}
+
+function bp_xprofile_update_field_meta( $field_id, $meta_key, $meta_value ) {
+	return bp_xprofile_update_meta( $field_id, 'field', $meta_key, $meta_value );
+}
+
+function bp_xprofile_update_fielddata_meta( $field_data_id, $meta_key, $meta_value ) {
+	return bp_xprofile_update_meta( $field_data_id, 'data', $meta_key, $meta_value );
+}
+
+/**
+ * Return the field ID for the Full Name xprofile field.
+ *
+ * @since 2.0.0
+ *
+ * @return int Field ID.
+ */
+function bp_xprofile_fullname_field_id() {
+	$id = wp_cache_get( 'fullname_field_id', 'bp_xprofile' );
+
+	if ( false === $id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+		$id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE name = %s", bp_xprofile_fullname_field_name() ) );
+
+		wp_cache_set( 'fullname_field_id', $id, 'bp_xprofile' );
+	}
+
+	return absint( $id );
+}
+
+/**
+ * Return the field name for the Full Name xprofile field.
+ *
+ * @package BuddyPress
+ * @since 1.5.0
+ *
+ * @return string The field name.
+ */
+function bp_xprofile_fullname_field_name() {
+
+	/**
+	 * Filters the field name for the Full Name xprofile field.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param string BP_XPROFILE_FULLNAME_FIELD_NAME Full name field constant.
+	 */
+	return apply_filters( 'bp_xprofile_fullname_field_name', BP_XPROFILE_FULLNAME_FIELD_NAME );
+}
+
+/**
+ * Is rich text enabled for this profile field?
+ *
+ * By default, rich text is enabled for textarea fields and disabled for all other field types.
+ *
+ * @since 2.4.0
+ *
+ * @param int $field_id Optional. Default current field ID.
+ * @return bool
+ */
+function bp_xprofile_is_richtext_enabled_for_field( $field_id = null ) {
+	if ( ! $field_id ) {
+		$field_id = bp_get_the_profile_field_id();
+	}
+
+	$field = xprofile_get_field( $field_id );
+
+	$enabled = false;
+	if ( $field instanceof BP_XProfile_Field ) {
+		$enabled = (bool) $field->type_obj->supports_richtext;
+	}
+
+	/**
+	 * Filters whether richtext is enabled for the given field.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param bool $enabled  True if richtext is enabled for the field, otherwise false.
+	 * @param int  $field_id ID of the field.
+	 */
+	return apply_filters( 'bp_xprofile_is_richtext_enabled_for_field', $enabled, $field_id );
+}
+
+/**
+ * Get visibility levels out of the $bp global.
+ *
+ * @return array
+ */
+function bp_xprofile_get_visibility_levels() {
+
+	/**
+	 * Filters the visibility levels out of the $bp global.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $visibility_levels Array of visibility levels.
+	 */
+	return apply_filters( 'bp_xprofile_get_visibility_levels', buddypress()->profile->visibility_levels );
+}
+
+/**
+ * Get the ids of fields that are hidden for this displayed/loggedin user pair.
+ *
+ * This is the function primarily responsible for profile field visibility. It works by determining
+ * the relationship between the displayed_user (ie the profile owner) and the current_user (ie the
+ * profile viewer). Then, based on that relationship, we query for the set of fields that should
+ * be excluded from the profile loop.
+ *
+ * @since 1.6.0
+ * @see BP_XProfile_Group::get()
+ * @uses apply_filters() Filter bp_xprofile_get_hidden_fields_for_user to modify visibility levels,
+ *   or if you have added your own custom levels.
+ *
+ * @param int $displayed_user_id The id of the user the profile fields belong to.
+ * @param int $current_user_id   The id of the user viewing the profile.
+ *
+ * @return array An array of field ids that should be excluded from the profile query
+ */
+function bp_xprofile_get_hidden_fields_for_user( $displayed_user_id = 0, $current_user_id = 0 ) {
+	if ( !$displayed_user_id ) {
+		$displayed_user_id = bp_displayed_user_id();
+	}
+
+	if ( !$displayed_user_id ) {
+		return array();
+	}
+
+	if ( !$current_user_id ) {
+		$current_user_id = bp_loggedin_user_id();
+	}
+
+	// @todo - This is where you'd swap out for current_user_can() checks
+	$hidden_levels = bp_xprofile_get_hidden_field_types_for_user( $displayed_user_id, $current_user_id );
+	$hidden_fields = bp_xprofile_get_fields_by_visibility_levels( $displayed_user_id, $hidden_levels );
+
+	/**
+	 * Filters the ids of fields that are hidden for this displayed/loggedin user pair.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param array $hidden_fields     Array of hidden fields for the displayed/logged in user.
+	 * @param int   $displayed_user_id ID of the displayed user.
+	 * @param int   $current_user_id   ID of the current user.
+	 */
+	return apply_filters( 'bp_xprofile_get_hidden_fields_for_user', $hidden_fields, $displayed_user_id, $current_user_id );
+}
+
+/**
+ * Get the visibility levels that should be hidden for this user pair.
+ *
+ * Field visibility is determined based on the relationship between the
+ * logged-in user, the displayed user, and the visibility setting for the
+ * current field. (See bp_xprofile_get_hidden_fields_for_user().) This
+ * utility function speeds up this matching by fetching the visibility levels
+ * that should be hidden for the current user pair.
+ *
+ * @since 1.8.2
+ * @see bp_xprofile_get_hidden_fields_for_user()
+ *
+ * @param int $displayed_user_id The id of the user the profile fields belong to.
+ * @param int $current_user_id   The id of the user viewing the profile.
+ *
+ * @return array An array of visibility levels hidden to the current user.
+ */
+function bp_xprofile_get_hidden_field_types_for_user( $displayed_user_id = 0, $current_user_id = 0 ) {
+
+	// Current user is logged in.
+	if ( ! empty( $current_user_id ) ) {
+
+		// Nothing's private when viewing your own profile, or when the
+		// current user is an admin.
+		if ( $displayed_user_id == $current_user_id || bp_current_user_can( 'bp_moderate' ) ) {
+			$hidden_levels = array();
+
+		// If the current user and displayed user are friends, show all.
+		} elseif ( bp_is_active( 'friends' ) && friends_check_friendship( $displayed_user_id, $current_user_id ) ) {
+			$hidden_levels = array( 'adminsonly', );
+
+		// Current user is logged in but not friends, so exclude friends-only.
+		} else {
+			$hidden_levels = array( 'friends', 'adminsonly', );
+		}
+
+	// Current user is not logged in, so exclude friends-only, loggedin, and adminsonly.
+	} else {
+		$hidden_levels = array( 'friends', 'loggedin', 'adminsonly', );
+	}
+
+	/**
+	 * Filters the visibility levels that should be hidden for this user pair.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $hidden_fields     Array of hidden fields for the displayed/logged in user.
+	 * @param int   $displayed_user_id ID of the displayed user.
+	 * @param int   $current_user_id   ID of the current user.
+	 */
+	return apply_filters( 'bp_xprofile_get_hidden_field_types_for_user', $hidden_levels, $displayed_user_id, $current_user_id );
+}
+
+/**
+ * Fetch an array of the xprofile fields that a given user has marked with certain visibility levels.
+ *
+ * @since 1.6.0
+ * @see bp_xprofile_get_hidden_fields_for_user()
+ *
+ * @param int   $user_id The id of the profile owner.
+ * @param array $levels  An array of visibility levels ('public', 'friends', 'loggedin', 'adminsonly' etc) to be
+ *                       checked against.
+ *
+ * @return array $field_ids The fields that match the requested visibility levels for the given user.
+ */
+function bp_xprofile_get_fields_by_visibility_levels( $user_id, $levels = array() ) {
+	if ( !is_array( $levels ) ) {
+		$levels = (array)$levels;
+	}
+
+	$user_visibility_levels = bp_get_user_meta( $user_id, 'bp_xprofile_visibility_levels', true );
+
+	// Parse the user-provided visibility levels with the default levels, which may take
+	// precedence.
+	$default_visibility_levels = BP_XProfile_Group::fetch_default_visibility_levels();
+
+	foreach( (array) $default_visibility_levels as $d_field_id => $defaults ) {
+		// If the admin has forbidden custom visibility levels for this field, replace
+		// the user-provided setting with the default specified by the admin.
+		if ( isset( $defaults['allow_custom'] ) && isset( $defaults['default'] ) && 'disabled' == $defaults['allow_custom'] ) {
+			$user_visibility_levels[$d_field_id] = $defaults['default'];
+		}
+	}
+
+	$field_ids = array();
+	foreach( (array) $user_visibility_levels as $field_id => $field_visibility ) {
+		if ( in_array( $field_visibility, $levels ) ) {
+			$field_ids[] = $field_id;
+		}
+	}
+
+	// Never allow the fullname field to be excluded.
+	if ( in_array( 1, $field_ids ) ) {
+		$key = array_search( 1, $field_ids );
+		unset( $field_ids[$key] );
+	}
+
+	return $field_ids;
+}
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-loader.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-loader.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-loader.php	(revision 10410)
@@ -0,0 +1,438 @@
+<?php
+/**
+ * BuddyPress XProfile Loader.
+ *
+ * An extended profile component for users. This allows site admins to create
+ * groups of fields for users to enter information about themselves.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileLoader
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+class BP_XProfile_Component extends BP_Component {
+
+	/**
+	 * Profile field types.
+	 *
+	 * @since 1.5.0
+	 * @var array
+	 */
+	public $field_types;
+
+	/**
+	 * The acceptable visibility levels for xprofile fields.
+	 *
+	 * @see bp_xprofile_get_visibility_levels()
+	 * @since 1.6.0
+	 * @var array
+	 */
+	public $visibility_levels = array();
+
+	/**
+	 * Start the xprofile component creation process.
+	 *
+	 * @since 1.5.0
+	 */
+	public function __construct() {
+		parent::start(
+			'xprofile',
+			_x( 'Extended Profiles', 'Component page <title>', 'buddypress' ),
+			buddypress()->plugin_dir,
+			array(
+				'adminbar_myaccount_order' => 20
+			)
+		);
+
+		$this->setup_hooks();
+	}
+
+	/**
+	 * Include files.
+	 *
+	 * @param array $includes
+	 */
+	public function includes( $includes = array() ) {
+		$includes = array(
+			'cssjs',
+			'cache',
+			'actions',
+			'activity',
+			'screens',
+			'caps',
+			'classes',
+			'filters',
+			'settings',
+			'template',
+			'functions',
+			'notifications',
+		);
+
+		if ( is_admin() ) {
+			$includes[] = 'admin';
+		}
+
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Setup globals.
+	 *
+	 * The BP_XPROFILE_SLUG constant is deprecated, and only used here for
+	 * backwards compatibility.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array $args Array of globals to set up.
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		// Define a slug, if necessary.
+		if ( !defined( 'BP_XPROFILE_SLUG' ) ) {
+			define( 'BP_XPROFILE_SLUG', 'profile' );
+		}
+
+		// Assign the base group and fullname field names to constants
+		// to use in SQL statements.
+		// Defined conditionally to accommodate unit tests.
+		if ( ! defined( 'BP_XPROFILE_BASE_GROUP_NAME' ) ) {
+			define( 'BP_XPROFILE_BASE_GROUP_NAME', stripslashes( bp_core_get_root_option( 'avatar_default' ) ) );
+		}
+
+		if ( ! defined( 'BP_XPROFILE_FULLNAME_FIELD_NAME' ) ) {
+			define( 'BP_XPROFILE_FULLNAME_FIELD_NAME', stripslashes( bp_core_get_root_option( 'bp-xprofile-fullname-field-name' ) ) );
+		}
+
+		/**
+		 * Filters the supported field type IDs.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param array $value Array of IDs for the supported field types.
+		 */
+		$this->field_types = apply_filters( 'xprofile_field_types', array_keys( bp_xprofile_get_field_types() ) );
+
+		// 'option' is a special case. It is not a top-level field, so
+		// does not have an associated BP_XProfile_Field_Type class,
+		// but it must be whitelisted.
+		$this->field_types[] = 'option';
+
+		// Register the visibility levels. See bp_xprofile_get_visibility_levels() to filter.
+		$this->visibility_levels = array(
+			'public' => array(
+				'id'	  => 'public',
+				'label' => _x( 'Everyone', 'Visibility level setting', 'buddypress' )
+			),
+			'adminsonly' => array(
+				'id'	  => 'adminsonly',
+				'label' => _x( 'Only Me', 'Visibility level setting', 'buddypress' )
+			),
+			'loggedin' => array(
+				'id'	  => 'loggedin',
+				'label' => _x( 'All Members', 'Visibility level setting', 'buddypress' )
+			)
+		);
+
+		if ( bp_is_active( 'friends' ) ) {
+			$this->visibility_levels['friends'] = array(
+				'id'	=> 'friends',
+				'label'	=> _x( 'My Friends', 'Visibility level setting', 'buddypress' )
+			);
+		}
+
+		// Tables.
+		$global_tables = array(
+			'table_name_data'   => $bp->table_prefix . 'bp_xprofile_data',
+			'table_name_groups' => $bp->table_prefix . 'bp_xprofile_groups',
+			'table_name_fields' => $bp->table_prefix . 'bp_xprofile_fields',
+			'table_name_meta'   => $bp->table_prefix . 'bp_xprofile_meta',
+		);
+
+		$meta_tables = array(
+			'xprofile_group' => $bp->table_prefix . 'bp_xprofile_meta',
+			'xprofile_field' => $bp->table_prefix . 'bp_xprofile_meta',
+			'xprofile_data'  => $bp->table_prefix . 'bp_xprofile_meta',
+		);
+
+		$globals = array(
+			'slug'                  => BP_XPROFILE_SLUG,
+			'has_directory'         => false,
+			'notification_callback' => 'xprofile_format_notifications',
+			'global_tables'         => $global_tables,
+			'meta_tables'           => $meta_tables,
+		);
+
+		parent::setup_globals( $globals );
+	}
+
+	/**
+	 * Set up navigation.
+	 *
+	 * @global BuddyPress $bp The one true BuddyPress instance
+	 *
+	 * @param array $main_nav Array of main nav items to set up.
+	 * @param array $sub_nav  Array of sub nav items to set up.
+	 */
+	public function setup_nav( $main_nav = array(), $sub_nav = array() ) {
+
+		// Determine user to use.
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		$access       = bp_core_can_edit_settings();
+		$slug         = bp_get_profile_slug();
+		$profile_link = trailingslashit( $user_domain . $slug );
+
+		// Add 'Profile' to the main navigation.
+		$main_nav = array(
+			'name'                => _x( 'Profile', 'Profile header menu', 'buddypress' ),
+			'slug'                => $slug,
+			'position'            => 20,
+			'screen_function'     => 'xprofile_screen_display_profile',
+			'default_subnav_slug' => 'public',
+			'item_css_id'         => $this->id
+		);
+
+		// Add the subnav items to the profile.
+		$sub_nav[] = array(
+			'name'            => _x( 'View', 'Profile header sub menu', 'buddypress' ),
+			'slug'            => 'public',
+			'parent_url'      => $profile_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'xprofile_screen_display_profile',
+			'position'        => 10
+		);
+
+		// Edit Profile.
+		$sub_nav[] = array(
+			'name'            => _x( 'Edit','Profile header sub menu', 'buddypress' ),
+			'slug'            => 'edit',
+			'parent_url'      => $profile_link,
+			'parent_slug'     => $slug,
+			'screen_function' => 'xprofile_screen_edit_profile',
+			'position'        => 20,
+			'user_has_access' => $access
+		);
+
+		// Change Avatar.
+		if ( buddypress()->avatar->show_avatars ) {
+			$sub_nav[] = array(
+				'name'            => _x( 'Change Profile Photo', 'Profile header sub menu', 'buddypress' ),
+				'slug'            => 'change-avatar',
+				'parent_url'      => $profile_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'xprofile_screen_change_avatar',
+				'position'        => 30,
+				'user_has_access' => $access
+			);
+		}
+
+		// Change Cover image
+		if ( bp_displayed_user_use_cover_image_header() ) {
+			$sub_nav[] = array(
+				'name'            => _x( 'Change Cover Image', 'Profile header sub menu', 'buddypress' ),
+				'slug'            => 'change-cover-image',
+				'parent_url'      => $profile_link,
+				'parent_slug'     => $slug,
+				'screen_function' => 'xprofile_screen_change_cover_image',
+				'position'        => 40,
+				'user_has_access' => $access
+			);
+		}
+
+		// The Settings > Profile nav item can only be set up after
+		// the Settings component has run its own nav routine.
+		add_action( 'bp_settings_setup_nav', array( $this, 'setup_settings_nav' ) );
+
+		parent::setup_nav( $main_nav, $sub_nav );
+	}
+
+	/**
+	 * Set up the Settings > Profile nav item.
+	 *
+	 * Loaded in a separate method because the Settings component may not
+	 * be loaded in time for BP_XProfile_Component::setup_nav().
+	 *
+	 * @since 2.1.0
+	 */
+	public function setup_settings_nav() {
+		if ( ! bp_is_active( 'settings' ) ) {
+			return;
+		}
+
+		// Determine user to use.
+		if ( bp_displayed_user_domain() ) {
+			$user_domain = bp_displayed_user_domain();
+		} elseif ( bp_loggedin_user_domain() ) {
+			$user_domain = bp_loggedin_user_domain();
+		} else {
+			return;
+		}
+
+		// Get the settings slug.
+		$settings_slug = bp_get_settings_slug();
+
+		bp_core_new_subnav_item( array(
+			'name'            => _x( 'Profile Visibility', 'Profile settings sub nav', 'buddypress' ),
+			'slug'            => 'profile',
+			'parent_url'      => trailingslashit( $user_domain . $settings_slug ),
+			'parent_slug'     => $settings_slug,
+			'screen_function' => 'bp_xprofile_screen_settings',
+			'position'        => 30,
+			'user_has_access' => bp_core_can_edit_settings()
+		) );
+	}
+
+	/**
+	 * Set up the Admin Bar.
+	 *
+	 * @param array $wp_admin_nav Admin Bar items.
+	 */
+	public function setup_admin_bar( $wp_admin_nav = array() ) {
+
+		// Menus for logged in user.
+		if ( is_user_logged_in() ) {
+
+			// Profile link.
+			$profile_link = trailingslashit( bp_loggedin_user_domain() . bp_get_profile_slug() );
+
+			// Add the "Profile" sub menu.
+			$wp_admin_nav[] = array(
+				'parent' => buddypress()->my_account_menu_id,
+				'id'     => 'my-account-' . $this->id,
+				'title'  => _x( 'Profile', 'My Account Profile', 'buddypress' ),
+				'href'   => $profile_link
+			);
+
+			// View Profile.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-public',
+				'title'  => _x( 'View', 'My Account Profile sub nav', 'buddypress' ),
+				'href'   => $profile_link
+			);
+
+			// Edit Profile.
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $this->id,
+				'id'     => 'my-account-' . $this->id . '-edit',
+				'title'  => _x( 'Edit', 'My Account Profile sub nav', 'buddypress' ),
+				'href'   => trailingslashit( $profile_link . 'edit' )
+			);
+
+			// Edit Avatar.
+			if ( buddypress()->avatar->show_avatars ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-change-avatar',
+					'title'  => _x( 'Change Profile Photo', 'My Account Profile sub nav', 'buddypress' ),
+					'href'   => trailingslashit( $profile_link . 'change-avatar' )
+				);
+			}
+
+			if ( bp_displayed_user_use_cover_image_header() ) {
+				$wp_admin_nav[] = array(
+					'parent' => 'my-account-' . $this->id,
+					'id'     => 'my-account-' . $this->id . '-change-cover-image',
+					'title'  => _x( 'Change Cover Image', 'My Account Profile sub nav', 'buddypress' ),
+					'href'   => trailingslashit( $profile_link . 'change-cover-image' )
+				);
+			}
+		}
+
+		parent::setup_admin_bar( $wp_admin_nav );
+	}
+
+	/**
+	 * Add custom hooks.
+	 *
+	 * @since 2.0.0
+	 */
+	public function setup_hooks() {
+		add_filter( 'bp_settings_admin_nav', array( $this, 'setup_settings_admin_nav' ), 2 );
+	}
+
+	/**
+	 * Sets up the title for pages and <title>.
+	 */
+	public function setup_title() {
+
+		if ( bp_is_profile_component() ) {
+			$bp = buddypress();
+
+			if ( bp_is_my_profile() ) {
+				$bp->bp_options_title = _x( 'My Profile', 'Page title', 'buddypress' );
+			} else {
+				$bp->bp_options_avatar = bp_core_fetch_avatar( array(
+					'item_id' => bp_displayed_user_id(),
+					'type'    => 'thumb',
+					'alt'	  => sprintf( _x( 'Profile picture of %s', 'Avatar alt', 'buddypress' ), bp_get_displayed_user_fullname() )
+				) );
+				$bp->bp_options_title = bp_get_displayed_user_fullname();
+			}
+		}
+
+		parent::setup_title();
+	}
+
+	/**
+	 * Setup cache groups.
+	 *
+	 * @since 2.2.0
+	 */
+	public function setup_cache_groups() {
+
+		// Global groups.
+		wp_cache_add_global_groups( array(
+			'bp_xprofile',
+			'bp_xprofile_data',
+			'bp_xprofile_fields',
+			'bp_xprofile_groups',
+			'xprofile_meta'
+		) );
+
+		parent::setup_cache_groups();
+	}
+
+	/**
+	 * Adds "Settings > Profile" subnav item under the "Settings" adminbar menu.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $wp_admin_nav The settings adminbar nav array.
+	 *
+	 * @return array
+	 */
+	public function setup_settings_admin_nav( $wp_admin_nav ) {
+
+		// Setup the logged in user variables.
+		$settings_link = trailingslashit( bp_loggedin_user_domain() . bp_get_settings_slug() );
+
+		// Add the "Profile" subnav item.
+		$wp_admin_nav[] = array(
+			'parent' => 'my-account-' . buddypress()->settings->id,
+			'id'     => 'my-account-' . buddypress()->settings->id . '-profile',
+			'title'  => _x( 'Profile', 'My Account Settings sub nav', 'buddypress' ),
+			'href'   => trailingslashit( $settings_link . 'profile' )
+		);
+
+		return $wp_admin_nav;
+	}
+}
+
+function bp_setup_xprofile() {
+	$bp = buddypress();
+
+	if ( ! isset( $bp->profile->id ) ) {
+		$bp->profile = new BP_XProfile_Component();
+	}
+}
+add_action( 'bp_setup_components', 'bp_setup_xprofile', 2 );
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-notifications.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-notifications.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-notifications.php	(revision 10410)
@@ -0,0 +1,49 @@
+<?php
+/** Notifications *************************************************************/
+
+/**
+ * Format notifications for the extended profile (Xprofile) component.
+ *
+ * @since 2.4.0
+ *
+ * @param string $action            The kind of notification being rendered.
+ * @param int    $item_id           The primary item ID.
+ * @param int    $secondary_item_id The secondary item ID.
+ * @param int    $total_items       The total number of messaging-related notifications
+ *                                  waiting for the user.
+ * @param string $format            'string' for BuddyBar-compatible notifications; 'array'
+ *                                  for WP Toolbar. Default: 'string'.
+ *
+ * @return string
+ */
+function xprofile_format_notifications( $action, $item_id, $secondary_item_id, $total_items, $format = 'string' ) {
+	switch ( $action ) {
+		default:
+
+			/**
+			 * Allows plugins to filter extended profile-related custom notifications.
+			 * Notifications must have a 'component_name' of 'xprofile' to be routed
+			 * to this function.
+			 *
+			 *
+			 * @since 2.4.0
+			 *
+			 * @param string $notification      Null value.
+			 * @param int    $item_id           The primary item ID.
+			 * @param int    $secondary_item_id The secondary item ID.
+			 * @param int    $total_items       The total number of messaging-related notifications
+			 *                                  waiting for the user.
+			 * @param string $format            'string' for BuddyBar-compatible notifications;
+			 *                                  'array' for WP Toolbar.
+			 */
+			$custom_action_notification = apply_filters( 'bp_xprofile_' . $action . '_notification', null, $item_id, $secondary_item_id, $total_items, $format );
+
+			if ( ! is_null( $custom_action_notification ) ) {
+				return $custom_action_notification;
+			}
+
+			break;
+	}
+
+	return false;
+}
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-screens.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-screens.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-screens.php	(revision 10410)
@@ -0,0 +1,347 @@
+<?php
+/**
+ * BuddyPress XProfile Screens.
+ *
+ * Screen functions are the controllers of BuddyPress. They will execute when
+ * their specific URL is caught. They will first save or manipulate data using
+ * business functions, then pass on the user to a template file.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileScreens
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Handles the display of the profile page by loading the correct template file.
+ *
+ * @uses bp_core_load_template() Looks for and loads a template file within the current member theme (folder/filename).
+ */
+function xprofile_screen_display_profile() {
+	$new = isset( $_GET['new'] ) ? $_GET['new'] : '';
+
+	/**
+	 * Fires right before the loading of the XProfile screen template file.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $new $_GET parameter holding the "new" parameter.
+	 */
+	do_action( 'xprofile_screen_display_profile', $new );
+
+	/**
+	 * Filters the template to load for the XProfile screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the XProfile template to load.
+	 */
+	bp_core_load_template( apply_filters( 'xprofile_template_display_profile', 'members/single/home' ) );
+}
+
+/**
+ * Handles the display of the profile edit page by loading the correct template file.
+ * Also checks to make sure this can only be accessed for the logged in users profile.
+ *
+ * @uses bp_is_my_profile() Checks to make sure the current user being viewed equals the logged in user.
+ * @uses bp_core_load_template() Looks for and loads a template file within the current member theme (folder/filename).
+ */
+function xprofile_screen_edit_profile() {
+
+	if ( ! bp_is_my_profile() && ! bp_current_user_can( 'bp_moderate' ) ) {
+		return false;
+	}
+
+	// Make sure a group is set.
+	if ( ! bp_action_variable( 1 ) ) {
+		bp_core_redirect( trailingslashit( bp_displayed_user_domain() . bp_get_profile_slug() . '/edit/group/1' ) );
+	}
+
+	// Check the field group exists.
+	if ( ! bp_is_action_variable( 'group' ) || ! xprofile_get_field_group( bp_action_variable( 1 ) ) ) {
+		bp_do_404();
+		return;
+	}
+
+	// No errors.
+	$errors = false;
+
+	// Check to see if any new information has been submitted.
+	if ( isset( $_POST['field_ids'] ) ) {
+
+		// Check the nonce.
+		check_admin_referer( 'bp_xprofile_edit' );
+
+		// Check we have field ID's.
+		if ( empty( $_POST['field_ids'] ) ) {
+			bp_core_redirect( trailingslashit( bp_displayed_user_domain() . bp_get_profile_slug() . '/edit/group/' . bp_action_variable( 1 ) ) );
+		}
+
+		// Explode the posted field IDs into an array so we know which
+		// fields have been submitted.
+		$posted_field_ids = wp_parse_id_list( $_POST['field_ids'] );
+		$is_required      = array();
+
+		// Loop through the posted fields formatting any datebox values
+		// then validate the field.
+		foreach ( (array) $posted_field_ids as $field_id ) {
+			if ( !isset( $_POST['field_' . $field_id] ) ) {
+
+				if ( !empty( $_POST['field_' . $field_id . '_day'] ) && !empty( $_POST['field_' . $field_id . '_month'] ) && !empty( $_POST['field_' . $field_id . '_year'] ) ) {
+					// Concatenate the values.
+					$date_value =   $_POST['field_' . $field_id . '_day'] . ' ' . $_POST['field_' . $field_id . '_month'] . ' ' . $_POST['field_' . $field_id . '_year'];
+
+					// Turn the concatenated value into a timestamp.
+					$_POST['field_' . $field_id] = date( 'Y-m-d H:i:s', strtotime( $date_value ) );
+				}
+
+			}
+
+			$is_required[ $field_id ] = xprofile_check_is_required_field( $field_id ) && ! bp_current_user_can( 'bp_moderate' );
+			if ( $is_required[$field_id] && empty( $_POST['field_' . $field_id] ) ) {
+				$errors = true;
+			}
+		}
+
+		// There are errors.
+		if ( !empty( $errors ) ) {
+			bp_core_add_message( __( 'Please make sure you fill in all required fields in this profile field group before saving.', 'buddypress' ), 'error' );
+
+		// No errors.
+		} else {
+
+			// Reset the errors var.
+			$errors = false;
+
+			// Now we've checked for required fields, lets save the values.
+			$old_values = $new_values = array();
+			foreach ( (array) $posted_field_ids as $field_id ) {
+
+				// Certain types of fields (checkboxes, multiselects) may come through empty. Save them as an empty array so that they don't get overwritten by the default on the next edit.
+				$value = isset( $_POST['field_' . $field_id] ) ? $_POST['field_' . $field_id] : '';
+
+				$visibility_level = !empty( $_POST['field_' . $field_id . '_visibility'] ) ? $_POST['field_' . $field_id . '_visibility'] : 'public';
+
+				// Save the old and new values. They will be
+				// passed to the filter and used to determine
+				// whether an activity item should be posted.
+				$old_values[ $field_id ] = array(
+					'value'      => xprofile_get_field_data( $field_id, bp_displayed_user_id() ),
+					'visibility' => xprofile_get_field_visibility_level( $field_id, bp_displayed_user_id() ),
+				);
+
+				// Update the field data and visibility level.
+				xprofile_set_field_visibility_level( $field_id, bp_displayed_user_id(), $visibility_level );
+				$field_updated = xprofile_set_field_data( $field_id, bp_displayed_user_id(), $value, $is_required[ $field_id ] );
+				$value         = xprofile_get_field_data( $field_id, bp_displayed_user_id() );
+
+				$new_values[ $field_id ] = array(
+					'value'      => $value,
+					'visibility' => xprofile_get_field_visibility_level( $field_id, bp_displayed_user_id() ),
+				);
+
+				if ( ! $field_updated ) {
+					$errors = true;
+				} else {
+
+					/**
+					 * Fires on each iteration of an XProfile field being saved with no error.
+					 *
+					 * @since 1.1.0
+					 *
+					 * @param int    $field_id ID of the field that was saved.
+					 * @param string $value    Value that was saved to the field.
+					 */
+					do_action( 'xprofile_profile_field_data_updated', $field_id, $value );
+				}
+			}
+
+			/**
+			 * Fires after all XProfile fields have been saved for the current profile.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param int   $value            Displayed user ID.
+			 * @param array $posted_field_ids Array of field IDs that were edited.
+			 * @param bool  $errors           Whether or not any errors occurred.
+			 * @param array $old_values       Array of original values before updated.
+			 * @param array $new_values       Array of newly saved values after update.
+			 */
+			do_action( 'xprofile_updated_profile', bp_displayed_user_id(), $posted_field_ids, $errors, $old_values, $new_values );
+
+			// Set the feedback messages.
+			if ( !empty( $errors ) ) {
+				bp_core_add_message( __( 'There was a problem updating some of your profile information. Please try again.', 'buddypress' ), 'error' );
+			} else {
+				bp_core_add_message( __( 'Changes saved.', 'buddypress' ) );
+			}
+
+			// Redirect back to the edit screen to display the updates and message.
+			bp_core_redirect( trailingslashit( bp_displayed_user_domain() . bp_get_profile_slug() . '/edit/group/' . bp_action_variable( 1 ) ) );
+		}
+	}
+
+	/**
+	 * Fires right before the loading of the XProfile edit screen template file.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'xprofile_screen_edit_profile' );
+
+	/**
+	 * Filters the template to load for the XProfile edit screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the XProfile edit template to load.
+	 */
+	bp_core_load_template( apply_filters( 'xprofile_template_edit_profile', 'members/single/home' ) );
+}
+
+/**
+ * Handles the uploading and cropping of a user avatar. Displays the change avatar page.
+ *
+ * @uses bp_is_my_profile() Checks to make sure the current user being viewed equals the logged in user.
+ * @uses bp_core_load_template() Looks for and loads a template file within the current member theme (folder/filename).
+ */
+function xprofile_screen_change_avatar() {
+
+	// Bail if not the correct screen.
+	if ( ! bp_is_my_profile() && ! bp_current_user_can( 'bp_moderate' ) ) {
+		return false;
+	}
+
+	// Bail if there are action variables.
+	if ( bp_action_variables() ) {
+		bp_do_404();
+		return;
+	}
+
+	$bp = buddypress();
+
+	if ( ! isset( $bp->avatar_admin ) ) {
+		$bp->avatar_admin = new stdClass();
+	}
+
+	$bp->avatar_admin->step = 'upload-image';
+
+	if ( !empty( $_FILES ) ) {
+
+		// Check the nonce.
+		check_admin_referer( 'bp_avatar_upload' );
+
+		// Pass the file to the avatar upload handler.
+		if ( bp_core_avatar_handle_upload( $_FILES, 'xprofile_avatar_upload_dir' ) ) {
+			$bp->avatar_admin->step = 'crop-image';
+
+			// Make sure we include the jQuery jCrop file for image cropping.
+			add_action( 'wp_print_scripts', 'bp_core_add_jquery_cropper' );
+		}
+	}
+
+	// If the image cropping is done, crop the image and save a full/thumb version.
+	if ( isset( $_POST['avatar-crop-submit'] ) ) {
+
+		// Check the nonce.
+		check_admin_referer( 'bp_avatar_cropstore' );
+
+		$args = array(
+			'item_id'       => bp_displayed_user_id(),
+			'original_file' => $_POST['image_src'],
+			'crop_x'        => $_POST['x'],
+			'crop_y'        => $_POST['y'],
+			'crop_w'        => $_POST['w'],
+			'crop_h'        => $_POST['h']
+		);
+
+		if ( ! bp_core_avatar_handle_crop( $args ) ) {
+			bp_core_add_message( __( 'There was a problem cropping your profile photo.', 'buddypress' ), 'error' );
+		} else {
+
+			/**
+			 * Fires right before the redirect, after processing a new avatar.
+			 *
+			 * @since 1.1.0
+			 * @since 2.3.4 Add two new parameters to inform about the user id and
+			 *              about the way the avatar was set (eg: 'crop' or 'camera').
+			 *
+			 * @param string $item_id Inform about the user id the avatar was set for.
+			 * @param string $value   Inform about the way the avatar was set ('crop').
+			 */
+			do_action( 'xprofile_avatar_uploaded', (int) $args['item_id'], 'crop' );
+			bp_core_add_message( __( 'Your new profile photo was uploaded successfully.', 'buddypress' ) );
+			bp_core_redirect( bp_displayed_user_domain() );
+		}
+	}
+
+	/**
+	 * Fires right before the loading of the XProfile change avatar screen template file.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'xprofile_screen_change_avatar' );
+
+	/**
+	 * Filters the template to load for the XProfile change avatar screen.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @param string $template Path to the XProfile change avatar template to load.
+	 */
+	bp_core_load_template( apply_filters( 'xprofile_template_change_avatar', 'members/single/home' ) );
+}
+
+/**
+ * Displays the change cover image page.
+ *
+ * @package BuddyPress XProfile
+ *
+ * @since 2.4.0
+ */
+function xprofile_screen_change_cover_image() {
+
+	// Bail if not the correct screen
+	if ( ! bp_is_my_profile() && ! bp_current_user_can( 'bp_moderate' ) ) {
+		return false;
+	}
+
+	/**
+	 * Fires right before the loading of the XProfile change cover image screen template file.
+	 *
+	 * @since 2.4.0
+	 */
+	do_action( 'xprofile_screen_change_cover_image' );
+
+	/**
+	 * Filters the template to load for the XProfile cover image screen.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string $template Path to the XProfile cover image template to load.
+	 */
+	bp_core_load_template( apply_filters( 'xprofile_template_cover_image', 'members/single/home' ) );
+}
+
+/**
+ * Show the xprofile settings template.
+ *
+ * @since 2.0.0
+ */
+function bp_xprofile_screen_settings() {
+
+	// Redirect if no privacy settings page is accessible.
+	if ( bp_action_variables() || ! bp_is_active( 'xprofile' ) ) {
+		bp_do_404();
+		return;
+	}
+
+	/**
+	 * Filters the template to load for the XProfile settings screen.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $template Path to the XProfile change avatar template to load.
+	 */
+	bp_core_load_template( apply_filters( 'bp_settings_screen_xprofile', '/members/single/settings/profile' ) );
+}
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-settings.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-settings.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-settings.php	(revision 10410)
@@ -0,0 +1,61 @@
+<?php
+/**
+ * BuddyPress XProfile Settings.
+ *
+ * @package    BuddyPress
+ * @subpackage XProfileSettings
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Query all profile fields and their visibility data for display in settings.
+ *
+ * @since 2.0.0
+ *
+ * @param array|string $args Array of args for the settings fields.
+ *
+ * @return array
+ */
+function bp_xprofile_get_settings_fields( $args = '' ) {
+
+	// Parse the possible arguments.
+	$r = bp_parse_args( $args, array(
+		'user_id'                => bp_displayed_user_id(),
+		'profile_group_id'       => false,
+		'hide_empty_groups'      => false,
+		'hide_empty_fields'      => false,
+		'fetch_fields'           => true,
+		'fetch_field_data'       => false,
+		'fetch_visibility_level' => true,
+		'exclude_groups'         => false,
+		'exclude_fields'         => false
+	), 'xprofile_get_settings_fields' );
+
+	return bp_has_profile( $r );
+}
+
+/**
+ * Adds feedback messages when successfully saving profile field settings.
+ *
+ * @since 2.0.0
+ *
+ * @uses bp_core_add_message()
+ * @uses bp_is_my_profile()
+ */
+function bp_xprofile_settings_add_feedback_message() {
+
+	// Default message type is success.
+	$type    = 'success';
+	$message = __( 'Your profile settings have been saved.',        'buddypress' );
+
+	// Community moderator editing another user's settings.
+	if ( ! bp_is_my_profile() && bp_core_can_edit_settings() ) {
+		$message = __( "This member's profile settings have been saved.", 'buddypress' );
+	}
+
+	// Add the message.
+	bp_core_add_message( $message, $type );
+}
+add_action( 'bp_xprofile_settings_after_save', 'bp_xprofile_settings_add_feedback_message' );
Index: /tags/2.4.3/src/bp-xprofile/bp-xprofile-template.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/bp-xprofile-template.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/bp-xprofile-template.php	(revision 10410)
@@ -0,0 +1,1348 @@
+<?php
+/**
+ * BuddyPress XProfile Template Tags.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileTemplate
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * The main profile template loop class.
+ *
+ * This is responsible for loading profile field, group, and data and displaying it.
+ *
+ * @since 1.0.0
+ */
+class BP_XProfile_Data_Template {
+
+	/**
+	 * The loop iterator.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $current_group = -1;
+
+	/**
+	 * The number of groups returned by the paged query.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $group_count;
+
+	/**
+	 * Array of groups located by the query.
+	 *
+	 * @since 1.5.0
+	 * @var array
+	 */
+	public $groups;
+
+	/**
+	 * The group object currently being iterated on.
+	 *
+	 * @since 1.5.0
+	 * @var object
+	 */
+	public $group;
+
+	/**
+	 * The current field.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $current_field = -1;
+
+	/**
+	 * The field count.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $field_count;
+
+	/**
+	 * Field has data.
+	 *
+	 * @since 1.5.0
+	 * @var bool
+	 */
+	public $field_has_data;
+
+	/**
+	 * The field.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $field;
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @since 1.5.0
+	 * @var bool
+	 */
+	public $in_the_loop;
+
+	/**
+	 * The user ID.
+	 *
+	 * @since 1.5.0
+	 * @var int
+	 */
+	public $user_id;
+
+	/**
+	 * Get activity items, as specified by parameters.
+	 *
+	 * @see BP_XProfile_Group::get() for more details about parameters.
+	 *
+	 * @since 2.4.0 Introduced `$member_type` argument.
+	 *
+	 * @param array|string $args {
+	 *     An array of arguments. All items are optional.
+	 *
+	 *     @type int          $user_id                 Fetch field data for this user ID.
+	 *     @type string|array $member_type             Limit results to those matching member type(s).
+	 *     @type int          $profile_group_id        Field group to fetch fields & data for.
+	 *     @type int|bool     $hide_empty_groups       Should empty field groups be skipped.
+	 *     @type int|bool     $fetch_fields            Fetch fields for field group.
+	 *     @type int|bool     $fetch_field_data        Fetch field data for fields in group.
+	 *     @type array        $exclude_groups          Exclude these field groups.
+	 *     @type array        $exclude_fields          Exclude these fields.
+	 *     @type int|bool     $hide_empty_fields       Should empty fields be skipped.
+	 *     @type int|bool     $fetch_visibility_level  Fetch visibility levels.
+	 *     @type int|bool     $update_meta_cache       Should metadata cache be updated.
+	 * }
+	 */
+	public function __construct( $args = '' ) {
+
+		// Backward compatibility with old method of passing arguments.
+		if ( ! is_array( $args ) || func_num_args() > 1 ) {
+			_deprecated_argument( __METHOD__, '2.3.0', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
+
+			$old_args_keys = array(
+				0 => 'user_id',
+				1 => 'profile_group_id',
+				2 => 'hide_empty_groups',
+				3 => 'fetch_fields',
+				4 => 'fetch_field_data',
+				5 => 'exclude_groups',
+				6 => 'exclude_fields',
+				7 => 'hide_empty_fields',
+				8 => 'fetch_visibility_level',
+				9 => 'update_meta_cache'
+			);
+
+			$func_args = func_get_args();
+			$args      = bp_core_parse_args_array( $old_args_keys, $func_args );
+		}
+
+		$r = wp_parse_args( $args, array(
+			'profile_group_id'       => false,
+			'user_id'                => false,
+			'member_type'            => 'any',
+			'hide_empty_groups'      => false,
+			'hide_empty_fields'      => false,
+			'fetch_fields'           => false,
+			'fetch_field_data'       => false,
+			'fetch_visibility_level' => false,
+			'exclude_groups'         => false,
+			'exclude_fields'         => false,
+			'update_meta_cache'      => true
+		) );
+
+		$this->groups      = bp_xprofile_get_groups( $r );
+		$this->group_count = count( $this->groups );
+		$this->user_id     = $r['user_id'];
+	}
+
+	public function has_groups() {
+		if ( ! empty( $this->group_count ) ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	public function next_group() {
+		$this->current_group++;
+
+		$this->group       = $this->groups[ $this->current_group ];
+		$this->field_count = 0;
+
+		if ( ! empty( $this->group->fields ) ) {
+
+			/**
+			 * Filters the group fields for the next_group method.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param array $fields Array of fields for the group.
+			 * @param int   $id     ID of the field group.
+			 */
+			$this->group->fields = apply_filters( 'xprofile_group_fields', $this->group->fields, $this->group->id );
+			$this->field_count   = count( $this->group->fields );
+		}
+
+		return $this->group;
+	}
+
+	public function rewind_groups() {
+		$this->current_group = -1;
+		if ( $this->group_count > 0 ) {
+			$this->group = $this->groups[0];
+		}
+	}
+
+	public function profile_groups() {
+		if ( $this->current_group + 1 < $this->group_count ) {
+			return true;
+		} elseif ( $this->current_group + 1 == $this->group_count ) {
+
+			/**
+			 * Fires right before the rewinding of profile groups.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'xprofile_template_loop_end' );
+
+			// Do some cleaning up after the loop.
+			$this->rewind_groups();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	public function the_profile_group() {
+		global $group;
+
+		$this->in_the_loop = true;
+		$group = $this->next_group();
+
+		// Loop has just started.
+		if ( 0 === $this->current_group ) {
+
+			/**
+			 * Fires if the current group is the first in the loop.
+			 *
+			 * @since 1.1.0
+			 */
+			do_action( 'xprofile_template_loop_start' );
+		}
+	}
+
+	/** Fields ****************************************************************/
+
+	public function next_field() {
+		$this->current_field++;
+
+		$this->field = $this->group->fields[ $this->current_field ];
+
+		return $this->field;
+	}
+
+	public function rewind_fields() {
+		$this->current_field = -1;
+		if ( $this->field_count > 0 ) {
+			$this->field = $this->group->fields[0];
+		}
+	}
+
+	public function has_fields() {
+		$has_data = false;
+
+		for ( $i = 0, $count = count( $this->group->fields ); $i < $count; ++$i ) {
+			$field = &$this->group->fields[ $i ];
+
+			if ( ! empty( $field->data ) && ( $field->data->value != null ) ) {
+				$has_data = true;
+			}
+		}
+
+		return $has_data;
+	}
+
+	public function profile_fields() {
+		if ( $this->current_field + 1 < $this->field_count ) {
+			return true;
+		} elseif ( $this->current_field + 1 == $this->field_count ) {
+			// Do some cleaning up after the loop.
+			$this->rewind_fields();
+		}
+
+		return false;
+	}
+
+	public function the_profile_field() {
+		global $field;
+
+		$field = $this->next_field();
+
+		// Valid field values of 0 or '0' get caught by empty(), so we have an extra check for these. See #BP5731.
+		if ( ! empty( $field->data ) && ( ! empty( $field->data->value ) || ( '0' === $field->data->value ) ) ) {
+			$value = maybe_unserialize( $field->data->value );
+		} else {
+			$value = false;
+		}
+
+		if ( ! empty( $value ) || ( '0' === $value ) ) {
+			$this->field_has_data = true;
+		} else {
+			$this->field_has_data = false;
+		}
+	}
+}
+
+/**
+ * Query for XProfile groups and fields.
+ *
+ * @since 1.0.0
+ *
+ * @global object $profile_template
+ * @see BP_XProfile_Group::get() for full description of `$args` array.
+ *
+ * @param array|string $args {
+ *     Array of arguments. See BP_XProfile_Group::get() for full description. Those arguments whose defaults differ
+ *     from that method are described here:
+ *     @type string|array $member_type            Default: 'any'.
+ *     @type bool         $hide_empty_groups      Default: true.
+ *     @type bool         $hide_empty_fields      Defaults to true on the Dashboard, on a user's Edit Profile page,
+ *                                                or during registration. Otherwise false.
+ *     @type bool         $fetch_visibility_level Defaults to true when an admin is viewing a profile, or when a user is
+ *                                                viewing her own profile, or during registration. Otherwise false.
+ *     @type bool         $fetch_fields           Default: true.
+ *     @type bool         $fetch_field_data       Default: true.
+ * }
+ *
+ * @return bool
+ */
+function bp_has_profile( $args = '' ) {
+	global $profile_template;
+
+	// Only show empty fields if we're on the Dashboard, or we're on a user's
+	// profile edit page, or this is a registration page.
+	$hide_empty_fields_default = ( ! is_network_admin() && ! is_admin() && ! bp_is_user_profile_edit() && ! bp_is_register_page() );
+
+	// We only need to fetch visibility levels when viewing your own profile.
+	if ( bp_is_my_profile() || bp_current_user_can( 'bp_moderate' ) || bp_is_register_page() ) {
+		$fetch_visibility_level_default = true;
+	} else {
+		$fetch_visibility_level_default = false;
+	}
+
+	// Parse arguments.
+	$r = bp_parse_args( $args, array(
+		'user_id'                => bp_displayed_user_id(),
+		'member_type'            => 'any',
+		'profile_group_id'       => false,
+		'hide_empty_groups'      => true,
+		'hide_empty_fields'      => $hide_empty_fields_default,
+		'fetch_fields'           => true,
+		'fetch_field_data'       => true,
+		'fetch_visibility_level' => $fetch_visibility_level_default,
+		'exclude_groups'         => false, // Comma-separated list of profile field group IDs to exclude.
+		'exclude_fields'         => false, // Comma-separated list of profile field IDs to exclude.
+		'update_meta_cache'      => true,
+	), 'has_profile' );
+
+	// Populate the template loop global.
+	$profile_template = new BP_XProfile_Data_Template( $r );
+
+	/**
+	 * Filters whether or not a group has a profile to display.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param bool   $has_groups       Whether or not there are group profiles to display.
+	 * @param string $profile_template Current profile template being used.
+	 */
+	return apply_filters( 'bp_has_profile', $profile_template->has_groups(), $profile_template );
+}
+
+function bp_profile_groups() {
+	global $profile_template;
+	return $profile_template->profile_groups();
+}
+
+function bp_the_profile_group() {
+	global $profile_template;
+	return $profile_template->the_profile_group();
+}
+
+function bp_profile_group_has_fields() {
+	global $profile_template;
+	return $profile_template->has_fields();
+}
+
+function bp_field_css_class( $class = false ) {
+	echo bp_get_field_css_class( $class );
+}
+	function bp_get_field_css_class( $class = false ) {
+		global $profile_template;
+
+		$css_classes = array();
+
+		if ( ! empty( $class ) ) {
+			$css_classes[] = sanitize_title( esc_attr( $class ) );
+		}
+
+		// Set a class with the field ID.
+		$css_classes[] = 'field_' . $profile_template->field->id;
+
+		// Set a class with the field name (sanitized).
+		$css_classes[] = 'field_' . sanitize_title( $profile_template->field->name );
+
+		// Set a class indicating whether the field is required or optional.
+		if ( ! empty( $profile_template->field->is_required ) ) {
+			$css_classes[] = 'required-field';
+		} else {
+			$css_classes[] = 'optional-field';
+		}
+
+		// Add the field visibility level.
+		$css_classes[] = 'visibility-' . esc_attr( bp_get_the_profile_field_visibility_level() );
+
+		if ( $profile_template->current_field % 2 == 1 ) {
+			$css_classes[] = 'alt';
+		}
+
+		$css_classes[] = 'field_type_' . sanitize_title( $profile_template->field->type );
+
+		/**
+		 * Filters the field classes to be applied to a field.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param array $css_classes Array of classes to be applied to field. Passed by reference.
+		 */
+		$css_classes = apply_filters_ref_array( 'bp_field_css_classes', array( &$css_classes ) );
+
+		/**
+		 * Filters the class HTML attribute to be used on a field.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value class HTML attribute with imploded classes.
+		 */
+		return apply_filters( 'bp_get_field_css_class', ' class="' . implode( ' ', $css_classes ) . '"' );
+	}
+
+function bp_field_has_data() {
+	global $profile_template;
+	return $profile_template->field_has_data;
+}
+
+function bp_field_has_public_data() {
+	global $profile_template;
+
+	if ( ! empty( $profile_template->field_has_data ) ) {
+		return true;
+	}
+
+	return false;
+}
+
+function bp_the_profile_group_id() {
+	echo bp_get_the_profile_group_id();
+}
+	function bp_get_the_profile_group_id() {
+		global $group;
+
+		/**
+		 * Filters the profile group ID.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int $id ID for the profile group.
+		 */
+		return apply_filters( 'bp_get_the_profile_group_id', $group->id );
+	}
+
+function bp_the_profile_group_name() {
+	echo bp_get_the_profile_group_name();
+}
+	function bp_get_the_profile_group_name() {
+		global $group;
+
+		/**
+		 * Filters the profile group name.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $name Name for the profile group.
+		 */
+		return apply_filters( 'bp_get_the_profile_group_name', $group->name );
+	}
+
+function bp_the_profile_group_slug() {
+	echo bp_get_the_profile_group_slug();
+}
+	function bp_get_the_profile_group_slug() {
+		global $group;
+
+		/**
+		 * Filters the profile group slug.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Slug for the profile group.
+		 */
+		return apply_filters( 'bp_get_the_profile_group_slug', sanitize_title( $group->name ) );
+	}
+
+function bp_the_profile_group_description() {
+	echo bp_get_the_profile_group_description();
+}
+	function bp_get_the_profile_group_description() {
+		global $group;
+
+		/**
+		 * Filters the profile group description.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $description Description for the profile group.
+		 */
+		return apply_filters( 'bp_get_the_profile_group_description', $group->description );
+	}
+
+function bp_the_profile_group_edit_form_action() {
+	echo bp_get_the_profile_group_edit_form_action();
+}
+	function bp_get_the_profile_group_edit_form_action() {
+		global $group;
+
+		// Build the form action URL.
+		$form_action = trailingslashit( bp_displayed_user_domain() . bp_get_profile_slug() . '/edit/group/' . $group->id );
+
+		/**
+		 * Filters the action for the profile group edit form.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value URL for the action attribute on the
+		 *                      profile group edit form.
+		 */
+		return apply_filters( 'bp_get_the_profile_group_edit_form_action', $form_action );
+	}
+
+function bp_the_profile_group_field_ids() {
+	echo bp_get_the_profile_group_field_ids();
+}
+	function bp_get_the_profile_group_field_ids() {
+		global $group;
+
+		$field_ids = '';
+
+		if ( !empty( $group->fields ) ) {
+			foreach ( (array) $group->fields as $field ) {
+				$field_ids .= $field->id . ',';
+			}
+		}
+
+		return substr( $field_ids, 0, -1 );
+	}
+
+/**
+ * Output a comma-separated list of field IDs that are to be submitted on profile edit.
+ *
+ * @since 2.1.0
+ */
+function bp_the_profile_field_ids() {
+	echo bp_get_the_profile_field_ids();
+}
+	/**
+	 * Generate a comma-separated list of field IDs that are to be submitted on profile edit.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @return string
+	 */
+	function bp_get_the_profile_field_ids() {
+		global $profile_template;
+
+		$field_ids = array();
+		foreach ( $profile_template->groups as $group ) {
+			if ( ! empty( $group->fields ) ) {
+				$field_ids = array_merge( $field_ids, wp_list_pluck( $group->fields, 'id' ) );
+			}
+		}
+
+		$field_ids = implode( ',', wp_parse_id_list( $field_ids ) );
+
+		/**
+		 * Filters the comma-separated list of field IDs.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param string $field_ids Comma-separated field IDs.
+		 */
+		return apply_filters( 'bp_get_the_profile_field_ids', $field_ids );
+	}
+
+function bp_profile_fields() {
+	global $profile_template;
+	return $profile_template->profile_fields();
+}
+
+function bp_the_profile_field() {
+	global $profile_template;
+	return $profile_template->the_profile_field();
+}
+
+function bp_the_profile_field_id() {
+	echo bp_get_the_profile_field_id();
+}
+	function bp_get_the_profile_field_id() {
+		global $field;
+
+		/**
+		 * Filters the profile field ID.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param int $id ID for the profile field.
+		 */
+		return apply_filters( 'bp_get_the_profile_field_id', $field->id );
+	}
+
+function bp_the_profile_field_name() {
+	echo bp_get_the_profile_field_name();
+}
+	function bp_get_the_profile_field_name() {
+		global $field;
+
+		/**
+		 * Filters the profile field name.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $name Name for the profile field.
+		 */
+		return apply_filters( 'bp_get_the_profile_field_name', $field->name );
+	}
+
+function bp_the_profile_field_value() {
+	echo bp_get_the_profile_field_value();
+}
+	function bp_get_the_profile_field_value() {
+		global $field;
+
+		$field->data->value = bp_unserialize_profile_field( $field->data->value );
+
+		/**
+		 * Filters the profile field value.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $value Value for the profile field.
+		 * @param string $type  Type for the profile field.
+		 * @param int    $id    ID for the profile field.
+		 */
+		return apply_filters( 'bp_get_the_profile_field_value', $field->data->value, $field->type, $field->id );
+	}
+
+function bp_the_profile_field_edit_value() {
+	echo bp_get_the_profile_field_edit_value();
+}
+	function bp_get_the_profile_field_edit_value() {
+		global $field;
+
+		/**
+		 * Check to see if the posted value is different, if it is re-display this
+		 * value as long as it's not empty and a required field.
+		 */
+		if ( ! isset( $field->data ) ) {
+			$field->data = new stdClass;
+		}
+
+		if ( ! isset( $field->data->value ) ) {
+			$field->data->value = '';
+		}
+
+		if ( isset( $_POST['field_' . $field->id] ) && $field->data->value != $_POST['field_' . $field->id] ) {
+			if ( ! empty( $_POST['field_' . $field->id] ) ) {
+				$field->data->value = $_POST['field_' . $field->id];
+			} else {
+				$field->data->value = '';
+			}
+		}
+
+		$field_value = isset( $field->data->value ) ? bp_unserialize_profile_field( $field->data->value ) : '';
+
+		/**
+		 * Filters the profile field edit value.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $field_value Current field edit value.
+		 * @param string $type        Type for the profile field.
+		 * @param int    $id          ID for the profile field.
+		 */
+		return apply_filters( 'bp_get_the_profile_field_edit_value', $field_value, $field->type, $field->id );
+	}
+
+function bp_the_profile_field_type() {
+	echo bp_get_the_profile_field_type();
+}
+	function bp_get_the_profile_field_type() {
+		global $field;
+
+		/**
+		 * Filters the profile field type.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $type Type for the profile field.
+		 */
+		return apply_filters( 'bp_the_profile_field_type', $field->type );
+	}
+
+function bp_the_profile_field_description() {
+	echo bp_get_the_profile_field_description();
+}
+	function bp_get_the_profile_field_description() {
+		global $field;
+
+		/**
+		 * Filters the profile field description.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $description Description for the profile field.
+		 */
+		return apply_filters( 'bp_get_the_profile_field_description', $field->description );
+	}
+
+function bp_the_profile_field_input_name() {
+	echo bp_get_the_profile_field_input_name();
+}
+	function bp_get_the_profile_field_input_name() {
+		global $field;
+
+		/**
+		 * Filters the profile field input name.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Value used for the name attribute on an input.
+		 */
+		return apply_filters( 'bp_get_the_profile_field_input_name', 'field_' . $field->id );
+	}
+
+/**
+ * Returns the action name for any signup errors related to this profile field.
+ *
+ * In the registration templates, signup errors are pulled from the global
+ * object and rendered at actions that look like 'bp_field_12_errors'. This
+ * function allows the action name to be easily concatenated and called in the
+ * following fashion:
+ *   do_action( bp_get_the_profile_field_errors_action() );
+ *
+ * @since 1.8.0
+ *
+ * @return string The _errors action name corresponding to this profile field.
+ */
+function bp_get_the_profile_field_errors_action() {
+	global $field;
+	return 'bp_field_' . $field->id . '_errors';
+}
+
+/**
+ * Displays field options HTML for field types of 'selectbox', 'multiselectbox',
+ * 'radio', 'checkbox', and 'datebox'.
+ *
+ * @since 1.1.0
+ *
+ * @uses bp_get_the_profile_field_options()
+ *
+ * @param array $args Specify type for datebox. Allowed 'day', 'month', 'year'.
+ */
+function bp_the_profile_field_options( $args = array() ) {
+	echo bp_get_the_profile_field_options( $args );
+}
+	/**
+	 * Retrieves field options HTML for field types of 'selectbox', 'multiselectbox', 'radio', 'checkbox', and 'datebox'.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @uses BP_XProfile_Field::get_children()
+	 * @uses BP_XProfile_ProfileData::get_value_byid()
+	 *
+	 * @param array $args {
+	 *     Array of optional arguments.
+	 *     @type string|bool $type    Type of datebox. False if it's not a
+	 *                                datebox, otherwise 'day, 'month', or 'year'. Default: false.
+	 *     @type int         $user_id ID of the user whose profile values should be
+	 *                                used when rendering options. Default: displayed user.
+	 * }
+	 *
+	 * @return string $vaue Field options markup.
+	 */
+	function bp_get_the_profile_field_options( $args = array() ) {
+		global $field;
+
+		$args = bp_parse_args( $args, array(
+			'type'    => false,
+			'user_id' => bp_displayed_user_id(),
+		), 'get_the_profile_field_options' );
+
+		/**
+		 * In some cases, the $field global is not an instantiation of the BP_XProfile_Field class.
+		 * However, we have to make sure that all data originally in $field gets merged back in, after reinstantiation.
+		 */
+		if ( ! method_exists( $field, 'get_children' ) ) {
+			$field_obj = xprofile_get_field( $field->id );
+
+			foreach ( $field as $field_prop => $field_prop_value ) {
+				if ( ! isset( $field_obj->{$field_prop} ) ) {
+					$field_obj->{$field_prop} = $field_prop_value;
+				}
+			}
+
+			$field = $field_obj;
+		}
+
+		ob_start();
+		$field->type_obj->edit_field_options_html( $args );
+		$html = ob_get_contents();
+		ob_end_clean();
+
+		return $html;
+	}
+
+function bp_the_profile_field_is_required() {
+	echo bp_get_the_profile_field_is_required();
+}
+	function bp_get_the_profile_field_is_required() {
+		global $field;
+
+		$retval = false;
+
+		if ( isset( $field->is_required ) ) {
+			$retval = $field->is_required;
+		}
+
+		/**
+		 * Filters whether or not a profile field is required.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param bool $retval Whether or not the field is required.
+		 */
+		return apply_filters( 'bp_get_the_profile_field_is_required', (bool) $retval );
+	}
+
+/**
+ * Echo the visibility level of this field.
+ */
+function bp_the_profile_field_visibility_level() {
+	echo bp_get_the_profile_field_visibility_level();
+}
+	/**
+	 * Return the visibility level of this field.
+	 */
+	function bp_get_the_profile_field_visibility_level() {
+		global $field;
+
+		// On the registration page, values stored in POST should take
+		// precedence over default visibility, so that submitted values
+		// are not lost on failure.
+		if ( bp_is_register_page() && ! empty( $_POST['field_' . $field->id . '_visibility'] ) ) {
+			$retval = esc_attr( $_POST['field_' . $field->id . '_visibility'] );
+		} else {
+			$retval = ! empty( $field->visibility_level ) ? $field->visibility_level : 'public';
+		}
+
+		/**
+		 * Filters the profile field visibility level.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $retval Field visibility level.
+		 */
+		return apply_filters( 'bp_get_the_profile_field_visibility_level', $retval );
+	}
+
+/**
+ * Echo the visibility level label of this field.
+ */
+function bp_the_profile_field_visibility_level_label() {
+	echo bp_get_the_profile_field_visibility_level_label();
+}
+	/**
+	 * Return the visibility level label of this field.
+	 */
+	function bp_get_the_profile_field_visibility_level_label() {
+		global $field;
+
+		// On the registration page, values stored in POST should take
+		// precedence over default visibility, so that submitted values
+		// are not lost on failure.
+		if ( bp_is_register_page() && ! empty( $_POST['field_' . $field->id . '_visibility'] ) ) {
+			$level = esc_html( $_POST['field_' . $field->id . '_visibility'] );
+		} else {
+			$level = ! empty( $field->visibility_level ) ? $field->visibility_level : 'public';
+		}
+
+		$fields = bp_xprofile_get_visibility_levels();
+
+		/**
+		 * Filters the profile field visibility level label.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $retval Field visibility level label.
+		 */
+		return apply_filters( 'bp_get_the_profile_field_visibility_level_label', $fields[ $level ]['label'] );
+	}
+
+
+function bp_unserialize_profile_field( $value ) {
+	if ( is_serialized($value) ) {
+		$field_value = maybe_unserialize($value);
+		$field_value = implode( ', ', $field_value );
+		return $field_value;
+	}
+
+	return $value;
+}
+
+function bp_profile_field_data( $args = '' ) {
+	echo bp_get_profile_field_data( $args );
+}
+	function bp_get_profile_field_data( $args = '' ) {
+
+		$r = wp_parse_args( $args, array(
+			'field'   => false, // Field name or ID.
+			'user_id' => bp_displayed_user_id()
+		) );
+
+		/**
+		 * Filters the profile field data.
+		 *
+		 * @since 1.2.0
+		 *
+		 * @param mixed $value Profile data for a specific field for the user.
+		 */
+		return apply_filters( 'bp_get_profile_field_data', xprofile_get_field_data( $r['field'], $r['user_id'] ) );
+	}
+
+/**
+ * Get all profile field groups.
+ *
+ * @since 2.1.0
+ *
+ * @return array $groups
+ */
+function bp_profile_get_field_groups() {
+
+	$groups = wp_cache_get( 'all', 'bp_xprofile_groups' );
+	if ( false === $groups ) {
+		$groups = bp_xprofile_get_groups( array( 'fetch_fields' => true ) );
+		wp_cache_set( 'all', $groups, 'bp_xprofile' );
+	}
+
+	/**
+	 * Filters all profile field groups.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param array $groups Array of available profile field groups.
+	 */
+	return apply_filters( 'bp_profile_get_field_groups', $groups );
+}
+
+/**
+ * Check if there is more than one group of fields for the profile being edited.
+ *
+ * @since 2.1.0
+ *
+ * @return bool True if there is more than one profile field group.
+ */
+function bp_profile_has_multiple_groups() {
+	$has_multiple_groups = count( (array) bp_profile_get_field_groups() ) > 1;
+
+	/**
+	 * Filters if there is more than one group of fields for the profile being edited.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param bool $has_multiple_groups Whether or not there are multiple groups.
+	 */
+	return (bool) apply_filters( 'bp_profile_has_multiple_groups', $has_multiple_groups );
+}
+
+/**
+ * Output the tabs to switch between profile field groups.
+ *
+ * @since 1.0.0
+ */
+function bp_profile_group_tabs() {
+	echo bp_get_profile_group_tabs();
+
+	/**
+	 * Fires at the end of the tab output for switching between profile field
+	 * groups. This action is in a strange place for legacy reasons.
+	 *
+	 * @since 1.0.0
+	 */
+	do_action( 'xprofile_profile_group_tabs' );
+}
+
+/**
+ * Return the XProfile group tabs.
+ *
+ * @since 2.3.0
+ *
+ * @return string
+ */
+function bp_get_profile_group_tabs() {
+
+	// Get field group data.
+	$groups     = bp_profile_get_field_groups();
+	$group_name = bp_get_profile_group_name();
+	$tabs       = array();
+
+	// Loop through field groups and put a tab-lst together.
+	for ( $i = 0, $count = count( $groups ); $i < $count; ++$i ) {
+
+		// Setup the selected class.
+		$selected = '';
+		if ( $group_name === $groups[ $i ]->name ) {
+			$selected = ' class="current"';
+		}
+
+		// Skip if group has no fields.
+		if ( empty( $groups[ $i ]->fields ) ) {
+			continue;
+		}
+
+		// Build the profile field group link.
+		$link   = trailingslashit( bp_displayed_user_domain() . bp_get_profile_slug() . '/edit/group/' . $groups[ $i ]->id );
+
+		// Add tab to end of tabs array.
+		$tabs[] = sprintf(
+			'<li %1$s><a href="%2$s">%3$s</a></li>',
+			$selected,
+			esc_url( $link ),
+			esc_html( apply_filters( 'bp_get_the_profile_group_name', $groups[ $i ]->name ) )
+		);
+	}
+
+	/**
+	 * Filters the tabs to display for profile field groups.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @param array  $tabs       Array of tabs to display.
+	 * @param array  $groups     Array of profile groups.
+	 * @param string $group_name Name of the current group displayed.
+	 */
+	$tabs = apply_filters( 'xprofile_filter_profile_group_tabs', $tabs, $groups, $group_name );
+
+	return join( '', $tabs );
+}
+
+function bp_profile_group_name( $deprecated = true ) {
+	if ( !$deprecated ) {
+		return bp_get_profile_group_name();
+	} else {
+		echo bp_get_profile_group_name();
+	}
+}
+	function bp_get_profile_group_name() {
+
+		// Check action variable.
+		$group_id = bp_action_variable( 1 );
+		if ( empty( $group_id ) || ! is_numeric( $group_id ) ) {
+			$group_id = 1;
+		}
+
+		// Check for cached group.
+		$group = new BP_XProfile_Group( $group_id );
+
+		/**
+		 * Filters the profile group name.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param string $name Name of the profile group.
+		 */
+		return apply_filters( 'bp_get_profile_group_name', $group->name );
+	}
+
+function bp_profile_last_updated() {
+
+	$last_updated = bp_get_profile_last_updated();
+
+	if ( empty( $last_updated ) ) {
+		_e( 'Profile not recently updated.', 'buddypress' );
+	} else {
+		echo $last_updated;
+	}
+}
+	function bp_get_profile_last_updated() {
+
+		$last_updated = bp_get_user_meta( bp_displayed_user_id(), 'profile_last_updated', true );
+
+		if ( ! empty( $last_updated ) ) {
+
+			/**
+			 * Filters the formatted string used to display when a profile was last updated.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param string $value Formatted last updated indicator string.
+			 */
+			return apply_filters( 'bp_get_profile_last_updated', sprintf( __( 'Profile updated %s', 'buddypress' ), bp_core_time_since( strtotime( $last_updated ) ) ) );
+		}
+
+		return false;
+	}
+
+function bp_current_profile_group_id() {
+	echo bp_get_current_profile_group_id();
+}
+	function bp_get_current_profile_group_id() {
+		$profile_group_id = bp_action_variable( 1 );
+		if ( empty( $profile_group_id ) ) {
+			$profile_group_id = 1;
+		}
+
+		/**
+		 * Filters the current profile group ID.
+		 *
+		 * Possible values are admin/profile/edit/[group-id].
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $profile_group_id Current profile group ID.
+		 */
+		return apply_filters( 'bp_get_current_profile_group_id', $profile_group_id );
+	}
+
+function bp_avatar_delete_link() {
+	echo bp_get_avatar_delete_link();
+}
+	function bp_get_avatar_delete_link() {
+
+		/**
+		 * Filters the link used for deleting an avatar.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $value Nonced URL used for deleting an avatar.
+		 */
+		return apply_filters( 'bp_get_avatar_delete_link', wp_nonce_url( bp_displayed_user_domain() . bp_get_profile_slug() . '/change-avatar/delete-avatar/', 'bp_delete_avatar_link' ) );
+	}
+
+function bp_edit_profile_button() {
+	bp_button( array(
+		'id'                => 'edit_profile',
+		'component'         => 'xprofile',
+		'must_be_logged_in' => true,
+		'block_self'        => true,
+		'link_href'         => trailingslashit( bp_displayed_user_domain() . bp_get_profile_slug() . '/edit' ),
+		'link_class'        => 'edit',
+		'link_text'         => __( 'Edit Profile', 'buddypress' ),
+		'link_title'        => __( 'Edit Profile', 'buddypress' ),
+	) );
+}
+
+/** Visibility ****************************************************************/
+
+/**
+ * Echo the field visibility radio buttons.
+ *
+ * @param array|string $args Args for the radio buttons.
+ */
+function bp_profile_visibility_radio_buttons( $args = '' ) {
+	echo bp_profile_get_visibility_radio_buttons( $args );
+}
+	/**
+	 * Return the field visibility radio buttons.
+	 *
+	 * @param array|string $args Args for the radio buttons.
+	 *
+	 * @return string $retval
+	 */
+	function bp_profile_get_visibility_radio_buttons( $args = '' ) {
+
+		// Parse optional arguments.
+		$r = bp_parse_args( $args, array(
+			'field_id'     => bp_get_the_profile_field_id(),
+			'before'       => '<ul class="radio">',
+			'after'        => '</ul>',
+			'before_radio' => '<li class="%s">',
+			'after_radio'  => '</li>',
+			'class'        => 'bp-xprofile-visibility'
+		), 'xprofile_visibility_radio_buttons' );
+
+		// Empty return value, filled in below if a valid field ID is found.
+		$retval = '';
+
+		// Only do-the-do if there's a valid field ID.
+		if ( ! empty( $r['field_id'] ) ) :
+
+			// Start the output buffer.
+			ob_start();
+
+			// Output anything before.
+			echo $r['before']; ?>
+
+			<?php if ( bp_current_user_can( 'bp_xprofile_change_field_visibility' ) ) : ?>
+
+				<?php foreach( bp_xprofile_get_visibility_levels() as $level ) : ?>
+
+					<?php printf( $r['before_radio'], esc_attr( $level['id'] ) ); ?>
+
+					<label for="<?php echo esc_attr( 'see-field_' . $r['field_id'] . '_' . $level['id'] ); ?>">
+						<input type="radio" id="<?php echo esc_attr( 'see-field_' . $r['field_id'] . '_' . $level['id'] ); ?>" name="<?php echo esc_attr( 'field_' . $r['field_id'] . '_visibility' ); ?>" value="<?php echo esc_attr( $level['id'] ); ?>" <?php checked( $level['id'], bp_get_the_profile_field_visibility_level() ); ?> />
+						<span class="field-visibility-text"><?php echo esc_html( $level['label'] ); ?></span>
+					</label>
+
+					<?php echo $r['after_radio']; ?>
+
+				<?php endforeach; ?>
+
+			<?php endif;
+
+			// Output anything after.
+			echo $r['after'];
+
+			// Get the output buffer and empty it.
+			$retval = ob_get_clean();
+		endif;
+
+		/**
+		 * Filters the radio buttons for setting visibility.
+		 *
+		 * @since 1.6.0
+		 *
+		 * @param string $retval HTML output for the visibility radio buttons.
+		 * @param array  $r      Parsed arguments to be used with display.
+		 * @param array  $args   Original passed in arguments to be used with display.
+		 */
+		return apply_filters( 'bp_profile_get_visibility_radio_buttons', $retval, $r, $args );
+	}
+
+/**
+ * Output the XProfile field visibility select list for settings.
+ *
+ * @since 2.0.0
+ *
+ * @param array|string $args Args for the select list.
+ */
+function bp_profile_settings_visibility_select( $args = '' ) {
+	echo bp_profile_get_settings_visibility_select( $args );
+}
+	/**
+	 * Return the XProfile field visibility select list for settings.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array|string $args Args for the select list.
+	 *
+	 * @return string $retval
+	 */
+	function bp_profile_get_settings_visibility_select( $args = '' ) {
+
+		// Parse optional arguments.
+		$r = bp_parse_args( $args, array(
+			'field_id' => bp_get_the_profile_field_id(),
+			'before'   => '',
+			'after'    => '',
+			'class'    => 'bp-xprofile-visibility'
+		), 'xprofile_settings_visibility_select' );
+
+		// Empty return value, filled in below if a valid field ID is found.
+		$retval = '';
+
+		// Only do-the-do if there's a valid field ID.
+		if ( ! empty( $r['field_id'] ) ) :
+
+			// Start the output buffer.
+			ob_start();
+
+			// Output anything before.
+			echo $r['before']; ?>
+
+			<?php if ( bp_current_user_can( 'bp_xprofile_change_field_visibility' ) ) : ?>
+
+				<label for="<?php echo esc_attr( 'field_' . $r['field_id'] ) ; ?>_visibility" class="bp-screen-reader-text"><?php _e( 'Select visibility', 'buddypress' ); ?></label>
+				<select class="<?php echo esc_attr( $r['class'] ); ?>" name="<?php echo esc_attr( 'field_' . $r['field_id'] ) ; ?>_visibility" id="<?php echo esc_attr( 'field_' . $r['field_id'] ) ; ?>_visibility">
+
+					<?php foreach ( bp_xprofile_get_visibility_levels() as $level ) : ?>
+
+						<option value="<?php echo esc_attr( $level['id'] ); ?>" <?php selected( $level['id'], bp_get_the_profile_field_visibility_level() ); ?>><?php echo esc_html( $level['label'] ); ?></option>
+
+					<?php endforeach; ?>
+
+				</select>
+
+			<?php else : ?>
+
+				<span class="field-visibility-settings-notoggle" title="<?php esc_attr_e( "This field's visibility cannot be changed.", 'buddypress' ); ?>"><?php bp_the_profile_field_visibility_level_label(); ?></span>
+
+			<?php endif;
+
+			// Output anything after.
+			echo $r['after'];
+
+			// Get the output buffer and empty it.
+			$retval = ob_get_clean();
+		endif;
+
+		/**
+		 * Filters the dropdown list for setting visibility.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string $retval HTML output for the visibility dropdown list.
+		 * @param array  $r      Parsed arguments to be used with display.
+		 * @param array  $args   Original passed in arguments to be used with display.
+		 */
+		return apply_filters( 'bp_profile_settings_visibility_select', $retval, $r, $args );
+	}
+
+/**
+ * Output the 'required' markup in extended profile field labels.
+ *
+ * @since 2.4.0
+ *
+ * @return string HTML for the required label.
+ */
+function bp_the_profile_field_required_label() {
+	echo bp_get_the_profile_field_required_label();
+}
+
+	/**
+	 * Return the 'required' markup in extended profile field labels.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @return string HTML for the required label.
+	 */
+	function bp_get_the_profile_field_required_label() {
+		$retval = '';
+
+		if ( bp_get_the_profile_field_is_required() ) {
+			$translated_string = __( '(required)', 'buddypress' );
+
+			$retval = ' <span class="bp-required-field-label">';
+			$retval .= apply_filters( 'bp_get_the_profile_field_required_label', $translated_string, bp_get_the_profile_field_id() );
+			$retval .= '</span>';
+
+		}
+
+		return $retval;
+	}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-checkbox.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-checkbox.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-checkbox.php	(revision 10410)
@@ -0,0 +1,193 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Checkbox xprofile field type.
+ *
+ * @since 2.0.0
+ */
+class BP_XProfile_Field_Type_Checkbox extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the checkbox field type.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Checkboxes', 'xprofile field type', 'buddypress' );
+
+		$this->supports_multiple_defaults = true;
+		$this->accepts_null_value         = true;
+		$this->supports_options           = true;
+
+		$this->set_format( '/^.+$/', 'replace' );
+
+		/**
+		 * Fires inside __construct() method for BP_XProfile_Field_Type_Checkbox class.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Field_Type_Checkbox $this Current instance of
+		 *                                              the field type checkbox.
+		 */
+		do_action( 'bp_xprofile_field_type_checkbox', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of
+	 *                              {@link http://dev.w3.org/html5/markup/input.checkbox.html permitted attributes}
+	 *                              that you want to add.
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// User_id is a special optional parameter that we pass to
+		// {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			$user_id = (int) $raw_properties['user_id'];
+			unset( $raw_properties['user_id'] );
+		} else {
+			$user_id = bp_displayed_user_id();
+		} ?>
+
+		<div class="checkbox">
+			<label for="<?php bp_the_profile_field_input_name(); ?>">
+				<?php bp_the_profile_field_name(); ?>
+				<?php bp_the_profile_field_required_label(); ?>
+			</label>
+
+			<?php
+
+			/** This action is documented in bp-xprofile/bp-xprofile-classes */
+			do_action( bp_get_the_profile_field_errors_action() ); ?>
+
+			<?php bp_the_profile_field_options( array(
+				'user_id' => $user_id
+			) ); ?>
+
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled separately.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 */
+	public function edit_field_options_html( array $args = array() ) {
+		$options       = $this->field_obj->get_children();
+		$option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ) );
+		$option_values = ( $option_values ) ? (array) $option_values : array();
+
+		$html = '';
+
+		// Check for updated posted values, but errors preventing them from
+		// being saved first time.
+		if ( isset( $_POST['field_' . $this->field_obj->id] ) && $option_values != maybe_serialize( $_POST['field_' . $this->field_obj->id] ) ) {
+			if ( ! empty( $_POST['field_' . $this->field_obj->id] ) ) {
+				$option_values = array_map( 'sanitize_text_field', $_POST['field_' . $this->field_obj->id] );
+			}
+		}
+
+		for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
+			$selected = '';
+
+			// First, check to see whether the user's saved values match the option.
+			for ( $j = 0, $count_values = count( $option_values ); $j < $count_values; ++$j ) {
+
+				// Run the allowed option name through the before_save filter,
+				// so we'll be sure to get a match.
+				$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
+
+				if ( $option_values[$j] === $allowed_options || in_array( $allowed_options, $option_values ) ) {
+					$selected = ' checked="checked"';
+					break;
+				}
+			}
+
+			// If the user has not yet supplied a value for this field, check to
+			// see whether there is a default value available.
+			if ( empty( $option_values ) && empty( $selected ) && ! empty( $options[$k]->is_default_option ) ) {
+				$selected = ' checked="checked"';
+			}
+
+			$new_html = sprintf( '<label for="%3$s"><input %1$s type="checkbox" name="%2$s" id="%3$s" value="%4$s">%5$s</label>',
+				$selected,
+				esc_attr( "field_{$this->field_obj->id}[]" ),
+				esc_attr( "field_{$options[$k]->id}_{$k}" ),
+				esc_attr( stripslashes( $options[$k]->name ) ),
+				esc_html( stripslashes( $options[$k]->name ) )
+			);
+
+			/**
+			 * Filters the HTML output for an individual field options checkbox.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param string $new_html Label and checkbox input field.
+			 * @param object $value    Current option being rendered for.
+			 * @param int    $id       ID of the field object being rendered.
+			 * @param string $selected Current selected value.
+			 * @param string $k        Current index in the foreach loop.
+			 */
+			$html .= apply_filters( 'bp_get_the_profile_field_options_checkbox', $new_html, $options[$k], $this->field_obj->id, $selected, $k );
+		}
+
+		echo $html;
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		bp_the_profile_field_options();
+	}
+
+	/**
+	 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the current
+	 *                                         field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {
+		parent::admin_new_field_html( $current_field, 'checkbox' );
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-datebox.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-datebox.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-datebox.php	(revision 10410)
@@ -0,0 +1,324 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Datebox xprofile field type.
+ *
+ * @since 2.0.0
+ */
+class BP_XProfile_Field_Type_Datebox extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the datebox field type.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Date Selector', 'xprofile field type', 'buddypress' );
+
+		$this->set_format( '/^\d{4}-\d{1,2}-\d{1,2} 00:00:00$/', 'replace' ); // "Y-m-d 00:00:00"
+
+		/**
+		 * Fires inside __construct() method for BP_XProfile_Field_Type_Datebox class.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Field_Type_Datebox $this Current instance of
+		 *                                             the field type datebox.
+		 */
+		do_action( 'bp_xprofile_field_type_datebox', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of
+	 *                              {@link http://dev.w3.org/html5/markup/input.html permitted attributes}
+	 *                              that you want to add.
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// User_id is a special optional parameter that we pass to.
+		// {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			$user_id = (int) $raw_properties['user_id'];
+			unset( $raw_properties['user_id'] );
+		} else {
+			$user_id = bp_displayed_user_id();
+		}
+
+		$day_r = bp_parse_args( $raw_properties, array(
+			'id'   => bp_get_the_profile_field_input_name() . '_day',
+			'name' => bp_get_the_profile_field_input_name() . '_day'
+		) );
+
+		$month_r = bp_parse_args( $raw_properties, array(
+			'id'   => bp_get_the_profile_field_input_name() . '_month',
+			'name' => bp_get_the_profile_field_input_name() . '_month'
+		) );
+
+		$year_r = bp_parse_args( $raw_properties, array(
+			'id'   => bp_get_the_profile_field_input_name() . '_year',
+			'name' => bp_get_the_profile_field_input_name() . '_year'
+		) ); ?>
+
+		<div class="datebox">
+
+			<label for="<?php bp_the_profile_field_input_name(); ?>_day">
+				<?php bp_the_profile_field_name(); ?>
+				<?php bp_the_profile_field_required_label(); ?>
+			</label>
+
+			<?php
+
+			/**
+			 * Fires after field label and displays associated errors for the field.
+			 *
+			 * This is a dynamic hook that is dependent on the associated
+			 * field ID. The hooks will be similar to `bp_field_12_errors`
+			 * where the 12 is the field ID. Simply replace the 12 with
+			 * your needed target ID.
+			 *
+			 * @since 1.8.0
+			 */
+			do_action( bp_get_the_profile_field_errors_action() ); ?>
+
+			<select <?php echo $this->get_edit_field_html_elements( $day_r ); ?>>
+				<?php bp_the_profile_field_options( array(
+					'type'    => 'day',
+					'user_id' => $user_id
+				) ); ?>
+			</select>
+
+			<select <?php echo $this->get_edit_field_html_elements( $month_r ); ?>>
+				<?php bp_the_profile_field_options( array(
+					'type'    => 'month',
+					'user_id' => $user_id
+				) ); ?>
+			</select>
+
+			<select <?php echo $this->get_edit_field_html_elements( $year_r ); ?>>
+				<?php bp_the_profile_field_options( array(
+					'type'    => 'year',
+					'user_id' => $user_id
+				) ); ?>
+			</select>
+
+		</div>
+	<?php
+	}
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled separately.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 */
+	public function edit_field_options_html( array $args = array() ) {
+
+		$date       = BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] );
+		$day        = 0;
+		$month      = 0;
+		$year       = 0;
+		$html       = '';
+		$eng_months = array( 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' );
+
+		// Set day, month, year defaults.
+		if ( ! empty( $date ) ) {
+
+			// If Unix timestamp.
+			if ( is_numeric( $date ) ) {
+				$day   = date( 'j', $date );
+				$month = date( 'F', $date );
+				$year  = date( 'Y', $date );
+
+			// If MySQL timestamp.
+			} else {
+				$day   = mysql2date( 'j', $date );
+				$month = mysql2date( 'F', $date, false ); // Not localized, so that selected() works below.
+				$year  = mysql2date( 'Y', $date );
+			}
+		}
+
+		// Check for updated posted values, and errors preventing them from
+		// being saved first time.
+		if ( ! empty( $_POST['field_' . $this->field_obj->id . '_day'] ) ) {
+			$new_day = (int) $_POST['field_' . $this->field_obj->id . '_day'];
+			$day     = ( $day != $new_day ) ? $new_day : $day;
+		}
+
+		if ( ! empty( $_POST['field_' . $this->field_obj->id . '_month'] ) ) {
+			if ( in_array( $_POST['field_' . $this->field_obj->id . '_month'], $eng_months ) ) {
+				$new_month = $_POST['field_' . $this->field_obj->id . '_month'];
+			} else {
+				$new_month = $month;
+			}
+
+			$month = ( $month !== $new_month ) ? $new_month : $month;
+		}
+
+		if ( ! empty( $_POST['field_' . $this->field_obj->id . '_year'] ) ) {
+			$new_year = (int) $_POST['field_' . $this->field_obj->id . '_year'];
+			$year     = ( $year != $new_year ) ? $new_year : $year;
+		}
+
+		// $type will be passed by calling function when needed.
+		switch ( $args['type'] ) {
+			case 'day':
+				$html = sprintf( '<option value="" %1$s>%2$s</option>', selected( $day, 0, false ), /* translators: no option picked in select box */ __( '----', 'buddypress' ) );
+
+				for ( $i = 1; $i < 32; ++$i ) {
+					$html .= sprintf( '<option value="%1$s" %2$s>%3$s</option>', (int) $i, selected( $day, $i, false ), (int) $i );
+				}
+			break;
+
+			case 'month':
+				$months = array(
+					__( 'January',   'buddypress' ),
+					__( 'February',  'buddypress' ),
+					__( 'March',     'buddypress' ),
+					__( 'April',     'buddypress' ),
+					__( 'May',       'buddypress' ),
+					__( 'June',      'buddypress' ),
+					__( 'July',      'buddypress' ),
+					__( 'August',    'buddypress' ),
+					__( 'September', 'buddypress' ),
+					__( 'October',   'buddypress' ),
+					__( 'November',  'buddypress' ),
+					__( 'December',  'buddypress' )
+				);
+
+				$html = sprintf( '<option value="" %1$s>%2$s</option>', selected( $month, 0, false ), /* translators: no option picked in select box */ __( '----', 'buddypress' ) );
+
+				for ( $i = 0; $i < 12; ++$i ) {
+					$html .= sprintf( '<option value="%1$s" %2$s>%3$s</option>', esc_attr( $eng_months[$i] ), selected( $month, $eng_months[$i], false ), $months[$i] );
+				}
+			break;
+
+			case 'year':
+				$html = sprintf( '<option value="" %1$s>%2$s</option>', selected( $year, 0, false ), /* translators: no option picked in select box */ __( '----', 'buddypress' ) );
+
+				for ( $i = 2037; $i > 1901; $i-- ) {
+					$html .= sprintf( '<option value="%1$s" %2$s>%3$s</option>', (int) $i, selected( $year, $i, false ), (int) $i );
+				}
+			break;
+		}
+
+		/**
+		 * Filters the output for the profile field datebox.
+		 *
+		 * @since 1.1.0
+		 *
+		 * @param string $html  HTML output for the field.
+		 * @param string $value Which date type is being rendered for.
+		 * @param string $day   Date formatted for the current day.
+		 * @param string $month Date formatted for the current month.
+		 * @param string $year  Date formatted for the current year.
+		 * @param int    $id    ID of the field object being rendered.
+		 * @param string $date  Current date.
+		 */
+		echo apply_filters( 'bp_get_the_profile_field_datebox', $html, $args['type'], $day, $month, $year, $this->field_obj->id, $date );
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+
+		$day_r = bp_parse_args( $raw_properties, array(
+			'id'   => bp_get_the_profile_field_input_name() . '_day',
+			'name' => bp_get_the_profile_field_input_name() . '_day'
+		) );
+
+		$month_r = bp_parse_args( $raw_properties, array(
+			'id'   => bp_get_the_profile_field_input_name() . '_month',
+			'name' => bp_get_the_profile_field_input_name() . '_month'
+		) );
+
+		$year_r = bp_parse_args( $raw_properties, array(
+			'id'   => bp_get_the_profile_field_input_name() . '_year',
+			'name' => bp_get_the_profile_field_input_name() . '_year'
+		) ); ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>_day" class="screen-reader-text"><?php esc_html_e( 'Select day', 'buddypress' ); ?></label>
+		<select <?php echo $this->get_edit_field_html_elements( $day_r ); ?>>
+			<?php bp_the_profile_field_options( array( 'type' => 'day' ) ); ?>
+		</select>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>_month" class="screen-reader-text"><?php esc_html_e( 'Select month', 'buddypress' ); ?></label>
+		<select <?php echo $this->get_edit_field_html_elements( $month_r ); ?>>
+			<?php bp_the_profile_field_options( array( 'type' => 'month' ) ); ?>
+		</select>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>_year" class="screen-reader-text"><?php esc_html_e( 'Select year', 'buddypress' ); ?></label>
+		<select <?php echo $this->get_edit_field_html_elements( $year_r ); ?>>
+			<?php bp_the_profile_field_options( array( 'type' => 'year' ) ); ?>
+		</select>
+
+	<?php
+	}
+
+	/**
+	 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields
+	 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the current
+	 *                                         field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {}
+
+	/**
+	 * Format Date values for display.
+	 *
+	 * @since 2.1.0
+	 * @since 2.4.0 Added the `$field_id` parameter.
+	 *
+	 * @param string $field_value The date value, as saved in the database. Typically, this is a MySQL-formatted
+	 *                            date string (Y-m-d H:i:s).
+	 * @param int    $field_id    Optional. ID of the field.
+	 *
+	 * @return string Date formatted by bp_format_time().
+	 */
+	public static function display_filter( $field_value, $field_id = '' ) {
+
+		// If Unix timestamp.
+		if ( ! is_numeric( $field_value ) ) {
+			$field_value = strtotime( $field_value );
+		}
+
+		return bp_format_time( $field_value, true, false );
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-multiselectbox.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-multiselectbox.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-multiselectbox.php	(revision 10410)
@@ -0,0 +1,208 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Multi-selectbox xprofile field type.
+ *
+ * @since 2.0.0
+ */
+class BP_XProfile_Field_Type_Multiselectbox extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the multi-selectbox field type.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Multi Select Box', 'xprofile field type', 'buddypress' );
+
+		$this->supports_multiple_defaults = true;
+		$this->accepts_null_value         = true;
+		$this->supports_options           = true;
+
+		$this->set_format( '/^.+$/', 'replace' );
+
+		/**
+		 * Fires inside __construct() method for BP_XProfile_Field_Type_Multiselectbox class.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Field_Type_Multiselectbox $this Current instance of
+		 *                                                    the field type multiple select box.
+		 */
+		do_action( 'bp_xprofile_field_type_multiselectbox', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of
+	 *                              {@link http://dev.w3.org/html5/markup/select.html permitted attributes}
+	 *                              that you want to add.
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// User_id is a special optional parameter that we pass to
+		// {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			$user_id = (int) $raw_properties['user_id'];
+			unset( $raw_properties['user_id'] );
+		} else {
+			$user_id = bp_displayed_user_id();
+		}
+
+		$r = bp_parse_args( $raw_properties, array(
+			'multiple' => 'multiple',
+			'id'       => bp_get_the_profile_field_input_name() . '[]',
+			'name'     => bp_get_the_profile_field_input_name() . '[]',
+		) ); ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>[]">
+			<?php bp_the_profile_field_name(); ?>
+			<?php bp_the_profile_field_required_label(); ?>
+		</label>
+
+		<?php
+
+		/** This action is documented in bp-xprofile/bp-xprofile-classes */
+		do_action( bp_get_the_profile_field_errors_action() ); ?>
+
+		<select <?php echo $this->get_edit_field_html_elements( $r ); ?>>
+			<?php bp_the_profile_field_options( array(
+				'user_id' => $user_id
+			) ); ?>
+		</select>
+
+		<?php if ( ! bp_get_the_profile_field_is_required() ) : ?>
+
+			<a class="clear-value" href="javascript:clear( '<?php echo esc_js( bp_get_the_profile_field_input_name() ); ?>[]' );">
+				<?php esc_html_e( 'Clear', 'buddypress' ); ?>
+			</a>
+
+		<?php endif; ?>
+	<?php
+	}
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled separately.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 */
+	public function edit_field_options_html( array $args = array() ) {
+		$original_option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ) );
+
+		$options = $this->field_obj->get_children();
+		$html    = '';
+
+		if ( empty( $original_option_values ) && ! empty( $_POST['field_' . $this->field_obj->id] ) ) {
+			$original_option_values = sanitize_text_field( $_POST['field_' . $this->field_obj->id] );
+		}
+
+		$option_values = ( $original_option_values ) ? (array) $original_option_values : array();
+		for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
+			$selected = '';
+
+			// Check for updated posted values, but errors preventing them from
+			// being saved first time.
+			foreach( $option_values as $i => $option_value ) {
+				if ( isset( $_POST['field_' . $this->field_obj->id] ) && $_POST['field_' . $this->field_obj->id][$i] != $option_value ) {
+					if ( ! empty( $_POST['field_' . $this->field_obj->id][$i] ) ) {
+						$option_values[] = sanitize_text_field( $_POST['field_' . $this->field_obj->id][$i] );
+					}
+				}
+			}
+
+			// Run the allowed option name through the before_save filter, so
+			// we'll be sure to get a match.
+			$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
+
+			// First, check to see whether the user-entered value matches.
+			if ( in_array( $allowed_options, $option_values ) ) {
+				$selected = ' selected="selected"';
+			}
+
+			// Then, if the user has not provided a value, check for defaults.
+			if ( ! is_array( $original_option_values ) && empty( $option_values ) && ! empty( $options[$k]->is_default_option ) ) {
+				$selected = ' selected="selected"';
+			}
+
+			/**
+			 * Filters the HTML output for options in a multiselect input.
+			 *
+			 * @since 1.5.0
+			 *
+			 * @param string $value    Option tag for current value being rendered.
+			 * @param object $value    Current option being rendered for.
+			 * @param int    $id       ID of the field object being rendered.
+			 * @param string $selected Current selected value.
+			 * @param string $k        Current index in the foreach loop.
+			 */
+			$html .= apply_filters( 'bp_get_the_profile_field_options_multiselect', '<option' . $selected . ' value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '">' . esc_html( stripslashes( $options[$k]->name ) ) . '</option>', $options[$k], $this->field_obj->id, $selected, $k );
+		}
+
+		echo $html;
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		$r = bp_parse_args( $raw_properties, array(
+			'multiple' => 'multiple'
+		) ); ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>" class="screen-reader-text"><?php esc_html_e( 'Select', 'buddypress' ); ?></label>
+		<select <?php echo $this->get_edit_field_html_elements( $r ); ?>>
+			<?php bp_the_profile_field_options(); ?>
+		</select>
+
+		<?php
+	}
+
+	/**
+	 * Output HTML for this field type's children options on the wp-admin Profile Fields,
+	 * "Add Field" and "Edit Field" screens.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the current
+	 *                                         field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {
+		parent::admin_new_field_html( $current_field, 'checkbox' );
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-number.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-number.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-number.php	(revision 10410)
@@ -0,0 +1,112 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Number xprofile field type.
+ *
+ * @since 2.0.0
+ */
+class BP_XProfile_Field_Type_Number extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the number field type.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Number', 'xprofile field type', 'buddypress' );
+
+		$this->set_format( '/^\d+|-\d+$/', 'replace' );
+
+		/**
+		 * Fires inside __construct() method for BP_XProfile_Field_Type_Number class.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Field_Type_Number $this Current instance of
+		 *                                            the field type number.
+		 */
+		do_action( 'bp_xprofile_field_type_number', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of
+	 *                              {@link http://dev.w3.org/html5/markup/input.number.html permitted attributes}
+	 *                              that you want to add.
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// User_id is a special optional parameter that certain other fields
+		// types pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			unset( $raw_properties['user_id'] );
+		}
+
+		$r = bp_parse_args( $raw_properties, array(
+			'type'  => 'number',
+			'value' =>  bp_get_the_profile_field_edit_value()
+		) ); ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>">
+			<?php bp_the_profile_field_name(); ?>
+			<?php bp_the_profile_field_required_label(); ?>
+		</label>
+
+		<?php
+
+		/** This action is documented in bp-xprofile/bp-xprofile-classes */
+		do_action( bp_get_the_profile_field_errors_action() ); ?>
+
+		<input <?php echo $this->get_edit_field_html_elements( $r ); ?>>
+
+		<?php
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		$r = bp_parse_args( $raw_properties, array(
+			'type' => 'number'
+		) ); ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>" class="screen-reader-text"><?php esc_html_e( 'Number field', 'buddypress' ); ?></label>
+		<input <?php echo $this->get_edit_field_html_elements( $r ); ?>>
+	<?php
+	}
+
+	/**
+	 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields
+	 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the current
+	 *                                         field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-placeholder.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-placeholder.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-placeholder.php	(revision 10410)
@@ -0,0 +1,62 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * A placeholder xprofile field type. Doesn't do anything.
+ *
+ * Used if an existing field has an unknown type (e.g. one provided by a missing third-party plugin).
+ *
+ * @since 2.0.1
+ */
+class BP_XProfile_Field_Type_Placeholder extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the placeholder field type.
+	 *
+	 * @since 2.0.1
+	 */
+	public function __construct() {
+		$this->set_format( '/.*/', 'replace' );
+	}
+
+	/**
+	 * Prevent any HTML being output for this field type.
+	 *
+	 * @since 2.0.1
+	 *
+	 * @param array $raw_properties Optional key/value array of
+	 *                              {@link http://dev.w3.org/html5/markup/input.text.html permitted attributes}
+	 *                              that you want to add.
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+	}
+
+	/**
+	 * Prevent any HTML being output for this field type.
+	 *
+	 * @since 2.0.1
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+	}
+
+	/**
+	 * Prevent any HTML being output for this field type.
+	 *
+	 * @since 2.0.1
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the current
+	 *                                         field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-radiobutton.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-radiobutton.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-radiobutton.php	(revision 10410)
@@ -0,0 +1,196 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Radio button xprofile field type.
+ *
+ * @since 2.0.0
+ */
+class BP_XProfile_Field_Type_Radiobutton extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the radio button field type
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Radio Buttons', 'xprofile field type', 'buddypress' );
+
+		$this->supports_options = true;
+
+		$this->set_format( '/^.+$/', 'replace' );
+
+		/**
+		 * Fires inside __construct() method for BP_XProfile_Field_Type_Radiobutton class.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Field_Type_Radiobutton $this Current instance of
+		 *                                                 the field type radio button.
+		 */
+		do_action( 'bp_xprofile_field_type_radiobutton', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of
+	 *                              {@link http://dev.w3.org/html5/markup/input.radio.html permitted attributes}
+	 *                              that you want to add.
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// User_id is a special optional parameter that we pass to
+		// {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			$user_id = (int) $raw_properties['user_id'];
+			unset( $raw_properties['user_id'] );
+		} else {
+			$user_id = bp_displayed_user_id();
+		} ?>
+
+		<div class="radio">
+
+			<label for="<?php bp_the_profile_field_input_name(); ?>">
+				<?php bp_the_profile_field_name(); ?>
+				<?php bp_the_profile_field_required_label(); ?>
+			</label>
+
+			<?php
+
+			/** This action is documented in bp-xprofile/bp-xprofile-classes */
+			do_action( bp_get_the_profile_field_errors_action() ); ?>
+
+			<?php bp_the_profile_field_options( array( 'user_id' => $user_id ) );
+
+			if ( ! bp_get_the_profile_field_is_required() ) : ?>
+
+				<a class="clear-value" href="javascript:clear( '<?php echo esc_js( bp_get_the_profile_field_input_name() ); ?>' );">
+					<?php esc_html_e( 'Clear', 'buddypress' ); ?>
+				</a>
+
+			<?php endif; ?>
+
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled separately.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 */
+	public function edit_field_options_html( array $args = array() ) {
+		$option_value = BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] );
+		$options      = $this->field_obj->get_children();
+
+		$html = sprintf( '<div id="%s">', esc_attr( 'field_' . $this->field_obj->id ) );
+
+		for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
+
+			// Check for updated posted values, but errors preventing them from
+			// being saved first time.
+			if ( isset( $_POST['field_' . $this->field_obj->id] ) && $option_value != $_POST['field_' . $this->field_obj->id] ) {
+				if ( ! empty( $_POST['field_' . $this->field_obj->id] ) ) {
+					$option_value = sanitize_text_field( $_POST['field_' . $this->field_obj->id] );
+				}
+			}
+
+			// Run the allowed option name through the before_save filter, so
+			// we'll be sure to get a match.
+			$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
+			$selected        = '';
+
+			if ( $option_value === $allowed_options || ( empty( $option_value ) && ! empty( $options[$k]->is_default_option ) ) ) {
+				$selected = ' checked="checked"';
+			}
+
+			$new_html = sprintf( '<label for="%3$s"><input %1$s type="radio" name="%2$s" id="%3$s" value="%4$s">%5$s</label>',
+				$selected,
+				esc_attr( "field_{$this->field_obj->id}" ),
+				esc_attr( "option_{$options[$k]->id}" ),
+				esc_attr( stripslashes( $options[$k]->name ) ),
+				esc_html( stripslashes( $options[$k]->name ) )
+			);
+
+			/**
+			 * Filters the HTML output for an individual field options radio button.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param string $new_html Label and radio input field.
+			 * @param object $value    Current option being rendered for.
+			 * @param int    $id       ID of the field object being rendered.
+			 * @param string $selected Current selected value.
+			 * @param string $k        Current index in the foreach loop.
+			 */
+			$html .= apply_filters( 'bp_get_the_profile_field_options_radio', $new_html, $options[$k], $this->field_obj->id, $selected, $k );
+		}
+
+		echo $html . '</div>';
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		bp_the_profile_field_options();
+
+		if ( bp_get_the_profile_field_is_required() ) {
+			return;
+		} ?>
+
+		<a class="clear-value" href="javascript:clear( '<?php echo esc_js( bp_get_the_profile_field_input_name() ); ?>' );">
+			<?php esc_html_e( 'Clear', 'buddypress' ); ?>
+		</a>
+
+		<?php
+	}
+
+	/**
+	 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the current
+	 *                                         field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {
+		parent::admin_new_field_html( $current_field, 'radio' );
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-selectbox.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-selectbox.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-selectbox.php	(revision 10410)
@@ -0,0 +1,188 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Selectbox xprofile field type.
+ *
+ * @since 2.0.0
+ */
+class BP_XProfile_Field_Type_Selectbox extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the selectbox field type.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Multi Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Drop Down Select Box', 'xprofile field type', 'buddypress' );
+
+		$this->supports_options = true;
+
+		$this->set_format( '/^.+$/', 'replace' );
+
+		/**
+		 * Fires inside __construct() method for BP_XProfile_Field_Type_Selectbox class.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Field_Type_Selectbox $this Current instance of
+		 *                                               the field type select box.
+		 */
+		do_action( 'bp_xprofile_field_type_selectbox', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of
+	 *                              {@link http://dev.w3.org/html5/markup/select.html permitted attributes}
+	 *                              that you want to add.
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// User_id is a special optional parameter that we pass to
+		// {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			$user_id = (int) $raw_properties['user_id'];
+			unset( $raw_properties['user_id'] );
+		} else {
+			$user_id = bp_displayed_user_id();
+		} ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>">
+			<?php bp_the_profile_field_name(); ?>
+			<?php bp_the_profile_field_required_label(); ?>
+		</label>
+
+		<?php
+
+		/** This action is documented in bp-xprofile/bp-xprofile-classes */
+		do_action( bp_get_the_profile_field_errors_action() ); ?>
+
+		<select <?php echo $this->get_edit_field_html_elements( $raw_properties ); ?>>
+			<?php bp_the_profile_field_options( array( 'user_id' => $user_id ) ); ?>
+		</select>
+
+		<?php
+	}
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled separately.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 */
+	public function edit_field_options_html( array $args = array() ) {
+		$original_option_values = maybe_unserialize( BP_XProfile_ProfileData::get_value_byid( $this->field_obj->id, $args['user_id'] ) );
+
+		$options = $this->field_obj->get_children();
+		$html    = '<option value="">' . /* translators: no option picked in select box */ esc_html__( '----', 'buddypress' ) . '</option>';
+
+		if ( empty( $original_option_values ) && !empty( $_POST['field_' . $this->field_obj->id] ) ) {
+			$original_option_values = sanitize_text_field(  $_POST['field_' . $this->field_obj->id] );
+		}
+
+		$option_values = ( $original_option_values ) ? (array) $original_option_values : array();
+		for ( $k = 0, $count = count( $options ); $k < $count; ++$k ) {
+			$selected = '';
+
+			// Check for updated posted values, but errors preventing them from
+			// being saved first time.
+			foreach( $option_values as $i => $option_value ) {
+				if ( isset( $_POST['field_' . $this->field_obj->id] ) && $_POST['field_' . $this->field_obj->id] != $option_value ) {
+					if ( ! empty( $_POST['field_' . $this->field_obj->id] ) ) {
+						$option_values[$i] = sanitize_text_field( $_POST['field_' . $this->field_obj->id] );
+					}
+				}
+			}
+
+			// Run the allowed option name through the before_save filter, so
+			// we'll be sure to get a match.
+			$allowed_options = xprofile_sanitize_data_value_before_save( $options[$k]->name, false, false );
+
+			// First, check to see whether the user-entered value matches.
+			if ( in_array( $allowed_options, $option_values ) ) {
+				$selected = ' selected="selected"';
+			}
+
+			// Then, if the user has not provided a value, check for defaults.
+			if ( ! is_array( $original_option_values ) && empty( $option_values ) && $options[$k]->is_default_option ) {
+				$selected = ' selected="selected"';
+			}
+
+			/**
+			 * Filters the HTML output for options in a select input.
+			 *
+			 * @since 1.1.0
+			 *
+			 * @param string $value    Option tag for current value being rendered.
+			 * @param object $value    Current option being rendered for.
+			 * @param int    $id       ID of the field object being rendered.
+			 * @param string $selected Current selected value.
+			 * @param string $k        Current index in the foreach loop.
+			 */
+			$html .= apply_filters( 'bp_get_the_profile_field_options_select', '<option' . $selected . ' value="' . esc_attr( stripslashes( $options[$k]->name ) ) . '">' . esc_html( stripslashes( $options[$k]->name ) ) . '</option>', $options[$k], $this->field_obj->id, $selected, $k );
+		}
+
+		echo $html;
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>" class="screen-reader-text"><?php esc_html_e( 'Select', 'buddypress' ); ?></label>
+		<select <?php echo $this->get_edit_field_html_elements( $raw_properties ); ?>>
+			<?php bp_the_profile_field_options(); ?>
+		</select>
+
+		<?php
+	}
+
+	/**
+	 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the current
+	 *                                         field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {
+		parent::admin_new_field_html( $current_field, 'radio' );
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-textarea.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-textarea.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-textarea.php	(revision 10410)
@@ -0,0 +1,172 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Textarea xprofile field type.
+ *
+ * @since 2.0.0
+ */
+class BP_XProfile_Field_Type_Textarea extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the textarea field type.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Multi-line Text Area', 'xprofile field type', 'buddypress' );
+		$this->supports_richtext = true;
+
+		$this->set_format( '/^.*$/m', 'replace' );
+
+		/**
+		 * Fires inside __construct() method for BP_XProfile_Field_Type_Textarea class.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Field_Type_Textarea $this Current instance of
+		 *                                              the field type textarea.
+		 */
+		do_action( 'bp_xprofile_field_type_textarea', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of
+	 *                              {@link http://dev.w3.org/html5/markup/textarea.html permitted attributes}
+	 *                              that you want to add.
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// User_id is a special optional parameter that certain other fields
+		// types pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			unset( $raw_properties['user_id'] );
+		}
+
+		$richtext_enabled = bp_xprofile_is_richtext_enabled_for_field(); ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>">
+			<?php bp_the_profile_field_name(); ?>
+			<?php bp_the_profile_field_required_label(); ?>
+		</label>
+
+		<?php
+
+		/** This action is documented in bp-xprofile/bp-xprofile-classes */
+		do_action( bp_get_the_profile_field_errors_action() );
+
+		if ( ! $richtext_enabled ) {
+			$r = wp_parse_args( $raw_properties, array(
+				'cols' => 40,
+				'rows' => 5,
+			) );
+
+			?>
+
+			<textarea <?php echo $this->get_edit_field_html_elements( $r ); ?>><?php bp_the_profile_field_edit_value(); ?></textarea>
+
+			<?php
+
+		} else {
+
+			/**
+			 * Filters the arguments passed to `wp_editor()` in richtext xprofile fields.
+			 *
+			 * @since 2.4.0
+			 *
+			 * @param array $args {
+			 *     Array of optional arguments. See `wp_editor()`.
+			 *     @type bool $teeny         Whether to use the teeny version of TinyMCE. Default true.
+			 *     @type bool $media_buttons Whether to show media buttons. Default false.
+			 *     @type bool $quicktags     Whether to show the quicktags buttons. Default true.
+			 *     @type int  $textarea_rows Number of rows to display in the editor. Defaults to 1 in the
+			 *                               'admin' context, and 10 in the 'edit' context.
+			 * }
+			 * @param string $context The display context. 'edit' when the markup is intended for the
+			 *                        profile edit screen, 'admin' when intended for the Profile Fields
+			 *                        Dashboard panel.
+			 */
+			$editor_args = apply_filters( 'bp_xprofile_field_type_textarea_editor_args', array(
+				'teeny'         => true,
+				'media_buttons' => false,
+				'quicktags'     => true,
+				'textarea_rows' => 10,
+			), 'edit' );
+
+			wp_editor(
+				bp_get_the_profile_field_edit_value(),
+				bp_get_the_profile_field_input_name(),
+				$editor_args
+			);
+		}
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+		$richtext_enabled = bp_xprofile_is_richtext_enabled_for_field();
+
+		if ( ! $richtext_enabled ) {
+
+			$r = bp_parse_args( $raw_properties, array(
+				'cols' => 40,
+				'rows' => 5,
+			) ); ?>
+
+			<textarea <?php echo $this->get_edit_field_html_elements( $r ); ?>></textarea>
+
+			<?php
+		} else {
+
+			/** This filter is documented in bp-xprofile/classes/class-bp-xprofile-field-type-textarea.php */
+			$editor_args = apply_filters( 'bp_xprofile_field_type_textarea_editor_args', array(
+				'teeny'         => true,
+				'media_buttons' => false,
+				'quicktags'     => true,
+				'textarea_rows' => 1,
+			), 'admin' );
+
+			wp_editor(
+				'',
+				'xprofile_textarea_' . bp_get_the_profile_field_id(),
+				$editor_args
+			);
+		}
+	}
+
+	/**
+	 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields
+	 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the current
+	 *                                         field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-textbox.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-textbox.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-textbox.php	(revision 10410)
@@ -0,0 +1,113 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Textbox xprofile field type.
+ *
+ * @since 2.0.0
+ */
+class BP_XProfile_Field_Type_Textbox extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the textbox field type.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'Text Box', 'xprofile field type', 'buddypress' );
+
+		$this->set_format( '/^.*$/', 'replace' );
+
+		/**
+		 * Fires inside __construct() method for BP_XProfile_Field_Type_Textbox class.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Field_Type_Textbox $this Current instance of
+		 *                                             the field type text box.
+		 */
+		do_action( 'bp_xprofile_field_type_textbox', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of
+	 *                              {@link http://dev.w3.org/html5/markup/input.text.html permitted attributes}
+	 *                              that you want to add.
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// User_id is a special optional parameter that certain other fields
+		// types pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			unset( $raw_properties['user_id'] );
+		}
+
+		$r = bp_parse_args( $raw_properties, array(
+			'type'  => 'text',
+			'value' => bp_get_the_profile_field_edit_value(),
+		) ); ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>">
+			<?php bp_the_profile_field_name(); ?>
+			<?php bp_the_profile_field_required_label(); ?>
+		</label>
+
+		<?php
+
+		/** This action is documented in bp-xprofile/bp-xprofile-classes */
+		do_action( bp_get_the_profile_field_errors_action() ); ?>
+
+		<input <?php echo $this->get_edit_field_html_elements( $r ); ?>>
+
+		<?php
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+
+		$r = bp_parse_args( $raw_properties, array(
+			'type' => 'text'
+		) ); ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>" class="screen-reader-text"><?php esc_html_e( 'Textbox', 'buddypress' ); ?></label>
+		<input <?php echo $this->get_edit_field_html_elements( $r ); ?>>
+
+		<?php
+	}
+
+	/**
+	 * This method usually outputs HTML for this field type's children options on the wp-admin Profile Fields
+	 * "Add Field" and "Edit Field" screens, but for this field type, we don't want it, so it's stubbed out.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the
+	 *                                         current field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-url.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-url.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type-url.php	(revision 10410)
@@ -0,0 +1,171 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * URL xprofile field type.
+ *
+ * @since 2.1.0
+ */
+class BP_XProfile_Field_Type_URL extends BP_XProfile_Field_Type {
+
+	/**
+	 * Constructor for the URL field type
+	 *
+	 * @since 2.1.0
+	 */
+	public function __construct() {
+		parent::__construct();
+
+		$this->category = _x( 'Single Fields', 'xprofile field type category', 'buddypress' );
+		$this->name     = _x( 'URL', 'xprofile field type', 'buddypress' );
+
+		$this->set_format( '_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:/[^\s]*)?$_iuS', 'replace' );
+
+		/**
+		 * Fires inside __construct() method for BP_XProfile_Field_Type_URL class.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Field_Type_URL $this Current instance of
+		 *                                         the field type URL.
+		 */
+		do_action( 'bp_xprofile_field_type_url', $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of
+	 *                              {@link http://dev.w3.org/html5/markup/input.number.html permitted attributes}
+	 *                              that you want to add.
+	 *
+	 * @since 2.1.0
+	 */
+	public function edit_field_html( array $raw_properties = array() ) {
+
+		// `user_id` is a special optional parameter that certain other
+		// fields types pass to {@link bp_the_profile_field_options()}.
+		if ( isset( $raw_properties['user_id'] ) ) {
+			unset( $raw_properties['user_id'] );
+		}
+
+		$r = bp_parse_args( $raw_properties, array(
+			'type'      => 'text',
+			'inputmode' => 'url',
+			'value'     => esc_url( bp_get_the_profile_field_edit_value() ),
+		) ); ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>">
+			<?php bp_the_profile_field_name(); ?>
+			<?php bp_the_profile_field_required_label(); ?>
+		</label>
+
+		<?php
+
+		/** This action is documented in bp-xprofile/bp-xprofile-classes */
+		do_action( bp_get_the_profile_field_errors_action() ); ?>
+
+		<input <?php echo $this->get_edit_field_html_elements( $r ); ?>>
+
+		<?php
+	}
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted
+	 *                              attributes that you want to add.
+	 * @since 2.1.0
+	 */
+	public function admin_field_html( array $raw_properties = array() ) {
+
+		$r = bp_parse_args( $raw_properties, array(
+			'type' => 'url'
+		) ); ?>
+
+		<label for="<?php bp_the_profile_field_input_name(); ?>" class="screen-reader-text"><?php esc_html_e( 'URL', 'buddypress' ); ?></label>
+		<input <?php echo $this->get_edit_field_html_elements( $r ); ?>>
+
+		<?php
+	}
+
+	/**
+	 * This method usually outputs HTML for this field type's children options
+	 * on the wp-admin Profile Fields "Add Field" and "Edit Field" screens, but
+	 * for this field type, we don't want it, so it's stubbed out.
+	 *
+	 * @since 2.1.0
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the current
+	 *                                         field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {}
+
+	/**
+	 * Modify submitted URL values before validation.
+	 *
+	 * The URL validation regex requires a http(s) protocol, so that all
+	 * values saved in the database are fully-formed URLs. However, we
+	 * still want to allow users to enter URLs without a protocol, for a
+	 * better user experience. So we catch submitted URL values, and if
+	 * the protocol is missing, we prepend 'http://' before passing to
+	 * is_valid().
+	 *
+	 * @since 2.1.0
+	 * @since 2.4.0 Added the `$field_id` parameter.
+	 *
+	 * @param string $submitted_value Raw value submitted by the user.
+	 * @param int    $field_id        Optional. ID of the field.
+	 *
+	 * @return string
+	 */
+	public static function pre_validate_filter( $submitted_value = '', $field_id = '' ) {
+
+		// Allow empty URL values.
+		if ( empty( $submitted_value ) ) {
+			return '';
+		}
+
+		// Run some checks on the submitted value.
+		if ( false === strpos( $submitted_value, ':'  )
+		     && substr( $submitted_value, 0, 1 ) !== '/'
+		     && substr( $submitted_value, 0, 1 ) !== '#'
+		     && ! preg_match( '/^[a-z0-9-]+?\.php/i', $submitted_value )
+		) {
+			$submitted_value = 'http://' . $submitted_value;
+		}
+
+		return $submitted_value;
+	}
+
+	/**
+	 * Format URL values for display.
+	 *
+	 * @since 2.1.0
+	 * @since 2.4.0 Added the `$field_id` parameter.
+	 *
+	 * @param string $field_value The URL value, as saved in the database.
+	 * @param int    $field_id    Optional. ID of the field.
+	 *
+	 * @return string URL converted to a link.
+	 */
+	public static function display_filter( $field_value, $field_id = '' ) {
+		$link      = strip_tags( $field_value );
+		$no_scheme = preg_replace( '#^https?://#', '', rtrim( $link, '/' ) );
+		$url_text  = str_replace( $link, $no_scheme, $field_value );
+		return '<a href="' . esc_url( $field_value ) . '" rel="nofollow">' . esc_html( $url_text ) . '</a>';
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field-type.php	(revision 10410)
@@ -0,0 +1,485 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Represents a type of XProfile field and holds meta information about the type of value that it accepts.
+ *
+ * @since 2.0.0
+ */
+abstract class BP_XProfile_Field_Type {
+
+	/**
+	 * Validation regex rules for field type.
+	 * @since 2.0.0
+	 * @var array Field type validation regexes.
+	 */
+	protected $validation_regex = array();
+
+	/**
+	 * Whitelisted values for field type.
+	 *
+	 * @since 2.0.0
+	 * @var array Field type whitelisted values.
+	 */
+	protected $validation_whitelist = array();
+
+	/**
+	 * Name for field type.
+	 *
+	 * @since 2.0.0
+	 * @var string The name of this field type.
+	 */
+	public $name = '';
+
+	/**
+	 * The name of the category that this field type should be grouped with. Used on the [Users > Profile Fields] screen in wp-admin.
+	 *
+	 * @since 2.0.0
+	 * @var string
+	 */
+	public $category = '';
+
+	/**
+	 * If allowed to store null/empty values.
+	 *
+	 * @since 2.0.0
+	 * @var bool If this is set, allow BP to store null/empty values for this field type.
+	 */
+	public $accepts_null_value = false;
+
+	/**
+	 * If this is set, BP will set this field type's validation whitelist from the field's options (e.g checkbox, selectbox).
+	 *
+	 * @since 2.0.0
+	 * @var bool Does this field support options? e.g. selectbox, radio buttons, etc.
+	 */
+	public $supports_options = false;
+
+	/**
+	 * If allowed to support multiple options as default.
+	 *
+	 * @since 2.0.0
+	 * @var bool Does this field type support multiple options being set as default values? e.g. multiselectbox, checkbox.
+	 */
+	public $supports_multiple_defaults = false;
+
+	/**
+	 * If the field type supports rich text by default.
+	 *
+	 * @since 2.4.0
+	 * @var bool
+	 */
+	public $supports_richtext = false;
+
+	/**
+	 * If object is created by an BP_XProfile_Field object.
+	 *
+	 * @since 2.0.0
+	 * @var BP_XProfile_Field If this object is created by instantiating a {@link BP_XProfile_Field},
+	 *                        this is a reference back to that object.
+	 */
+	public $field_obj = null;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 2.0.0
+	 */
+	public function __construct() {
+
+		/**
+		 * Fires inside __construct() method for BP_XProfile_Field_Type class.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Field_Type $this Current instance of
+		 *                                     the field type class.
+		 */
+		do_action( 'bp_xprofile_field_type', $this );
+	}
+
+	/**
+	 * Set a regex that profile data will be asserted against.
+	 *
+	 * You can call this method multiple times to set multiple formats. When validation is performed,
+	 * it's successful as long as the new value matches any one of the registered formats.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string $format         Regex string.
+	 * @param string $replace_format Optional; if 'replace', replaces the format instead of adding to it.
+	 *                               Defaults to 'add'.
+	 *
+	 * @return BP_XProfile_Field_Type
+	 */
+	public function set_format( $format, $replace_format = 'add' ) {
+
+		/**
+		 * Filters the regex format for the field type.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param string                 $format         Regex string.
+		 * @param string                 $replace_format Optional replace format If "replace", replaces the
+		 *                                               format instead of adding to it. Defaults to "add".
+		 * @param BP_XProfile_Field_Type $this           Current instance of the BP_XProfile_Field_Type class.
+		 */
+		$format = apply_filters( 'bp_xprofile_field_type_set_format', $format, $replace_format, $this );
+
+		if ( 'add' === $replace_format ) {
+			$this->validation_regex[] = $format;
+		} elseif ( 'replace' === $replace_format ) {
+			$this->validation_regex = array( $format );
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Add a value to this type's whitelist that profile data will be asserted against.
+	 *
+	 * You can call this method multiple times to set multiple formats. When validation is performed,
+	 * it's successful as long as the new value matches any one of the registered formats.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string|array $values Whitelisted values.
+	 *
+	 * @return BP_XProfile_Field_Type
+	 */
+	public function set_whitelist_values( $values ) {
+		foreach ( (array) $values as $value ) {
+
+			/**
+			 * Filters values for field type's whitelist that profile data will be asserted against.
+			 *
+			 * @since 2.0.0
+			 *
+			 * @param string                 $value  Field value.
+			 * @param array                  $values Original array of values.
+			 * @param BP_XProfile_Field_Type $this   Current instance of the BP_XProfile_Field_Type class.
+			 */
+			$this->validation_whitelist[] = apply_filters( 'bp_xprofile_field_type_set_whitelist_values', $value, $values, $this );
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Check the given string against the registered formats for this field type.
+	 *
+	 * This method doesn't support chaining.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param string|array $values Value to check against the registered formats.
+	 *
+	 * @return bool True if the value validates
+	 */
+	public function is_valid( $values ) {
+		$validated = false;
+
+		// Some types of field (e.g. multi-selectbox) may have multiple values to check.
+		foreach ( (array) $values as $value ) {
+
+			// Validate the $value against the type's accepted format(s).
+			foreach ( $this->validation_regex as $format ) {
+				if ( 1 === preg_match( $format, $value ) ) {
+					$validated = true;
+					continue;
+
+				} else {
+					$validated = false;
+				}
+			}
+		}
+
+		// Handle field types with accepts_null_value set if $values is an empty array.
+		if ( ( false === $validated ) && is_array( $values ) && empty( $values ) && $this->accepts_null_value ) {
+			$validated = true;
+		}
+
+		// If there's a whitelist set, also check the $value.
+		if ( ( true === $validated ) && ! empty( $values ) && ! empty( $this->validation_whitelist ) ) {
+			foreach ( (array) $values as $value ) {
+				$validated = in_array( $value, $this->validation_whitelist, true );
+			}
+		}
+
+		/**
+		 * Filters whether or not field type is a valid format.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param bool                   $validated Whether or not the field type is valid.
+		 * @param string|array           $values    Value to check against the registered formats.
+		 * @param BP_XProfile_Field_Type $this      Current instance of the BP_XProfile_Field_Type class.
+		 */
+		return (bool) apply_filters( 'bp_xprofile_field_type_is_valid', $validated, $values, $this );
+	}
+
+	/**
+	 * Output the edit field HTML for this field type.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 */
+	abstract public function edit_field_html( array $raw_properties = array() );
+
+	/**
+	 * Output HTML for this field type on the wp-admin Profile Fields screen.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @param array $raw_properties Optional key/value array of permitted attributes that you want to add.
+	 * @since 2.0.0
+	 */
+	abstract public function admin_field_html( array $raw_properties = array() );
+
+	/**
+	 * Output the edit field options HTML for this field type.
+	 *
+	 * BuddyPress considers a field's "options" to be, for example, the items in a selectbox.
+	 * These are stored separately in the database, and their templating is handled separately.
+	 * Populate this method in a child class if it's required. Otherwise, you can leave it out.
+	 *
+	 * This templating is separate from {@link BP_XProfile_Field_Type::edit_field_html()} because
+	 * it's also used in the wp-admin screens when creating new fields, and for backwards compatibility.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $args Optional. The arguments passed to {@link bp_the_profile_field_options()}.
+	 */
+	public function edit_field_options_html( array $args = array() ) {}
+
+	/**
+	 * Output HTML for this field type's children options on the wp-admin Profile Fields "Add Field" and "Edit Field" screens.
+	 *
+	 * You don't need to implement this method for all field types. It's used in core by the
+	 * selectbox, multi selectbox, checkbox, and radio button fields, to allow the admin to
+	 * enter the child option values (e.g. the choices in a select box).
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param BP_XProfile_Field $current_field The current profile field on the add/edit screen.
+	 * @param string            $control_type  Optional. HTML input type used to render the current
+	 *                          field's child options.
+	 */
+	public function admin_new_field_html( BP_XProfile_Field $current_field, $control_type = '' ) {
+		$type = array_search( get_class( $this ), bp_xprofile_get_field_types() );
+		if ( false === $type ) {
+			return;
+		}
+
+		$class            = $current_field->type != $type ? 'display: none;' : '';
+		$current_type_obj = bp_xprofile_create_field_type( $type );
+		?>
+
+		<div id="<?php echo esc_attr( $type ); ?>" class="postbox bp-options-box" style="<?php echo esc_attr( $class ); ?> margin-top: 15px;">
+			<h3><?php esc_html_e( 'Please enter options for this Field:', 'buddypress' ); ?></h3>
+			<div class="inside">
+				<p>
+					<label for="sort_order_<?php echo esc_attr( $type ); ?>"><?php esc_html_e( 'Sort Order:', 'buddypress' ); ?></label>
+					<select name="sort_order_<?php echo esc_attr( $type ); ?>" id="sort_order_<?php echo esc_attr( $type ); ?>" >
+						<option value="custom" <?php selected( 'custom', $current_field->order_by ); ?>><?php esc_html_e( 'Custom',     'buddypress' ); ?></option>
+						<option value="asc"    <?php selected( 'asc',    $current_field->order_by ); ?>><?php esc_html_e( 'Ascending',  'buddypress' ); ?></option>
+						<option value="desc"   <?php selected( 'desc',   $current_field->order_by ); ?>><?php esc_html_e( 'Descending', 'buddypress' ); ?></option>
+					</select>
+				</p>
+
+				<?php
+
+				// Does option have children?
+				$options = $current_field->get_children( true );
+
+				// If no children options exists for this field, check in $_POST
+				// for a submitted form (e.g. on the "new field" screen).
+				if ( empty( $options ) ) {
+
+					$options = array();
+					$i       = 1;
+
+					while ( isset( $_POST[$type . '_option'][$i] ) ) {
+
+						// Multiselectbox and checkboxes support MULTIPLE default options; all other core types support only ONE.
+						if ( $current_type_obj->supports_options && ! $current_type_obj->supports_multiple_defaults && isset( $_POST["isDefault_{$type}_option"][$i] ) && (int) $_POST["isDefault_{$type}_option"] === $i ) {
+							$is_default_option = true;
+						} elseif ( isset( $_POST["isDefault_{$type}_option"][$i] ) ) {
+							$is_default_option = (bool) $_POST["isDefault_{$type}_option"][$i];
+						} else {
+							$is_default_option = false;
+						}
+
+						// Grab the values from $_POST to use as the form's options.
+						$options[] = (object) array(
+							'id'                => -1,
+							'is_default_option' => $is_default_option,
+							'name'              => sanitize_text_field( stripslashes( $_POST[$type . '_option'][$i] ) ),
+						);
+
+						++$i;
+					}
+
+					// If there are still no children options set, this must be the "new field" screen, so add one new/empty option.
+					if ( empty( $options ) ) {
+						$options[] = (object) array(
+							'id'                => -1,
+							'is_default_option' => false,
+							'name'              => '',
+						);
+					}
+				}
+
+				// Render the markup for the children options.
+				if ( ! empty( $options ) ) {
+					$default_name = '';
+
+					for ( $i = 0, $count = count( $options ); $i < $count; ++$i ) :
+						$j = $i + 1;
+
+						// Multiselectbox and checkboxes support MULTIPLE default options; all other core types support only ONE.
+						if ( $current_type_obj->supports_options && $current_type_obj->supports_multiple_defaults ) {
+							$default_name = '[' . $j . ']';
+						}
+						?>
+
+						<div id="<?php echo esc_attr( "{$type}_div{$j}" ); ?>" class="bp-option sortable">
+							<span class="bp-option-icon grabber"></span>
+							<label for="<?php echo esc_attr( "{$type}_option{$j}" ); ?>" class="screen-reader-text"><?php esc_html_e( 'Add an option', 'buddypress' ); ?></label>
+							<input type="text" name="<?php echo esc_attr( "{$type}_option[{$j}]" ); ?>" id="<?php echo esc_attr( "{$type}_option{$j}" ); ?>" value="<?php echo esc_attr( stripslashes( $options[$i]->name ) ); ?>" />
+							<label for="<?php echo esc_attr( "{$type}_option{$default_name}" ); ?>">
+								<input type="<?php echo esc_attr( $control_type ); ?>" id="<?php echo esc_attr( "{$type}_option{$default_name}" ); ?>" name="<?php echo esc_attr( "isDefault_{$type}_option{$default_name}" ); ?>" <?php checked( $options[$i]->is_default_option, true ); ?> value="<?php echo esc_attr( $j ); ?>" />
+								<?php _e( 'Default Value', 'buddypress' ); ?>
+							</label>
+
+							<?php if ( 1 !== $j ) : ?>
+								<div class ="delete-button">
+									<a href='javascript:hide("<?php echo esc_attr( "{$type}_div{$j}" ); ?>")' class="delete"><?php esc_html_e( 'Delete', 'buddypress' ); ?></a>
+								</div>
+							<?php endif; ?>
+
+						</div>
+
+					<?php endfor; ?>
+
+					<input type="hidden" name="<?php echo esc_attr( "{$type}_option_number" ); ?>" id="<?php echo esc_attr( "{$type}_option_number" ); ?>" value="<?php echo esc_attr( $j + 1 ); ?>" />
+				<?php } ?>
+
+				<div id="<?php echo esc_attr( "{$type}_more" ); ?>"></div>
+				<p><a href="javascript:add_option('<?php echo esc_js( $type ); ?>')"><?php esc_html_e( 'Add Another Option', 'buddypress' ); ?></a></p>
+
+				<?php
+
+				/**
+				 * Fires at the end of the new field additional settings area.
+				 *
+				 * @since 2.3.0
+				 *
+				 * @param BP_XProfile_Field $current_field Current field being rendered.
+				 */
+				do_action( 'bp_xprofile_admin_new_field_additional_settings', $current_field ) ?>
+			</div>
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Allow field types to modify submitted values before they are validated.
+	 *
+	 * In some cases, it may be appropriate for a field type to catch
+	 * submitted values and modify them before they are passed to the
+	 * is_valid() method. For example, URL validation requires the
+	 * 'http://' scheme (so that the value saved in the database is always
+	 * a fully-formed URL), but in order to allow users to enter a URL
+	 * without this scheme, BP_XProfile_Field_Type_URL prepends 'http://'
+	 * when it's not present.
+	 *
+	 * By default, this is a pass-through method that does nothing. Only
+	 * override in your own field type if you need this kind of pre-
+	 * validation filtering.
+	 *
+	 * @since 2.1.0
+	 * @since 2.4.0 Added the `$field_id` parameter.
+	 *
+	 * @param mixed $field_value Submitted field value.
+	 * @param int   $field_id    Optional. ID of the field.
+	 *
+	 * @return mixed
+	 */
+	public static function pre_validate_filter( $field_value, $field_id = '' ) {
+		return $field_value;
+	}
+
+	/**
+	 * Allow field types to modify the appearance of their values.
+	 *
+	 * By default, this is a pass-through method that does nothing. Only
+	 * override in your own field type if you need to provide custom
+	 * filtering for output values.
+	 *
+	 * @since 2.1.0
+	 * @since 2.4.0 Added `$field_id` parameter.
+	 *
+	 * @param mixed $field_value Field value.
+	 * @param int   $field_id    ID of the field.
+	 *
+	 * @return mixed
+	 */
+	public static function display_filter( $field_value, $field_id = '' ) {
+		return $field_value;
+	}
+
+	/** Protected *************************************************************/
+
+	/**
+	 * Get a sanitised and escaped string of the edit field's HTML elements and attributes.
+	 *
+	 * Must be used inside the {@link bp_profile_fields()} template loop.
+	 * This method was intended to be static but couldn't be because php.net/lsb/ requires PHP >= 5.3.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $properties Optional key/value array of attributes for this edit field.
+	 *
+	 * @return string
+	 */
+	protected function get_edit_field_html_elements( array $properties = array() ) {
+
+		$r = bp_parse_args( $properties, array(
+			'id'   => bp_get_the_profile_field_input_name(),
+			'name' => bp_get_the_profile_field_input_name(),
+		) );
+
+		if ( bp_get_the_profile_field_is_required() ) {
+			$r['aria-required'] = 'true';
+		}
+
+		/**
+		 * Filters the edit html elements and attributes.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param array  $r     Array of parsed arguments.
+		 * @param string $value Class name for the current class instance.
+		 */
+		$r = (array) apply_filters( 'bp_xprofile_field_edit_html_elements', $r, get_class( $this ) );
+
+		return bp_get_form_field_attributes( sanitize_key( bp_get_the_profile_field_name() ), $r );
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-field.php	(revision 10410)
@@ -0,0 +1,1503 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+class BP_XProfile_Field {
+
+	/**
+	 * Field ID.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var int ID of field.
+	 */
+	public $id;
+
+	/**
+	 * Field group ID.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var int Field group ID for field.
+	 */
+	public $group_id;
+
+	/**
+	 * Field parent ID.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var int Parent ID of field.
+	 */
+	public $parent_id;
+
+	/**
+	 * Field type.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var string Field type.
+	 */
+	public $type;
+
+	/**
+	 * Field name.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var string Field name.
+	 */
+	public $name;
+
+	/**
+	 * Field description.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var string Field description.
+	 */
+	public $description;
+
+	/**
+	 * Required field?
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var bool Is field required to be filled out?
+	 */
+	public $is_required;
+
+	/**
+	 * Deletable field?
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var int Can field be deleted?
+	 */
+	public $can_delete = '1';
+
+	/**
+	 * Field position.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var int Field position.
+	 */
+	public $field_order;
+
+	/**
+	 * Option order.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var int Option order.
+	 */
+	public $option_order;
+
+	/**
+	 * Order child fields.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var string Order child fields by.
+	 */
+	public $order_by;
+
+	/**
+	 * Is this the default option?
+	 *
+	 * @since 1.0.0
+	 *
+	 * @var bool Is this the default option for this field?
+	 */
+	public $is_default_option;
+
+	/**
+	 * Field data visibility.
+	 *
+	 * @since 1.9.0
+	 * @since 2.4.0 Property marked protected. Now accessible by magic method or by `get_default_visibility()`.
+	 *
+	 * @var string Default field data visibility.
+	 */
+	protected $default_visibility;
+
+	/**
+	 * Is the visibility able to be modified?
+	 *
+	 * @since 2.3.0
+	 * @since 2.4.0 Property marked protected. Now accessible by magic method or by `get_allow_custom_visibility()`.
+	 *
+	 * @var string Members are allowed/disallowed to modify data visibility.
+	 */
+	protected $allow_custom_visibility;
+
+	/**
+	 * Field type option.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @var BP_XProfile_Field_Type Field type object used for validation.
+	 */
+	public $type_obj = null;
+
+	/**
+	 * Field data for user ID.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @var BP_XProfile_ProfileData Field data for user ID.
+	 */
+	public $data;
+
+	/**
+	 * Member types to which the profile field should be applied.
+	 *
+	 * @since 2.4.0
+	 * @var array Array of member types.
+	 */
+	protected $member_types;
+
+	/**
+	 * Initialize and/or populate profile field.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param int|null $id Field ID.
+	 * @param int|null $user_id User ID.
+	 * @param bool     $get_data Get data.
+	 */
+	public function __construct( $id = null, $user_id = null, $get_data = true ) {
+
+		if ( ! empty( $id ) ) {
+			$this->populate( $id, $user_id, $get_data );
+
+		// Initialise the type obj to prevent fatals when creating new profile fields.
+		} else {
+			$this->type_obj            = bp_xprofile_create_field_type( 'textbox' );
+			$this->type_obj->field_obj = $this;
+		}
+	}
+
+	/**
+	 * Populate a profile field object.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @global object $wpdb
+	 * @global object $userdata
+	 *
+	 * @param int  $id Field ID.
+	 * @param int  $user_id User ID.
+	 * @param bool $get_data Get data.
+	 */
+	public function populate( $id, $user_id = null, $get_data = true ) {
+		global $wpdb, $userdata;
+
+		if ( empty( $user_id ) ) {
+			$user_id = isset( $userdata->ID ) ? $userdata->ID : 0;
+		}
+
+		$bp    = buddypress();
+		$field = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_fields} WHERE id = %d", $id ) );
+
+		$this->fill_data( $field );
+
+		if ( ! empty( $get_data ) && ! empty( $user_id ) ) {
+			$this->data = $this->get_field_data( $user_id );
+		}
+	}
+
+	/**
+	 * Retrieve a `BP_XProfile_Field` instance.
+	 *
+	 * @static
+	 *
+	 * @param int $field_id ID of the field.
+	 * @return BP_XProfile_Field|false Field object if found, otherwise false.
+	 */
+	public static function get_instance( $field_id ) {
+		global $wpdb;
+
+		$field_id = (int) $field_id;
+		if ( ! $field_id ) {
+			return false;
+		}
+
+		$field = wp_cache_get( $field_id, 'bp_xprofile_fields' );
+		if ( false === $field ) {
+			$bp = buddypress();
+
+			$field = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_fields} WHERE id = %d", $field_id ) );
+
+			wp_cache_add( $field->id, $field, 'bp_xprofile_fields' );
+
+			if ( ! $field ) {
+				return false;
+			}
+		}
+
+		$_field = new BP_XProfile_Field();
+		$_field->fill_data( $field );
+
+		return $_field;
+	}
+
+	/**
+	 * Fill object vars based on data passed to the method.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param array|object $args Array or object representing the `BP_XProfile_Field` properties.
+	 *                           Generally, this is a row from the fields database table.
+	 */
+	public function fill_data( $args ) {
+		if ( is_object( $args ) ) {
+			$args = (array) $args;
+		}
+
+		foreach ( $args as $k => $v ) {
+			if ( 'name' === $k || 'description' === $k ) {
+				$v = stripslashes( $v );
+			}
+			$this->{$k} = $v;
+		}
+
+		// Create the field type and store a reference back to this object.
+		$this->type_obj            = bp_xprofile_create_field_type( $this->type );
+		$this->type_obj->field_obj = $this;;
+	}
+
+	/**
+	 * Magic getter.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string $key Property name.
+	 * @return mixed
+	 */
+	public function __get( $key ) {
+		switch ( $key ) {
+			case 'default_visibility' :
+				return $this->get_default_visibility();
+				break;
+
+			case 'allow_custom_visibility' :
+				return $this->get_allow_custom_visibility();
+				break;
+		}
+	}
+
+	/**
+	 * Magic issetter.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string $key Property name.
+	 * @return bool
+	 */
+	public function __isset( $key ) {
+		switch ( $key ) {
+			// Backward compatibility for when these were public methods.
+			case 'allow_custom_visibility' :
+			case 'default_visibility' :
+				return true;
+				break;
+		}
+	}
+
+	/**
+	 * Delete a profile field.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @global object $wpdb
+	 *
+	 * @param boolean $delete_data Whether or not to delete data.
+	 *
+	 * @return boolean
+	 */
+	public function delete( $delete_data = false ) {
+		global $wpdb;
+
+		// Prevent deletion if no ID is present.
+		// Prevent deletion by url when can_delete is false.
+		// Prevent deletion of option 1 since this invalidates fields with options.
+		if ( empty( $this->id ) || empty( $this->can_delete ) || ( $this->parent_id && $this->option_order == 1 ) ) {
+			return false;
+		}
+
+		$bp  = buddypress();
+		$sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE id = %d OR parent_id = %d", $this->id, $this->id );
+
+		if ( ! $wpdb->query( $sql ) ) {
+			return false;
+		}
+
+		// Delete the data in the DB for this field.
+		if ( true === $delete_data ) {
+			BP_XProfile_ProfileData::delete_for_field( $this->id );
+		}
+
+		return true;
+	}
+
+	/**
+	 * Save a profile field.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @global object $wpdb
+	 *
+	 * @return boolean
+	 */
+	public function save() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$this->group_id     = apply_filters( 'xprofile_field_group_id_before_save',     $this->group_id,     $this->id );
+		$this->parent_id    = apply_filters( 'xprofile_field_parent_id_before_save',    $this->parent_id,    $this->id );
+		$this->type         = apply_filters( 'xprofile_field_type_before_save',         $this->type,         $this->id );
+		$this->name         = apply_filters( 'xprofile_field_name_before_save',         $this->name,         $this->id );
+		$this->description  = apply_filters( 'xprofile_field_description_before_save',  $this->description,  $this->id );
+		$this->is_required  = apply_filters( 'xprofile_field_is_required_before_save',  $this->is_required,  $this->id );
+		$this->order_by	    = apply_filters( 'xprofile_field_order_by_before_save',     $this->order_by,     $this->id );
+		$this->field_order  = apply_filters( 'xprofile_field_field_order_before_save',  $this->field_order,  $this->id );
+		$this->option_order = apply_filters( 'xprofile_field_option_order_before_save', $this->option_order, $this->id );
+		$this->can_delete   = apply_filters( 'xprofile_field_can_delete_before_save',   $this->can_delete,   $this->id );
+		$this->type_obj     = bp_xprofile_create_field_type( $this->type );
+
+		/**
+		 * Fires before the current field instance gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_XProfile_Field $this Current instance of the field being saved.
+		 */
+		do_action_ref_array( 'xprofile_field_before_save', array( $this ) );
+
+		$is_new_field = is_null( $this->id );
+
+		if ( ! $is_new_field ) {
+			$sql = $wpdb->prepare( "UPDATE {$bp->profile->table_name_fields} SET group_id = %d, parent_id = 0, type = %s, name = %s, description = %s, is_required = %d, order_by = %s, field_order = %d, option_order = %d, can_delete = %d, is_default_option = %d WHERE id = %d", $this->group_id, $this->type, $this->name, $this->description, $this->is_required, $this->order_by, $this->field_order, $this->option_order, $this->can_delete, $this->is_default_option, $this->id );
+		} else {
+			$sql = $wpdb->prepare( "INSERT INTO {$bp->profile->table_name_fields} (group_id, parent_id, type, name, description, is_required, order_by, field_order, option_order, can_delete, is_default_option ) VALUES ( %d, %d, %s, %s, %s, %d, %s, %d, %d, %d, %d )", $this->group_id, $this->parent_id, $this->type, $this->name, $this->description, $this->is_required, $this->order_by, $this->field_order, $this->option_order, $this->can_delete, $this->is_default_option );
+		}
+
+		/**
+		 * Check for null so field options can be changed without changing any
+		 * other part of the field. The described situation will return 0 here.
+		 */
+		if ( $wpdb->query( $sql ) !== null ) {
+
+			if ( $is_new_field ) {
+				$this->id = $wpdb->insert_id;
+			}
+
+			// Only do this if we are editing an existing field.
+			if ( ! $is_new_field ) {
+
+				/**
+				 * Remove any radio or dropdown options for this
+				 * field. They will be re-added if needed.
+				 * This stops orphan options if the user changes a
+				 * field from a radio button field to a text box.
+				 */
+				$this->delete_children();
+			}
+
+			/**
+			 * Check to see if this is a field with child options.
+			 * We need to add the options to the db, if it is.
+			 */
+			if ( $this->type_obj->supports_options ) {
+
+				$parent_id = $this->id;
+
+				// Allow plugins to filter the field's child options (i.e. the items in a selectbox).
+				$post_option  = ! empty( $_POST["{$this->type}_option"]           ) ? $_POST["{$this->type}_option"]           : '';
+				$post_default = ! empty( $_POST["isDefault_{$this->type}_option"] ) ? $_POST["isDefault_{$this->type}_option"] : '';
+
+				/**
+				 * Filters the submitted field option value before saved.
+				 *
+				 * @since 1.5.0
+				 *
+				 * @param string            $post_option Submitted option value.
+				 * @param BP_XProfile_Field $type        Current field type being saved for.
+				 */
+				$options      = apply_filters( 'xprofile_field_options_before_save', $post_option,  $this->type );
+
+				/**
+				 * Filters the default field option value before saved.
+				 *
+				 * @since 1.5.0
+				 *
+				 * @param string            $post_default Default option value.
+				 * @param BP_XProfile_Field $type         Current field type being saved for.
+				 */
+				$defaults     = apply_filters( 'xprofile_field_default_before_save', $post_default, $this->type );
+
+				$counter = 1;
+				if ( !empty( $options ) ) {
+					foreach ( (array) $options as $option_key => $option_value ) {
+						$is_default = 0;
+
+						if ( is_array( $defaults ) ) {
+							if ( isset( $defaults[ $option_key ] ) ) {
+								$is_default = 1;
+							}
+						} else {
+							if ( (int) $defaults == $option_key ) {
+								$is_default = 1;
+							}
+						}
+
+						if ( '' != $option_value ) {
+							$sql = $wpdb->prepare( "INSERT INTO {$bp->profile->table_name_fields} (group_id, parent_id, type, name, description, is_required, option_order, is_default_option) VALUES (%d, %d, 'option', %s, '', 0, %d, %d)", $this->group_id, $parent_id, $option_value, $counter, $is_default );
+							if ( ! $wpdb->query( $sql ) ) {
+								return false;
+							}
+						}
+
+						$counter++;
+					}
+				}
+			}
+
+			/**
+			 * Fires after the current field instance gets saved.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param BP_XProfile_Field $this Current instance of the field being saved.
+			 */
+			do_action_ref_array( 'xprofile_field_after_save', array( $this ) );
+
+			// Recreate type_obj in case someone changed $this->type via a filter
+			$this->type_obj            = bp_xprofile_create_field_type( $this->type );
+			$this->type_obj->field_obj = $this;
+
+			return $this->id;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * Get field data for a user ID.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @param int $user_id ID of the user to get field data for.
+	 *
+	 * @return object
+	 */
+	public function get_field_data( $user_id = 0 ) {
+		return new BP_XProfile_ProfileData( $this->id, $user_id );
+	}
+
+	/**
+	 * Get all child fields for this field ID.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $wpdb
+	 *
+	 * @param bool $for_editing Whether or not the field is for editing.
+	 *
+	 * @return array
+	 */
+	public function get_children( $for_editing = false ) {
+		global $wpdb;
+
+		// This is done here so we don't have problems with sql injection.
+		if ( empty( $for_editing ) && ( 'asc' === $this->order_by ) ) {
+			$sort_sql = 'ORDER BY name ASC';
+		} elseif ( empty( $for_editing ) && ( 'desc' === $this->order_by ) ) {
+			$sort_sql = 'ORDER BY name DESC';
+		} else {
+			$sort_sql = 'ORDER BY option_order ASC';
+		}
+
+		// This eliminates a problem with getting all fields when there is no
+		// id for the object.
+		if ( empty( $this->id ) ) {
+			$parent_id = -1;
+		} else {
+			$parent_id = $this->id;
+		}
+
+		$bp  = buddypress();
+		$sql = $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_fields} WHERE parent_id = %d AND group_id = %d {$sort_sql}", $parent_id, $this->group_id );
+
+		$children = $wpdb->get_results( $sql );
+
+		/**
+		 * Filters the found children for a field.
+		 *
+		 * @since 1.2.5
+		 *
+		 * @param object $children    Found children for a field.
+		 * @param bool   $for_editing Whether or not the field is for editing.
+		 */
+		return apply_filters( 'bp_xprofile_field_get_children', $children, $for_editing );
+	}
+
+	/**
+	 * Delete all field children for this field.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $wpdb
+	 */
+	public function delete_children() {
+		global $wpdb;
+
+		$bp  = buddypress();
+		$sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE parent_id = %d", $this->id );
+
+		$wpdb->query( $sql );
+	}
+
+	/**
+	 * Gets the member types to which this field should be available.
+	 *
+	 * Will not return inactive member types, even if associated metadata is found.
+	 *
+	 * 'null' is a special pseudo-type, which represents users that do not have a member type.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @return array Array of member type names.
+	 */
+	public function get_member_types() {
+		if ( ! is_null( $this->member_types ) ) {
+			return $this->member_types;
+		}
+
+		$raw_types = bp_xprofile_get_meta( $this->id, 'field', 'member_type', false );
+
+		// If `$raw_types` is not an array, it probably means this is a new field (id=0).
+		if ( ! is_array( $raw_types ) ) {
+			$raw_types = array();
+		}
+
+		// If '_none' is found in the array, it overrides all types.
+		$types = array();
+		if ( ! in_array( '_none', $raw_types ) ) {
+			$registered_types = bp_get_member_types();
+
+			// Eliminate invalid member types saved in the database.
+			foreach ( $raw_types as $raw_type ) {
+				// 'null' is a special case - it represents users without a type.
+				if ( 'null' === $raw_type || isset( $registered_types[ $raw_type ] ) ) {
+					$types[] = $raw_type;
+				}
+			}
+
+			// If no member types have been saved, intepret as *all* member types.
+			if ( empty( $types ) ) {
+				$types = array_values( $registered_types );
+
+				// + the "null" type, ie users without a type.
+				$types[] = 'null';
+			}
+		}
+
+		/**
+		 * Filters the member types to which an XProfile object should be applied.
+		 *
+		 * @since 2.4.0
+		 *
+		 * @param array             $types Member types.
+		 * @param BP_XProfile_Field $field Field object.
+		 */
+		$this->member_types = apply_filters( 'bp_xprofile_field_member_types', $types, $this );
+
+		return $this->member_types;
+	}
+
+	/**
+	 * Sets the member types for this field.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param array $member_types Array of member types. Can include 'null' (users with no type) in addition to any
+	 *                            registered types.
+	 * @param bool  $append       Whether to append to existing member types. If false, all existing member type
+	 *                            associations will be deleted before adding your `$member_types`. Default false.
+	 *
+	 * @return array Member types for the current field, after being saved.
+	 */
+	public function set_member_types( $member_types, $append = false ) {
+		// Unset invalid member types.
+		$types = array();
+		foreach ( $member_types as $member_type ) {
+			// 'null' is a special case - it represents users without a type.
+			if ( 'null' === $member_type || bp_get_member_type_object( $member_type ) ) {
+				$types[] = $member_type;
+			}
+		}
+
+		// When `$append` is false, delete all existing types before adding new ones.
+		if ( ! $append ) {
+			bp_xprofile_delete_meta( $this->id, 'field', 'member_type' );
+
+			/*
+			 * We interpret an empty array as disassociating the field from all types. This is
+			 * represented internally with the '_none' flag.
+			 */
+			if ( empty( $types ) ) {
+				bp_xprofile_add_meta( $this->id, 'field', 'member_type', '_none' );
+			}
+		}
+
+		/*
+		 * Unrestricted fields are represented in the database as having no 'member_type'.
+		 * We detect whether a field is being set to unrestricted by checking whether the
+		 * list of types passed to the method is the same as the list of registered types,
+		 * plus the 'null' pseudo-type.
+		 */
+		$_rtypes  = bp_get_member_types();
+		$rtypes   = array_values( $_rtypes );
+		$rtypes[] = 'null';
+
+		sort( $types );
+		sort( $rtypes );
+
+		// Only save if this is a restricted field.
+		if ( $types !== $rtypes ) {
+			// Save new types.
+			foreach ( $types as $type ) {
+				bp_xprofile_add_meta( $this->id, 'field', 'member_type', $type );
+			}
+		}
+
+		// Reset internal cache of member types.
+		$this->member_types = null;
+
+		/**
+		 * Fires after a field's member types have been updated.
+		 *
+		 * @since 2.4.0
+		 *
+		 * @param BP_XProfile_Field $this Field object.
+		 */
+		do_action( 'bp_xprofile_field_set_member_type', $this );
+
+		// Refetch fresh items from the database.
+		return $this->get_member_types();
+	}
+
+	/**
+	 * Gets a label representing the field's member types.
+	 *
+	 * This label is displayed alongside the field's name on the Profile Fields Dashboard panel.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @return string
+	 */
+	public function get_member_type_label() {
+		// Field 1 is always displayed to everyone, so never gets a label.
+		if ( 1 == $this->id ) {
+			return '';
+		}
+
+		// Return an empty string if no member types are registered.
+		$all_types = bp_get_member_types();
+		if ( empty( $all_types ) ) {
+			return '';
+		}
+
+		$member_types = $this->get_member_types();
+
+		// If the field applies to all member types, show no message.
+		$all_types[] = 'null';
+		if ( array_values( $all_types ) == $member_types ) {
+			return '';
+		}
+
+		$label = '';
+		if ( ! empty( $member_types ) ) {
+			$has_null = false;
+			$member_type_labels = array();
+			foreach ( $member_types as $member_type ) {
+				if ( 'null' === $member_type ) {
+					$has_null = true;
+					continue;
+				} else {
+					$mt_obj = bp_get_member_type_object( $member_type );
+					$member_type_labels[] = $mt_obj->labels['name'];
+				}
+			}
+
+			// Alphabetical sort.
+			natcasesort( $member_type_labels );
+			$member_type_labels = array_values( $member_type_labels );
+
+			// Add the 'null' option to the end of the list.
+			if ( $has_null ) {
+				$member_type_labels[] = __( 'Users with no member type', 'buddypress' );
+			}
+
+			$label = sprintf( __( '(Member types: %s)', 'buddypress' ), implode( ', ', array_map( 'esc_html', $member_type_labels ) ) );
+		} else {
+			$label = '<span class="member-type-none-notice">' . __( '(Unavailable to all members)', 'buddypress' ) . '</span>';
+		}
+
+		return $label;
+	}
+
+	/**
+	 * Get the field's default visibility setting.
+	 *
+	 * Lazy-loaded to reduce overhead.
+	 *
+	 * Defaults to 'public' if no visibility setting is found in the database.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @return string
+	 */
+	public function get_default_visibility() {
+		if ( ! isset( $this->default_visibility ) ) {
+			$this->default_visibility = bp_xprofile_get_meta( $this->id, 'field', 'default_visibility' );
+
+			if ( ! $this->default_visibility ) {
+				$this->default_visibility = 'public';
+			}
+		}
+
+		return $this->default_visibility;
+	}
+
+	/**
+	 * Get whether the field's default visibility can be overridden by users.
+	 *
+	 * Lazy-loaded to reduce overhead.
+	 *
+	 * Defaults to 'allowed'.
+	 *
+	 * @since 4.4.0
+	 *
+	 * @return string 'disabled' or 'allowed'.
+	 */
+	public function get_allow_custom_visibility() {
+		if ( ! isset( $this->allow_custom_visibility ) ) {
+			$allow_custom_visibility = bp_xprofile_get_meta( $this->id, 'field', 'allow_custom_visibility' );
+
+			if ( 'disabled' === $allow_custom_visibility ) {
+				$this->allow_custom_visibility = 'disabled';
+			} else {
+				$this->allow_custom_visibility = 'allowed';
+			}
+		}
+
+		return $this->allow_custom_visibility;
+	}
+
+	/** Static Methods ********************************************************/
+
+	public static function get_type( $field_id = 0 ) {
+		global $wpdb;
+
+		// Bail if no field ID.
+		if ( empty( $field_id ) ) {
+			return false;
+		}
+
+		$bp   = buddypress();
+		$sql  = $wpdb->prepare( "SELECT type FROM {$bp->profile->table_name_fields} WHERE id = %d", $field_id );
+		$type = $wpdb->get_var( $sql );
+
+		// Return field type.
+		if ( ! empty( $type ) ) {
+			return $type;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Delete all fields in a field group.
+	 *
+	 * @since 1.2.0
+	 *
+	 * @global object $wpdb
+	 *
+	 * @param int $group_id ID of the field group to delete fields from.
+	 *
+	 * @return boolean
+	 */
+	public static function delete_for_group( $group_id = 0 ) {
+		global $wpdb;
+
+		// Bail if no group ID.
+		if ( empty( $group_id ) ) {
+			return false;
+		}
+
+		$bp      = buddypress();
+		$sql     = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE group_id = %d", $group_id );
+		$deleted = $wpdb->get_var( $sql );
+
+		// Return true if fields were deleted.
+		if ( false !== $deleted ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Get field ID from field name.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @global object $wpdb
+	 *
+	 * @param string $field_name Name of the field to query the ID for.
+	 *
+	 * @return boolean
+	 */
+	public static function get_id_from_name( $field_name = '' ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		if ( empty( $bp->profile->table_name_fields ) || empty( $field_name ) ) {
+			return false;
+		}
+
+		$sql = $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE name = %s AND parent_id = 0", $field_name );
+
+		return $wpdb->get_var( $sql );
+	}
+
+	/**
+	 * Update field position and/or field group when relocating.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @global object $wpdb
+	 *
+	 * @param int      $field_id       ID of the field to update.
+	 * @param int|null $position       Field position to update.
+	 * @param int|null $field_group_id ID of the field group.
+	 *
+	 * @return boolean
+	 */
+	public static function update_position( $field_id, $position = null, $field_group_id = null ) {
+		global $wpdb;
+
+		// Bail if invalid position or field group.
+		if ( ! is_numeric( $position ) || ! is_numeric( $field_group_id ) ) {
+			return false;
+		}
+
+		// Get table name and field parent.
+		$table_name = buddypress()->profile->table_name_fields;
+		$sql        = $wpdb->prepare( "UPDATE {$table_name} SET field_order = %d, group_id = %d WHERE id = %d", $position, $field_group_id, $field_id );
+		$parent     = $wpdb->query( $sql );
+
+		// Update $field_id with new $position and $field_group_id.
+		if ( ! empty( $parent ) && ! is_wp_error( $parent ) ) {
+
+			// Update any children of this $field_id.
+			$sql = $wpdb->prepare( "UPDATE {$table_name} SET group_id = %d WHERE parent_id = %d", $field_group_id, $field_id );
+			$wpdb->query( $sql );
+
+			return $parent;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Gets the IDs of fields applicable for a given member type or array of member types.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string|array $member_types Member type or array of member types. Use 'any' to return unrestricted
+	 *                                   fields (those available for anyone, regardless of member type).
+	 *
+	 * @return array Multi-dimensional array, with field IDs as top-level keys, and arrays of member types
+	 *               associated with each field as values.
+	 */
+	public static function get_fields_for_member_type( $member_types ) {
+		global $wpdb;
+
+		$fields = array();
+
+		if ( empty( $member_types ) ) {
+			$member_types = array( 'any' );
+		} elseif ( ! is_array( $member_types ) ) {
+			$member_types = array( $member_types );
+		}
+
+		$bp = buddypress();
+
+		// Pull up all recorded field member type data.
+		$mt_meta = wp_cache_get( 'field_member_types', 'bp_xprofile' );
+		if ( false === $mt_meta ) {
+			$mt_meta = $wpdb->get_results( "SELECT object_id, meta_value FROM {$bp->profile->table_name_meta} WHERE meta_key = 'member_type' AND object_type = 'field'" );
+			wp_cache_set( 'field_member_types', $mt_meta, 'bp_xprofile' );
+		}
+
+		// Keep track of all fields with recorded member_type metadata.
+		$all_recorded_field_ids = wp_list_pluck( $mt_meta, 'object_id' );
+
+		// Sort member_type matches in arrays, keyed by field_id.
+		foreach ( $mt_meta as $_mt_meta ) {
+			if ( ! isset( $fields[ $_mt_meta->object_id ] ) ) {
+				$fields[ $_mt_meta->object_id ] = array();
+			}
+
+			$fields[ $_mt_meta->object_id ][] = $_mt_meta->meta_value;
+		}
+
+		/*
+		 * Filter out fields that don't match any passed types, or those marked '_none'.
+		 * The 'any' type is implicitly handled here: it will match no types.
+		 */
+		foreach ( $fields as $field_id => $field_types ) {
+			if ( ! array_intersect( $field_types, $member_types ) ) {
+				unset( $fields[ $field_id ] );
+			}
+		}
+
+		// Any fields with no member_type metadata are available to all member types.
+		if ( ! in_array( '_none', $member_types ) ) {
+			if ( ! empty( $all_recorded_field_ids ) ) {
+				$all_recorded_field_ids_sql = implode( ',', array_map( 'absint', $all_recorded_field_ids ) );
+				$unrestricted_field_ids = $wpdb->get_col( "SELECT id FROM {$bp->profile->table_name_fields} WHERE id NOT IN ({$all_recorded_field_ids_sql})" );
+			} else {
+				$unrestricted_field_ids = $wpdb->get_col( "SELECT id FROM {$bp->profile->table_name_fields}" );
+			}
+
+			// Append the 'null' pseudo-type.
+			$all_member_types   = bp_get_member_types();
+			$all_member_types   = array_values( $all_member_types );
+			$all_member_types[] = 'null';
+
+			foreach ( $unrestricted_field_ids as $unrestricted_field_id ) {
+				$fields[ $unrestricted_field_id ] = $all_member_types;
+			}
+		}
+
+		return $fields;
+	}
+
+	/**
+	 * Validate form field data on sumbission.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @global $message
+	 *
+	 * @return boolean
+	 */
+	public static function admin_validate() {
+		global $message;
+
+		// Check field name.
+		if ( ! isset( $_POST['title'] ) || ( '' === $_POST['title'] ) ) {
+			$message = esc_html__( 'Profile fields must have a name.', 'buddypress' );
+			return false;
+		}
+
+		// Check field requirement.
+		if ( ! isset( $_POST['required'] ) ) {
+			$message = esc_html__( 'Profile field requirement is missing.', 'buddypress' );
+			return false;
+		}
+
+		// Check field type.
+		if ( empty( $_POST['fieldtype'] ) ) {
+			$message = esc_html__( 'Profile field type is missing.', 'buddypress' );
+			return false;
+		}
+
+		// Check that field is of valid type.
+		if ( ! in_array( $_POST['fieldtype'], array_keys( bp_xprofile_get_field_types() ), true ) ) {
+			$message = sprintf( esc_html__( 'The profile field type %s is not registered.', 'buddypress' ), '<code>' . esc_attr( $_POST['fieldtype'] ) . '</code>' );
+			return false;
+		}
+
+		// Get field type so we can check for and lavidate any field options.
+		$field_type = bp_xprofile_create_field_type( $_POST['fieldtype'] );
+
+		// Field type requires options.
+		if ( true === $field_type->supports_options ) {
+
+			// Build the field option key.
+			$option_name = sanitize_key( $_POST['fieldtype'] ) . '_option';
+
+			// Check for missing or malformed options.
+			if ( empty( $_POST[ $option_name ] ) || ! is_array( $_POST[ $option_name ] ) ) {
+				$message = esc_html__( 'These field options are invalid.', 'buddypress' );
+				return false;
+			}
+
+			// Trim out empty field options.
+			$field_values  = array_values( $_POST[ $option_name ] );
+			$field_options = array_map( 'sanitize_text_field', $field_values );
+			$field_count   = count( $field_options );
+
+			// Check for missing or malformed options.
+			if ( 0 === $field_count ) {
+				$message = sprintf( esc_html__( '%s require at least one option.', 'buddypress' ), $field_type->name );
+				return false;
+			}
+
+			// If only one option exists, it cannot be an empty string.
+			if ( ( 1 === $field_count ) && ( '' === $field_options[0] ) ) {
+				$message = sprintf( esc_html__( '%s require at least one option.', 'buddypress' ), $field_type->name );
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * This function populates the items for radio buttons checkboxes and drop
+	 * down boxes.
+	 */
+	public function render_admin_form_children() {
+		foreach ( array_keys( bp_xprofile_get_field_types() ) as $field_type ) {
+			$type_obj = bp_xprofile_create_field_type( $field_type );
+			$type_obj->admin_new_field_html( $this );
+		}
+	}
+
+	/**
+	 * Oupput the admin form for this field.
+	 *
+	 * @since 1.9.0
+	 *
+	 * @param string $message Message to display.
+	 */
+	public function render_admin_form( $message = '' ) {
+		if ( empty( $this->id ) ) {
+			$title  = __( 'Add New Field', 'buddypress' );
+			$action	= "users.php?page=bp-profile-setup&amp;group_id=" . $this->group_id . "&amp;mode=add_field#tabs-" . $this->group_id;
+			$button	= __( 'Save', 'buddypress' );
+
+			if ( !empty( $_POST['saveField'] ) ) {
+				$this->name        = $_POST['title'];
+				$this->description = $_POST['description'];
+				$this->is_required = $_POST['required'];
+				$this->type        = $_POST['fieldtype'];
+				$this->field_order = $_POST['field_order'];
+
+				if ( ! empty( $_POST["sort_order_{$this->type}"] ) ) {
+					$this->order_by = $_POST["sort_order_{$this->type}"];
+				}
+			}
+		} else {
+			$title  = __( 'Edit Field', 'buddypress' );
+			$action = "users.php?page=bp-profile-setup&amp;mode=edit_field&amp;group_id=" . $this->group_id . "&amp;field_id=" . $this->id . "#tabs-" . $this->group_id;
+			$button	= __( 'Update', 'buddypress' );
+		} ?>
+
+		<div class="wrap">
+
+			<h2><?php echo esc_html( $title ); ?></h2>
+
+			<?php if ( !empty( $message ) ) : ?>
+
+				<div id="message" class="error fade">
+					<p><?php echo esc_html( $message ); ?></p>
+				</div>
+
+			<?php endif; ?>
+
+			<form id="bp-xprofile-add-field" action="<?php echo esc_url( $action ); ?>" method="post">
+				<div id="poststuff">
+					<div id="post-body" class="metabox-holder columns-<?php echo ( 1 == get_current_screen()->get_columns() ) ? '1' : '2'; ?>">
+						<div id="post-body-content">
+
+							<?php
+
+							// Output the name & description fields.
+							$this->name_and_description(); ?>
+
+						</div><!-- #post-body-content -->
+
+						<div id="postbox-container-1" class="postbox-container">
+
+							<?php
+
+							// Output the sumbit metabox.
+							$this->submit_metabox( $button );
+
+							// Output the required metabox.
+							$this->required_metabox();
+
+							// Output the Member Types metabox.
+							$this->member_type_metabox();
+
+							// Output the field visibility metaboxes.
+							$this->visibility_metabox();
+
+							/**
+							 * Fires after XProfile Field sidebar metabox.
+							 *
+							 * @since 2.2.0
+							 *
+							 * @param BP_XProfile_Field $this Current XProfile field.
+							 */
+							do_action( 'xprofile_field_after_sidebarbox', $this ); ?>
+
+						</div>
+
+						<div id="postbox-container-2" class="postbox-container">
+
+							<?php
+
+							/**
+							 * Fires before XProfile Field content metabox.
+							 *
+							 * @since 2.3.0
+							 *
+							 * @param BP_XProfile_Field $this Current XProfile field.
+							 */
+							do_action( 'xprofile_field_before_contentbox', $this );
+
+							// Output the field attributes metabox.
+							$this->type_metabox();
+
+							// Output hidden inputs for default field.
+							$this->default_field_hidden_inputs();
+
+							/**
+							 * Fires after XProfile Field content metabox.
+							 *
+							 * @since 2.2.0
+							 *
+							 * @param BP_XProfile_Field $this Current XProfile field.
+							 */
+							do_action( 'xprofile_field_after_contentbox', $this ); ?>
+
+						</div>
+					</div><!-- #post-body -->
+				</div><!-- #poststuff -->
+			</form>
+		</div>
+
+	<?php
+	}
+
+	/**
+	 * Private method used to display the submit metabox.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $button_text Text to put on button.
+	 */
+	private function submit_metabox( $button_text = '' ) {
+
+		/**
+		 * Fires before XProfile Field submit metabox.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param BP_XProfile_Field $this Current XProfile field.
+		 */
+		do_action( 'xprofile_field_before_submitbox', $this ); ?>
+
+		<div id="submitdiv" class="postbox">
+			<h3><?php esc_html_e( 'Submit', 'buddypress' ); ?></h3>
+			<div class="inside">
+				<div id="submitcomment" class="submitbox">
+					<div id="major-publishing-actions">
+
+						<?php
+
+						/**
+						 * Fires at the beginning of the XProfile Field publishing actions section.
+						 *
+						 * @since 2.1.0
+						 *
+						 * @param BP_XProfile_Field $this Current XProfile field.
+						 */
+						do_action( 'xprofile_field_submitbox_start', $this ); ?>
+
+						<input type="hidden" name="field_order" id="field_order" value="<?php echo esc_attr( $this->field_order ); ?>" />
+
+						<?php if ( ! empty( $button_text ) ) : ?>
+
+							<div id="publishing-action">
+								<input type="submit" name="saveField" value="<?php echo esc_attr( $button_text ); ?>" class="button-primary" />
+							</div>
+
+						<?php endif; ?>
+
+						<div id="delete-action">
+							<a href="users.php?page=bp-profile-setup" class="deletion"><?php esc_html_e( 'Cancel', 'buddypress' ); ?></a>
+						</div>
+
+						<?php wp_nonce_field( 'xprofile_delete_option' ); ?>
+
+						<div class="clear"></div>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<?php
+
+		/**
+		 * Fires after XProfile Field submit metabox.
+		 *
+		 * @since 2.1.0
+		 *
+		 * @param BP_XProfile_Field $this Current XProfile field.
+		 */
+		do_action( 'xprofile_field_after_submitbox', $this );
+	}
+
+	/**
+	 * Private method used to output field name and description fields.
+	 *
+	 * @since 2.3.0
+	 */
+	private function name_and_description() {
+	?>
+
+		<div id="titlediv">
+			<div class="titlewrap">
+				<label id="title-prompt-text" for="title"><?php echo esc_html_x( 'Name', 'XProfile admin edit field', 'buddypress' ); ?></label>
+				<input type="text" name="title" id="title" value="<?php echo esc_attr( $this->name ); ?>" autocomplete="off" />
+			</div>
+		</div>
+
+		<div class="postbox">
+			<h3><?php echo esc_html_x( 'Description', 'XProfile admin edit field', 'buddypress' ); ?></h3>
+			<div class="inside">
+				<label for="description" class="screen-reader-text"><?php esc_html_e( 'Add description', 'buddypress' ); ?></label>
+				<textarea name="description" id="description" rows="8" cols="60"><?php echo esc_textarea( $this->description ); ?></textarea>
+			</div>
+		</div>
+
+	<?php
+	}
+
+	/**
+	 * Private method used to output field Member Type metabox.
+	 *
+	 * @since 2.4.0
+	 */
+	private function member_type_metabox() {
+
+		// The primary field is for all, so bail.
+		if ( 1 === (int) $this->id ) {
+			return;
+		}
+
+		// Bail when no member types are registered.
+		if ( ! $member_types = bp_get_member_types( array(), 'objects' ) ) {
+			return;
+		}
+
+		$field_member_types = $this->get_member_types();
+
+		?>
+
+		<div id="member-types-div" class="postbox">
+			<h3><?php _e( 'Member Types', 'buddypress' ); ?></h3>
+			<div class="inside">
+				<p class="description"><?php _e( 'This field should be available to:', 'buddypress' ); ?></p>
+
+				<ul>
+					<?php foreach ( $member_types as $member_type ) : ?>
+					<li>
+						<label for="member-type-<?php echo $member_type->labels['name']; ?>">
+							<input name="member-types[]" id="member-type-<?php echo $member_type->labels['name']; ?>" class="member-type-selector" type="checkbox" value="<?php echo $member_type->name; ?>" <?php checked( in_array( $member_type->name, $field_member_types ) ); ?>/>
+							<?php echo $member_type->labels['name']; ?>
+						</label>
+					</li>
+					<?php endforeach; ?>
+
+					<li>
+						<label for="member-type-none">
+							<input name="member-types[]" id="member-type-none" class="member-type-selector" type="checkbox" value="null" <?php checked( in_array( 'null', $field_member_types ) ); ?>/>
+							<?php _e( 'Users with no member type', 'buddypress' ); ?>
+						</label>
+					</li>
+
+				</ul>
+				<p class="description member-type-none-notice<?php if ( ! empty( $field_member_types ) ) : ?> hide<?php endif; ?>"><?php _e( 'Unavailable to all members.', 'buddypress' ) ?></p>
+			</div>
+
+			<input type="hidden" name="has-member-types" value="1" />
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Private method used to output field visibility metaboxes.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @return void If default field id 1.
+	 */
+	private function visibility_metabox() {
+
+		// Default field cannot have custom visibility.
+		if ( true === $this->is_default_field() ) {
+			return;
+		} ?>
+
+		<div class="postbox">
+			<h3><label for="default-visibility"><?php esc_html_e( 'Visibility', 'buddypress' ); ?></label></h3>
+			<div class="inside">
+				<div>
+					<select name="default-visibility" id="default-visibility">
+
+						<?php foreach( bp_xprofile_get_visibility_levels() as $level ) : ?>
+
+							<option value="<?php echo esc_attr( $level['id'] ); ?>" <?php selected( $this->get_default_visibility(), $level['id'] ); ?>>
+								<?php echo esc_html( $level['label'] ); ?>
+							</option>
+
+						<?php endforeach ?>
+
+					</select>
+				</div>
+
+				<div>
+					<ul>
+						<li>
+							<input type="radio" id="allow-custom-visibility-allowed" name="allow-custom-visibility" value="allowed" <?php checked( $this->get_allow_custom_visibility(), 'allowed' ); ?> />
+							<label for="allow-custom-visibility-allowed"><?php esc_html_e( 'Allow members to override', 'buddypress' ); ?></label>
+						</li>
+						<li>
+							<input type="radio" id="allow-custom-visibility-disabled" name="allow-custom-visibility" value="disabled" <?php checked( $this->get_allow_custom_visibility(), 'disabled' ); ?> />
+							<label for="allow-custom-visibility-disabled"><?php esc_html_e( 'Enforce field visibility', 'buddypress' ); ?></label>
+						</li>
+					</ul>
+				</div>
+			</div>
+		</div>
+
+		<?php
+	}
+
+	/**
+	 * Output the metabox for setting if field is required or not.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @return void If default field.
+	 */
+	private function required_metabox() {
+
+		// Default field is always required.
+		if ( true === $this->is_default_field() ) {
+			return;
+		} ?>
+
+		<div class="postbox">
+			<h3><label for="required"><?php esc_html_e( 'Requirement', 'buddypress' ); ?></label></h3>
+			<div class="inside">
+				<select name="required" id="required">
+					<option value="0"<?php selected( $this->is_required, '0' ); ?>><?php esc_html_e( 'Not Required', 'buddypress' ); ?></option>
+					<option value="1"<?php selected( $this->is_required, '1' ); ?>><?php esc_html_e( 'Required',     'buddypress' ); ?></option>
+				</select>
+			</div>
+		</div>
+
+	<?php
+	}
+
+	/**
+	 * Output the metabox for setting what type of field this is.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @return void If default field.
+	 */
+	private function type_metabox() {
+
+		// Default field cannot change type.
+		if ( true === $this->is_default_field() ) {
+			return;
+		} ?>
+
+		<div class="postbox">
+			<h3><label for="fieldtype"><?php esc_html_e( 'Type', 'buddypress'); ?></label></h3>
+			<div class="inside">
+				<select name="fieldtype" id="fieldtype" onchange="show_options(this.value)" style="width: 30%">
+
+					<?php bp_xprofile_admin_form_field_types( $this->type ); ?>
+
+				</select>
+
+				<?php
+
+				// Deprecated filter, don't use. Go look at {@link BP_XProfile_Field_Type::admin_new_field_html()}.
+				do_action( 'xprofile_field_additional_options', $this );
+
+				$this->render_admin_form_children(); ?>
+
+			</div>
+		</div>
+
+	<?php
+	}
+
+	/**
+	 * Output hidden fields used by default field.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @return void If not default field.
+	 */
+	private function default_field_hidden_inputs() {
+
+		// Field 1 is the fullname field, which cannot have custom visibility.
+		if ( false === $this->is_default_field() ) {
+			return;
+		} ?>
+
+		<input type="hidden" name="required"  id="required"  value="1"       />
+		<input type="hidden" name="fieldtype" id="fieldtype" value="textbox" />
+
+		<?php
+	}
+
+	/**
+	 * Return if a field ID is the default field.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param int $field_id ID of field to check.
+	 *
+	 * @return bool
+	 */
+	private function is_default_field( $field_id = 0 ) {
+
+		// Fallback to current field ID if none passed.
+		if ( empty( $field_id ) ) {
+			$field_id = $this->id;
+		}
+
+		// Compare & return.
+		return (bool) ( 1 === (int) $field_id );
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-group.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-group.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-group.php	(revision 10410)
@@ -0,0 +1,817 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+class BP_XProfile_Group {
+
+	/**
+	 * Field group ID.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @var int ID of field group
+	 */
+	public $id = null;
+
+	/**
+	 * Field group name.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @var string Name of field group
+	 */
+	public $name;
+
+	/**
+	 * Field group Description.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @var string Description of field group
+	 */
+	public $description;
+
+	/**
+	 * Group deletion boolean.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @var bool Can this group be deleted?
+	 */
+	public $can_delete;
+
+	/**
+	 * Group order.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @var int Group order relative to other groups
+	 */
+	public $group_order;
+
+	/**
+	 * Group fields.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @var array Fields of group
+	 */
+	public $fields;
+
+	/**
+	 * Initialize and/or populate profile field group.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @param int|null $id Field group ID.
+	 */
+	public function __construct( $id = null ) {
+		if ( ! empty( $id ) ) {
+			$this->populate( $id );
+		}
+	}
+
+	/**
+	 * Populate a profile field group.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global $wpdb $wpdb
+	 *
+	 * @param int $id Field group ID.
+	 *
+	 * @return boolean
+	 */
+	public function populate( $id ) {
+
+		// Get this group.
+		$group = self::get( array(
+			'profile_group_id' => $id
+		) );
+
+		// Bail if group not found.
+		if ( empty( $group ) ) {
+			return false;
+		}
+
+		// Get the first array element.
+		$group = reset( $group );
+
+		// Set object properties.
+		$this->id          = $group->id;
+		$this->name        = $group->name;
+		$this->description = $group->description;
+		$this->can_delete  = $group->can_delete;
+		$this->group_order = $group->group_order;
+	}
+
+	/**
+	 * Save a profile field group.
+	 *
+	 * @since 1.1.0
+	 *
+	 * @global object $wpdb
+	 *
+	 * @return boolean
+	 */
+	public function save() {
+		global $wpdb;
+
+		// Filter the field group attributes.
+		$this->name        = apply_filters( 'xprofile_group_name_before_save',        $this->name,        $this->id );
+		$this->description = apply_filters( 'xprofile_group_description_before_save', $this->description, $this->id );
+
+		/**
+		 * Fires before the current group instance gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_XProfile_Group $this Current instance of the group being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'xprofile_group_before_save', array( &$this ) );
+
+		$bp = buddypress();
+
+		// Update or insert.
+		if ( ! empty( $this->id ) ) {
+			$sql = $wpdb->prepare( "UPDATE {$bp->profile->table_name_groups} SET name = %s, description = %s WHERE id = %d", $this->name, $this->description, $this->id );
+		} else {
+			$sql = $wpdb->prepare( "INSERT INTO {$bp->profile->table_name_groups} (name, description, can_delete) VALUES (%s, %s, 1)", $this->name, $this->description );
+		}
+
+		// Attempt to insert or update.
+		$query = $wpdb->query( $sql );
+
+		// Bail if query fails. If `$query` is 0, it means the save was successful, but no fields were updated.
+		if ( false === $query || is_wp_error( $query ) ) {
+			return false;
+		}
+
+		// If not set, update the ID in the group object.
+		if ( empty( $this->id ) ) {
+			$this->id = $wpdb->insert_id;
+		}
+
+		/**
+		 * Fires after the current group instance gets saved.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_XProfile_Group $this Current instance of the group being saved. Passed by reference.
+		 */
+		do_action_ref_array( 'xprofile_group_after_save', array( &$this ) );
+
+		return $this->id;
+	}
+
+	/**
+	 * Delete a profile field group
+	 *
+	 * @since 1.1.0
+	 *
+	 * @global object  $wpdb
+	 * @return boolean
+	 */
+	public function delete() {
+		global $wpdb;
+
+		// Bail if field group cannot be deleted.
+		if ( empty( $this->can_delete ) ) {
+			return false;
+		}
+
+		/**
+		 * Fires before the current group instance gets deleted.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Group $this Current instance of the group being deleted. Passed by reference.
+		 */
+		do_action_ref_array( 'xprofile_group_before_delete', array( &$this ) );
+
+		$bp      = buddypress();
+		$sql     = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_groups} WHERE id = %d", $this->id );
+		$deleted = $wpdb->query( $sql );
+
+		// Delete field group.
+		if ( empty( $deleted ) || is_wp_error( $deleted ) ) {
+			return false;
+		}
+
+		// Remove the group's fields.
+		if ( BP_XProfile_Field::delete_for_group( $this->id ) ) {
+
+			// Remove profile data for the groups fields.
+			for ( $i = 0, $count = count( $this->fields ); $i < $count; ++$i ) {
+				BP_XProfile_ProfileData::delete_for_field( $this->fields[$i]->id );
+			}
+		}
+
+		/**
+		 * Fires after the current group instance gets deleted.
+		 *
+		 * @since 2.0.0
+		 *
+		 * @param BP_XProfile_Group $this Current instance of the group being deleted. Passed by reference.
+		 */
+		do_action_ref_array( 'xprofile_group_after_delete', array( &$this ) );
+
+		return true;
+	}
+
+	/** Static Methods ********************************************************/
+
+	/**
+	 * Populates the BP_XProfile_Group object with profile field groups, fields,
+	 * and field data
+	 *
+	 * @package BuddyPress XProfile
+	 *
+	 * @global object $wpdb WordPress DB access object.
+	 *
+	 * @param array $args {
+	 *  Array of optional arguments:
+	 *      @type int          $profile_group_id  Limit results to a single profile group.
+	 *      @type int          $user_id           Required if you want to load a specific user's data.
+	 *                                            Default: displayed user's ID.
+	 *      @type array|string $member_type       Limit fields by those restricted to a given member type, or array of
+	 *                                            member types. If `$user_id` is provided, the value of `$member_type`
+	 *                                            will be overridden by the member types of the provided user. The
+	 *                                            special value of 'any' will return only those fields that are
+	 *                                            unrestricted by member type - i.e., those applicable to any type.
+	 *      @type bool         $hide_empty_groups True to hide groups that don't have any fields. Default: false.
+	 *      @type bool         $hide_empty_fields True to hide fields where the user has not provided data.
+	 *                                            Default: false.
+	 *      @type bool         $fetch_fields      Whether to fetch each group's fields. Default: false.
+	 *      @type bool         $fetch_field_data  Whether to fetch data for each field. Requires a $user_id.
+	 *                                            Default: false.
+	 *      @type array        $exclude_groups    Comma-separated list or array of group IDs to exclude.
+	 *      @type array        $exclude_fields    Comma-separated list or array of field IDs to exclude.
+	 *      @type bool         $update_meta_cache Whether to pre-fetch xprofilemeta for all retrieved groups, fields,
+	 *                                            and data. Default: true.
+	 * }
+	 * @return array $groups
+	 */
+	public static function get( $args = array() ) {
+		global $wpdb;
+
+		// Parse arguments.
+		$r = wp_parse_args( $args, array(
+			'profile_group_id'       => false,
+			'user_id'                => bp_displayed_user_id(),
+			'member_type'            => false,
+			'hide_empty_groups'      => false,
+			'hide_empty_fields'      => false,
+			'fetch_fields'           => false,
+			'fetch_field_data'       => false,
+			'fetch_visibility_level' => false,
+			'exclude_groups'         => false,
+			'exclude_fields'         => false,
+			'update_meta_cache'      => true,
+		) );
+
+		// Keep track of object IDs for cache-priming.
+		$object_ids = array(
+			'group' => array(),
+			'field' => array(),
+			'data'  => array(),
+		);
+
+		// WHERE.
+		if ( ! empty( $r['profile_group_id'] ) ) {
+			$where_sql = $wpdb->prepare( 'WHERE g.id = %d', $r['profile_group_id'] );
+		} elseif ( $r['exclude_groups'] ) {
+			$exclude   = join( ',', wp_parse_id_list( $r['exclude_groups'] ) );
+			$where_sql = "WHERE g.id NOT IN ({$exclude})";
+		} else {
+			$where_sql = '';
+		}
+
+		$bp = buddypress();
+
+		// Include or exclude empty groups.
+		if ( ! empty( $r['hide_empty_groups'] ) ) {
+			$group_ids = $wpdb->get_col( "SELECT DISTINCT g.id FROM {$bp->profile->table_name_groups} g INNER JOIN {$bp->profile->table_name_fields} f ON g.id = f.group_id {$where_sql} ORDER BY g.group_order ASC" );
+		} else {
+			$group_ids = $wpdb->get_col( "SELECT DISTINCT g.id FROM {$bp->profile->table_name_groups} g {$where_sql} ORDER BY g.group_order ASC" );
+		}
+
+		// Get all group data.
+		$groups = self::get_group_data( $group_ids );
+
+		// Bail if not also getting fields.
+		if ( empty( $r['fetch_fields'] ) ) {
+			return $groups;
+		}
+
+		// Get the group ids from the groups we found.
+		$group_ids = wp_list_pluck( $groups, 'id' );
+
+		// Store for meta cache priming.
+		$object_ids['group'] = $group_ids;
+
+		// Bail if no groups found.
+		if ( empty( $group_ids ) ) {
+			return $groups;
+		}
+
+		// Setup IN query from group IDs.
+		$group_ids_in = implode( ',', (array) $group_ids );
+
+		// Support arrays and comma-separated strings.
+		$exclude_fields_cs = wp_parse_id_list( $r['exclude_fields'] );
+
+		// Visibility - Handled here so as not to be overridden by sloppy use of the
+		// exclude_fields parameter. See bp_xprofile_get_hidden_fields_for_user().
+		$hidden_user_fields = bp_xprofile_get_hidden_fields_for_user( $r['user_id'] );
+		$exclude_fields_cs  = array_merge( $exclude_fields_cs, $hidden_user_fields );
+		$exclude_fields_cs  = implode( ',', $exclude_fields_cs );
+
+		// Set up NOT IN query for excluded field IDs.
+		if ( ! empty( $exclude_fields_cs ) ) {
+			$exclude_fields_sql = "AND id NOT IN ({$exclude_fields_cs})";
+		} else {
+			$exclude_fields_sql = '';
+		}
+
+		// Set up IN query for included field IDs.
+		$include_field_ids = array();
+
+		// Member-type restrictions.
+		if ( bp_get_member_types() ) {
+			if ( $r['user_id'] || false !== $r['member_type'] ) {
+				$member_types = $r['member_type'];
+				if ( $r['user_id'] ) {
+					$member_types = bp_get_member_type( $r['user_id'], false );
+					if ( empty( $member_types ) ) {
+						$member_types = array( 'null' );
+					}
+				}
+
+				$member_types_fields = BP_XProfile_Field::get_fields_for_member_type( $member_types );
+				$include_field_ids += array_keys( $member_types_fields );
+			}
+		}
+
+		$in_sql = '';
+		if ( ! empty( $include_field_ids ) ) {
+			$include_field_ids_cs = implode( ',', array_map( 'intval', $include_field_ids ) );
+			$in_sql = " AND id IN ({$include_field_ids_cs}) ";
+		}
+
+		// Fetch the fields.
+		$field_ids = $wpdb->get_col( "SELECT id FROM {$bp->profile->table_name_fields} WHERE group_id IN ( {$group_ids_in} ) AND parent_id = 0 {$exclude_fields_sql} {$in_sql} ORDER BY field_order" );
+
+		// Bail if no fields.
+		if ( empty( $field_ids ) ) {
+			return $groups;
+		}
+
+		$field_ids = array_map( 'intval', $field_ids );
+
+		// Prime the field cache.
+		$uncached_field_ids = bp_get_non_cached_ids( $field_ids, 'bp_xprofile_fields' );
+		if ( ! empty( $uncached_field_ids ) ) {
+			$_uncached_field_ids = implode( ',', array_map( 'intval', $uncached_field_ids ) );
+			$uncached_fields = $wpdb->get_results( "SELECT * FROM {$bp->profile->table_name_fields} WHERE id IN ({$_uncached_field_ids})" );
+			foreach ( $uncached_fields as $uncached_field ) {
+				$fid = intval( $uncached_field->id );
+				wp_cache_set( $fid, $uncached_field, 'bp_xprofile_fields' );
+			}
+		}
+
+		// Pull field objects from the cache.
+		$fields = array();
+		foreach ( $field_ids as $field_id ) {
+			$fields[] = xprofile_get_field( $field_id );
+		}
+
+		// Store field IDs for meta cache priming.
+		$object_ids['field'] = $field_ids;
+
+		// Maybe fetch field data.
+		if ( ! empty( $r['fetch_field_data'] ) ) {
+
+			// Get field data for user ID.
+			if ( ! empty( $field_ids ) && ! empty( $r['user_id'] ) ) {
+				$field_data = BP_XProfile_ProfileData::get_data_for_user( $r['user_id'], $field_ids );
+			}
+
+			// Remove data-less fields, if necessary.
+			if ( ! empty( $r['hide_empty_fields'] ) && ! empty( $field_ids ) && ! empty( $field_data ) ) {
+
+				// Loop through the results and find the fields that have data.
+				foreach( (array) $field_data as $data ) {
+
+					// Empty fields may contain a serialized empty array.
+					$maybe_value = maybe_unserialize( $data->value );
+
+					// Valid field values of 0 or '0' get caught by empty(), so we have an extra check for these. See #BP5731.
+					if ( ( ! empty( $maybe_value ) || '0' == $maybe_value ) && false !== $key = array_search( $data->field_id, $field_ids ) ) {
+
+						// Fields that have data get removed from the list.
+						unset( $field_ids[ $key ] );
+					}
+				}
+
+				// The remaining members of $field_ids are empty. Remove them.
+				foreach( $fields as $field_key => $field ) {
+					if ( in_array( $field->id, $field_ids ) ) {
+						unset( $fields[ $field_key ] );
+					}
+				}
+
+				// Reset indexes.
+				$fields = array_values( $fields );
+			}
+
+			// Field data was found.
+			if ( ! empty( $fields ) && ! empty( $field_data ) && ! is_wp_error( $field_data ) ) {
+
+				// Loop through fields.
+				foreach( (array) $fields as $field_key => $field ) {
+
+					// Loop through the data in each field.
+					foreach( (array) $field_data as $data ) {
+
+						// Assign correct data value to the field.
+						if ( $field->id == $data->field_id ) {
+							$fields[ $field_key ]->data        = new stdClass;
+							$fields[ $field_key ]->data->value = $data->value;
+							$fields[ $field_key ]->data->id    = $data->id;
+						}
+
+						// Store for meta cache priming.
+						$object_ids['data'][] = $data->id;
+					}
+				}
+			}
+		}
+
+		// Prime the meta cache, if necessary.
+		if ( ! empty( $r['update_meta_cache'] ) ) {
+			bp_xprofile_update_meta_cache( $object_ids );
+		}
+
+		// Maybe fetch visibility levels.
+		if ( ! empty( $r['fetch_visibility_level'] ) ) {
+			$fields = self::fetch_visibility_level( $r['user_id'], $fields );
+		}
+
+		// Merge the field array back in with the group array.
+		foreach( (array) $groups as $group ) {
+
+			// Indexes may have been shifted after previous deletions, so we get a
+			// fresh one each time through the loop.
+			$index = array_search( $group, $groups );
+
+			foreach( (array) $fields as $field ) {
+				if ( $group->id === $field->group_id ) {
+					$groups[ $index ]->fields[] = $field;
+				}
+			}
+
+			// When we unset fields above, we may have created empty groups.
+			// Remove them, if necessary.
+			if ( empty( $group->fields ) && ! empty( $r['hide_empty_groups'] ) ) {
+				unset( $groups[ $index ] );
+			}
+
+			// Reset indexes.
+			$groups = array_values( $groups );
+		}
+
+		return $groups;
+	}
+
+	/**
+	 * Get data about a set of groups, based on IDs.
+	 *
+	 * @since 2.0.0
+	 *
+	 * @param array $group_ids Array of IDs.
+	 *
+	 * @return array
+	 */
+	protected static function get_group_data( $group_ids ) {
+		global $wpdb;
+
+		// Bail if no group IDs are passed.
+		if ( empty( $group_ids ) ) {
+			return array();
+		}
+
+		// Setup empty arrays.
+		$groups        = array();
+		$uncached_gids = array();
+
+		// Loop through groups and look for cached & uncached data.
+		foreach ( $group_ids as $group_id ) {
+
+			// If cached data is found, use it.
+			$group_data = wp_cache_get( $group_id, 'bp_xprofile_groups' );
+			if ( false !== $group_data ) {
+				$groups[ $group_id ] = $group_data;
+
+			// Otherwise leave a placeholder so we don't lose the order.
+			} else {
+				$groups[ $group_id ] = '';
+
+				// Add to the list of items to be queried.
+				$uncached_gids[] = $group_id;
+			}
+		}
+
+		// Fetch uncached data from the DB if necessary.
+		if ( ! empty( $uncached_gids ) ) {
+
+			// Setup IN query for uncached group data.
+			$uncached_gids_sql = implode( ',', wp_parse_id_list( $uncached_gids ) );
+
+			// Get table name to query.
+			$table_name = buddypress()->profile->table_name_groups;
+
+			// Fetch data, preserving order.
+			$queried_gdata = $wpdb->get_results( "SELECT * FROM {$table_name} WHERE id IN ({$uncached_gids_sql}) ORDER BY FIELD( id, {$uncached_gids_sql} )");
+
+			// Make sure query returned valid data.
+			if ( ! empty( $queried_gdata ) && ! is_wp_error( $queried_gdata ) ) {
+
+				// Put queried data into the placeholders created earlier,
+				// and add it to the cache.
+				foreach ( (array) $queried_gdata as $gdata ) {
+
+					// Add group to groups array.
+					$groups[ $gdata->id ] = $gdata;
+
+					// Cache previously uncached group data.
+					wp_cache_set( $gdata->id, $gdata, 'bp_xprofile_groups' );
+				}
+			}
+		}
+
+		// Reset indexes & return.
+		return array_values( $groups );
+	}
+
+	/**
+	 * Validate field group when form submitted.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global string $message
+	 *
+	 * @return boolean
+	 */
+	public static function admin_validate() {
+		global $message;
+
+		// Validate Form.
+		if ( empty( $_POST['group_name'] ) ) {
+			$message = __( 'Please make sure you give the group a name.', 'buddypress' );
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 * Update field group position.
+	 *
+	 * @since 1.5.0
+	 *
+	 * @global $wpdb $wpdb
+	 * @param  int $field_group_id ID of the group the field belongs to.
+	 * @param  int $position       Field group position.
+	 *
+	 * @return boolean
+	 */
+	public static function update_position( $field_group_id, $position ) {
+		global $wpdb;
+
+		if ( ! is_numeric( $position ) ) {
+			return false;
+		}
+
+		// Purge profile field group cache.
+		wp_cache_delete( 'all', 'bp_xprofile_groups' );
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "UPDATE {$bp->profile->table_name_groups} SET group_order = %d WHERE id = %d", $position, $field_group_id ) );
+	}
+
+	/**
+	 * Fetch the field visibility level for the fields returned by the query.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @param int   $user_id The profile owner's user_id.
+	 * @param array $fields  The database results returned by the get() query.
+	 *
+	 * @return array $fields The database results, with field_visibility added
+	 */
+	public static function fetch_visibility_level( $user_id = 0, $fields = array() ) {
+
+		// Get the user's visibility level preferences.
+		$visibility_levels = bp_get_user_meta( $user_id, 'bp_xprofile_visibility_levels', true );
+
+		foreach( (array) $fields as $key => $field ) {
+
+			// Does the admin allow this field to be customized?
+			$visibility   = bp_xprofile_get_meta( $field->id, 'field', 'allow_custom_visibility' );
+			$allow_custom = (bool) ( 'disabled' !== $visibility );
+
+			// Look to see if the user has set the visibility for this field.
+			if ( ( true === $allow_custom ) && isset( $visibility_levels[ $field->id ] ) ) {
+				$field_visibility = $visibility_levels[ $field->id ];
+
+			// If no admin-set default is saved, fall back on a global default.
+			} else {
+				$fallback_visibility = bp_xprofile_get_meta( $field->id, 'field', 'default_visibility' );
+
+				/**
+				 * Filters the XProfile default visibility level for a field.
+				 *
+				 * @since 1.6.0
+				 *
+				 * @param string $value Default visibility value.
+				 */
+				$field_visibility = ! empty( $fallback_visibility )
+					? $fallback_visibility
+					: apply_filters( 'bp_xprofile_default_visibility_level', 'public' );
+			}
+
+			$fields[ $key ]->visibility_level = $field_visibility;
+		}
+
+		return $fields;
+	}
+
+	/**
+	 * Fetch the admin-set preferences for all fields.
+	 *
+	 * @since 1.6.0
+	 *
+	 * @return array $default_visibility_levels An array, keyed by field_id, of default
+	 *                                          visibility level + allow_custom
+	 *                                          (whether the admin allows this
+	 *                                          field to be set by user)
+	 */
+	public static function fetch_default_visibility_levels() {
+		global $wpdb;
+
+		$default_visibility_levels = wp_cache_get( 'default_visibility_levels', 'bp_xprofile' );
+
+		if ( false === $default_visibility_levels ) {
+			$bp = buddypress();
+
+			$levels = $wpdb->get_results( "SELECT object_id, meta_key, meta_value FROM {$bp->profile->table_name_meta} WHERE object_type = 'field' AND ( meta_key = 'default_visibility' OR meta_key = 'allow_custom_visibility' )" );
+
+			// Arrange so that the field id is the key and the visibility level the value.
+			$default_visibility_levels = array();
+			foreach ( $levels as $level ) {
+				switch ( $level->meta_key ) {
+					case 'default_visibility' :
+						$default_visibility_levels[ $level->object_id ]['default']      = $level->meta_value;
+						break;
+					case 'allow_custom_visibility' :
+						$default_visibility_levels[ $level->object_id ]['allow_custom'] = $level->meta_value;
+						break;
+				}
+			}
+
+			wp_cache_set( 'default_visibility_levels', $default_visibility_levels, 'bp_xprofile' );
+		}
+
+		return $default_visibility_levels;
+	}
+
+	/** Admin Output **********************************************************/
+
+	/**
+	 * Output the admin area field group form.
+	 *
+	 * @since 1.0.0
+	 *
+	 * @global string $message
+	 */
+	public function render_admin_form() {
+		global $message;
+
+		// New field group.
+		if ( empty( $this->id ) ) {
+			$title	= __( 'Add New Field Group', 'buddypress' );
+			$action	= add_query_arg( array( 'page' => 'bp-profile-setup', 'mode' => 'add_group' ), 'users.php' );
+			$button	= __( 'Save', 'buddypress' );
+
+		// Existing field group.
+		} else {
+			$title  = __( 'Edit Field Group', 'buddypress' );
+			$action	= add_query_arg( array( 'page' => 'bp-profile-setup', 'mode' => 'edit_group', 'group_id' => $this->id ), 'users.php' );
+			$button	= __( 'Update', 'buddypress' );
+		} ?>
+
+		<div class="wrap">
+
+			<h2><?php echo esc_html( $title ); ?></h2>
+
+			<?php if ( ! empty( $message ) ) : ?>
+
+				<div id="message" class="error fade">
+					<p><?php echo esc_html( $message ); ?></p>
+				</div>
+
+			<?php endif; ?>
+
+			<form id="bp-xprofile-add-field-group" action="<?php echo esc_url( $action ); ?>" method="post">
+				<div id="poststuff">
+					<div id="post-body" class="metabox-holder columns-<?php echo ( 1 == get_current_screen()->get_columns() ) ? '1' : '2'; ?>">
+						<div id="post-body-content">
+							<div id="titlediv">
+								<div class="titlewrap">
+									<label id="title-prompt-text" for="title"><?php esc_html_e( 'Field Group Name', 'buddypress') ?></label>
+									<input type="text" name="group_name" id="title" value="<?php echo esc_attr( $this->name ); ?>" autocomplete="off" />
+								</div>
+							</div>
+							<div class="postbox">
+								<h3><?php esc_html_e( 'Field Group Description', 'buddypress' ); ?></h3>
+								<div class="inside">
+									<label for="group_description" class="screen-reader-text"><?php esc_html_e( 'Add description', 'buddypress' ); ?></label>
+									<textarea name="group_description" id="group_description" rows="8" cols="60"><?php echo esc_textarea( $this->description ); ?></textarea>
+								</div>
+							</div>
+						</div><!-- #post-body-content -->
+
+						<div id="postbox-container-1" class="postbox-container">
+
+							<?php
+
+							/**
+							 * Fires before XProfile Group submit metabox.
+							 *
+							 * @since 2.1.0
+							 *
+							 * @param BP_XProfile_Group $this Current XProfile group.
+							 */
+							do_action( 'xprofile_group_before_submitbox', $this ); ?>
+
+							<div id="submitdiv" class="postbox">
+								<h3><?php _e( 'Submit', 'buddypress' ); ?></h3>
+								<div class="inside">
+									<div id="submitcomment" class="submitbox">
+										<div id="major-publishing-actions">
+
+											<?php
+
+											/**
+											 * Fires at the beginning of the XProfile Group publishing actions section.
+											 *
+											 * @since 2.1.0
+											 *
+											 * @param BP_XProfile_Group $this Current XProfile group.
+											 */
+											do_action( 'xprofile_group_submitbox_start', $this ); ?>
+
+											<input type="hidden" name="group_order" id="group_order" value="<?php echo esc_attr( $this->group_order ); ?>" />
+											<div id="publishing-action">
+												<input type="submit" name="save_group" value="<?php echo esc_attr( $button ); ?>" class="button-primary"/>
+											</div>
+											<div id="delete-action">
+												<a href="users.php?page=bp-profile-setup" class="deletion"><?php _e( 'Cancel', 'buddypress' ); ?></a>
+											</div>
+											<div class="clear"></div>
+										</div>
+									</div>
+								</div>
+							</div>
+
+							<?php
+
+							/**
+							 * Fires after XProfile Group submit metabox.
+							 *
+							 * @since 2.1.0
+							 *
+							 * @param BP_XProfile_Group $this Current XProfile group.
+							 */
+							do_action( 'xprofile_group_after_submitbox', $this ); ?>
+
+						</div>
+					</div>
+				</div>
+			</form>
+		</div>
+
+	<?php
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-meta-query.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-meta-query.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-meta-query.php	(revision 10410)
@@ -0,0 +1,365 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Class for generating SQL clauses that filter a primary query according to
+ * XProfile metadata keys and values.
+ *
+ * `BP_XProfile_Meta_Query` is a helper that allows primary query classes, such
+ * as {@see WP_Query} and {@see WP_User_Query}, to filter their results by object
+ * metadata, by generating `JOIN` and `WHERE` subclauses to be attached
+ * to the primary SQL query string.
+ *
+ * @since 2.3.0
+ */
+class BP_XProfile_Meta_Query extends WP_Meta_Query {
+
+	/**
+	 * Determine whether a query clause is first-order.
+	 *
+	 * A first-order meta query clause is one that has either a 'key', 'value',
+	 * or 'object' array key.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param array $query Meta query arguments.
+	 *
+	 * @return bool Whether the query clause is a first-order clause.
+	 */
+	protected function is_first_order_clause( $query ) {
+		return isset( $query['key'] ) || isset( $query['value'] ) || isset( $query['object'] );
+	}
+
+	/**
+	 * Constructs a meta query based on 'meta_*' query vars
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param array $qv The query variables.
+	 */
+	public function parse_query_vars( $qv ) {
+		$meta_query = array();
+
+		/*
+		 * For orderby=meta_value to work correctly, simple query needs to be
+		 * first (so that its table join is against an unaliased meta table) and
+		 * needs to be its own clause (so it doesn't interfere with the logic of
+		 * the rest of the meta_query).
+		 */
+		$primary_meta_query = array();
+		foreach ( array( 'key', 'compare', 'type' ) as $key ) {
+			if ( ! empty( $qv[ "meta_$key" ] ) ) {
+				$primary_meta_query[ $key ] = $qv[ "meta_$key" ];
+			}
+		}
+
+		// WP_Query sets 'meta_value' = '' by default.
+		if ( isset( $qv['meta_value'] ) && ( '' !== $qv['meta_value'] ) && ( ! is_array( $qv['meta_value'] ) || $qv['meta_value'] ) ) {
+			$primary_meta_query['value'] = $qv['meta_value'];
+		}
+
+		// BP_XProfile_Query sets 'object_type' = '' by default.
+		if ( isset( $qv[ 'object_type' ] ) && ( '' !== $qv[ 'object_type' ] ) && ( ! is_array( $qv[ 'object_type' ] ) || $qv[ 'object_type' ] ) ) {
+			$meta_query[0]['object'] = $qv[ 'object_type' ];
+		}
+
+		$existing_meta_query = isset( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ? $qv['meta_query'] : array();
+
+		if ( ! empty( $primary_meta_query ) && ! empty( $existing_meta_query ) ) {
+			$meta_query = array(
+				'relation' => 'AND',
+				$primary_meta_query,
+				$existing_meta_query,
+			);
+		} elseif ( ! empty( $primary_meta_query ) ) {
+			$meta_query = array(
+				$primary_meta_query,
+			);
+		} elseif ( ! empty( $existing_meta_query ) ) {
+			$meta_query = $existing_meta_query;
+		}
+
+		$this->__construct( $meta_query );
+	}
+
+	/**
+	 * Generates SQL clauses to be appended to a main query.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param string $type              Type of meta, eg 'user', 'post'.
+	 * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
+	 * @param string $primary_id_column ID column for the filtered object in $primary_table.
+	 * @param object $context           Optional. The main query object.
+	 *
+	 * @return array {
+	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
+	 *
+	 *     @type string $join  SQL fragment to append to the main JOIN clause.
+	 *     @type string $where SQL fragment to append to the main WHERE clause.
+	 * }
+	 */
+	public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) {
+		if ( ! $meta_table = _get_meta_table( $type ) ) {
+			return false;
+		}
+
+		$this->meta_table     = $meta_table;
+		$this->meta_id_column = 'object_id';
+
+		$this->primary_table     = $primary_table;
+		$this->primary_id_column = $primary_id_column;
+
+		$sql = $this->get_sql_clauses();
+
+		/*
+		 * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should
+		 * be LEFT. Otherwise posts with no metadata will be excluded from results.
+		 */
+		if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) {
+			$sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] );
+		}
+
+		/**
+		 * Filter the meta query's generated SQL.
+		 *
+		 * @since 2.3.0
+		 *
+		 * @param array $args {
+		 *     An array of meta query SQL arguments.
+		 *
+		 *     @type array  $clauses           Array containing the query's JOIN and WHERE clauses.
+		 *     @type array  $queries           Array of meta queries.
+		 *     @type string $type              Type of meta.
+		 *     @type string $primary_table     Primary table.
+		 *     @type string $primary_id_column Primary column ID.
+		 *     @type object $context           The main query object.
+		 * }
+		 */
+		return apply_filters_ref_array( 'bp_xprofile_get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) );
+	}
+
+	/**
+	 * Generate SQL JOIN and WHERE clauses for a first-order query clause.
+	 *
+	 * "First-order" means that it's an array with a 'key' or 'value'.
+	 *
+	 * @since 2.3.0
+	 *
+	 * @param array  $clause       Query clause, passed by reference.
+	 * @param array  $parent_query Parent query array.
+	 * @param string $clause_key   Optional. The array key used to name the clause in the original `$meta_query`
+	 *                             parameters. If not provided, a key will be generated automatically.
+	 *
+	 * @return array {
+	 *     Array containing JOIN and WHERE SQL clauses to append to a first-order query.
+	 *
+	 *     @type string $join  SQL fragment to append to the main JOIN clause.
+	 *     @type string $where SQL fragment to append to the main WHERE clause.
+	 * }
+	 */
+	public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) {
+		global $wpdb;
+
+		$sql_chunks = array(
+			'where' => array(),
+			'join'  => array(),
+		);
+
+		if ( isset( $clause['compare'] ) ) {
+			$clause['compare'] = strtoupper( $clause['compare'] );
+		} else {
+			$clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
+		}
+
+		if ( ! in_array( $clause['compare'], array(
+			'=', '!=', '>', '>=', '<', '<=',
+			'LIKE', 'NOT LIKE',
+			'IN', 'NOT IN',
+			'BETWEEN', 'NOT BETWEEN',
+			'EXISTS', 'NOT EXISTS',
+			'REGEXP', 'NOT REGEXP', 'RLIKE'
+		) ) ) {
+			$clause['compare'] = '=';
+		}
+
+		$meta_compare = $clause['compare'];
+
+		// First build the JOIN clause, if one is required.
+		$join = '';
+
+		// We prefer to avoid joins if possible. Look for an existing join compatible with this clause.
+		$alias = $this->find_compatible_table_alias( $clause, $parent_query );
+		if ( false === $alias ) {
+			$i = count( $this->table_aliases );
+			$alias = $i ? 'mt' . $i : $this->meta_table;
+
+			// JOIN clauses for NOT EXISTS have their own syntax.
+			if ( 'NOT EXISTS' === $meta_compare ) {
+				$join .= " LEFT JOIN $this->meta_table";
+				$join .= $i ? " AS $alias" : '';
+				$join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
+
+			// All other JOIN clauses.
+			} else {
+				$join .= " INNER JOIN $this->meta_table";
+				$join .= $i ? " AS $alias" : '';
+				$join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )";
+			}
+
+			$this->table_aliases[] = $alias;
+			$sql_chunks['join'][]  = $join;
+		}
+
+		// Save the alias to this clause, for future siblings to find.
+		$clause['alias'] = $alias;
+
+		// Determine the data type.
+		$_meta_type     = isset( $clause['type'] ) ? $clause['type'] : '';
+		$meta_type      = $this->get_cast_for_type( $_meta_type );
+		$clause['cast'] = $meta_type;
+
+		// Fallback for clause keys is the table alias.
+		if ( ! $clause_key ) {
+			$clause_key = $clause['alias'];
+		}
+
+		// Ensure unique clause keys, so none are overwritten.
+		$iterator = 1;
+		$clause_key_base = $clause_key;
+		while ( isset( $this->clauses[ $clause_key ] ) ) {
+			$clause_key = $clause_key_base . '-' . $iterator;
+			$iterator++;
+		}
+
+		// Store the clause in our flat array.
+		$this->clauses[ $clause_key ] =& $clause;
+
+		// Next, build the WHERE clause.
+		// Meta_key.
+		if ( array_key_exists( 'key', $clause ) ) {
+			if ( 'NOT EXISTS' === $meta_compare ) {
+				$sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
+			} else {
+				$sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
+			}
+		}
+
+		// Meta_value.
+		if ( array_key_exists( 'value', $clause ) ) {
+			$meta_value = $clause['value'];
+
+			if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
+				if ( ! is_array( $meta_value ) ) {
+					$meta_value = preg_split( '/[,\s]+/', $meta_value );
+				}
+			} else {
+				$meta_value = trim( $meta_value );
+			}
+
+			switch ( $meta_compare ) {
+				case 'IN' :
+				case 'NOT IN' :
+					$meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
+					$where = $wpdb->prepare( $meta_compare_string, $meta_value );
+					break;
+
+				case 'BETWEEN' :
+				case 'NOT BETWEEN' :
+					$meta_value = array_slice( $meta_value, 0, 2 );
+					$where = $wpdb->prepare( '%s AND %s', $meta_value );
+					break;
+
+				case 'LIKE' :
+				case 'NOT LIKE' :
+					$meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%';
+					$where = $wpdb->prepare( '%s', $meta_value );
+					break;
+
+				// EXISTS with a value is interpreted as '='.
+				case 'EXISTS' :
+					$meta_compare = '=';
+					$where = $wpdb->prepare( '%s', $meta_value );
+					break;
+
+				// 'value' is ignored for NOT EXISTS.
+				case 'NOT EXISTS' :
+					$where = '';
+					break;
+
+				default :
+					$where = $wpdb->prepare( '%s', $meta_value );
+					break;
+
+			}
+
+			if ( $where ) {
+				$sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}";
+			}
+		}
+
+		// Object_type.
+		if ( array_key_exists( 'object', $clause ) ) {
+			$object_type = $clause['object'];
+
+			if ( in_array( $meta_compare, array( 'IN', 'NOT IN' ) ) ) {
+				if ( ! is_array( $object_type ) ) {
+					$object_type = preg_split( '/[,\s]+/', $object_type );
+				}
+			} else {
+				$object_type = trim( $object_type );
+			}
+
+			switch ( $meta_compare ) {
+				case 'IN' :
+				case 'NOT IN' :
+					$meta_compare_string = '(' . substr( str_repeat( ',%s', count( $object_type ) ), 1 ) . ')';
+					$object_where        = $wpdb->prepare( $meta_compare_string, $object_type );
+					break;
+
+				case 'LIKE' :
+				case 'NOT LIKE' :
+					$object_type  = '%' . $wpdb->esc_like( $object_type ) . '%';
+					$object_where = $wpdb->prepare( '%s', $object_type );
+					break;
+
+				// EXISTS with a value is interpreted as '='.
+				case 'EXISTS' :
+					$meta_compare = '=';
+					$object_where = $wpdb->prepare( '%s', $object_type );
+					break;
+
+				// 'value' is ignored for NOT EXISTS.
+				case 'NOT EXISTS' :
+					$object_where = '';
+					break;
+
+				default :
+					$object_where = $wpdb->prepare( '%s', $object_type );
+					break;
+			}
+
+			if ( ! empty( $object_where ) ) {
+				$sql_chunks['where'][] = "{$alias}.object_type {$meta_compare} {$object_where}";
+			}
+		}
+
+		/*
+		 * Multiple WHERE clauses (for meta_key and meta_value) should
+		 * be joined in parentheses.
+		 */
+		if ( 1 < count( $sql_chunks['where'] ) ) {
+			$sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
+		}
+
+		return $sql_chunks;
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-profiledata.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-profiledata.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-profiledata.php	(revision 10410)
@@ -0,0 +1,532 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+class BP_XProfile_ProfileData {
+	public $id;
+	public $user_id;
+	public $field_id;
+	public $value;
+	public $last_updated;
+
+	public function __construct( $field_id = null, $user_id = null ) {
+		if ( !empty( $field_id ) ) {
+			$this->populate( $field_id, $user_id );
+		}
+	}
+
+	public function populate( $field_id, $user_id ) {
+		global $wpdb;
+
+		$cache_key   = "{$user_id}:{$field_id}";
+		$profiledata = wp_cache_get( $cache_key, 'bp_xprofile_data' );
+
+		if ( false === $profiledata ) {
+			$bp = buddypress();
+
+			$sql         = $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_data} WHERE field_id = %d AND user_id = %d", $field_id, $user_id );
+			$profiledata = $wpdb->get_row( $sql );
+
+			if ( $profiledata ) {
+				wp_cache_set( $cache_key, $profiledata, 'bp_xprofile_data' );
+			}
+		}
+
+		if ( $profiledata ) {
+			$this->id           = $profiledata->id;
+			$this->user_id      = $profiledata->user_id;
+			$this->field_id     = $profiledata->field_id;
+			$this->value        = stripslashes( $profiledata->value );
+			$this->last_updated = $profiledata->last_updated;
+
+		} else {
+			// When no row is found, we'll need to set these properties manually.
+			$this->field_id	    = $field_id;
+			$this->user_id	    = $user_id;
+		}
+	}
+
+	/**
+	 * Check if there is data already for the user.
+	 *
+	 * @global object $wpdb
+	 * @global array $bp
+	 * @return bool
+	 */
+	public function exists() {
+		global $wpdb;
+
+		// Check cache first.
+		$cache_key = "{$this->user_id}:{$this->field_id}";
+		$cached    = wp_cache_get( $cache_key, 'bp_xprofile_data' );
+
+		if ( $cached && ! empty( $cached->id ) ) {
+			$retval = true;
+		} else {
+			$bp = buddypress();
+			$retval = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_data} WHERE user_id = %d AND field_id = %d", $this->user_id, $this->field_id ) );
+		}
+
+		/**
+		 * Filters whether or not data already exists for the user.
+		 *
+		 * @since 1.2.7
+		 *
+		 * @param bool                    $retval Whether or not data already exists.
+		 * @param BP_XProfile_ProfileData $this   Instance of the current BP_XProfile_ProfileData class.
+		 */
+		return apply_filters_ref_array( 'xprofile_data_exists', array( (bool)$retval, $this ) );
+	}
+
+	/**
+	 * Check if this data is for a valid field.
+	 *
+	 * @global object $wpdb
+	 * @return bool
+	 */
+	public function is_valid_field() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$retval = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE id = %d", $this->field_id ) );
+
+		/**
+		 * Filters whether or not data is for a valid field.
+		 *
+		 * @since 1.2.7
+		 *
+		 * @param bool                    $retval Whether or not data is valid.
+		 * @param BP_XProfile_ProfileData $this   Instance of the current BP_XProfile_ProfileData class.
+		 */
+		return apply_filters_ref_array( 'xprofile_data_is_valid_field', array( (bool)$retval, $this ) );
+	}
+
+	public function save() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$this->user_id      = apply_filters( 'xprofile_data_user_id_before_save',      $this->user_id,         $this->id );
+		$this->field_id     = apply_filters( 'xprofile_data_field_id_before_save',     $this->field_id,        $this->id );
+		$this->value        = apply_filters( 'xprofile_data_value_before_save',        $this->value,           $this->id, true, $this );
+		$this->last_updated = apply_filters( 'xprofile_data_last_updated_before_save', bp_core_current_time(), $this->id );
+
+		/**
+		 * Fires before the current profile data instance gets saved.
+		 *
+		 * Please use this hook to filter the properties above. Each part will be passed in.
+		 *
+		 * @since 1.0.0
+		 *
+		 * @param BP_XProfile_ProfileData $this Current instance of the profile data being saved.
+		 */
+		do_action_ref_array( 'xprofile_data_before_save', array( $this ) );
+
+		if ( $this->is_valid_field() ) {
+			if ( $this->exists() && strlen( trim( $this->value ) ) ) {
+				$result   = $wpdb->query( $wpdb->prepare( "UPDATE {$bp->profile->table_name_data} SET value = %s, last_updated = %s WHERE user_id = %d AND field_id = %d", $this->value, $this->last_updated, $this->user_id, $this->field_id ) );
+
+			} elseif ( $this->exists() && empty( $this->value ) ) {
+				// Data removed, delete the entry.
+				$result   = $this->delete();
+
+			} else {
+				$result   = $wpdb->query( $wpdb->prepare("INSERT INTO {$bp->profile->table_name_data} (user_id, field_id, value, last_updated) VALUES (%d, %d, %s, %s)", $this->user_id, $this->field_id, $this->value, $this->last_updated ) );
+				$this->id = $wpdb->insert_id;
+			}
+
+			if ( false === $result ) {
+				return false;
+			}
+
+			/**
+			 * Fires after the current profile data instance gets saved.
+			 *
+			 * @since 1.0.0
+			 *
+			 * @param BP_XProfile_ProfileData $this Current instance of the profile data being saved.
+			 */
+			do_action_ref_array( 'xprofile_data_after_save', array( $this ) );
+
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Delete specific XProfile field data.
+	 *
+	 * @global object $wpdb
+	 * @return boolean
+	 */
+	public function delete() {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		/**
+		 * Fires before the current profile data instance gets deleted.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param BP_XProfile_ProfileData $this Current instance of the profile data being deleted.
+		 */
+		do_action_ref_array( 'xprofile_data_before_delete', array( $this ) );
+
+		$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_data} WHERE field_id = %d AND user_id = %d", $this->field_id, $this->user_id ) );
+		if ( empty( $deleted ) ) {
+			return false;
+		}
+
+		/**
+		 * Fires after the current profile data instance gets deleted.
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param BP_XProfile_ProfileData $this Current instance of the profile data being deleted.
+		 */
+		do_action_ref_array( 'xprofile_data_after_delete', array( $this ) );
+
+		return true;
+	}
+
+	/** Static Methods ********************************************************/
+
+	/**
+	 * Get a user's profile data for a set of fields.
+	 *
+	 * @param int   $user_id   ID of user whose data is being queried.
+	 * @param array $field_ids Array of field IDs to query for.
+	 *
+	 * @return array
+	 */
+	public static function get_data_for_user( $user_id, $field_ids ) {
+		global $wpdb;
+
+		$data = array();
+
+		$uncached_field_ids = bp_xprofile_get_non_cached_field_ids( $user_id, $field_ids );
+
+		// Prime the cache.
+		if ( ! empty( $uncached_field_ids ) ) {
+			$bp = buddypress();
+			$uncached_field_ids_sql = implode( ',', wp_parse_id_list( $uncached_field_ids ) );
+			$uncached_data = $wpdb->get_results( $wpdb->prepare( "SELECT id, user_id, field_id, value, last_updated FROM {$bp->profile->table_name_data} WHERE field_id IN ({$uncached_field_ids_sql}) AND user_id = %d", $user_id ) );
+
+			// Rekey.
+			$queried_data = array();
+			foreach ( $uncached_data as $ud ) {
+				$d               = new stdClass;
+				$d->id           = $ud->id;
+				$d->user_id      = $ud->user_id;
+				$d->field_id     = $ud->field_id;
+				$d->value        = $ud->value;
+				$d->last_updated = $ud->last_updated;
+
+				$queried_data[ $ud->field_id ] = $d;
+			}
+
+			// Set caches.
+			foreach ( $uncached_field_ids as $field_id ) {
+
+				$cache_key = "{$user_id}:{$field_id}";
+
+				// If a value was found, cache it.
+				if ( isset( $queried_data[ $field_id ] ) ) {
+					wp_cache_set( $cache_key, $queried_data[ $field_id ], 'bp_xprofile_data' );
+
+				// If no value was found, cache an empty item
+				// to avoid future cache misses.
+				} else {
+					$d               = new stdClass;
+					$d->id           = '';
+					$d->user_id      = '';
+					$d->field_id     = $field_id;
+					$d->value        = '';
+					$d->last_updated = '';
+
+					wp_cache_set( $cache_key, $d, 'bp_xprofile_data' );
+				}
+			}
+		}
+
+		// Now that all items are cached, fetch them.
+		foreach ( $field_ids as $field_id ) {
+			$cache_key = "{$user_id}:{$field_id}";
+			$data[]    = wp_cache_get( $cache_key, 'bp_xprofile_data' );
+		}
+
+		return $data;
+	}
+
+	/**
+	 * Get all of the profile information for a specific user.
+	 *
+	 * @param int $user_id ID of the user.
+	 *
+	 * @return array
+	 */
+	public static function get_all_for_user( $user_id ) {
+
+		$groups = bp_xprofile_get_groups( array(
+			'user_id'                => $user_id,
+			'hide_empty_groups'      => true,
+			'hide_empty_fields'      => true,
+			'fetch_fields'           => true,
+			'fetch_field_data'       => true,
+		) );
+
+		$profile_data = array();
+
+		if ( ! empty( $groups ) ) {
+			$user = new WP_User( $user_id );
+
+			$profile_data['user_login']    = $user->user_login;
+			$profile_data['user_nicename'] = $user->user_nicename;
+			$profile_data['user_email']    = $user->user_email;
+
+			foreach ( (array) $groups as $group ) {
+				if ( empty( $group->fields ) ) {
+					continue;
+				}
+
+				foreach ( (array) $group->fields as $field ) {
+					$profile_data[ $field->name ] = array(
+						'field_group_id'   => $group->id,
+						'field_group_name' => $group->name,
+						'field_id'         => $field->id,
+						'field_type'       => $field->type,
+						'field_data'       => $field->data->value,
+					);
+				}
+			}
+		}
+
+		return $profile_data;
+	}
+
+	/**
+	 * Get the user's field data id by the id of the xprofile field.
+	 *
+	 * @param int $field_id Field ID being queried for.
+	 * @param int $user_id  User ID associated with field.
+	 *
+	 * @return int $fielddata_id
+	 */
+	public static function get_fielddataid_byid( $field_id, $user_id ) {
+		global $wpdb;
+
+		if ( empty( $field_id ) || empty( $user_id ) ) {
+			$fielddata_id = 0;
+		} else {
+			$bp = buddypress();
+
+			// Check cache first.
+			$cache_key = "{$user_id}:{$field_id}";
+			$fielddata = wp_cache_get( $cache_key, 'bp_xprofile_data' );
+			if ( false === $fielddata || empty( $fielddata->id ) ) {
+				$fielddata_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_data} WHERE field_id = %d AND user_id = %d", $field_id, $user_id ) );
+			} else {
+				$fielddata_id = $fielddata->id;
+			}
+		}
+
+		return $fielddata_id;
+	}
+
+	/**
+	 * Get profile field values by field ID and user IDs.
+	 *
+	 * Supports multiple user IDs.
+	 *
+	 * @param int            $field_id ID of the field.
+	 * @param int|array|null $user_ids ID or IDs of user(s).
+	 *
+	 * @return string|array Single value if a single user is queried,
+	 *                      otherwise an array of results.
+	 */
+	public static function get_value_byid( $field_id, $user_ids = null ) {
+		global $wpdb;
+
+		if ( empty( $user_ids ) ) {
+			$user_ids = bp_displayed_user_id();
+		}
+
+		$is_single = false;
+		if ( ! is_array( $user_ids ) ) {
+			$user_ids  = array( $user_ids );
+			$is_single = true;
+		}
+
+		// Assemble uncached IDs.
+		$uncached_ids = array();
+		foreach ( $user_ids as $user_id ) {
+			$cache_key = "{$user_id}:{$field_id}";
+			if ( false === wp_cache_get( $cache_key, 'bp_xprofile_data' ) ) {
+				$uncached_ids[] = $user_id;
+			}
+		}
+
+		// Prime caches.
+		if ( ! empty( $uncached_ids ) ) {
+			$bp = buddypress();
+			$uncached_ids_sql = implode( ',', $uncached_ids );
+			$queried_data = $wpdb->get_results( $wpdb->prepare( "SELECT id, user_id, field_id, value, last_updated FROM {$bp->profile->table_name_data} WHERE field_id = %d AND user_id IN ({$uncached_ids_sql})", $field_id ) );
+
+			// Rekey.
+			$qd = array();
+			foreach ( $queried_data as $data ) {
+				$qd[ $data->user_id ] = $data;
+			}
+
+			foreach ( $uncached_ids as $id ) {
+				// The value was successfully fetched.
+				if ( isset( $qd[ $id ] ) ) {
+					$d = $qd[ $id ];
+
+				// No data found for the user, so we fake it to
+				// avoid cache misses and PHP notices.
+				} else {
+					$d = new stdClass;
+					$d->id           = '';
+					$d->user_id      = $id;
+					$d->field_id     = '';
+					$d->value        = '';
+					$d->last_updated = '';
+				}
+
+				$cache_key = "{$d->user_id}:{$field_id}";
+				wp_cache_set( $cache_key, $d, 'bp_xprofile_data' );
+			}
+		}
+
+		// Now that the cache is primed with all data, fetch it.
+		$data = array();
+		foreach ( $user_ids as $user_id ) {
+			$cache_key = "{$user_id}:{$field_id}";
+			$data[]    = wp_cache_get( $cache_key, 'bp_xprofile_data' );
+		}
+
+		// If a single ID was passed, just return the value.
+		if ( $is_single ) {
+			return $data[0]->value;
+
+		// Otherwise return the whole array.
+		} else {
+			return $data;
+		}
+	}
+
+	public static function get_value_byfieldname( $fields, $user_id = null ) {
+		global $wpdb;
+
+		if ( empty( $fields ) ) {
+			return false;
+		}
+
+		$bp = buddypress();
+
+		if ( empty( $user_id ) ) {
+			$user_id = bp_displayed_user_id();
+		}
+
+		$field_sql = '';
+
+		if ( is_array( $fields ) ) {
+			for ( $i = 0, $count = count( $fields ); $i < $count; ++$i ) {
+				if ( $i == 0 ) {
+					$field_sql .= $wpdb->prepare( "AND ( f.name = %s ", $fields[$i] );
+				} else {
+					$field_sql .= $wpdb->prepare( "OR f.name = %s ", $fields[$i] );
+				}
+			}
+
+			$field_sql .= ')';
+		} else {
+			$field_sql .= $wpdb->prepare( "AND f.name = %s", $fields );
+		}
+
+		$sql    = $wpdb->prepare( "SELECT d.value, f.name FROM {$bp->profile->table_name_data} d, {$bp->profile->table_name_fields} f WHERE d.field_id = f.id AND d.user_id = %d AND f.parent_id = 0 $field_sql", $user_id );
+		$values = $wpdb->get_results( $sql );
+
+		if ( empty( $values ) || is_wp_error( $values ) ) {
+			return false;
+		}
+
+		$new_values = array();
+
+		if ( is_array( $fields ) ) {
+			for ( $i = 0, $count = count( $values ); $i < $count; ++$i ) {
+				for ( $j = 0; $j < count( $fields ); $j++ ) {
+					if ( $values[$i]->name == $fields[$j] ) {
+						$new_values[$fields[$j]] = $values[$i]->value;
+					} elseif ( !array_key_exists( $fields[$j], $new_values ) ) {
+						$new_values[$fields[$j]] = NULL;
+					}
+				}
+			}
+		} else {
+			$new_values = $values[0]->value;
+		}
+
+		return $new_values;
+	}
+
+	public static function delete_for_field( $field_id ) {
+		global $wpdb;
+
+		$bp      = buddypress();
+		$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_data} WHERE field_id = %d", $field_id ) );
+		if ( empty( $deleted ) || is_wp_error( $deleted ) ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	public static function get_last_updated( $user_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$last_updated = $wpdb->get_var( $wpdb->prepare( "SELECT last_updated FROM {$bp->profile->table_name_data} WHERE user_id = %d ORDER BY last_updated LIMIT 1", $user_id ) );
+
+		return $last_updated;
+	}
+
+	public static function delete_data_for_user( $user_id ) {
+		global $wpdb;
+
+		$bp = buddypress();
+
+		return $wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_data} WHERE user_id = %d", $user_id ) );
+	}
+
+	public static function get_random( $user_id, $exclude_fullname ) {
+		global $wpdb;
+
+		$exclude_sql = ! empty( $exclude_fullname ) ? ' AND pf.id != 1' : '';
+
+		$bp = buddypress();
+
+		return $wpdb->get_results( $wpdb->prepare( "SELECT pf.type, pf.name, pd.value FROM {$bp->profile->table_name_data} pd INNER JOIN {$bp->profile->table_name_fields} pf ON pd.field_id = pf.id AND pd.user_id = %d {$exclude_sql} ORDER BY RAND() LIMIT 1", $user_id ) );
+	}
+
+	public static function get_fullname( $user_id = 0 ) {
+
+		if ( empty( $user_id ) ) {
+			$user_id = bp_displayed_user_id();
+		}
+
+		return xprofile_get_field_data( bp_xprofile_fullname_field_id(), $user_id );
+	}
+}
Index: /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-query.php
===================================================================
--- /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-query.php	(revision 10410)
+++ /tags/2.4.3/src/bp-xprofile/classes/class-bp-xprofile-query.php	(revision 10410)
@@ -0,0 +1,538 @@
+<?php
+/**
+ * BuddyPress XProfile Classes.
+ *
+ * @package BuddyPress
+ * @subpackage XProfileClasses
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Class for generating SQL clauses to filter a user query by xprofile data.
+ *
+ * @since 2.2.0
+ */
+class BP_XProfile_Query {
+
+	/**
+	 * Array of xprofile queries.
+	 *
+	 * See {@see WP_XProfile_Query::__construct()} for information on parameters.
+	 *
+	 * @since 2.2.0
+	 * @var    array
+	 */
+	public $queries = array();
+
+	/**
+	 * Database table that where the metadata's objects are stored (eg $wpdb->users).
+	 *
+	 * @since 2.2.0
+	 * @var    string
+	 */
+	public $primary_table;
+
+	/**
+	 * Column in primary_table that represents the ID of the object.
+	 *
+	 * @since 2.2.0
+	 * @var    string
+	 */
+	public $primary_id_column;
+
+	/**
+	 * A flat list of table aliases used in JOIN clauses.
+	 *
+	 * @since 2.2.0
+	 * @var    array
+	 */
+	protected $table_aliases = array();
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $xprofile_query {
+	 *     Array of xprofile query clauses.
+	 *
+	 *     @type string $relation Optional. The MySQL keyword used to join the clauses of the query.
+	 *                            Accepts 'AND', or 'OR'. Default 'AND'.
+	 *     @type array {
+	 *         Optional. An array of first-order clause parameters, or another fully-formed xprofile query.
+	 *
+	 *         @type string|int $field   XProfile field to filter by. Accepts a field name or ID.
+	 *         @type string     $value   XProfile value to filter by.
+	 *         @type string     $compare MySQL operator used for comparing the $value. Accepts '=', '!=', '>',
+	 *                                   '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN',
+	 *                                   'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', or 'RLIKE'. Default is 'IN'
+	 *                                   when `$value` is an array, '=' otherwise.
+	 *         @type string     $type    MySQL data type that the `value` column will be CAST to for comparisons.
+	 *                                   Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL',
+	 *                                   'SIGNED', 'TIME', or 'UNSIGNED'. Default is 'CHAR'.
+	 *     }
+	 * }
+	 */
+	public function __construct( $xprofile_query ) {
+		if ( empty( $xprofile_query ) ) {
+			return;
+		}
+
+		$this->queries = $this->sanitize_query( $xprofile_query );
+	}
+
+	/**
+	 * Ensure the `xprofile_query` argument passed to the class constructor is well-formed.
+	 *
+	 * Eliminates empty items and ensures that a 'relation' is set.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $queries Array of query clauses.
+	 *
+	 * @return array Sanitized array of query clauses.
+	 */
+	public function sanitize_query( $queries ) {
+		$clean_queries = array();
+
+		if ( ! is_array( $queries ) ) {
+			return $clean_queries;
+		}
+
+		foreach ( $queries as $key => $query ) {
+			if ( 'relation' === $key ) {
+				$relation = $query;
+
+			} elseif ( ! is_array( $query ) ) {
+				continue;
+
+			// First-order clause.
+			} elseif ( $this->is_first_order_clause( $query ) ) {
+				if ( isset( $query['value'] ) && array() === $query['value'] ) {
+					unset( $query['value'] );
+				}
+
+				$clean_queries[] = $query;
+
+			// Otherwise, it's a nested query, so we recurse.
+			} else {
+				$cleaned_query = $this->sanitize_query( $query );
+
+				if ( ! empty( $cleaned_query ) ) {
+					$clean_queries[] = $cleaned_query;
+				}
+			}
+		}
+
+		if ( empty( $clean_queries ) ) {
+			return $clean_queries;
+		}
+
+		// Sanitize the 'relation' key provided in the query.
+		if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
+			$clean_queries['relation'] = 'OR';
+
+		/*
+		 * If there is only a single clause, call the relation 'OR'.
+		 * This value will not actually be used to join clauses, but it
+		 * simplifies the logic around combining key-only queries.
+		 */
+		} elseif ( 1 === count( $clean_queries ) ) {
+			$clean_queries['relation'] = 'OR';
+
+		// Default to AND.
+		} else {
+			$clean_queries['relation'] = 'AND';
+		}
+
+		return $clean_queries;
+	}
+
+	/**
+	 * Determine whether a query clause is first-order.
+	 *
+	 * A first-order query clause is one that has either a 'key' or a 'value' array key.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param  array $query XProfile query arguments.
+	 *
+	 * @return bool  Whether the query clause is a first-order clause.
+	 */
+	protected function is_first_order_clause( $query ) {
+		return isset( $query['field'] ) || isset( $query['value'] );
+	}
+
+	/**
+	 * Return the appropriate alias for the given field type if applicable.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string $type MySQL type to cast `value`.
+	 *
+	 * @return string MySQL type.
+	 */
+	public function get_cast_for_type( $type = '' ) {
+		if ( empty( $type ) ) {
+			return 'CHAR';
+		}
+
+		$meta_type = strtoupper( $type );
+
+		if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) ) {
+			return 'CHAR';
+		}
+
+		if ( 'NUMERIC' === $meta_type ) {
+			$meta_type = 'SIGNED';
+		}
+
+		return $meta_type;
+	}
+
+	/**
+	 * Generate SQL clauses to be appended to a main query.
+	 *
+	 * Called by the public {@see BP_XProfile_Query::get_sql()}, this method is abstracted out to maintain parity
+	 * with WP's Query classes.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @return array {
+	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
+	 *
+	 *     @type string $join  SQL fragment to append to the main JOIN clause.
+	 *     @type string $where SQL fragment to append to the main WHERE clause.
+	 * }
+	 */
+	protected function get_sql_clauses() {
+		/*
+		 * $queries are passed by reference to get_sql_for_query() for recursion.
+		 * To keep $this->queries unaltered, pass a copy.
+		 */
+		$queries = $this->queries;
+		$sql = $this->get_sql_for_query( $queries );
+
+		if ( ! empty( $sql['where'] ) ) {
+			$sql['where'] = ' AND ' . $sql['where'];
+		}
+
+		return $sql;
+	}
+
+	/**
+	 * Generate SQL clauses for a single query array.
+	 *
+	 * If nested subqueries are found, this method recurses the tree to produce the properly nested SQL.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param  array $query Query to parse.
+	 * @param  int   $depth Optional. Number of tree levels deep we currently are. Used to calculate indentation.
+	 *
+	 * @return array {
+	 *     Array containing JOIN and WHERE SQL clauses to append to a single query array.
+	 *
+	 *     @type string $join  SQL fragment to append to the main JOIN clause.
+	 *     @type string $where SQL fragment to append to the main WHERE clause.
+	 * }
+	 */
+	protected function get_sql_for_query( &$query, $depth = 0 ) {
+		$sql_chunks = array(
+			'join'  => array(),
+			'where' => array(),
+		);
+
+		$sql = array(
+			'join'  => '',
+			'where' => '',
+		);
+
+		$indent = '';
+		for ( $i = 0; $i < $depth; $i++ ) {
+			$indent .= "  ";
+		}
+
+		foreach ( $query as $key => &$clause ) {
+			if ( 'relation' === $key ) {
+				$relation = $query['relation'];
+			} elseif ( is_array( $clause ) ) {
+
+				// This is a first-order clause.
+				if ( $this->is_first_order_clause( $clause ) ) {
+					$clause_sql = $this->get_sql_for_clause( $clause, $query );
+
+					$where_count = count( $clause_sql['where'] );
+					if ( ! $where_count ) {
+						$sql_chunks['where'][] = '';
+					} elseif ( 1 === $where_count ) {
+						$sql_chunks['where'][] = $clause_sql['where'][0];
+					} else {
+						$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
+					}
+
+					$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
+				// This is a subquery, so we recurse.
+				} else {
+					$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
+
+					$sql_chunks['where'][] = $clause_sql['where'];
+					$sql_chunks['join'][]  = $clause_sql['join'];
+				}
+			}
+		}
+
+		// Filter to remove empties.
+		$sql_chunks['join']  = array_filter( $sql_chunks['join'] );
+		$sql_chunks['where'] = array_filter( $sql_chunks['where'] );
+
+		if ( empty( $relation ) ) {
+			$relation = 'AND';
+		}
+
+		// Filter duplicate JOIN clauses and combine into a single string.
+		if ( ! empty( $sql_chunks['join'] ) ) {
+			$sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
+		}
+
+		// Generate a single WHERE clause with proper brackets and indentation.
+		if ( ! empty( $sql_chunks['where'] ) ) {
+			$sql['where'] = '( ' . "\n  " . $indent . implode( ' ' . "\n  " . $indent . $relation . ' ' . "\n  " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')';
+		}
+
+		return $sql;
+	}
+
+	/**
+	 * Generates SQL clauses to be appended to a main query.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
+	 * @param string $primary_id_column ID column for the filtered object in $primary_table.
+	 *
+	 * @return array {
+	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
+	 *
+	 *     @type string $join  SQL fragment to append to the main JOIN clause.
+	 *     @type string $where SQL fragment to append to the main WHERE clause.
+	 * }
+	 */
+	public function get_sql( $primary_table, $primary_id_column ) {
+
+		$this->primary_table     = $primary_table;
+		$this->primary_id_column = $primary_id_column;
+
+		$sql = $this->get_sql_clauses();
+
+		/*
+		 * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should
+		 * be LEFT. Otherwise posts with no metadata will be excluded from results.
+		 */
+		if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) {
+			$sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] );
+		}
+
+		return $sql;
+	}
+
+	/**
+	 * Generate SQL JOIN and WHERE clauses for a first-order query clause.
+	 *
+	 * "First-order" means that it's an array with a 'field' or 'value'.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $clause       Query clause.
+	 * @param array $parent_query Parent query array.
+	 *
+	 * @return array {
+	 *     Array containing JOIN and WHERE SQL clauses to append to a first-order query.
+	 *
+	 *     @type string $join  SQL fragment to append to the main JOIN clause.
+	 *     @type string $where SQL fragment to append to the main WHERE clause.
+	 * }
+	 */
+	public function get_sql_for_clause( &$clause, $parent_query ) {
+		global $wpdb;
+
+		$sql_chunks = array(
+			'where' => array(),
+			'join' => array(),
+		);
+
+		if ( isset( $clause['compare'] ) ) {
+			$clause['compare'] = strtoupper( $clause['compare'] );
+		} else {
+			$clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
+		}
+
+		if ( ! in_array( $clause['compare'], array(
+			'=', '!=', '>', '>=', '<', '<=',
+			'LIKE', 'NOT LIKE',
+			'IN', 'NOT IN',
+			'BETWEEN', 'NOT BETWEEN',
+			'EXISTS', 'NOT EXISTS',
+			'REGEXP', 'NOT REGEXP', 'RLIKE'
+		) ) ) {
+			$clause['compare'] = '=';
+		}
+
+		$field_compare = $clause['compare'];
+
+		// First build the JOIN clause, if one is required.
+		$join = '';
+
+		$data_table = buddypress()->profile->table_name_data;
+
+		// We prefer to avoid joins if possible. Look for an existing join compatible with this clause.
+		$alias = $this->find_compatible_table_alias( $clause, $parent_query );
+		if ( false === $alias ) {
+			$i = count( $this->table_aliases );
+			$alias = $i ? 'xpq' . $i : $data_table;
+
+			// JOIN clauses for NOT EXISTS have their own syntax.
+			if ( 'NOT EXISTS' === $field_compare ) {
+				$join .= " LEFT JOIN $data_table";
+				$join .= $i ? " AS $alias" : '';
+				$join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.user_id AND $alias.field_id = %d )", $clause['field'] );
+
+			// All other JOIN clauses.
+			} else {
+				$join .= " INNER JOIN $data_table";
+				$join .= $i ? " AS $alias" : '';
+				$join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.user_id )";
+			}
+
+			$this->table_aliases[] = $alias;
+			$sql_chunks['join'][] = $join;
+		}
+
+		// Save the alias to this clause, for future siblings to find.
+		$clause['alias'] = $alias;
+
+		// Next, build the WHERE clause.
+		$where = '';
+
+		// Field_id.
+		if ( array_key_exists( 'field', $clause ) ) {
+			// Convert field name to ID if necessary.
+			if ( ! is_numeric( $clause['field'] ) ) {
+				$clause['field'] = xprofile_get_field_id_from_name( $clause['field'] );
+			}
+
+			// NOT EXISTS has its own syntax.
+			if ( 'NOT EXISTS' === $field_compare ) {
+				$sql_chunks['where'][] = $alias . '.user_id IS NULL';
+			} else {
+				$sql_chunks['where'][] = $wpdb->prepare( "$alias.field_id = %d", $clause['field'] );
+			}
+		}
+
+		// Value.
+		if ( array_key_exists( 'value', $clause ) ) {
+			$field_value = $clause['value'];
+			$field_type = $this->get_cast_for_type( isset( $clause['type'] ) ? $clause['type'] : '' );
+
+			if ( in_array( $field_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
+				if ( ! is_array( $field_value ) ) {
+					$field_value = preg_split( '/[,\s]+/', $field_value );
+				}
+			} else {
+				$field_value = trim( $field_value );
+			}
+
+			switch ( $field_compare ) {
+				case 'IN' :
+				case 'NOT IN' :
+					$field_compare_string = '(' . substr( str_repeat( ',%s', count( $field_value ) ), 1 ) . ')';
+					$where = $wpdb->prepare( $field_compare_string, $field_value );
+					break;
+
+				case 'BETWEEN' :
+				case 'NOT BETWEEN' :
+					$field_value = array_slice( $field_value, 0, 2 );
+					$where = $wpdb->prepare( '%s AND %s', $field_value );
+					break;
+
+				case 'LIKE' :
+				case 'NOT LIKE' :
+					$field_value = '%' . bp_esc_like( $field_value ) . '%';
+					$where = $wpdb->prepare( '%s', $field_value );
+					break;
+
+				default :
+					$where = $wpdb->prepare( '%s', $field_value );
+					break;
+
+			}
+
+			if ( $where ) {
+				$sql_chunks['where'][] = "CAST($alias.value AS {$field_type}) {$field_compare} {$where}";
+			}
+		}
+
+		/*
+		 * Multiple WHERE clauses (`field` and `value` pairs) should be joined in parentheses.
+		 */
+		if ( 1 < count( $sql_chunks['where'] ) ) {
+			$sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
+		}
+
+		return $sql_chunks;
+	}
+
+	/**
+	 * Identify an existing table alias that is compatible with the current query clause.
+	 *
+	 * We avoid unnecessary table joins by allowing each clause to look for an existing table alias that is
+	 * compatible with the query that it needs to perform. An existing alias is compatible if (a) it is a
+	 * sibling of $clause (ie, it's under the scope of the same relation), and (b) the combination of
+	 * operator and relation between the clauses allows for a shared table join. In the case of BP_XProfile_Query,
+	 * this * only applies to IN clauses that are connected by the relation OR.
+	 *
+	 * @since 2.2.0
+	 *
+	 * @param array $clause       Query clause.
+	 * @param array $parent_query Parent query of $clause.
+	 *
+	 * @return string|bool Table alias if found, otherwise false.
+	 */
+	protected function find_compatible_table_alias( $clause, $parent_query ) {
+		$alias = false;
+
+		foreach ( $parent_query as $sibling ) {
+			// If the sibling has no alias yet, there's nothing to check.
+			if ( empty( $sibling['alias'] ) ) {
+				continue;
+			}
+
+			// We're only interested in siblings that are first-order clauses.
+			if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) {
+				continue;
+			}
+
+			$compatible_compares = array();
+
+			// Clauses connected by OR can share joins as long as they have "positive" operators.
+			if ( 'OR' === $parent_query['relation'] ) {
+				$compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' );
+
+			// Clauses joined by AND with "negative" operators share a join only if they also share a key.
+			} elseif ( isset( $sibling['field_id'] ) && isset( $clause['field_id'] ) && $sibling['field_id'] === $clause['field_id'] ) {
+				$compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' );
+			}
+
+			$clause_compare  = strtoupper( $clause['compare'] );
+			$sibling_compare = strtoupper( $sibling['compare'] );
+			if ( in_array( $clause_compare, $compatible_compares ) && in_array( $sibling_compare, $compatible_compares ) ) {
+				$alias = $sibling['alias'];
+				break;
+			}
+		}
+
+		return $alias;
+	}
+}
Index: /tags/2.4.3/src/composer.json
===================================================================
--- /tags/2.4.3/src/composer.json	(revision 10410)
+++ /tags/2.4.3/src/composer.json	(revision 10410)
@@ -0,0 +1,33 @@
+{
+	"name": "buddypress/buddypress",
+	"description": "BuddyPress helps you run any kind of social network on your WordPress, with member profiles, activity streams, user groups, messaging, and more.",
+	"type": "wordpress-plugin",
+	"keywords": [
+		"activity",
+		"community",
+		"forum",
+		"friends",
+		"groups",
+		"messaging",
+		"notifications",
+		"profiles",
+		"social network"
+	],
+	"homepage": "https://buddypress.org",
+	"license": "GPL-2.0+",
+	"authors": [ {
+		"name": "BuddyPress Community",
+		"homepage": "https://buddypress.org/about/"
+	} ],
+	"support": {
+		"forum": "https://buddypress.org/support/",
+		"irc": "irc://irc.freenode.net/buddypress-dev",
+		"issues": "https://buddypress.trac.wordpress.org/",
+		"wiki": "https://codex.buddypress.org/",
+		"source": "https://buddypress.trac.wordpress.org/browser"
+	},
+	"require": {
+		"composer/installers": "~1.0",
+		"php": ">=5.2.4"
+	}
+}
Index: /tags/2.4.3/src/humans.txt
===================================================================
--- /tags/2.4.3/src/humans.txt	(revision 10410)
+++ /tags/2.4.3/src/humans.txt	(revision 10410)
@@ -0,0 +1,84 @@
+               .$$$$$$$Z$.
+          .~$$7...........$$$.
+        .$$...?$$$$$$$$$$Z....$Z.
+      .Z$..Z$$$$$$$$$$$$$$$$Z:..$$.
+     ,$..$$$$$$$$$$$$$7...$$$$$?..$.
+   .$Z .$$$$$$$$$$..?$$.....$$$$$..Z:
+  .$$.$$$$$$$$$........$$....Z$$$$..Z,
+  :$.7$$$$$$$$..........$$....$$$$$..$.
+ .$. $$$$$$$$$...........$....$$$$$$..$.
+.$$.$$$$$$$$$$...........$...$$$$$$$$.$.
+.$. $$$$$$$$$$ ..........Z..$$$$$$$$$.$$.
+.$.,$$$$$$$$$$$.........$$$$Z$$$$$$$$..$.
+.Z $$$$$$$$$$$$$$....+$$$$$,,,,~$$$$$..$.
+.$.=$$$$$$Z,,,,,?$$$$$,,,,,Z$,,,,$$$$..$.
+.$..$$$$$$,,,,,,,,Z$$,,,,,,,,$:,,~$$$.:$.
+.$$.$$$$$,,,,,,,,,,,,,,,,,,,,:$,,:$$$.Z.
+ .$..$$$$,,,,,,,,,,,,,,,,,,,,:$,,,$$..$.
+ .$$.$$$:,,,,,,,,,,,,,,,,,,,,,Z$$$$+.$.
+  .$= $$~,,,,,,:::,:,,:,,::,:,$$$$Z.$Z.
+   .$+.$$$$$$$$$$$$$$$$$$$$$$$$$$:.$$.
+    .$$..Z$$$$$$$$$$$$$$$$$$$$$Z..$7.
+      .$...$$$$$$$$$$$$$$$$$$$..Z$.
+        .Z$=..?Z$$$$$$$$$$$Z...$$.
+          .$$7.............Z$$.
+             .7Z$$$$$$$$$$~
+
+   We are the humans behind BuddyPress
+
+/* TEAM */
+Name: Andy Peatling
+Title: Founding Developer
+Twitter: apeatling
+
+Name: John James Jacoby
+Title: Project Lead
+Twitter: jjj
+Favorite Food: Truffle Lasagna
+
+Name: Boone B. Gorges
+Title: Lead Developer
+Twitter: boone
+Favorite Pudgy Pie Filling: Cherry
+
+Name: Paul Gibbs
+Title: Lead Developer
+Twitter: pgibbs
+Favourite Food: Pizza
+
+Name: Ray
+Title: Core Developer
+Twitter: r-a-y
+Favourite Food: tbd
+
+Name: Mathieu Viet
+Title: Core Developer
+Twitter: imath
+Favorite Food: Spaghetti Bolognese
+
+Name: Mercime
+Title: Navigator
+Twitter: mercime_one
+Favorite Food: Paella Valenciana
+
+Name: David Cavins
+Title: Core Developer
+Twitter: daveycavey
+Favorite Food: Quad-Cities pizza
+
+Name: Michael Beckwith
+Title: Core Developer
+Twitter: tw2113
+Favorite Food: Jelly Beans
+
+Name: Hugo Ashmore (hnla)
+Title: Core Developer
+Twitter: hnla
+Favourite Food: Sunday Roast or Curries
+
+/* THANKS */
+modemlooper, cnorris23, karmatosed, photomatt
+
+/* META */
+Updated: 2015/05/09
+See: http://humanstxt.org/
Index: /tags/2.4.3/src/license.txt
===================================================================
--- /tags/2.4.3/src/license.txt	(revision 10410)
+++ /tags/2.4.3/src/license.txt	(revision 10410)
@@ -0,0 +1,280 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                          675 Mass Ave, Cambridge, MA 02139, 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.4.3/src/readme.txt
===================================================================
--- /tags/2.4.3/src/readme.txt	(revision 10410)
+++ /tags/2.4.3/src/readme.txt	(revision 10410)
@@ -0,0 +1,313 @@
+=== BuddyPress ===
+Contributors: johnjamesjacoby, DJPaul, boonebgorges, r-a-y, imath, mercime, tw2113, dcavins, hnla
+Tags: social networking, activity, profiles, messaging, friends, groups, forums, notifications, settings, social, community, networks, networking
+Requires at least: 3.8
+Tested up to: 4.4
+Stable tag: 2.4.3
+License: GPLv2 or later
+License URI: https://www.gnu.org/licenses/gpl-2.0.html
+
+BuddyPress helps you run any kind of social network on your WordPress, with member profiles, activity streams, user groups, messaging, and more.
+
+== Description ==
+
+Are you looking for modern, robust, and sophisticated social network software? BuddyPress is a suite of components that are common to a typical social network, and allows for great add-on features through WordPress's extensive plugin system.
+
+BuddyPress is focused on ease of integration, ease of use, and extensibility. It is deliberately powerful yet unbelievably simple social network software, built by contributors to WordPress.
+
+Enable registered members to create profiles, have private conversations, make connections, create & interact in groups, and much more. Truly a social network in a box, BuddyPress helps you more easily build a home for your company, school, sports team, or other niche community.
+
+= Extensions =
+
+BuddyPress has an ever-increasing array of extended features developed by an active and thriving plugin development community, with hundreds of free-and-open BuddyPress-compatible plugins available. We list them on both <a href="https://buddypress.org/extend/recommended-plugins/">our plugin directory</a> and <a href="https://wordpress.org/plugins/search.php?q=buddypress">WordPress.org</a>. Any plugin can be conveniently installed using the plugin installer in your WordPress Dashboard.
+
+= More Information =
+
+Visit the <a href="https://buddypress.org/">BuddyPress website</a> for documentation, support, and information on getting involved in the project and community.
+
+== Installation ==
+
+= From your WordPress dashboard =
+
+1. Visit 'Plugins > Add New'
+2. Search for 'BuddyPress'
+3. Activate BuddyPress from your Plugins page. (You will be greeted with a Welcome page.)
+
+= From WordPress.org =
+
+1. Download BuddyPress.
+2. Upload the 'buddypress' directory to your '/wp-content/plugins/' directory, using your favorite method (ftp, sftp, scp, etc...)
+3. Activate BuddyPress from your Plugins page. (You will be greeted with a Welcome page.)
+
+= Once Activated =
+
+1. If you do not have pretty permalinks enabled, you will see a notice to enable them. (BuddyPress will not currently work without them.)
+2. Visit 'Settings > BuddyPress > Components' and adjust the active components to match your community. (You can always toggle these later.)
+3. Visit 'Settings > BuddyPress > Pages' and setup your directories and special pages. We create a few automatically, but suggest you customize these to fit the flow and verbiage of your site.
+4. Visit 'Settings > BuddyPress > Settings' and take a moment to match BuddyPress's settings to your expectations. We pick the most common configuration by default, but every community is different.
+
+= Once Configured =
+
+* BuddyPress comes with a robust theme-compatibility API that does its best to make every BuddyPress page 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.
+* A few BuddyPress specific themes are readily available for download from WordPress.org, and hundreds more are available from third-party theme authors. BuddyPress themes are just WordPress themes with additional templates for each component, and with a little work you could easily create your own too!
+* BuddyPress also comes with built-in support for Akismet and bbPress, two very popular and very powerful WordPress plugins. If you're using either, visit their settings pages and ensure everything is configured to your liking.
+
+= Multisite & Multiple Networks =
+
+BuddyPress can be activated and operate in just about any scope you need for it to.
+
+* Activate at the site level to only load BuddyPress on that site.
+* Activate at the network level for full integration with all sites in your network. (This is the most common multisite installation type.)
+* Enable <a href="https://codex.buddypress.org/getting-started/customizing/bp_enable_multiblog/">multiblog</a> mode to allow your BuddyPress content to be displayed on any site in your WordPress Multisite network, using the same central data.
+* Extend BuddyPress with a third-party multi-network plugin to allow each site or network to have an isolated and dedicated community, all from the same WordPress installation.
+
+Read more about custom BuddyPress activations <a href="https://codex.buddypress.org/getting-started/installation-in-wordpress-multisite/">on our codex page.</a>
+
+= Discussion Forums =
+
+Try <a href="https://wordpress.org/plugins/bbpress/">bbPress</a>. It integrates with BuddyPress Groups, Profiles, and Notifications. Each group on your site can choose to have its own forum, and each user's topics, replies, favorites, and subscriptions appear in their profiles.
+
+== Frequently Asked Questions ==
+
+= Can I use my existing WordPress theme? =
+
+Yes! BuddyPress works out-of-the-box with nearly every WordPress theme.
+
+= Will this work on WordPress multisite? =
+
+Yes! If your WordPress installation has multisite enabled, BuddyPress will support the global tracking of blogs, posts, comments, and even custom post types with a little bit of custom code.
+
+= Where can I get support? =
+
+Our community provides free support at <a href="https://buddypress.org/support/">https://buddypress.org/support/</a>.
+
+For dedicated consultations, see our <a href="https://buddypress.org/consulting/">unofficial list</a> of freelancers, contractors, and agencies offering BuddyPress services.
+
+= Where can I find documentation? =
+
+Our codex can be found at <a href="https://codex.buddypress.org/">https://codex.buddypress.org/</a>.
+
+= Where can I report a bug? =
+
+Report bugs, suggest ideas, and participate in development at <a href="https://buddypress.trac.wordpress.org/">https://buddypress.trac.wordpress.org</a>.
+
+= Where can I get the bleeding edge version of BuddyPress? =
+
+Check out the development trunk of BuddyPress from Subversion at <a href="https://buddypress.svn.wordpress.org/trunk/">https://buddypress.svn.wordpress.org/trunk/</a>, or clone from Git at git://buddypress.git.wordpress.org/.
+
+= Who builds BuddyPress? =
+
+BuddyPress is free software, built by an international community of volunteers. Some contributors to BuddyPress are employed by companies that use BuddyPress, while others are consultants who offer BuddyPress-related services for hire. No one is paid by the BuddyPress project for his or her contributions. If you would like to provide monetary support to BuddyPress, please consider a donation to the <a href="http://wordpressfoundation.org">WordPress Foundation</a>, or ask your favorite contributor how they prefer to have their efforts rewarded.
+
+== Screenshots ==
+
+1. **Activity Streams** - Global, personal, and group activity streams with threaded commenting, direct posting, favoriting and @mentions. All with full RSS feeds and email notification support.
+2. **Extended Profiles** - Fully editable profile fields allow you to define the fields users can fill in to describe themselves. Tailor profile fields to suit your audience.
+3. **User Settings** - Give your users complete control over profile and notification settings. Settings are fully integrated into your theme, and can be disabled by the administrator.
+4. **Extensible Groups** - Powerful public, private or hidden groups allow your users to break the discussion down into specific topics. Extend groups with your own custom features using the group extension API.
+5. **Friend Connections** - Let your users make connections so they can track the activity of others, or filter to show only those users they care about the most.
+6. **Private Messaging** - Private messaging will allow your users to talk to each other directly and in private. Not just limited to one-on-one discussions, your users can send messages to multiple recipients.
+7. **Site Tracking** - Track posts and comments in the activity stream, and allow your users to add their own blogs using WordPress' Multisite feature.
+8. **Notifications** - Keep your members up-to-date with relevant activity via toolbar and email notifications.
+
+== Languages ==
+
+BuddyPress is available in many languages thanks to the volunteer efforts of individuals all around the world. Check out our <a href="https://codex.buddypress.org/translations/">translations page</a> on the BuddyPress Codex for more details.
+
+Please consider helping translate BuddyPress at our <a href="https://translate.wordpress.org/projects/wp-plugins/buddypress">GlotPress project</a>. Growing the BuddyPress community means better software for everyone!
+
+== Upgrade Notice ==
+
+= 2.4.3 =
+See: https://codex.buddypress.org/releases/version-2-4-3/
+
+= 2.4.2 =
+See: https://codex.buddypress.org/releases/version-2-4-2/
+
+= 2.4.1 =
+See: https://codex.buddypress.org/releases/version-2-4-1/
+
+= 2.4.0 =
+See: https://codex.buddypress.org/releases/version-2-4-0/
+
+= 2.3.5 =
+See: https://codex.buddypress.org/releases/version-2-3-5/
+
+= 2.3.4 =
+See: https://codex.buddypress.org/releases/version-2-3-4/
+
+= 2.3.3 =
+See: https://codex.buddypress.org/releases/version-2-3-3/
+
+= 2.3.2 =
+See: https://codex.buddypress.org/releases/version-2-3-2/
+
+= 2.3.1 =
+See: https://codex.buddypress.org/releases/version-2-3-1/
+
+= 2.3.0 =
+See: https://codex.buddypress.org/releases/version-2-3-0/
+
+= 2.2.3.1 =
+See: https://codex.buddypress.org/releases/version-2-2-3-1/
+
+= 2.2.3 =
+See: https://codex.buddypress.org/releases/version-2-2-3/
+
+= 2.2.2.1 =
+See: https://codex.buddypress.org/releases/version-2-2-2-1/
+
+= 2.2.2 =
+See: https://codex.buddypress.org/releases/version-2-2-2/
+
+= 2.2.1 =
+See: https://codex.buddypress.org/releases/version-2-2-1/
+
+= 2.2 =
+See: https://codex.buddypress.org/releases/version-2-2/
+
+= 2.1 =
+See: https://codex.buddypress.org/releases/version-2-1/
+
+= 2.0.3 =
+See: https://codex.buddypress.org/releases/version-2-0-3/
+
+= 2.0.2 =
+See: https://codex.buddypress.org/releases/version-2-0-2/
+
+= 2.0.1 =
+See: https://codex.buddypress.org/releases/version-2-0-1/
+
+= 2.0 =
+See: https://codex.buddypress.org/releases/version-2-0/
+
+= 1.9.2 =
+See: https://codex.buddypress.org/releases/version-1-9-2/
+
+= 1.9.1 =
+See: https://codex.buddypress.org/releases/version-1-9-1/
+
+= 1.9 =
+See: https://codex.buddypress.org/releases/version-1-9/
+
+= 1.8.1 =
+See: https://codex.buddypress.org/releases/version-1-8-1/
+
+= 1.8 =
+See: https://codex.buddypress.org/releases/version-1-8/
+
+= 1.7.3 =
+See: https://codex.buddypress.org/releases/version-1-7-3/
+
+= 1.7.2 =
+See: https://codex.buddypress.org/releases/version-1-7-2/
+
+= 1.7.1 =
+See: https://codex.buddypress.org/releases/version-1-7-1/
+
+= 1.7 =
+See: https://codex.buddypress.org/releases/version-1-7/
+
+= 1.6.5 =
+See: https://codex.buddypress.org/releases/version-1-6-5/
+
+= 1.6.4 =
+See: https://codex.buddypress.org/releases/version-1-6-4/
+
+= 1.6.3 =
+See: https://codex.buddypress.org/releases/version-1-6-3/
+
+= 1.6.2 =
+Compatibility with WordPress 3.5
+
+= 1.6.1 =
+Fixes 4 bugs
+
+= 1.6 =
+See: https://codex.buddypress.org/releases/version-1-6/
+
+= 1.5 =
+See: https://codex.buddypress.org/releases/version-1-5/
+
+= 1.2.9 =
+Compatibility with WordPress 3.2
+
+= 1.2.8 =
+Compatibility with WordPress 3.1
+
+= 1.2.7 =
+Fixes over 10 bugs.
+
+== Changelog ==
+
+= 2.4.3 =
+See: https://codex.buddypress.org/releases/version-2-4-3/
+
+= 2.4.2 =
+See: https://codex.buddypress.org/releases/version-2-4-2/
+
+= 2.4.1 =
+See: https://codex.buddypress.org/releases/version-2-4-1/
+
+= 2.4.0 =
+See: https://codex.buddypress.org/releases/version-2-4-0/
+
+= 2.3.5 =
+See: https://codex.buddypress.org/releases/version-2-3-5/
+
+= 2.3.4 =
+See: https://codex.buddypress.org/releases/version-2-3-4/
+
+= 2.3.3 =
+See: https://codex.buddypress.org/releases/version-2-3-3/
+
+= 2.3.2 =
+See: https://codex.buddypress.org/releases/version-2-3-2/
+
+= 2.3.1 =
+See: https://codex.buddypress.org/releases/version-2-3-1/
+
+= 2.3.0 =
+See: https://codex.buddypress.org/releases/version-2-3-0/
+
+= 2.2.1 =
+See: https://codex.buddypress.org/releases/version-2-2-1/
+
+= 2.2 =
+See: https://codex.buddypress.org/releases/version-2-2/
+
+= 2.1 =
+See: https://codex.buddypress.org/releases/version-2-1/
+
+= 2.0.3 =
+See: https://codex.buddypress.org/releases/version-2-0-3/
+
+= 2.0.2 =
+See: https://codex.buddypress.org/releases/version-2-0-2/
+
+= 2.0.1 =
+See: https://codex.buddypress.org/releases/version-2-0-1/
+
+= 2.0 =
+See: https://codex.buddypress.org/releases/version-2-0/
+
+= 1.9 =
+See: https://codex.buddypress.org/releases/version-1-9/
+
+= 1.8.1 =
+See: https://codex.buddypress.org/releases/version-1-8-1/
+
+= 1.8 =
+See: https://codex.buddypress.org/releases/version-1-8/
+
+= 1.7 =
+See: https://codex.buddypress.org/releases/version-1-7/
+
+= 1.6 =
+See: https://codex.buddypress.org/releases/version-1-6/
+
+= 1.5 =
+See: https://codex.buddypress.org/releases/version-1-5/
+
+= Older =
+See: https://codex.buddypress.org/releases/
Index: /tags/2.4.3/tests/phpunit/assets/attachment-extensions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/assets/attachment-extensions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/assets/attachment-extensions.php	(revision 10410)
@@ -0,0 +1,16 @@
+<?php
+/**
+ * The following implementations of BP_Attachment act as dummy plugins
+ * for our unit tests
+ */
+class BPTest_Attachment_Extension extends BP_Attachment {
+	public function __construct( $args = array() ) {
+		return parent::__construct( $args );
+	}
+
+	public function upload_dir_filter( $upload_dir = array() ) {
+		$this->original_upload_dir = $upload_dir;
+
+		return parent::upload_dir_filter( $upload_dir );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/assets/group-extensions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/assets/group-extensions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/assets/group-extensions.php	(revision 10410)
@@ -0,0 +1,411 @@
+<?php
+
+/**
+ * The following implementations of BP_Group_Extension act as dummy plugins
+ * for our unit tests
+ */
+
+class BPTest_Group_Extension_Parse_Legacy_Properties extends BP_Group_Extension {
+	function __construct() {
+		$class_name = get_class( $this );
+		$this->name = $class_name;
+		$this->slug = sanitize_title( $class_name );
+		$this->admin_name = $this->name . ' Edit';
+		$this->admin_slug = $this->slug . '-edit';
+		$this->create_name = $this->name . ' Create';
+		$this->create_slug = $this->slug . '-create';
+		$this->visibility = 'private';
+		$this->create_step_position = 58;
+		$this->nav_item_position = 63;
+		$this->admin_metabox_context = 'high';
+		$this->admin_metabox_priority = 'side';
+		$this->enable_create_step = false;
+		$this->enable_nav_item = true;
+		$this->enable_edit_item = false;
+		$this->enable_admin_item = true;
+		$this->nav_item_name = $this->name . ' Nav';
+		$this->display_hook = 'foo_hook';
+		$this->template_file = 'foo_template';
+	}
+
+	/**
+	 * Provides access to protected method unneeded in BP
+	 */
+	function _parse_legacy_properties() {
+		return $this->parse_legacy_properties();
+	}
+
+	/**
+	 * Provides access to protected property unneeded in BP
+	 */
+	function _get_legacy_properties_converted() {
+		return $this->legacy_properties_converted;
+	}
+}
+
+class BPTest_Group_Extension_Setup_Screens_Use_Global_Fallbacks extends BP_Group_Extension {
+	function __construct() {
+		$class_name = get_class( $this );
+		$this->slug = sanitize_title( $class_name );
+		$this->name = $class_name;
+	}
+
+	/**
+	 * Provides access to protected method unneeded in BP
+	 */
+	function _get_default_screens() {
+		return $this->get_default_screens();
+	}
+
+	/**
+	 * Provides access to protected method unneeded in BP
+	 */
+	function _setup_class_info() {
+		return $this->setup_class_info();
+	}
+
+	function settings_screen( $group_id = null ) {}
+	function settings_screen_save( $group_id = null ) {}
+}
+
+class BPTest_Group_Extension_Setup_Screens_Define_Edit_Screens_Locally extends BP_Group_Extension {
+	function __construct() {
+		$class_name = get_class( $this );
+		$this->slug = sanitize_title( $class_name );
+		$this->name = $class_name;
+	}
+
+	function edit_screen( $group_id = null ) {}
+	function edit_screen_save( $group_id = null ) {}
+	function settings_screen( $group_id = null ) {}
+	function settings_screen_save( $group_id = null ) {}
+
+	/**
+	 * Provides access to protected method unneeded in BP
+	 */
+	function _get_default_screens() {
+		return $this->get_default_screens();
+	}
+
+	/**
+	 * Provides access to protected method unneeded in BP
+	 */
+	function _setup_class_info() {
+		return $this->setup_class_info();
+	}
+
+}
+
+class BPTest_Group_Extension_Access_Root_Property extends BP_Group_Extension {
+	function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'slug' => sanitize_title( $class_name ),
+			'name' => $class_name,
+			'nav_item_position' => 39,
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Access_Init_Property_Using_Legacy_Location extends BP_Group_Extension {
+	function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'slug' => sanitize_title( $class_name ),
+			'name' => $class_name,
+			'screens' => array(
+				'create' => array(
+					'position' => 18,
+				),
+			),
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Get_Screen_Callback_Fallbacks extends BP_Group_Extension {
+	function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'slug' => sanitize_title( $class_name ),
+			'name' => $class_name,
+		);
+
+		parent::init( $args );
+	}
+
+	function settings_screen( $group_id = null ) {}
+	function settings_screen_save( $group_id = null ) {}
+
+	function edit_screen( $group_id = null ) {}
+	function edit_screen_save( $group_id = null ) {}
+}
+
+class BPTest_Group_Extension_Enable_Nav_Item_True extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'enable_nav_item' => true,
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Enable_Nav_Item_False extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'enable_nav_item' => false,
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Visibility_Private extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'visibility' => 'private',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Visibility_Public extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'visibility' => 'public',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Inferred_Access_Settings_EnableNavItem_True extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'enable_nav_item' => true,
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Inferred_Access_Settings_EnableNavItem_False extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'enable_nav_item' => false,
+		);
+
+		parent::init( $args );
+	}
+}
+class BPTest_Group_Extension_Access_Anyone extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'anyone',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Access_Loggedin extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'loggedin',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Access_Member extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'member',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Access_AdminMod extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => array(
+				'mod',
+				'admin',
+			),
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Access_Admin extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'admin',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_Access_Noone extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'noone',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_ShowTab_Anyone extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'admin',
+			'show_tab' => 'anyone',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_ShowTab_Loggedin extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'admin',
+			'show_tab' => 'loggedin',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_ShowTab_Member extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'admin',
+			'show_tab' => 'member',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_ShowTab_AdminMod extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'admin',
+			'show_tab' => array(
+				'mod',
+				'admin',
+			),
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_ShowTab_Admin extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'admin',
+			'show_tab' => 'admin',
+		);
+
+		parent::init( $args );
+	}
+}
+
+class BPTest_Group_Extension_ShowTab_Noone extends BP_Group_Extension {
+	public function __construct() {
+		$class_name = get_class( $this );
+
+		$args = array(
+			'name' => $class_name,
+			'slug' => sanitize_title( $class_name ),
+			'access' => 'noone',
+			'show_tab' => 'noone',
+		);
+
+		parent::init( $args );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/bootstrap.php
===================================================================
--- /tags/2.4.3/tests/phpunit/bootstrap.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/bootstrap.php	(revision 10410)
@@ -0,0 +1,19 @@
+<?php
+
+require( dirname( __FILE__ ) . '/includes/define-constants.php' );
+
+if ( ! file_exists( WP_TESTS_DIR . '/includes/functions.php' ) ) {
+	die( "The WordPress PHPUnit test suite could not be found.\n" );
+}
+
+require_once WP_TESTS_DIR . '/includes/functions.php';
+
+function _install_and_load_buddypress() {
+	require BP_TESTS_DIR . '/includes/loader.php';
+}
+tests_add_filter( 'muplugins_loaded', '_install_and_load_buddypress' );
+
+require WP_TESTS_DIR . '/includes/bootstrap.php';
+
+// Load the BP-specific testing tools
+require BP_TESTS_DIR . '/includes/testcase.php';
Index: /tags/2.4.3/tests/phpunit/includes/define-constants.php
===================================================================
--- /tags/2.4.3/tests/phpunit/includes/define-constants.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/includes/define-constants.php	(revision 10410)
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * Define constants needed by test suite.
+ */
+
+define( 'BP_PLUGIN_DIR', dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) . '/src/' );
+
+if ( ! defined( 'BP_TESTS_DIR' ) ) {
+	define( 'BP_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( dirname( __FILE__ ) ) ) ) ) ) ) ) );
+	}
+
+	define( 'WP_TESTS_DIR', WP_ROOT_DIR . '/tests/phpunit' );
+}
+
+// Based on the tests directory, look for a config file
+if ( file_exists( WP_ROOT_DIR . '/wp-tests-config.php' ) ) {
+	// Standard develop.svn.wordpress.org setup
+	define( 'WP_TESTS_CONFIG_PATH', WP_ROOT_DIR . '/wp-tests-config.php' );
+
+} elseif ( file_exists( WP_TESTS_DIR . '/wp-tests-config.php' ) ) {
+	// Legacy unit-test.svn.wordpress.org setup
+	define( 'WP_TESTS_CONFIG_PATH', WP_TESTS_DIR . '/wp-tests-config.php' );
+
+} elseif ( file_exists( dirname( dirname( WP_TESTS_DIR ) ) . '/wp-tests-config.php' ) ) {
+	// Environment variable exists and points to tests/phpunit of
+	// develop.svn.wordpress.org setup
+	define( 'WP_TESTS_CONFIG_PATH', dirname( dirname( WP_TESTS_DIR ) ) . '/wp-tests-config.php' );
+
+} else {
+	die( "wp-tests-config.php could not be found.\n" );
+}
Index: /tags/2.4.3/tests/phpunit/includes/factory.php
===================================================================
--- /tags/2.4.3/tests/phpunit/includes/factory.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/includes/factory.php	(revision 10410)
@@ -0,0 +1,261 @@
+<?php
+class BP_UnitTest_Factory extends WP_UnitTest_Factory {
+	public $activity = null;
+
+	function __construct() {
+		parent::__construct();
+
+		$this->user = new BP_UnitTest_Factory_For_User( $this );
+		$this->activity = new BP_UnitTest_Factory_For_Activity( $this );
+		$this->group = new BP_UnitTest_Factory_For_Group( $this );
+		$this->message = new BP_UnitTest_Factory_For_Message( $this );
+		$this->xprofile_group = new BP_UnitTest_Factory_For_XProfileGroup( $this );
+		$this->xprofile_field = new BP_UnitTest_Factory_For_XProfileField( $this );
+		$this->notification = new BP_UnitTest_Factory_For_Notification( $this );
+		$this->signup = new BP_UnitTest_Factory_For_Signup( $this );
+	}
+}
+
+class BP_UnitTest_Factory_For_User extends WP_UnitTest_Factory_For_User {
+	/**
+	 * When creating a new user, it's almost always necessary to have the
+	 * last_activity usermeta set right away, so that the user shows up in
+	 * directory queries. This is a shorthand wrapper for the user factory
+	 * create() method.
+	 *
+	 * Also set a display name
+	 */
+	public function create_object( $args ) {
+		$r = wp_parse_args( $args, array(
+			'role' => 'subscriber',
+			'last_activity' => date( 'Y-m-d H:i:s', strtotime( bp_core_current_time() ) - 60*60*24*365 ),
+		) );
+
+		$last_activity = $r['last_activity'];
+		unset( $r['last_activity'] );
+
+		$user_id = wp_insert_user( $r );
+
+		bp_update_user_last_activity( $user_id, $last_activity );
+
+		if ( bp_is_active( 'xprofile' ) ) {
+			$user = new WP_User( $user_id );
+			xprofile_set_field_data( 1, $user_id, $user->display_name );
+		}
+
+		return $user_id;
+	}
+}
+
+class BP_UnitTest_Factory_For_Activity extends WP_UnitTest_Factory_For_Thing {
+
+	function __construct( $factory = null ) {
+		parent::__construct( $factory );
+
+		$this->default_generation_definitions = array(
+			'component'    => buddypress()->activity->id,
+			'content'      => new WP_UnitTest_Generator_Sequence( 'Activity content %s' ),
+			'primary_link' => 'http://example.com',
+			'type'         => 'activity_update',
+			'recorded_time' => bp_core_current_time(),
+		);
+	}
+
+	function create_object( $args ) {
+		if ( ! isset( $args['user_id'] ) )
+			$args['user_id'] = get_current_user_id();
+
+		return bp_activity_add( $args );
+	}
+
+	function update_object( $activity_id, $fields ) {
+		$activity = new BP_Activity_Activity( $activity_id );
+
+		foreach ( $fields as $field_name => $value ) {
+			if ( isset( $activity->$field_name ) )
+				$activity->$field_name = $value;
+		}
+
+		$activity->save();
+		return $activity;
+	}
+
+	function get_object_by_id( $user_id ) {
+		return new BP_Activity_Activity( $user_id );
+	}
+}
+
+class BP_UnitTest_Factory_For_Group extends WP_UnitTest_Factory_For_Thing {
+
+	function __construct( $factory = null ) {
+		parent::__construct( $factory );
+
+		$this->default_generation_definitions = array(
+			'name'         => new WP_UnitTest_Generator_Sequence( 'Group %s' ),
+			'description'  => new WP_UnitTest_Generator_Sequence( 'Group description %s' ),
+			'slug'         => new WP_UnitTest_Generator_Sequence( 'group-slug-%s' ),
+			'status'       => 'public',
+			'enable_forum' => true,
+			'date_created' => bp_core_current_time(),
+		);
+	}
+
+	function create_object( $args ) {
+		if ( ! isset( $args['creator_id'] ) ) {
+			if ( is_user_logged_in() ) {
+				$args['creator_id'] = get_current_user_id();
+
+			// Create a user. This is based on from BP_UnitTestCase->create_user().
+			} else {
+				$last_activity      = date( 'Y-m-d H:i:s', strtotime( bp_core_current_time() ) - 60 * 60 * 24 * 365 );
+				$user_factory       = new WP_UnitTest_Factory_For_User();
+				$args['creator_id'] = $this->factory->user->create( array( 'role' => 'subscriber' ) );
+
+				bp_update_user_last_activity( $args['creator_id'] , $last_activity );
+
+				if ( bp_is_active( 'xprofile' ) ) {
+					$user = new WP_User( $args['creator_id']  );
+					xprofile_set_field_data( 1, $args['creator_id'] , $user->display_name );
+				}
+			}
+		}
+
+		$group_id = groups_create_group( $args );
+		if ( ! $group_id ) {
+			return false;
+		}
+
+		groups_update_groupmeta( $group_id, 'total_member_count', 1 );
+		$last_activity = isset( $args['last_activity'] ) ? $args['last_activity'] : bp_core_current_time();
+		groups_update_groupmeta( $group_id, 'last_activity', $last_activity );
+
+		return $group_id;
+	}
+
+	function update_object( $group_id, $fields ) {
+		$group = new BP_Groups_Group( $group_id );
+
+		foreach ( $fields as $field_name => $value ) {
+			if ( isset( $group->field_name ) )
+				$group->field_name = $value;
+		}
+
+		$group->save();
+		return $group;
+	}
+
+	function get_object_by_id( $group_id ) {
+		return new BP_Groups_Group( $group_id );
+	}
+}
+
+class BP_UnitTest_Factory_For_Message extends WP_UnitTest_Factory_For_Thing {
+
+	function __construct( $factory = null ) {
+		parent::__construct( $factory );
+
+		$this->default_generation_definitions = array(
+			'sender_id'  => get_current_user_id(),
+			'thread_id'  => 0,
+			'recipients' => array(),
+			'subject'    => new WP_UnitTest_Generator_Sequence( 'Message subject %s' ),
+			'content'    => new WP_UnitTest_Generator_Sequence( 'Message content %s' ),
+			'date_sent'  => bp_core_current_time(),
+		);
+	}
+
+	function create_object( $args ) {
+		$message_id = messages_new_message( $args );
+		return $message_id;
+	}
+
+	function update_object( $group_id, $fields ) {
+		// todo
+	}
+
+	function get_object_by_id( $group_id ) {
+		// todo
+	}
+}
+
+class BP_UnitTest_Factory_For_XProfileGroup extends WP_UnitTest_Factory_For_Thing {
+
+	function __construct( $factory = null ) {
+		parent::__construct( $factory );
+
+		$this->default_generation_definitions = array(
+			'name'         => new WP_UnitTest_Generator_Sequence( 'XProfile group %s' ),
+			'description'  => new WP_UnitTest_Generator_Sequence( 'XProfile group description %s' ),
+			'slug'         => new WP_UnitTest_Generator_Sequence( 'xprofile-group-slug-%s' ),
+		);
+	}
+
+	function create_object( $args ) {
+		$group_id = xprofile_insert_field_group( $args );
+		return $group_id;
+	}
+
+	function update_object( $group_id, $fields ) {
+	}
+
+	function get_object_by_id( $group_id ) {
+		return new BP_XProfile_Group( $group_id );
+	}
+}
+
+class BP_UnitTest_Factory_For_XProfileField extends WP_UnitTest_Factory_For_Thing {
+
+	function __construct( $factory = null ) {
+		parent::__construct( $factory );
+
+		$this->default_generation_definitions = array(
+			'name'        => new WP_UnitTest_Generator_Sequence( 'XProfile field %s' ),
+			'description' => new WP_UnitTest_Generator_Sequence( 'XProfile field description %s' ),
+			'type'        => 'textbox',
+		);
+	}
+
+	function create_object( $args ) {
+		$field_id = xprofile_insert_field( $args );
+		return $field_id;
+	}
+
+	function update_object( $field_id, $fields ) {
+	}
+
+	function get_object_by_id( $field_id ) {
+		return new BP_XProfile_Field( $field_id );
+	}
+}
+
+class BP_UnitTest_Factory_For_Notification extends WP_UnitTest_Factory_For_Thing {
+	public function __construct( $factory = null ) {
+		parent::__construct( $factory );
+	}
+
+	public function create_object( $args ) {
+		return bp_notifications_add_notification( $args );
+	}
+
+	public function update_object( $id, $fields ) {}
+
+	public function get_object_by_id( $id ) {
+		return new BP_Notifications_Notification( $id );
+	}
+}
+
+class BP_UnitTest_Factory_For_Signup extends WP_UnitTest_Factory_For_Thing {
+	public function __construct( $factory = null ) {
+		parent::__construct( $factory );
+	}
+
+	public function create_object( $args ) {
+		return BP_Signup::add( $args );
+	}
+
+	public function update_object( $id, $fields ) {}
+
+	public function get_object_by_id( $id ) {
+		return new BP_Signup( $id );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/includes/install.php
===================================================================
--- /tags/2.4.3/tests/phpunit/includes/install.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/includes/install.php	(revision 10410)
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Installs BuddyPress for the purpose of the unit-tests
+ *
+ * @todo Reuse the init/load code in init.php
+ * @todo Support MULTIBLOG
+ */
+error_reporting( E_ALL & ~E_DEPRECATED & ~E_STRICT );
+
+$config_file_path = $argv[1];
+$tests_dir_path = $argv[2];
+$multisite = ! empty( $argv[3] );
+
+require_once $config_file_path;
+require_once $tests_dir_path . '/includes/functions.php';
+
+function _load_buddypress() {
+	require dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) . '/src/bp-loader.php';
+}
+tests_add_filter( 'muplugins_loaded', '_load_buddypress' );
+
+define( 'BP_PLUGIN_DIR', dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) . '/src/' );
+define( 'BP_ROOT_BLOG', 1 );
+
+// Always load admin bar
+tests_add_filter( 'show_admin_bar', '__return_true' );
+
+function wp_test_bp_install( $value ) {
+	return array( 'activity' => 1, 'blogs' => 1, 'friends' => 1, 'groups' => 1, 'members' => 1, 'messages' => 1, 'notifications' => 1, 'settings' => 1, 'xprofile' => 1, );
+}
+tests_add_filter( 'bp_new_install_default_components', 'wp_test_bp_install' );
+
+tests_add_filter( 'bp_get_signup_allowed', '__return_true' );
+
+$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
+$_SERVER['HTTP_HOST'] = WP_TESTS_DOMAIN;
+$PHP_SELF = $GLOBALS['PHP_SELF'] = $_SERVER['PHP_SELF'] = '/index.php';
+
+require_once ABSPATH . '/wp-settings.php';
+
+echo "Installing BuddyPress...\n";
+
+$wpdb->query( 'SET storage_engine = INNODB' );
+$wpdb->select( DB_NAME, $wpdb->dbh );
+
+// Drop BuddyPress tables.
+foreach ( $wpdb->get_col( "SHOW TABLES LIKE '" . $wpdb->prefix . "bp%'" ) as $bp_table ) {
+	$wpdb->query( "DROP TABLE {$bp_table}" );
+}
+
+// Install BuddyPress
+bp_version_updater();
Index: /tags/2.4.3/tests/phpunit/includes/loader.php
===================================================================
--- /tags/2.4.3/tests/phpunit/includes/loader.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/includes/loader.php	(revision 10410)
@@ -0,0 +1,9 @@
+<?php
+
+require_once( dirname( __FILE__ ) . '/define-constants.php' );
+
+$multisite = (int) ( defined( 'WP_TESTS_MULTISITE') && WP_TESTS_MULTISITE );
+system( WP_PHP_BINARY . ' ' . escapeshellarg( dirname( __FILE__ ) . '/install.php' ) . ' ' . escapeshellarg( WP_TESTS_CONFIG_PATH ) . ' ' . escapeshellarg( WP_TESTS_DIR ) . ' ' . $multisite );
+
+// Bootstrap BP
+require dirname( __FILE__ ) . '/../../../src/bp-loader.php';
Index: /tags/2.4.3/tests/phpunit/includes/testcase.php
===================================================================
--- /tags/2.4.3/tests/phpunit/includes/testcase.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/includes/testcase.php	(revision 10410)
@@ -0,0 +1,585 @@
+<?php
+
+require_once dirname( __FILE__ ) . '/factory.php';
+
+class BP_UnitTestCase extends WP_UnitTestCase {
+
+	protected $temp_has_bp_moderate = array();
+	protected static $cached_SERVER_NAME = null;
+
+	/**
+	 * A flag indicating whether an autocommit has been detected inside of a test.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @var bool
+	 */
+	protected $autocommitted = false;
+
+	/**
+	 * A list of components that have been deactivated during a test.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @var array
+	 */
+	protected $deactivated_components = array();
+
+	public static function setUpBeforeClass() {
+
+		/*
+		 * WP's test suite wipes out BP's directory page mappings with `_delete_all_posts()`.
+		 * We must reestablish them before our tests can be successfully run.
+		 */
+		bp_core_add_page_mappings( bp_get_option( 'bp-active-components' ), 'delete' );
+
+		// Fake WP mail globals, to avoid errors
+		add_filter( 'wp_mail', array( 'BP_UnitTestCase', 'setUp_wp_mail' ) );
+		add_filter( 'wp_mail_from', array( 'BP_UnitTestCase', 'tearDown_wp_mail' ) );
+	}
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->factory = new BP_UnitTest_Factory;
+
+		// Fixes warnings in multisite functions
+		$_SERVER['REMOTE_ADDR'] = '';
+		global $wpdb;
+
+		// Clean up after autocommits.
+		add_action( 'bp_blogs_recorded_existing_blogs', array( $this, 'set_autocommit_flag' ) );
+	}
+
+	public function tearDown() {
+		global $wpdb;
+
+		remove_action( 'bp_blogs_recorded_existing_blogs', array( $this, 'set_autocommit_flag' ) );
+
+		parent::tearDown();
+
+		// If we detect that a COMMIT has been triggered during the test, clean up blog and user fixtures.
+		if ( $this->autocommitted ) {
+			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();
+
+		// Reactivate any components that have been deactivated.
+		foreach ( $this->deactivated_components as $component ) {
+			buddypress()->active_components[ $component ] = 1;
+		}
+		$this->deactivated_components = array();
+	}
+
+	function clean_up_global_scope() {
+		buddypress()->bp_nav                = buddypress()->bp_options_nav = buddypress()->action_variables = buddypress()->canonical_stack = buddypress()->unfiltered_uri = $GLOBALS['bp_unfiltered_uri'] = array();
+		buddypress()->current_component     = buddypress()->current_item = buddypress()->current_action = buddypress()->current_member_type = '';
+		buddypress()->unfiltered_uri_offset = 0;
+		buddypress()->is_single_item        = false;
+		buddypress()->current_user          = new stdClass();
+		buddypress()->displayed_user        = new stdClass();
+		buddypress()->loggedin_user         = new stdClass();
+		buddypress()->pages                 = array();
+
+		parent::clean_up_global_scope();
+	}
+
+	function assertPreConditions() {
+		parent::assertPreConditions();
+
+		// Reinit some of the globals that might have been cleared by BP_UnitTestCase::clean_up_global_scope().
+		// This is here because it didn't work in clean_up_global_scope(); I don't know why.
+		do_action( 'bp_setup_globals' );
+	}
+
+	function go_to( $url ) {
+		global $wpdb;
+		global $current_site, $current_blog, $blog_id, $switched, $_wp_switched_stack, $public, $table_prefix, $current_user, $wp_roles;
+
+		// note: the WP and WP_Query classes like to silently fetch parameters
+		// from all over the place (globals, GET, etc), which makes it tricky
+		// to run them more than once without very carefully clearing everything
+		$_GET = $_POST = array();
+		foreach (array('query_string', 'id', 'postdata', 'authordata', 'day', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages', 'pagenow') as $v) {
+			if ( isset( $GLOBALS[$v] ) ) unset( $GLOBALS[$v] );
+		}
+		$parts = parse_url($url);
+		if (isset($parts['scheme'])) {
+			// set the HTTP_HOST
+			$GLOBALS['_SERVER']['HTTP_HOST'] = $parts['host'];
+
+			$req = $parts['path'];
+			if (isset($parts['query'])) {
+				$req .= '?' . $parts['query'];
+				// parse the url query vars into $_GET
+				parse_str($parts['query'], $_GET);
+			}
+		} else {
+			$req = $url;
+		}
+		if ( ! isset( $parts['query'] ) ) {
+			$parts['query'] = '';
+		}
+
+		// Scheme
+		if ( 0 === strpos( $req, '/wp-admin' ) && force_ssl_admin() ) {
+			$_SERVER['HTTPS'] = 'on';
+		} else {
+			unset( $_SERVER['HTTPS'] );
+		}
+
+		// Set this for bp_core_set_uri_globals()
+		$GLOBALS['_SERVER']['REQUEST_URI'] = $req;
+		unset($_SERVER['PATH_INFO']);
+
+		// setup $current_site and $current_blog globals for multisite based on
+		// REQUEST_URI; mostly copied from /wp-includes/ms-settings.php
+		if ( is_multisite() ) {
+			$current_blog = $current_site = $blog_id = null;
+
+			$domain = addslashes( $_SERVER['HTTP_HOST'] );
+			if ( false !== strpos( $domain, ':' ) ) {
+				if ( substr( $domain, -3 ) == ':80' ) {
+					$domain = substr( $domain, 0, -3 );
+					$_SERVER['HTTP_HOST'] = substr( $_SERVER['HTTP_HOST'], 0, -3 );
+				} elseif ( substr( $domain, -4 ) == ':443' ) {
+					$domain = substr( $domain, 0, -4 );
+					$_SERVER['HTTP_HOST'] = substr( $_SERVER['HTTP_HOST'], 0, -4 );
+				}
+			}
+			$path = stripslashes( $_SERVER['REQUEST_URI'] );
+
+			// Get a cleaned-up version of the wp_version string
+			// (strip -src, -alpha, etc which may trip up version_compare())
+			$wp_version = (float) $GLOBALS['wp_version'];
+			if ( version_compare( $wp_version, '4.4', '>=' ) ) {
+				if ( ! $current_site = wp_cache_get( 'current_network', 'site-options' ) ) {
+					// Are there even two networks installed?
+					$one_network = $wpdb->get_row( "SELECT * FROM $wpdb->site LIMIT 2" ); // [sic]
+					if ( 1 === $wpdb->num_rows ) {
+						$current_site = new WP_Network( $one_network );
+						wp_cache_add( 'current_network', $current_site, 'site-options' );
+					} elseif ( 0 === $wpdb->num_rows ) {
+						ms_not_installed( $domain, $path );
+					}
+				}
+				if ( empty( $current_site ) ) {
+					$current_site = WP_Network::get_by_path( $domain, $path, 1 );
+				}
+
+				// The network declared by the site trumps any constants.
+				if ( $current_blog && $current_blog->site_id != $current_site->id ) {
+					$current_site = WP_Network::get_instance( $current_blog->site_id );
+				}
+
+				if ( empty( $current_site ) ) {
+					do_action( 'ms_network_not_found', $domain, $path );
+
+					ms_not_installed( $domain, $path );
+				} elseif ( $path === $current_site->path ) {
+					$current_blog = get_site_by_path( $domain, $path );
+				} else {
+					// Search the network path + one more path segment (on top of the network path).
+					$current_blog = get_site_by_path( $domain, $path, substr_count( $current_site->path, '/' ) );
+				}
+
+				// Figure out the current network's main site.
+				if ( empty( $current_site->blog_id ) ) {
+					if ( $current_blog->domain === $current_site->domain && $current_blog->path === $current_site->path ) {
+						$current_site->blog_id = $current_blog->blog_id;
+					} elseif ( ! $current_site->blog_id = wp_cache_get( 'network:' . $current_site->id . ':main_site', 'site-options' ) ) {
+						$current_site->blog_id = $wpdb->get_var( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs WHERE domain = %s AND path = %s",
+							$current_site->domain, $current_site->path ) );
+						wp_cache_add( 'network:' . $current_site->id . ':main_site', $current_site->blog_id, 'site-options' );
+					}
+				}
+
+				$blog_id = $current_blog->blog_id;
+				$public  = $current_blog->public;
+
+				if ( empty( $current_blog->site_id ) ) {
+					// This dates to [MU134] and shouldn't be relevant anymore,
+					// but it could be possible for arguments passed to insert_blog() etc.
+					$current_blog->site_id = 1;
+				}
+
+				$site_id = $current_blog->site_id;
+				wp_load_core_site_options( $site_id );
+
+			} elseif ( version_compare( $wp_version, '3.9', '>=' ) ) {
+
+				if ( is_admin() ) {
+					$path = preg_replace( '#(.*)/wp-admin/.*#', '$1/', $path );
+				}
+				list( $path ) = explode( '?', $path );
+
+				// Are there even two networks installed?
+				$one_network = $wpdb->get_row( "SELECT * FROM $wpdb->site LIMIT 2" ); // [sic]
+				if ( 1 === $wpdb->num_rows ) {
+					$current_site = wp_get_network( $one_network );
+				} elseif ( 0 === $wpdb->num_rows ) {
+					ms_not_installed();
+				}
+
+				if ( empty( $current_site ) ) {
+					$current_site = get_network_by_path( $domain, $path, 1 );
+				}
+
+				if ( empty( $current_site ) ) {
+					ms_not_installed();
+				} elseif ( $path === $current_site->path ) {
+					$current_blog = get_site_by_path( $domain, $path );
+				} else {
+					// Search the network path + one more path segment (on top of the network path).
+					$current_blog = get_site_by_path( $domain, $path, substr_count( $current_site->path, '/' ) );
+				}
+
+				// The network declared by the site trumps any constants.
+				if ( $current_blog && $current_blog->site_id != $current_site->id ) {
+					$current_site = wp_get_network( $current_blog->site_id );
+				}
+
+				// If we don't have a network by now, we have a problem.
+				if ( empty( $current_site ) ) {
+					ms_not_installed();
+				}
+
+				// @todo What if the domain of the network doesn't match the current site?
+				$current_site->cookie_domain = $current_site->domain;
+				if ( 'www.' === substr( $current_site->cookie_domain, 0, 4 ) ) {
+					$current_site->cookie_domain = substr( $current_site->cookie_domain, 4 );
+				}
+
+				// Figure out the current network's main site.
+				if ( ! isset( $current_site->blog_id ) ) {
+					if ( $current_blog && $current_blog->domain === $current_site->domain && $current_blog->path === $current_site->path ) {
+						$current_site->blog_id = $current_blog->blog_id;
+					} else {
+						// @todo we should be able to cache the blog ID of a network's main site easily.
+						$current_site->blog_id = $wpdb->get_var( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs WHERE domain = %s AND path = %s",
+							$current_site->domain, $current_site->path ) );
+					}
+				}
+
+				$blog_id = $current_blog->blog_id;
+				$public  = $current_blog->public;
+
+				if ( empty( $current_blog->site_id ) ) {
+					// This dates to [MU134] and shouldn't be relevant anymore,
+					// but it could be possible for arguments passed to insert_blog() etc.
+					$current_blog->site_id = 1;
+				}
+
+				$site_id = $current_blog->site_id;
+				wp_load_core_site_options( $site_id );
+
+
+			// Pre WP 3.9
+			} else {
+
+				$domain = rtrim( $domain, '.' );
+				$cookie_domain = $domain;
+				if ( substr( $cookie_domain, 0, 4 ) == 'www.' )
+					$cookie_domain = substr( $cookie_domain, 4 );
+
+				$path = preg_replace( '|([a-z0-9-]+.php.*)|', '', $GLOBALS['_SERVER']['REQUEST_URI'] );
+				$path = str_replace ( '/wp-admin/', '/', $path );
+				$path = preg_replace( '|(/[a-z0-9-]+?/).*|', '$1', $path );
+
+				$GLOBALS['current_site'] = wpmu_current_site();
+				if ( ! isset( $GLOBALS['current_site']->blog_id ) && ! empty( $GLOBALS['current_site'] ) )
+					$GLOBALS['current_site']->blog_id = $wpdb->get_var( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs WHERE domain = %s AND path = %s", $GLOBALS['current_site']->domain, $GLOBALS['current_site']->path ) );
+
+				$blogname = htmlspecialchars( substr( $GLOBALS['_SERVER']['REQUEST_URI'], strlen( $path ) ) );
+				if ( false !== strpos( $blogname, '/' ) )
+					$blogname = substr( $blogname, 0, strpos( $blogname, '/' ) );
+				if ( false !== strpos( $blogname, '?' ) )
+					$blogname = substr( $blogname, 0, strpos( $blogname, '?' ) );
+				$reserved_blognames = array( 'page', 'comments', 'blog', 'wp-admin', 'wp-includes', 'wp-content', 'files', 'feed' );
+				if ( $blogname != '' && ! in_array( $blogname, $reserved_blognames ) && ! is_file( $blogname ) )
+					$path .= $blogname . '/';
+
+				$GLOBALS['current_blog'] = get_blog_details( array( 'domain' => $domain, 'path' => $path ), false );
+
+				unset($reserved_blognames);
+
+				if ( $GLOBALS['current_site'] && ! $GLOBALS['current_blog'] ) {
+					$GLOBALS['current_blog'] = get_blog_details( array( 'domain' => $GLOBALS['current_site']->domain, 'path' => $GLOBALS['current_site']->path ), false );
+				}
+
+				$GLOBALS['blog_id'] = $GLOBALS['current_blog']->blog_id;
+			}
+
+			// Emulate a switch_to_blog()
+			$table_prefix = $wpdb->get_blog_prefix( $current_blog->blog_id );
+			$wpdb->set_blog_id( $current_blog->blog_id, $current_blog->site_id );
+			$_wp_switched_stack = array();
+			$switched = false;
+
+			if ( ! isset( $current_site->site_name ) ) {
+				$current_site->site_name = get_site_option( 'site_name' );
+				if ( ! $current_site->site_name ) {
+					$current_site->site_name = ucfirst( $current_site->domain );
+				}
+			}
+		}
+
+		$this->flush_cache();
+		unset($GLOBALS['wp_query'], $GLOBALS['wp_the_query']);
+		$GLOBALS['wp_the_query'] = new WP_Query();
+		$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
+		$GLOBALS['wp'] = new WP();
+
+		// clean out globals to stop them polluting wp and wp_query
+		foreach ($GLOBALS['wp']->public_query_vars as $v) {
+			unset($GLOBALS[$v]);
+		}
+		foreach ($GLOBALS['wp']->private_query_vars as $v) {
+			unset($GLOBALS[$v]);
+		}
+
+		$GLOBALS['wp']->main($parts['query']);
+
+		$wp_roles->reinit();
+		$current_user = wp_get_current_user();
+		$current_user->for_blog( $blog_id );
+
+		// For BuddyPress, James.
+		$this->clean_up_global_scope();
+		$GLOBALS['bp']->loggedin_user = NULL;
+		$GLOBALS['bp']->pages = bp_core_get_directory_pages();
+		do_action( 'bp_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 ) {
+		$bp = buddypress();
+
+		$bp->loggedin_user->id = $user_id;
+		$bp->loggedin_user->fullname       = bp_core_get_user_displayname( $user_id );
+		$bp->loggedin_user->is_super_admin = $bp->loggedin_user->is_site_admin = is_super_admin( $user_id );
+		$bp->loggedin_user->domain         = bp_core_get_user_domain( $user_id );
+		$bp->loggedin_user->userdata       = bp_core_get_core_userdata( $user_id );
+
+		wp_set_current_user( $user_id );
+	}
+
+	public static function add_user_to_group( $user_id, $group_id, $args = array() ) {
+		$r = wp_parse_args( $args, array(
+			'date_modified' => bp_core_current_time(),
+			'is_confirmed'  => 1,
+			'is_admin'      => 0,
+			'is_mod'        => 0,
+			'invite_sent'   => 0,
+			'inviter_id'    => 0,
+		) );
+
+		$new_member                = new BP_Groups_Member;
+		$new_member->group_id      = $group_id;
+		$new_member->user_id       = $user_id;
+		$new_member->inviter_id    = 0;
+		$new_member->is_admin      = $r['is_admin'];
+		$new_member->is_mod        = $r['is_mod'];
+		$new_member->user_title    = '';
+		$new_member->date_modified = $r['date_modified'];
+		$new_member->is_confirmed  = $r['is_confirmed'];
+		$new_member->invite_sent   = $r['invite_sent'];
+		$new_member->inviter_id    = $r['inviter_id'];
+
+		$new_member->save();
+		return $new_member->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;
+	}
+
+	public function restore_admins() {
+		// We assume that the global can be wiped out
+		// @see grant_super_admin()
+		unset( $GLOBALS['super_admins'] );
+	}
+
+	public function grant_bp_moderate( $user_id ) {
+		if ( ! isset( $this->temp_has_bp_moderate[ $user_id ] ) ) {
+			$this->temp_has_bp_moderate[ $user_id ] = 1;
+		}
+		add_filter( 'bp_current_user_can', array( $this, 'grant_bp_moderate_cb' ), 10, 2 );
+	}
+
+	public function revoke_bp_moderate( $user_id ) {
+		if ( isset( $this->temp_has_bp_moderate[ $user_id ] ) ) {
+			unset( $this->temp_has_bp_moderate[ $user_id ] );
+		}
+		remove_filter( 'bp_current_user_can', array( $this, 'grant_bp_moderate_cb' ), 10, 2 );
+	}
+
+	public function grant_bp_moderate_cb( $retval, $capability ) {
+		$current_user = bp_loggedin_user_id();
+		if ( ! isset( $this->temp_has_bp_moderate[ $current_user ] ) ) {
+			return $retval;
+		}
+
+		if ( 'bp_moderate' == $capability ) {
+			$retval = true;
+		}
+
+		return $retval;
+	}
+
+	/**
+	 * Go to the root blog. This helps reset globals after moving between
+	 * blogs.
+	 */
+	public function go_to_root() {
+		$blog_1_url = get_blog_option( 1, 'home' );
+		$this->go_to( str_replace( $blog_1_url, '', trailingslashit( bp_get_root_domain() ) ) );
+	}
+
+	/**
+	 * 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;' );
+	}
+
+	/**
+	 * Clean up created directories/files
+	 */
+	public function rrmdir( $dir ) {
+		// Make sure we are only removing files/dir from uploads
+		if ( 0 !== strpos( $dir, bp_core_avatar_upload_path() ) ) {
+			return;
+		}
+
+		$d = glob( $dir . '/*' );
+
+		if ( ! empty( $d ) ) {
+			foreach ( $d as $file ) {
+				if ( is_dir( $file ) ) {
+					$this->rrmdir( $file );
+				} else {
+					@unlink( $file );
+				}
+			}
+		}
+
+		@rmdir( $dir );
+	}
+
+	/**
+	 * Set a flag that an autocommit has taken place inside of a test method.
+	 *
+	 * @since 2.4.0
+	 */
+	public function set_autocommit_flag() {
+		$this->autocommitted = true;
+	}
+
+	/**
+	 * Deactivate a component for the duration of a test.
+	 *
+	 * @since 2.4.0
+	 *
+	 * @param string $component Component name.
+	 */
+	public function deactivate_component( $component ) {
+		$is_active = isset( buddypress()->active_components[ $component ] );
+
+		if ( ! isset( $component ) ) {
+			return false;
+		}
+
+		unset( buddypress()->active_components[ $component ] );
+		$this->deactivated_components[] = $component;
+	}
+
+	/**
+	 * Fake an attachment upload (doesn't actually upload a file).
+	 *
+	 * @param string $file Absolute path to valid file.
+	 * @param int $parent Optional. Post ID to attach the new post to.
+	 * @return int Attachment post ID.
+	 */
+	public function fake_attachment_upload( $file, $parent = 0 ) {
+		$mime = wp_check_filetype( $file );
+		if ( $mime ) {
+			$type = $mime['type'];
+		} else {
+			$type = '';
+		}
+
+		$url = 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . basename( $file );
+		$attachment = array(
+			'guid'           => 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/' . $url,
+			'post_content'   => '',
+			'post_mime_type' => $type,
+			'post_parent'    => $parent,
+			'post_title'     => basename( $file ),
+			'post_type'      => 'attachment',
+		);
+
+		$id = wp_insert_attachment( $attachment, $url, $parent );
+		wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $url ) );
+
+		return $id;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/multisite.xml
===================================================================
--- /tags/2.4.3/tests/phpunit/multisite.xml	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/multisite.xml	(revision 10410)
@@ -0,0 +1,17 @@
+<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>
+</phpunit>
Index: /tags/2.4.3/tests/phpunit/testcases/activity/actions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/activity/actions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/activity/actions.php	(revision 10410)
@@ -0,0 +1,159 @@
+<?php
+/**
+ * @group activity
+ */
+class BP_Tests_Activity_Actions extends BP_UnitTestCase {
+
+	/**
+	 * @group bp_activity_catch_transition_post_type_status
+	 * @group activity_tracking
+	 */
+	public function test_bp_activity_catch_transition_post_type_status_publish() {
+		$bp = buddypress();
+
+		register_post_type( 'foo', array(
+			'label'   => 'foo',
+			'public'   => true,
+			'supports' => array( 'buddypress-activity' ),
+		) );
+
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type'   => 'foo',
+		) );
+
+		$post = get_post( $post_id );
+
+		// 'new' => 'publish'
+		$this->assertTrue( $this->activity_exists_for_post( $post_id, 'new_foo' ), 'Published post type should have activity' );
+
+		_unregister_post_type( 'foo' );
+
+		// Reset globals
+		unset( $bp->activity->actions->activity->new_foo );
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * @group bp_activity_catch_transition_post_type_status
+	 * @group activity_tracking
+	 */
+	public function test_bp_activity_catch_transition_post_type_status_publish_to_publish() {
+		$bp = buddypress();
+
+		register_post_type( 'foo', array(
+			'label'   => 'foo',
+			'public'   => true,
+			'supports' => array( 'buddypress-activity' ),
+		) );
+
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type'   => 'foo',
+		) );
+
+		$post = get_post( $post_id );
+
+		// 'new' => 'publish'
+		$this->assertTrue( $this->activity_exists_for_post( $post_id, 'new_foo' ), 'Published post type should have activity' );
+
+		// Delete the activity
+		bp_activity_post_type_unpublish( $post_id, $post );
+
+		$post->post_status = 'publish';
+		$post->post_content .= ' foo';
+
+		wp_update_post( $post );
+
+		$this->assertFalse( $this->activity_exists_for_post( $post_id, 'new_foo' ), 'Updating a post type should not create a new activity' );
+
+		_unregister_post_type( 'foo' );
+
+		// Reset globals
+		unset( $bp->activity->actions->activity->new_foo );
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * @group bp_activity_catch_transition_post_type_status
+	 * @group activity_tracking
+	 */
+	public function test_bp_activity_catch_transition_post_type_status_publish_password() {
+		$bp = buddypress();
+
+		register_post_type( 'foo', array(
+			'label'   => 'foo',
+			'public'   => true,
+			'supports' => array( 'buddypress-activity' ),
+		) );
+
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type'   => 'foo',
+		) );
+
+		$post = get_post( $post_id );
+
+		// 'new' => 'publish'
+		$this->assertTrue( $this->activity_exists_for_post( $post_id, 'new_foo' ), 'Published post type should have activity' );
+
+		$post->post_status   = 'publish';
+		$post->post_password = 'foo';
+
+		wp_update_post( $post );
+
+		// 'publish' => 'publish' (password protected)
+		$this->assertFalse( $this->activity_exists_for_post( $post_id, 'new_foo' ), 'Password protected post type should not have activity' );
+
+		_unregister_post_type( 'foo' );
+
+		// Reset globals
+		unset( $bp->activity->actions->activity->new_foo );
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * @group bp_activity_catch_transition_post_type_status
+	 * @group activity_tracking
+	 */
+	public function test_bp_activity_catch_transition_post_type_status_publish_trash() {
+		$bp = buddypress();
+
+		register_post_type( 'foo', array(
+			'label'   => 'foo',
+			'public'   => true,
+			'supports' => array( 'buddypress-activity' ),
+		) );
+
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type'   => 'foo',
+		) );
+
+		$post = get_post( $post_id );
+
+		// 'new' => 'publish'
+		$this->assertTrue( $this->activity_exists_for_post( $post_id, 'new_foo' ), 'Published post type should have activity' );
+
+		wp_trash_post( $post->ID );
+
+		// 'publish' => 'publish' (password protected)
+		$this->assertFalse( $this->activity_exists_for_post( $post_id, 'new_foo' ), 'Unpublished post type should not have activity' );
+
+		_unregister_post_type( 'foo' );
+
+		// Reset globals
+		unset( $bp->activity->actions->activity->new_foo );
+		$bp->activity->track = array();
+	}
+
+	protected function activity_exists_for_post( $post_id, $action ) {
+		$a = bp_activity_get( array(
+			'action'            => $action,
+			'item_id'           => get_current_blog_id(),
+			'secondary_item_id' => $post_id,
+		) );
+
+		return ! empty( $a['activities'] );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/activity/cache.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/activity/cache.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/activity/cache.php	(revision 10410)
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @group activity
+ * @group cache
+ */
+class BP_Tests_Activity_Cache extends BP_UnitTestCase {
+	/**
+	 * @group bp_activity_update_meta_cache
+	 */
+	public function test_bp_activity_update_meta_cache() {
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+
+		// Set up some data
+		bp_activity_update_meta( $a1, 'foo', 'bar' );
+		bp_activity_update_meta( $a1, 'Boone', 'Rules' );
+
+		bp_activity_update_meta( $a2, 'foo', 'baz' );
+		bp_activity_update_meta( $a2, 'BuddyPress', 'Is Cool' );
+
+		// Prime the cache for $a1
+		bp_activity_get_meta( $a1, 'foo' );
+
+		// Ensure an empty cache for $a2
+		wp_cache_delete( $a2, 'activity_meta' );
+
+		bp_activity_update_meta_cache( array( $a1, $a2 ) );
+
+		$expected = array(
+			$a1 => array(
+				'foo' => array(
+					'bar',
+				),
+				'Boone' => array(
+					'Rules',
+				),
+			),
+			$a2 => array(
+				'foo' => array(
+					'baz',
+				),
+				'BuddyPress' => array(
+					'Is Cool',
+				),
+			),
+		);
+
+		$found = array(
+			$a1 => wp_cache_get( $a1, 'activity_meta' ),
+			$a2 => wp_cache_get( $a2, 'activity_meta' ),
+		);
+
+		$this->assertEquals( $expected, $found );
+	}
+
+	/**
+	 * @group bp_activity_clear_cache_for_activity
+	 */
+	public function test_bp_activity_clear_cache_for_activity() {
+		$u = $this->factory->user->create();
+
+		$a = $this->factory->activity->create( array(
+			'component'     => buddypress()->activity->id,
+			'type'          => 'activity_update',
+			'user_id'       => $u,
+			'content'       => 'foo bar',
+		) );
+
+		$a_fp = bp_activity_get( array(
+			'type' => 'activity_update',
+			'user' => array( 'filter' => array( 'user_id' => $u ) ),
+		) );
+
+		$activity_updated = new BP_Activity_Activity( $a );
+		$activity_updated->content = 'bar foo';
+		$activity_updated->save();
+
+		$a_fp = bp_activity_get( array(
+			'type' => 'activity_update',
+			'user' => array( 'filter' => array( 'user_id' => $u ) ),
+		) );
+
+		$this->assertSame( 'bar foo', $a_fp['activities'][0]->content );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/activity/class.BP_Activity_Activity.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/activity/class.BP_Activity_Activity.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/activity/class.BP_Activity_Activity.php	(revision 10410)
@@ -0,0 +1,777 @@
+<?php
+/**
+ * @group activity
+ */
+class BP_Tests_Activity_Class extends BP_UnitTestCase {
+
+	/**
+	 * @group check_exists_by_content
+	 */
+	public function test_check_exists_by_content() {
+		$content  = 'A classy girl who know how to enjoy the freedom of a cup of coffee';
+		$activity = $this->factory->activity->create( array(
+			'content' => $content,
+			'type'    => 'activity_update',
+		) );
+
+		$result = BP_Activity_Activity::check_exists_by_content( $content );
+		$this->assertEquals( $activity, $result );
+	}
+
+	/**
+	 * @group delete_activity_item_comments
+	 */
+	public function test_delete_activity_item_comments() {
+		$parent_activity = $this->factory->activity->create( array(
+			'type' => 'activity_update',
+		) );
+
+		$comments = $this->factory->activity->create_many( 3, array(
+			'item_id' => $parent_activity,
+			'type'    => 'activity_comment',
+		) );
+
+		BP_Activity_Activity::delete_activity_item_comments( $parent_activity );
+
+		$result = BP_Activity_Activity::get( array( 'in' => wp_list_pluck( $comments, 'id' ), ) );
+		$this->assertEmpty( $result['activities'] );
+	}
+
+	/**
+	 * @ticket BP4804
+	 * @group delete_activity_meta_entries
+	 * @group activitymeta
+	 */
+	public function test_delete_activity_meta_entries() {
+		$activity = $this->factory->activity->create( array(
+			'type' => 'activity_update',
+		) );
+
+		bp_activity_update_meta( $activity, 'Paul', 'is cool' );
+		BP_Activity_Activity::delete_activity_meta_entries( $activity );
+
+		$meta = bp_activity_get_meta( $activity, 'Paul' );
+		$this->assertSame( '', $meta );
+	}
+
+	/**
+	 * @group get
+	 * @group fields
+	 * @ticket BP6426
+	 */
+	public function test_get_with_fields_parameter_by_id() {
+		$a = $this->factory->activity->create_many( 3, array(
+			'type' => 'activity_update',
+		) );
+
+		$result = BP_Activity_Activity::get( array(
+			'fields' => 'ids',
+		) );
+		$this->assertEqualSets( $a, $result['activities'] );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_hide_all_for_user() {
+		$activity = $this->factory->activity->create( array(
+			'type' => 'activity_update',
+		) );
+
+		BP_Activity_Activity::hide_all_for_user( get_current_user_id() );
+
+		$activity = BP_Activity_Activity::get( array(
+			'in'          => $activity,
+			'show_hidden' => true,
+		) );
+		$this->assertEquals( $activity['activities'][0]->hide_sitewide, 1 );
+	}
+
+	/**
+	 * @group get
+	 * @group meta_query
+	 */
+	public function test_get_with_meta_query() {
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+		bp_activity_update_meta( $a1, 'foo', 'bar' );
+
+		$activity = BP_Activity_Activity::get( array(
+			'meta_query' => array(
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+				),
+			),
+		) );
+		$ids = wp_list_pluck( $activity['activities'], 'id' );
+		$this->assertEquals( $ids, array( $a1 ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_meta_query_two_clauses_with_or_relation() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 60 ),
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 120 ),
+		) );
+		bp_activity_update_meta( $a1, 'foo', 'bar' );
+		bp_activity_update_meta( $a2, 'foo', 'bar' );
+		bp_activity_update_meta( $a1, 'baz', 'barry' );
+
+		$activity = BP_Activity_Activity::get( array(
+			'meta_query' => array(
+				'relation' => 'OR',
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+				),
+				array(
+					'key' => 'baz',
+					'value' => 'barry',
+				),
+			),
+			'count_total' => 'count_query',
+		) );
+
+		$ids = wp_list_pluck( $activity['activities'], 'id' );
+		$this->assertEquals( array( $a1, $a2 ), $ids );
+		$this->assertEquals( 2, $activity['total'] );
+	}
+
+	/**
+	 * @group get
+	 * @group date_query
+	 * @requires PHP 5.3
+	 */
+	public function test_get_with_date_query_before() {
+		if ( ! class_exists( 'WP_Date_Query' ) ) {
+			return;
+		}
+
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create( array(
+			'recorded_time' => '2001-01-01 12:00'
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'recorded_time' => '2005-01-01 12:00'
+		) );
+
+		// 'date_query' before test
+		$query = BP_Activity_Activity::get( array(
+			'date_query' => array( array(
+				'before' => array(
+					'year'  => 2004,
+					'month' => 1,
+					'day'   => 1,
+				),
+			) )
+		) );
+		$this->assertEquals( array( $a2 ), wp_list_pluck( $query['activities'], 'id' ) );
+	}
+
+	/**
+	 * @group get
+	 * @group date_query
+	 * @requires PHP 5.3
+	 */
+	public function test_get_with_date_query_range() {
+		if ( ! class_exists( 'WP_Date_Query' ) ) {
+			return;
+		}
+
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create( array(
+			'recorded_time' => '2001-01-01 12:00'
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'recorded_time' => '2005-01-01 12:00'
+		) );
+
+		// 'date_query' range test
+		$query = BP_Activity_Activity::get( array(
+			'date_query' => array( array(
+				'after'  => 'January 2nd, 2001',
+				'before' => array(
+					'year'  => 2013,
+					'month' => 1,
+					'day'   => 1,
+				),
+				'inclusive' => true,
+			) )
+		) );
+		$this->assertEquals( array( $a3 ), wp_list_pluck( $query['activities'], 'id' ) );
+	}
+
+	/**
+	 * @group get
+	 * @group date_query
+	 * @requires PHP 5.3
+	 */
+	public function test_get_with_date_query_after() {
+		if ( ! class_exists( 'WP_Date_Query' ) ) {
+			return;
+		}
+
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create( array(
+			'recorded_time' => '2001-01-01 12:00'
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'recorded_time' => '2005-01-01 12:00'
+		) );
+
+		// 'date_query' after and relative test
+		$query = BP_Activity_Activity::get( array(
+			'date_query' => array( array(
+				'after' => '1 day ago'
+			) )
+		) );
+		$this->assertEquals( array( $a1 ), wp_list_pluck( $query['activities'], 'id' ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_search_terms() {
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Boone is a cool guy',
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'No he isn\'t',
+		) );
+
+		$activity = BP_Activity_Activity::get( array(
+			'search_terms' => 'cool',
+		) );
+		$ids = wp_list_pluck( $activity['activities'], 'id' );
+		$this->assertEquals( $ids, array( $a1 ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_display_comments_threaded() {
+		$u = $this->factory->user->create();
+
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'user_id' => $u,
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'user_id' => $u,
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = bp_activity_new_comment( array(
+			'user_id' => $u,
+			'activity_id' => $a1,
+			'content' => 'Candy is good',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+
+		$activity = BP_Activity_Activity::get( array(
+			'display_comments' => 'threaded',
+		) );
+
+		// Kinda crummy, but let's construct a skeleton
+		$expected = array(
+			$a1 => array( $a3 ),
+			$a2 => array(),
+		);
+
+		$found = array();
+		foreach ( $activity['activities'] as $a ) {
+			$found[ $a->id ] = ! empty( $a->children ) ? array_keys( $a->children ) : array();
+		}
+
+		$this->assertEquals( $expected, $found );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_display_comments_stream() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		// bp_activity_new_comment() doesn't allow date_recorded
+		$a3 = bp_activity_add( array(
+			'action'            => sprintf( __( '%s posted a new activity comment', 'buddypress' ), bp_get_loggedin_user_link() ) ,
+			'content'           => 'Candy is good',
+			'component'         => buddypress()->activity->id,
+			'type'              => 'activity_comment',
+			'user_id'           => bp_loggedin_user_id(),
+			'item_id'           => $a1,
+			'secondary_item_id' => $a1,
+			'recorded_time'     => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+
+		$activity = BP_Activity_Activity::get( array(
+			'display_comments' => 'stream',
+		) );
+		$ids = wp_list_pluck( $activity['activities'], 'id' );
+		$this->assertEquals( array( $a1, $a3, $a2 ), $ids );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_display_comments_false() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'content' => 'Candy is good',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+
+		$activity = BP_Activity_Activity::get( array(
+			'display_comments' => false,
+		) );
+		$ids = wp_list_pluck( $activity['activities'], 'id' );
+		$this->assertEquals( array( $a1, $a2 ), $ids );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_offset() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 10 ),
+		) );
+
+		$activity = BP_Activity_Activity::get( array(
+			'filter' => array(
+				'offset' => $a2,
+			),
+		) );
+		$ids = wp_list_pluck( $activity['activities'], 'id' );
+		$this->assertEquals( array( $a3, $a2 ), $ids );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_since() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 10 ),
+		) );
+
+		$activity = BP_Activity_Activity::get( array(
+			'filter' => array(
+				'since' => date( 'Y-m-d H:i:s', $now - 70 ),
+			),
+		) );
+		$ids = wp_list_pluck( $activity['activities'], 'id' );
+		$this->assertEquals( array( $a3, $a2 ), $ids );
+	}
+
+	/**
+	 * @group get
+	 * @group count_total
+	 */
+	public function test_get_with_count_total() {
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+
+		$activity = BP_Activity_Activity::get( array(
+			'count_total' => 'count_query',
+		) );
+
+		$this->assertEquals( 2, $activity['total'] );
+	}
+
+	/**
+	 * @group get
+	 * @group count_total
+	 */
+	public function test_get_with_count_total_false() {
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+
+		$activity = BP_Activity_Activity::get( array(
+			'count_total' => false,
+		) );
+
+		$this->assertSame( null, $activity['total'] );
+	}
+
+	/**
+	 * @group get
+	 * @group count_total
+	 */
+	public function test_get_with_count_total_default_to_false() {
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+
+		$activity = BP_Activity_Activity::get();
+
+		$this->assertSame( null, $activity['total'] );
+	}
+
+	/**
+	 * @group get_id
+	 */
+	public function test_get_id_with_item_id() {
+		$a1 = $this->factory->activity->create( array(
+			'item_id' => 523,
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'item_id' => 1888,
+		) );
+
+		$activity = BP_Activity_Activity::get_id( false, false, false, 523, false, false, false, false );
+		$this->assertEquals( $a1, $activity );
+	}
+
+	/**
+	 * @group get_id
+	 */
+	public function test_get_id_with_secondary_item_id() {
+		$a1 = $this->factory->activity->create( array(
+			'secondary_item_id' => 523,
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'secondary_content' => 1888,
+		) );
+
+		$activity = BP_Activity_Activity::get_id( false, false, false, false, 523, false, false, false );
+		$this->assertEquals( $a1, $activity );
+	}
+
+	/**
+	 * @group delete
+	 */
+	public function test_delete_with_item_id() {
+		$a1 = $this->factory->activity->create( array(
+			'item_id' => 523,
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'item_id' => 1888,
+		) );
+
+		$activity = BP_Activity_Activity::delete( array(
+			'item_id' => 523,
+		) );
+		$this->assertEquals( array( $a1 ), $activity );
+	}
+
+	/**
+	 * @group delete
+	 */
+	public function test_delete_with_secondary_item_id() {
+		$a1 = $this->factory->activity->create( array(
+			'secondary_item_id' => 523,
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'secondary_item_id' => 1888,
+		) );
+
+		$activity = BP_Activity_Activity::delete( array(
+			'secondary_item_id' => 523,
+		) );
+		$this->assertEquals( array( $a1 ), $activity );
+	}
+
+	/**
+	 * @group get_activity_comments
+	 *
+	 * Verify the format of the activity comments array, for internal
+	 * refactoring
+	 */
+	public function test_get_activity_comments_format() {
+		$now = time();
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+			'user_id' => $u1,
+		) );
+		$a2 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'content' => 'Candy is good',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 50 ),
+			'user_id' => $u1,
+		) );
+		$a3 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'content' => 'Bread is good',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 25 ),
+			'user_id' => $u2,
+		) );
+
+		$keys = array( 'id', 'item_id', 'secondary_item_id', 'user_id', 'primary_link', 'component', 'type', 'action', 'content', 'date_recorded', 'hide_sitewide', 'mptt_left', 'mptt_right', 'is_spam' );
+
+		$a2_obj = new BP_Activity_Activity( $a2 );
+
+		$e2 = new stdClass;
+
+		foreach ( $keys as $key ) {
+			$e2->{$key} = $a2_obj->{$key};
+		}
+
+		$e2_user = new WP_User( $a2_obj->user_id );
+
+		$e2->user_email = $e2_user->user_email;
+		$e2->user_nicename = $e2_user->user_nicename;
+		$e2->user_login = $e2_user->user_login;
+		$e2->display_name = $e2_user->display_name;
+		$e2->user_fullname = bp_core_get_user_displayname( $e2->user_id );
+		$e2->children = array();
+		$e2->depth = 1;
+
+		$a3_obj = new BP_Activity_Activity( $a3 );
+
+		$e3 = new stdClass;
+
+		foreach ( $keys as $key ) {
+			$e3->{$key} = $a3_obj->{$key};
+		}
+
+		$e3_user = new WP_User( $e3->user_id );
+
+		$e3->user_email = $e3_user->user_email;
+		$e3->user_nicename = $e3_user->user_nicename;
+		$e3->user_login = $e3_user->user_login;
+		$e3->display_name = $e3_user->display_name;
+		$e3->user_fullname = bp_core_get_user_displayname( $e3->user_id );
+		$e3->children = array();
+		$e3->depth = 1;
+
+		$expected = array(
+			$a2 => $e2,
+			$a3 => $e3,
+		);
+
+		$a1_obj = new BP_Activity_Activity( $a1 );
+		$comments = BP_Activity_Activity::get_activity_comments( $a1, $a1_obj->mptt_left, $a1_obj->mptt_right, 'ham_only', $a1 );
+
+		$this->assertEquals( $expected, $comments );
+	}
+
+	/**
+	 * @group get_last_updated
+	 */
+	public function test_get_last_updated() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 500 ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 300 ),
+		) );
+
+		$this->assertSame( date( 'Y-m-d H:i:s', $now - 100 ), BP_Activity_Activity::get_last_updated() );
+	}
+
+	/**
+	 * @group get_recorded_components
+	 */
+	public function test_get_recorded_components_skip_last_activity_false() {
+		$a1 = $this->factory->activity->create( array(
+			'component' => 'members',
+			'action' => 'last_activity',
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'component' => 'groups',
+			'action' => 'created_group',
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'component' => 'friends',
+			'action' => 'friendship_accepted',
+		) );
+
+		$found = BP_Activity_Activity::get_recorded_components( false );
+		sort( $found );
+
+		$this->assertSame( array( 'friends', 'groups', 'members' ), BP_Activity_Activity::get_recorded_components( false ) );
+	}
+
+	/**
+	 * @group get_recorded_components
+	 */
+	public function test_get_recorded_components_skip_last_activity_true_filter_empty_component() {
+		$a1 = $this->factory->activity->create( array(
+			'component' => 'members',
+			'action' => 'last_activity',
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'component' => 'groups',
+			'action' => 'created_group',
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'component' => 'friends',
+			'action' => 'friendship_accepted',
+		) );
+
+		$found = BP_Activity_Activity::get_recorded_components( true );
+		sort( $found );
+
+		$this->assertSame( array( 'friends', 'groups' ), BP_Activity_Activity::get_recorded_components() );
+	}
+
+	/**
+	 * @group get_recorded_components
+	 */
+	public function test_get_recorded_components_skip_last_activity_true_members_component_not_empty() {
+		$a1 = $this->factory->activity->create( array(
+			'component' => 'members',
+			'action' => 'last_activity',
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'component' => 'groups',
+			'action' => 'created_group',
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'component' => 'friends',
+			'action' => 'friendship_accepted',
+		) );
+		$a4 = $this->factory->activity->create( array(
+			'component' => 'members',
+			'action' => 'foo',
+		) );
+
+		$found = BP_Activity_Activity::get_recorded_components( true );
+		sort( $found );
+
+		$this->assertSame( array( 'friends', 'groups', 'members' ), BP_Activity_Activity::get_recorded_components() );
+	}
+
+	/**
+	 * @group get_recorded_components
+	 */
+	public function test_get_recorded_components_skip_last_activity_true_la_in_multiple_components() {
+		$a1 = $this->factory->activity->create( array(
+			'component' => 'members',
+			'action' => 'last_activity',
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'component' => 'groups',
+			'action' => 'created_group',
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'component' => 'friends',
+			'action' => 'friendship_accepted',
+		) );
+		$a4 = $this->factory->activity->create( array(
+			'component' => 'groups',
+			'action' => 'last_activity',
+		) );
+
+		$found = BP_Activity_Activity::get_recorded_components( true );
+		sort( $found );
+
+		$this->assertSame( array( 'friends', 'groups', ), BP_Activity_Activity::get_recorded_components() );
+	}
+	/**
+	 * @group activity_action
+	 */
+	public function test_instantiated_action_with_dynamic_callback() {
+		bp_activity_set_action(
+			'foo',
+			'bar',
+			'Foo Bar',
+			array( $this, 'action_cb' )
+		);
+
+		// Create the activity item with a manual activity string
+		$a = $this->factory->activity->create( array(
+			'component' => 'foo',
+			'type' => 'bar',
+			'action' => 'baz',
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$this->assertSame( 'Woo Hoo!', $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 */
+	public function test_instantiated_action_without_dynamic_callback_but_with_stored_action() {
+		// No callback is registered - this mimics a legacy plugin
+
+		// Create the activity item with a manual activity string
+		$a = $this->factory->activity->create( array(
+			'component' => 'foo',
+			'type' => 'bar1',
+			'action' => 'baz',
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$this->assertSame( 'baz', $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 */
+	public function test_instantiated_action_without_dynamic_callback_but_with_no_stored_action() {
+		// No callback is registered - this mimics a legacy plugin
+
+		// Create the activity item with a manual activity string
+		$a = $this->factory->activity->create( array(
+			'component' => 'foo',
+			'type' => 'bar2',
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$this->assertSame( '', $a_obj->action );
+	}
+
+	public function action_cb( $activity ) {
+		return 'Woo Hoo!';
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/activity/filters.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/activity/filters.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/activity/filters.php	(revision 10410)
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @group activity
+ */
+class BP_Tests_Activity_Filters extends BP_UnitTestCase {
+	/**
+	 * @group mentions
+	 * @group bp_activity_at_name_filter
+	 */
+	public function test_bp_activity_at_name_filter() {
+		$u1 = $this->factory->user->create( array(
+			'user_login' => 'foobarbaz',
+			'user_nicename' => 'foobarbaz',
+		) );
+
+		$u2 = $this->factory->user->create( array(
+			'user_login' => 'foo2',
+			'user_nicename' => 'foo2',
+		) );
+
+		$u1_mention_name = bp_activity_get_user_mentionname( $u1 );
+		$u1_domain = bp_core_get_user_domain( $u1 );
+		$u2_mention_name = bp_activity_get_user_mentionname( $u2 );
+		$u2_domain = bp_core_get_user_domain( $u2 );
+
+		// mentions normal text should be replaced
+		$at_name_in_text = sprintf( 'Hello @%s', $u1_mention_name );
+		$at_name_in_text_final = "Hello <a href='" . $u1_domain . "' rel='nofollow'>@$u1_mention_name</a>";
+		$this->assertEquals( $at_name_in_text_final, bp_activity_at_name_filter( $at_name_in_text ) );
+
+		// mentions inside links sholudn't be replaced
+		// inside href
+		$at_name_in_mailto = sprintf( "Send messages to <a href='mail@%s.com'>Foo Bar Baz</a>", $u1_mention_name );
+		$at_name_in_mailto_final = sprintf( "Send messages to <a href='mail@%s.com'>Foo Bar Baz</a>", $u1_mention_name );
+		$this->assertEquals( $at_name_in_mailto_final, bp_activity_at_name_filter( $at_name_in_mailto ) );
+
+		// inside linked text
+		$at_name_in_link = sprintf( '<a href="https://twitter.com/%1$s">@%1$s</a>', $u1_mention_name );
+		$at_name_in_link_final = sprintf( '<a href="https://twitter.com/%1$s">@%1$s</a>', $u1_mention_name );
+		$this->assertEquals( $at_name_in_link_final, bp_activity_at_name_filter( $at_name_in_link ) );
+
+		// Don't link non-existent users
+		$text = "Don't link @non @existent @users";
+		$this->assertSame( $text, bp_activity_at_name_filter( $text ) );
+
+		// Don't link the domain name of the site
+		preg_match( '|https?://([^/]+)|', home_url(), $matches );
+		if ( ! empty( $matches[1] ) ) {
+			$text = $matches[1] . " Don't link the domain name " . $matches[1];
+		}
+		$this->assertSame( $text, bp_activity_at_name_filter( $text ) );
+
+		// Multiples
+		$at_name_in_mailto = sprintf( "Send messages to @%s <a href='mail@%s.com'>Foo Bar Baz</a>. Please CC <a href='http://twitter.com/foo2'>@foo2</a>.", $u1_mention_name, $u1_mention_name, $u2_mention_name, $u2_mention_name );
+		$at_name_in_mailto_final = sprintf( 'Send messages to <a href=\'%s\' rel=\'nofollow\'>@%s</a> <a href=\'mail@%s.com\'>Foo Bar Baz</a>. Please CC <a href=\'http://twitter.com/%s\'>@%s</a>.', $u1_domain, $u1_mention_name, $u1_mention_name, $u2_mention_name, $u2_mention_name );
+		$this->assertEquals( $at_name_in_mailto_final, bp_activity_at_name_filter( $at_name_in_mailto ) );
+	}
+
+}
Index: /tags/2.4.3/tests/phpunit/testcases/activity/functions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/activity/functions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/activity/functions.php	(revision 10410)
@@ -0,0 +1,1402 @@
+<?php
+/**
+ * @group activity
+ */
+class BP_Tests_Activity_Functions extends BP_UnitTestCase {
+
+	/**
+	 * @ticket BP4488
+	 */
+	public function test_thumbnail_content_images() {
+		// No images
+		$post_content = 'foo bar';
+		$this->assertEquals( bp_activity_thumbnail_content_images( $post_content ), 'foo bar' );
+
+		// Image first, no caption. See #BP4488
+		$post_content = '<img src="http://example.com/foo.jpg" alt="foo" width="40" height="40" class="alignnone size-full wp-image-236" /> foo bar';
+		$this->assertEquals( bp_activity_thumbnail_content_images( $post_content ), '<img src="http://example.com/foo.jpg" width="40" height="40" alt="Thumbnail" class="align-left thumbnail" /> foo bar' );
+
+		// Image first, caption. See #BP4488
+		$post_content = '[caption id="attachment_236" align="alignnone" width="40"]<img src="http://example.com/foo.jpg" alt="FOO!" width="40" height="40" class="size-full wp-image-236" /> FOO![/caption] Awesome.';
+		$this->assertEquals( bp_activity_thumbnail_content_images( $post_content ), '<img src="http://example.com/foo.jpg" width="40" height="40" alt="Thumbnail" class="align-left thumbnail" /> Awesome.' );
+	}
+
+	/**
+	 * @group delete
+	 */
+	public function test_delete_activity_by_id() {
+
+		// create an activity update
+		$activity = $this->factory->activity->create( array(
+			'type' => 'activity_update'
+		) );
+
+		// now delete the activity item
+		bp_activity_delete( array(
+			'id' => $activity
+		) );
+
+		// now fetch the deleted activity entries
+		$get = bp_activity_get( array(
+			'id' => $activity
+		) );
+
+		// activities should equal zero
+		$this->assertEquals( 0, $get['total'] );
+	}
+
+	/**
+	 * @group delete
+	 */
+	public function test_delete_activity_by_type() {
+
+		// Setup criteria
+		$criteria = array(
+			'type' => 'activity_update'
+		);
+
+		// create an activity update
+		$this->factory->activity->create( $criteria );
+		$this->factory->activity->create( $criteria );
+		$this->factory->activity->create( $criteria );
+		$this->factory->activity->create( $criteria );
+
+		// now delete the activity items
+		bp_activity_delete( $criteria );
+
+		// now fetch the deleted activity entries
+		$get = bp_activity_get( $criteria );
+
+		// activities should equal zero
+		$this->assertEquals( 0, $get['total'] );
+	}
+
+	/**
+	 * @group delete
+	 */
+	public function test_delete_activity_by_component() {
+
+		// Setup criteria
+		$criteria = array(
+			'component' => 'xprofile'
+		);
+
+		// create an activity update
+		$this->factory->activity->create( $criteria );
+		$this->factory->activity->create( $criteria );
+		$this->factory->activity->create( $criteria );
+		$this->factory->activity->create( $criteria );
+
+		// now delete the activity items
+		bp_activity_delete( $criteria );
+
+		// now fetch the deleted activity entries
+		$get = bp_activity_get( $criteria );
+
+		// activities should equal zero
+		$this->assertEquals( 0, $get['total'] );
+	}
+
+	/**
+	 * @group delete
+	 */
+	public function test_delete_activity_by_user_id() {
+
+		// Setup criteria
+		$criteria = array(
+			'user_id' => '1'
+		);
+
+		// create an activity update
+		$this->factory->activity->create( $criteria );
+		$this->factory->activity->create( $criteria );
+		$this->factory->activity->create( $criteria );
+		$this->factory->activity->create( $criteria );
+
+		// now delete the activity items
+		bp_activity_delete( $criteria );
+
+		// now fetch the deleted activity entries
+		$get = bp_activity_get( $criteria );
+
+		// activities should equal zero
+		$this->assertEquals( 0, $get['total'] );
+	}
+
+	/**
+	 * @group delete
+	 */
+	public function test_delete_activity_meta() {
+
+		// create an activity update
+		$activity = $this->factory->activity->create( array(
+			'type' => 'activity_update'
+		) );
+
+		// add some meta to the activity items
+		bp_activity_update_meta( $activity, 'foo', 'bar' );
+
+		// now delete the parent activity item meta entry
+		bp_activity_delete_meta(  $activity, 'foo', 'bar' );
+
+		// now fetch activity meta for the deleted activity entries
+		$m1 = bp_activity_get_meta( $activity );
+
+		// test if activity meta entries still exist
+		$this->assertEmpty( $m1 );
+	}
+
+	/**
+	 * @group delete
+	 */
+	public function test_delete_activity_all_meta() {
+
+		// create an activity update
+		$activity = $this->factory->activity->create( array(
+			'type' => 'activity_update'
+		) );
+
+		// add some meta to the activity items
+		bp_activity_update_meta( $activity, 'foo1', 'bar' );
+		bp_activity_update_meta( $activity, 'foo2', 'bar' );
+		bp_activity_update_meta( $activity, 'foo3', 'bar' );
+		bp_activity_update_meta( $activity, 'foo4', 'bar' );
+		bp_activity_update_meta( $activity, 'foo5', 'bar' );
+
+		// now delete the parent activity item meta entry
+		bp_activity_delete_meta( $activity );
+
+		// now fetch activity meta for the deleted activity entries
+		$m1 = bp_activity_get_meta( $activity );
+		$m2 = bp_activity_get_meta( $activity );
+		$m3 = bp_activity_get_meta( $activity );
+		$m4 = bp_activity_get_meta( $activity );
+		$m5 = bp_activity_get_meta( $activity );
+
+		// test if activity meta entries still exist
+		$this->assertEmpty( $m1 );
+		$this->assertEmpty( $m2 );
+		$this->assertEmpty( $m3 );
+		$this->assertEmpty( $m4 );
+		$this->assertEmpty( $m5 );
+	}
+
+	/**
+	 * @group delete
+	 */
+	public function test_delete_activity_and_comments() {
+
+		// create an activity update
+		$parent_activity = $this->factory->activity->create( array(
+			'type' => 'activity_update',
+		) );
+
+		// create some activity comments
+		$comment_one = $this->factory->activity->create( array(
+			'type'              => 'activity_comment',
+			'item_id'           => $parent_activity,
+			'secondary_item_id' => $parent_activity,
+		) );
+
+		$comment_two = $this->factory->activity->create( array(
+			'type'              => 'activity_comment',
+			'item_id'           => $parent_activity,
+			'secondary_item_id' => $parent_activity,
+		) );
+
+		// now delete the parent activity item
+		// this should hopefully delete the associated comments and meta entries
+		bp_activity_delete( array(
+			'id' => $parent_activity
+		) );
+
+		// now fetch the deleted activity entries
+		$get = bp_activity_get( array(
+			'in'               => array( $parent_activity, $comment_one, $comment_two ),
+			'display_comments' => 'stream'
+		) );
+
+		// activities should equal zero
+		$this->assertEquals( 0, $get['total'] );
+	}
+
+	/**
+	 * @group delete
+	 */
+	public function test_delete_activity_meta_for_comments() {
+
+		// create an activity update
+		$parent_activity = $this->factory->activity->create( array(
+			'type' => 'activity_update',
+		) );
+
+		// create some activity comments
+		$comment_one = $this->factory->activity->create( array(
+			'type'              => 'activity_comment',
+			'item_id'           => $parent_activity,
+			'secondary_item_id' => $parent_activity,
+		) );
+
+		$comment_two = $this->factory->activity->create( array(
+			'type'              => 'activity_comment',
+			'item_id'           => $parent_activity,
+			'secondary_item_id' => $parent_activity,
+		) );
+
+		// add some meta to the activity items
+		bp_activity_update_meta( $parent_activity, 'foo', 'bar' );
+		bp_activity_update_meta( $comment_one,     'foo', 'bar' );
+		bp_activity_update_meta( $comment_two,     'foo', 'bar' );
+
+		// now delete the parent activity item
+		// this should hopefully delete the associated comments and meta entries
+		bp_activity_delete( array(
+			'id' => $parent_activity
+		) );
+
+		// now fetch activity meta for the deleted activity entries
+		$m1 = bp_activity_get_meta( $parent_activity );
+		$m2 = bp_activity_get_meta( $comment_one );
+		$m3 = bp_activity_get_meta( $comment_two );
+
+		// test if activity meta entries still exist
+		$this->assertEmpty( $m1 );
+		$this->assertEmpty( $m2 );
+		$this->assertEmpty( $m3 );
+	}
+
+	/**
+	 * @group bp_activity_update_meta
+	 * @ticket BP5180
+	 */
+	public function test_bp_activity_update_meta_with_line_breaks() {
+		$a = $this->factory->activity->create();
+		$meta_value = 'Foo!
+
+
+Bar!';
+		bp_activity_update_meta( $a, 'linebreak_test', $meta_value );
+		$this->assertEquals( $meta_value, bp_activity_get_meta( $a, 'linebreak_test' ) );
+	}
+
+	/**
+	 * @group bp_activity_update_meta
+	 * @ticket BP5083
+	 */
+	public function test_bp_activity_update_meta_with_0() {
+		$a = $this->factory->activity->create();
+		$meta_value = 0;
+
+		bp_activity_update_meta( $a, '0_test', $meta_value );
+
+		$this->assertNotSame( false, bp_activity_get_meta( $a, '0_test' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_update_meta
+	 */
+	public function test_bp_activity_update_meta_non_numeric_id() {
+		$this->assertFalse( bp_activity_update_meta( 'foo', 'bar', 'baz' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_update_meta
+	 * @ticket BP5399
+	 */
+	public function test_bp_activity_update_meta_with_illegal_key_characters() {
+		$a = $this->factory->activity->create();
+		$krazy_key = ' f!@#$%^o *(){}o?+';
+		bp_activity_update_meta( $a, $krazy_key, 'bar' );
+
+		$this->assertSame( '', bp_activity_get_meta( $a, 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_update_meta
+	 */
+	public function test_bp_activity_update_meta_stripslashes() {
+		$a = $this->factory->activity->create();
+		$value = "This string is totally slashin\'!";
+		bp_activity_update_meta( $a, 'foo', $value );
+
+		$this->assertSame( stripslashes( $value ), bp_activity_get_meta( $a, 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_update_meta
+	 */
+	public function test_bp_activity_update_meta_false_value_deletes() {
+		$a = $this->factory->activity->create();
+		bp_activity_update_meta( $a, 'foo', false );
+		$this->assertSame( '', bp_activity_get_meta( $a, 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_update_meta
+	 */
+	public function test_bp_activity_update_meta_new() {
+		$a = $this->factory->activity->create();
+		$this->assertSame( '', bp_activity_get_meta( $a, 'foo' ), '"foo" meta should be empty for this activity item.' );
+		$this->assertNotEmpty( bp_activity_update_meta( $a, 'foo', 'bar' ) );
+		$this->assertSame( 'bar', bp_activity_get_meta( $a, 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_update_meta
+	 */
+	public function test_bp_activity_update_meta_existing() {
+		$a = $this->factory->activity->create();
+		bp_activity_update_meta( $a, 'foo', 'bar' );
+		$this->assertSame( 'bar', bp_activity_get_meta( $a, 'foo' ) );
+		$this->assertTrue( bp_activity_update_meta( $a, 'foo', 'baz' ) );
+		$this->assertSame( 'baz', bp_activity_get_meta( $a, 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_update_meta
+	 */
+	public function test_bp_activity_update_meta_same_value() {
+		$a = $this->factory->activity->create();
+		bp_activity_update_meta( $a, 'foo', 'bar' );
+		$this->assertSame( 'bar', bp_activity_get_meta( $a, 'foo' ) );
+		$this->assertFalse( bp_activity_update_meta( $a, 'foo', 'bar' ) );
+		$this->assertSame( 'bar', bp_activity_get_meta( $a, 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_update_meta
+	 */
+	public function test_bp_activity_update_meta_prev_value() {
+		$a = $this->factory->activity->create();
+		bp_activity_add_meta( $a, 'foo', 'bar' );
+
+		// In earlier versions of WordPress, bp_activity_update_meta()
+		// returns true even on failure. However, we know that in these
+		// cases the update is failing as expected, so we skip this
+		// assertion just to keep our tests passing
+		// See https://core.trac.wordpress.org/ticket/24933
+		if ( version_compare( $GLOBALS['wp_version'], '3.7', '>=' ) ) {
+			$this->assertFalse( bp_activity_update_meta( $a, 'foo', 'bar2', 'baz' ) );
+		}
+
+		$this->assertTrue( bp_activity_update_meta( $a, 'foo', 'bar2', 'bar' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_get_meta
+	 */
+	public function test_bp_activity_get_meta_empty_activity_id() {
+		$this->assertFalse( bp_activity_get_meta( 0 ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_get_meta
+	 */
+	public function test_bp_activity_get_meta_non_numeric_activity_id() {
+		$this->assertFalse( bp_activity_get_meta( 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_get_meta
+	 * @ticket BP5399
+	 */
+	public function test_bp_activity_get_meta_with_illegal_characters() {
+		$a = $this->factory->activity->create();
+		bp_activity_update_meta( $a, 'foo', 'bar' );
+
+		$krazy_key = ' f!@#$%^o *(){}o?+';
+		$this->assertSame( '', bp_activity_get_meta( $a, $krazy_key ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_get_meta
+	 */
+	public function test_bp_activity_get_meta_multiple() {
+		$a = $this->factory->activity->create();
+		bp_activity_update_meta( $a, 'foo', 'bar' );
+		bp_activity_update_meta( $a, 'foo1', 'bar1' );
+
+		$expected = array(
+			'foo' => array(
+				'bar',
+			),
+			'foo1' => array(
+				'bar1',
+			),
+		);
+
+		$this->assertEquals( $expected, bp_activity_get_meta( $a ) );
+	}
+
+	/**
+	 * @group bp_activity_get_meta
+	 * @group activitymeta
+	 * @ticket BP5399
+	 */
+	public function test_bp_activity_get_meta_no_results_returns_false() {
+		$a = $this->factory->activity->create();
+
+		$this->assertSame( '', bp_activity_get_meta( $a, 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_get_meta
+	 */
+	public function test_bp_activity_get_meta_single_true() {
+		$a = $this->factory->activity->create();
+		bp_activity_add_meta( $a, 'foo', 'bar' );
+		bp_activity_add_meta( $a, 'foo', 'baz' );
+		$this->assertSame( 'bar', bp_activity_get_meta( $a, 'foo' ) ); // default is true
+		$this->assertSame( 'bar', bp_activity_get_meta( $a, 'foo', true ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_get_meta
+	 */
+	public function test_bp_activity_get_meta_single_false() {
+		$a = $this->factory->activity->create();
+		bp_activity_add_meta( $a, 'foo', 'bar' );
+		bp_activity_add_meta( $a, 'foo', 'baz' );
+		$this->assertSame( array( 'bar', 'baz' ), bp_activity_get_meta( $a, 'foo', false ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_get_meta
+	 * @group cache
+	 */
+	public function test_bp_activity_get_meta_cache_all_on_get() {
+		$a = $this->factory->activity->create();
+		bp_activity_add_meta( $a, 'foo', 'bar' );
+		bp_activity_add_meta( $a, 'foo1', 'baz' );
+		$this->assertFalse( wp_cache_get( $a, 'activity_meta' ) );
+
+		// A single query should prime the whole meta cache
+		bp_activity_get_meta( $a, 'foo' );
+
+		$c = wp_cache_get( $a, 'activity_meta' );
+		$this->assertNotEmpty( $c['foo1'] );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_delete_meta
+	 */
+	public function test_bp_activity_delete_meta_non_numeric_activity_id() {
+		$this->assertFalse( bp_activity_delete_meta( 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_delete_meta
+	 * @ticket BP5399
+	 */
+	public function test_bp_activity_delete_meta_trim_meta_value() {
+		$a = $this->factory->activity->create();
+		bp_activity_update_meta( $a, 'foo', 'bar' );
+		bp_activity_delete_meta( $a, 'foo', ' bar ' );
+		$this->assertSame( 'bar', bp_activity_get_meta( $a, 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_delete_meta
+	 */
+	public function test_bp_activity_delete_meta_single() {
+		$a = $this->factory->activity->create();
+		bp_activity_update_meta( $a, 'foo', 'bar' );
+		$this->assertTrue( bp_activity_delete_meta( $a, 'foo', 'bar' ) );
+		$this->assertSame( '', bp_activity_get_meta( $a, 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_delete_meta
+	 */
+	public function test_bp_activity_delete_meta_all_for_activity() {
+		$a = $this->factory->activity->create();
+		bp_activity_update_meta( $a, 'foo', 'bar' );
+		bp_activity_update_meta( $a, 'foo1', 'bar1' );
+		$this->assertTrue( bp_activity_delete_meta( $a ) );
+		$this->assertSame( '', bp_activity_get_meta( $a, 'foo' ) );
+		$this->assertSame( '', bp_activity_get_meta( $a, 'foo1' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_delete_meta
+	 */
+	public function test_bp_activity_delete_meta_with_meta_value() {
+		$a = $this->factory->activity->create();
+		bp_activity_update_meta( $a, 'foo', 'bar' );
+		$this->assertTrue( bp_activity_delete_meta( $a, 'foo', 'bar' ) );
+		$this->assertSame( '', bp_activity_get_meta( $a, 'foo' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_delete_meta
+	 */
+	public function test_bp_activity_delete_meta_with_delete_all_but_no_meta_key() {
+		// With no meta key, don't delete for all items - just delete
+		// all for a single item
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+		bp_activity_update_meta( $a1, 'foo', 'bar' );
+		bp_activity_update_meta( $a1, 'foo1', 'bar1' );
+		bp_activity_update_meta( $a2, 'foo', 'bar' );
+		bp_activity_update_meta( $a2, 'foo1', 'bar1' );
+
+		$this->assertTrue( bp_activity_delete_meta( $a1, '', '', true ) );
+		$this->assertEmpty( bp_activity_get_meta( $a1 ) );
+		$this->assertSame( 'bar', bp_activity_get_meta( $a2, 'foo' ) );
+		$this->assertSame( 'bar1', bp_activity_get_meta( $a2, 'foo1' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_delete_meta
+	 */
+	public function test_bp_activity_delete_meta_with_delete_all() {
+		// With no meta key, don't delete for all items - just delete
+		// all for a single item
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+		bp_activity_update_meta( $a1, 'foo', 'bar' );
+		bp_activity_update_meta( $a1, 'foo1', 'bar1' );
+		bp_activity_update_meta( $a2, 'foo', 'bar' );
+		bp_activity_update_meta( $a2, 'foo1', 'bar1' );
+
+		$this->assertTrue( bp_activity_delete_meta( $a1, 'foo', '', true ) );
+		$this->assertSame( '', bp_activity_get_meta( $a1, 'foo' ) );
+		$this->assertSame( '', bp_activity_get_meta( $a2, 'foo' ) );
+		$this->assertSame( 'bar1', bp_activity_get_meta( $a1, 'foo1' ) );
+		$this->assertSame( 'bar1', bp_activity_get_meta( $a2, 'foo1' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_add_meta
+	 */
+	public function test_bp_activity_add_meta_no_meta_key() {
+		$this->assertFalse( bp_activity_add_meta( 1, '', 'bar' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_add_meta
+	 */
+	public function test_bp_activity_add_meta_empty_object_id() {
+		$this->assertFalse( bp_activity_add_meta( 0, 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_add_meta
+	 */
+	public function test_bp_activity_add_meta_existing_unique() {
+		$a = $this->factory->activity->create();
+		bp_activity_add_meta( $a, 'foo', 'bar' );
+		$this->assertFalse( bp_activity_add_meta( $a, 'foo', 'baz', true ) );
+	}
+
+	/**
+	 * @group activitymeta
+	 * @group bp_activity_add_meta
+	 */
+	public function test_bp_activity_add_meta_existing_not_unique() {
+		$a = $this->factory->activity->create();
+		bp_activity_add_meta( $a, 'foo', 'bar' );
+		$this->assertNotEmpty( bp_activity_add_meta( $a, 'foo', 'baz' ) );
+	}
+
+	/**
+	 * @group bp_activity_get_user_mentionname
+	 */
+	public function test_bp_activity_get_user_mentionname_compatibilitymode_off() {
+		add_filter( 'bp_is_username_compatibility_mode', '__return_false' );
+
+		$u = $this->factory->user->create( array(
+			'user_login' => 'foo bar baz',
+			'user_nicename' => 'foo-bar-baz',
+		) );
+
+		$this->assertEquals( 'foo-bar-baz', bp_activity_get_user_mentionname( $u ) );
+
+		remove_filter( 'bp_is_username_compatibility_mode', '__return_false' );
+	}
+
+	/**
+	 * @group bp_activity_get_user_mentionname
+	 */
+	public function test_bp_activity_get_user_mentionname_compatibilitymode_on() {
+		add_filter( 'bp_is_username_compatibility_mode', '__return_true' );
+
+		$u1 = $this->factory->user->create( array(
+			'user_login' => 'foo bar baz',
+			'user_nicename' => 'foo-bar-baz',
+		) );
+
+		$u2 = $this->factory->user->create( array(
+			'user_login' => 'foo.bar.baz',
+			'user_nicename' => 'foo-bar-baz',
+		) );
+
+		$this->assertEquals( 'foo-bar-baz', bp_activity_get_user_mentionname( $u1 ) );
+		$this->assertEquals( 'foo.bar.baz', bp_activity_get_user_mentionname( $u2 ) );
+
+		remove_filter( 'bp_is_username_compatibility_mode', '__return_true' );
+	}
+
+	/**
+	 * @group bp_activity_get_userid_from_mentionname
+	 */
+	public function test_bp_activity_get_userid_from_mentionname_compatibilitymode_off() {
+		add_filter( 'bp_is_username_compatibility_mode', '__return_false' );
+
+		$u = $this->factory->user->create( array(
+			'user_login' => 'foo bar baz',
+			'user_nicename' => 'foo-bar-baz',
+		) );
+
+		$this->assertEquals( $u, bp_activity_get_userid_from_mentionname( 'foo-bar-baz' ) );
+
+		remove_filter( 'bp_is_username_compatibility_mode', '__return_false' );
+	}
+
+	/**
+	 * @group bp_activity_get_userid_from_mentionname
+	 */
+	public function test_bp_activity_get_userid_from_mentionname_compatibilitymode_on() {
+		add_filter( 'bp_is_username_compatibility_mode', '__return_true' );
+
+		// all spaces are hyphens
+		$u1 = $this->factory->user->create( array(
+			'user_login' => 'foo bar baz',
+			'user_nicename' => 'foobarbaz',
+		) );
+
+		// no spaces are hyphens
+		$u2 = $this->factory->user->create( array(
+			'user_login' => 'foo-bar-baz-1',
+			'user_nicename' => 'foobarbaz-1',
+		) );
+
+		// some spaces are hyphens
+		$u3 = $this->factory->user->create( array(
+			'user_login' => 'foo bar-baz 2',
+			'user_nicename' => 'foobarbaz-2',
+		) );
+
+		$u4 = $this->factory->user->create( array(
+			'user_login' => 'foo.bar.baz',
+			'user_nicename' => 'foo-bar-baz',
+		) );
+
+		$this->assertEquals( $u1, bp_activity_get_userid_from_mentionname( 'foo-bar-baz' ) );
+		$this->assertEquals( $u2, bp_activity_get_userid_from_mentionname( 'foo-bar-baz-1' ) );
+		$this->assertEquals( $u3, bp_activity_get_userid_from_mentionname( 'foo-bar-baz-2' ) );
+		$this->assertEquals( $u4, bp_activity_get_userid_from_mentionname( 'foo.bar.baz' ) );
+
+		remove_filter( 'bp_is_username_compatibility_mode', '__return_true' );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_activity_format_activity_action_activity_update
+	 */
+	public function test_bp_activity_format_activity_action_activity_update() {
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->activity->id,
+			'type' => 'activity_update',
+			'user_id' => $u,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$expected = sprintf( '%s posted an update', bp_core_get_userlink( $u ) );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_activity_format_activity_action_activity_comment
+	 */
+	public function test_bp_activity_format_activity_action_activity_comment() {
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->activity->id,
+			'type' => 'activity_comment',
+			'user_id' => $u,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$expected = sprintf( '%s posted a new activity comment', bp_core_get_userlink( $u ) );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_activity_format_activity_action_custom_post_type_post
+	 * @group activity_tracking
+	 */
+	public function test_bp_activity_format_activity_action_custom_post_type_post_nonms() {
+		if ( is_multisite() ) {
+			return;
+		}
+
+		$bp = buddypress();
+
+		register_post_type( 'foo', array(
+			'label'   => 'foo',
+			'public'   => true,
+			'supports' => array( 'buddypress-activity' ),
+		) );
+
+		// Build the actions to fetch the tracking args
+		bp_activity_get_actions();
+
+		$u = $this->factory->user->create();
+		$p = $this->factory->post->create( array(
+			'post_author' => $u,
+			'post_type'   => 'foo',
+		) );
+
+		$a = $this->factory->activity->create( array(
+			'component'         => 'activity',
+			'type'              => 'new_foo',
+			'user_id'           => $u,
+			'item_id'           => 1,
+			'secondary_item_id' => $p,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$user_link = bp_core_get_userlink( $u );
+		$blog_url = get_home_url();
+		$post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
+		$post_link = '<a href="' . $post_url . '">item</a>';
+
+		$expected = sprintf( '%s wrote a new %s', $user_link, $post_link );
+
+		$this->assertSame( $expected, $a_obj->action );
+
+		_unregister_post_type( 'foo' );
+
+		// Reset globals
+		unset( $bp->activity->actions->activity->new_foo );
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_activity_format_activity_action_custom_post_type_post_ms
+	 * @group activity_tracking
+	 */
+	public function test_bp_activity_format_activity_action_custom_post_type_post_ms() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$bp = buddypress();
+
+		$b = $this->factory->blog->create();
+		$u = $this->factory->user->create();
+
+		switch_to_blog( $b );
+
+		register_post_type( 'foo', array(
+			'label'   => 'foo',
+			'public'   => true,
+			'supports' => array( 'buddypress-activity' ),
+		) );
+
+		// Build the actions to fetch the tracking args
+		bp_activity_get_actions();
+
+		$p = $this->factory->post->create( array(
+			'post_author' => $u,
+			'post_type'   => 'foo',
+		) );
+
+		$activity_args = array(
+			'component'         => 'activity',
+			'type'              => 'new_foo',
+			'user_id'           => $u,
+			'item_id'           => $b,
+			'secondary_item_id' => $p,
+		);
+
+		_unregister_post_type( 'foo' );
+		bp_activity_get_actions();
+
+		restore_current_blog();
+
+		$a = $this->factory->activity->create( $activity_args );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$user_link = bp_core_get_userlink( $u );
+		$blog_url = get_blog_option( $a_obj->item_id, 'home' );
+		$post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
+
+		$post_link = '<a href="' . $post_url . '">item</a>';
+
+		$expected = sprintf( '%s wrote a new %s, on the site %s', $user_link, $post_link, '<a href="' . $blog_url . '">' . get_blog_option( $a_obj->item_id, 'blogname' ) . '</a>' );
+
+		$this->assertSame( $expected, $a_obj->action );
+
+		// Reset globals
+		unset( $bp->activity->actions->activity->new_foo );
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_activity_get_actions
+	 */
+	public function test_bp_activity_get_actions_should_sort_by_position() {
+		$old_actions = bp_activity_get_actions();
+		buddypress()->activity->actions = new stdClass;
+
+		register_post_type( 'foo5', array(
+			'public'      => true,
+			'supports'    => array( 'buddypress-activity' ),
+			'bp_activity' => array(
+				'component_id' => 'foo',
+				'action_id' => 'foo_bar_5',
+				'position' => 5,
+			),
+		) );
+
+		register_post_type( 'foo50', array(
+			'public'      => true,
+			'supports'    => array( 'buddypress-activity' ),
+			'bp_activity' => array(
+				'component_id' => 'foo',
+				'action_id' => 'foo_bar_50',
+				'position' => 50,
+			),
+		) );
+
+		register_post_type( 'foo25', array(
+			'public'      => true,
+			'supports'    => array( 'buddypress-activity' ),
+			'bp_activity' => array(
+				'component_id' => 'foo',
+				'action_id' => 'foo_bar_25',
+				'position' => 25,
+			),
+		) );
+
+		$actions = bp_activity_get_actions();
+
+		$expected = array(
+			'foo_bar_5',
+			'foo_bar_25',
+			'foo_bar_50',
+		);
+		$foo_actions = (array) $actions->foo;
+		$this->assertEquals( $expected, array_values( wp_list_pluck( $foo_actions, 'key' ) ) );
+
+		buddypress()->activity->actions = $old_actions;
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_activity_format_activity_action_custom_post_type_post
+	 */
+	public function test_bp_activity_format_activity_action_custom_string_post_type_post_nonms() {
+		if ( is_multisite() ) {
+			return;
+		}
+
+		$bp = buddypress();
+
+		$labels = array(
+			'name'                 => 'bars',
+			'singular_name'        => 'bar',
+			'bp_activity_new_post' => '%1$s shared a new <a href="%2$s">bar</a>',
+		);
+
+		register_post_type( 'foo', array(
+			'labels'      => $labels,
+			'public'      => true,
+			'supports'    => array( 'buddypress-activity' ),
+			'bp_activity' => array(
+				'action_id' => 'foo_bar',
+			),
+		) );
+
+		// Build the actions to fetch the tracking args
+		bp_activity_get_actions();
+
+		$u = $this->factory->user->create();
+		$p = $this->factory->post->create( array(
+			'post_author' => $u,
+			'post_type'   => 'foo',
+		) );
+
+		$a = $this->factory->activity->create( array(
+			'component'         => 'activity',
+			'type'              => 'foo_bar',
+			'user_id'           => $u,
+			'item_id'           => 1,
+			'secondary_item_id' => $p,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$user_link = bp_core_get_userlink( $u );
+		$blog_url = get_home_url();
+		$post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
+
+		$expected = sprintf( '%1$s shared a new <a href="%2$s">bar</a>', $user_link, $post_url );
+
+		$this->assertSame( $expected, $a_obj->action );
+
+		_unregister_post_type( 'foo' );
+
+		// Reset globals
+		unset( $bp->activity->actions->activity->foo_bar );
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_activity_format_activity_action_custom_post_type_post_ms
+	 * @group activity_tracking
+	 */
+	public function test_bp_activity_format_activity_action_custom_string_post_type_post_ms() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$bp = buddypress();
+		$reset = $bp->activity->actions;
+
+		$b = $this->factory->blog->create();
+		$u = $this->factory->user->create();
+
+		switch_to_blog( $b );
+
+		$labels = array(
+			'name'                    => 'bars',
+			'singular_name'           => 'bar',
+			'bp_activity_new_post_ms' => '%1$s shared a new <a href="%2$s">bar</a>, on the site %3$s',
+		);
+
+		register_post_type( 'foo', array(
+			'labels'   => $labels,
+			'public'   => true,
+			'supports' => array( 'buddypress-activity' ),
+		) );
+
+		// Build the actions to fetch the tracking args
+		bp_activity_get_actions();
+
+		$p = $this->factory->post->create( array(
+			'post_author' => $u,
+			'post_type'   => 'foo',
+		) );
+
+		$activity_args = array(
+			'component'         => 'activity',
+			'type'              => 'new_foo',
+			'user_id'           => $u,
+			'item_id'           => $b,
+			'secondary_item_id' => $p,
+		);
+
+		_unregister_post_type( 'foo' );
+
+		restore_current_blog();
+
+		$a = $this->factory->activity->create( $activity_args );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$user_link = bp_core_get_userlink( $u );
+		$blog_url = get_blog_option( $a_obj->item_id, 'home' );
+		$post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
+
+		$expected = sprintf( '%1$s shared a new <a href="%2$s">bar</a>, on the site %3$s', $user_link, $post_url, '<a href="' . $blog_url . '">' . get_blog_option( $a_obj->item_id, 'blogname' ) . '</a>' );
+
+		$this->assertSame( $expected, $a_obj->action );
+
+		// Reset globals
+		unset( $bp->activity->actions->activity->new_foo );
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * @group bp_activity_set_post_type_tracking_args
+	 * @group activity_tracking
+	 */
+	public function test_bp_activity_set_post_type_tracking_args() {
+		$bp = buddypress();
+
+		add_post_type_support( 'page', 'buddypress-activity' );
+
+		bp_activity_set_post_type_tracking_args( 'page', array(
+			'component_id' => $bp->blogs->id,
+			'dummy'        => 'dummy value',
+		) );
+
+		// Build the actions to fetch the tracking args
+		bp_activity_get_actions();
+
+		$u = $this->factory->user->create();
+
+		$post_id = $this->factory->post->create( array(
+			'post_author' => $u,
+			'post_status' => 'publish',
+			'post_type'   => 'page',
+		) );
+
+		$a = bp_activity_get( array(
+			'action'            => 'new_page',
+			'item_id'           => get_current_blog_id(),
+			'secondary_item_id' => $post_id,
+		) );
+
+		$this->assertSame( $bp->blogs->id, $a['activities'][0]->component );
+
+		remove_post_type_support( 'page', 'buddypress-activity' );
+
+		// Reset globals
+		unset( $bp->activity->actions->blogs->new_page );
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * @group bp_activity_new_comment
+	 * @group cache
+	 */
+	public function test_bp_activity_new_comment_clear_comment_caches() {
+		$u = $this->factory->user->create();
+		$a1 = $this->factory->activity->create( array(
+			'user_id' => $u,
+		) );
+		$a2 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a1,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+		$a3 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a2,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+		$a4 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a3,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+		$a5 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a3,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+
+		// prime caches
+		bp_activity_get( array(
+			'in' => array( $a1 ),
+			'display_comments' => 'threaded',
+		) );
+
+		// should be populated
+		$this->assertNotEmpty( wp_cache_get( $a1, 'bp_activity_comments' ) );
+
+		bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a4,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+
+		// should be empty
+		$this->assertFalse( wp_cache_get( $a1, 'bp_activity_comments' ) );
+	}
+
+	/**
+	 * @group bp_activity_new_comment
+	 * @group cache
+	 */
+	public function test_bp_activity_new_comment_clear_activity_caches() {
+		$u = $this->factory->user->create();
+		$a1 = $this->factory->activity->create( array(
+			'user_id' => $u,
+		) );
+		$a2 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a1,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+		$a3 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a2,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+		$a4 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a3,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+		$a5 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a3,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+
+		// prime caches
+		bp_activity_get( array(
+			'in' => array( $a1 ),
+			'display_comments' => 'threaded',
+		) );
+
+		// should be populated
+		$this->assertNotEmpty( wp_cache_get( $a1, 'bp_activity' ) );
+		$this->assertNotEmpty( wp_cache_get( $a2, 'bp_activity' ) );
+		$this->assertNotEmpty( wp_cache_get( $a3, 'bp_activity' ) );
+		$this->assertNotEmpty( wp_cache_get( $a4, 'bp_activity' ) );
+		$this->assertNotEmpty( wp_cache_get( $a5, 'bp_activity' ) );
+
+		// Stuff may run on bp_activity_comment_posted that loads the
+		// cache, so we use this dumb technique to check cache values
+		// before any of that stuff gets a chance to run. WordPress
+		// sure is neat sometimes
+		$this->acaches = array(
+			$a1 => '',
+			$a2 => '',
+			$a3 => '',
+			$a4 => '',
+		);
+		add_action( 'bp_activity_comment_posted', array( $this, 'check_activity_caches' ), 0 );
+
+		bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a4,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+
+		// should be empty
+		foreach ( $this->acaches as $k => $v ) {
+			$this->assertFalse( $v, "Cache should be false for $k" );
+		}
+	}
+
+	/**
+	 * @group bp_activity_delete_comment
+	 * @group cache
+	 */
+	public function test_bp_activity_delete_comment_clear_cache() {
+		$u = $this->factory->user->create();
+		// add new activity update and comment to this update
+		$a1 = $this->factory->activity->create( array(
+			'user_id' => $u,
+		) );
+		$a2 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'parent_id' => $a1,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+
+		// prime cache
+		bp_activity_get( array(
+			'in' => array( $a1 ),
+			'display_comments' => 'threaded',
+		) );
+
+		// delete activity comment
+		bp_activity_delete_comment( $a1, $a2 );
+
+		// assert comment cache as empty for $a1
+		$this->assertEmpty( wp_cache_get( $a1, 'bp_activity_comments' ) );
+	}
+
+	/**
+	 * @group bp_activity_new_comment
+	 * @group BP5907
+	 */
+	public function test_bp_activity_comment_on_deleted_activity() {
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create();
+
+		bp_activity_delete_by_activity_id( $a );
+
+		$c = bp_activity_new_comment( array(
+			'activity_id' => $a,
+			'parent_id' => $a,
+			'content' => 'foo',
+			'user_id' => $u,
+		) );
+
+		$this->assertEmpty( $c );
+	}
+
+	/**
+	 * @group favorites
+	 * @group bp_activity_add_user_favorite
+	 */
+	public function test_add_user_favorite_already_favorited() {
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create();
+
+		// bp_activity_add_user_favorite() requires a logged-in user.
+		$current_user = bp_loggedin_user_id();
+		$this->set_current_user( $u );
+
+		$this->assertTrue( bp_activity_add_user_favorite( $a, $u ) );
+
+		$this->assertFalse( bp_activity_add_user_favorite( $a, $u ) );
+		$this->assertSame( array( $a ), bp_activity_get_user_favorites( $u ) );
+		$this->assertEquals( 1, bp_activity_get_meta( $a, 'favorite_count' ) );
+
+		$this->set_current_user( $current_user );
+	}
+
+	/**
+	 * @group favorites
+	 * @group bp_activity_add_user_favorite
+	 */
+	public function test_add_user_favorite_not_yet_favorited() {
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create();
+
+		// bp_activity_add_user_favorite() requires a logged-in user.
+		$current_user = bp_loggedin_user_id();
+		$this->set_current_user( $u );
+		$this->assertTrue( bp_activity_add_user_favorite( $a, $u ) );
+
+		$this->set_current_user( $current_user );
+	}
+
+	/**
+	 * @group favorites
+	 * @group bp_activity_remove_user_favorite
+	 */
+	public function test_remove_user_favorite_bad_activity_id() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$a = $this->factory->activity->create();
+
+		// bp_activity_add_user_favorite() requires a logged-in user.
+		$current_user = bp_loggedin_user_id();
+		$this->set_current_user( $u1 );
+
+		// Only favorite for user 1
+		bp_activity_add_user_favorite( $a, $u1 );
+
+		// Removing for user 2 should fail
+		$this->assertFalse( bp_activity_remove_user_favorite( $a, $u2 ) );
+		$this->assertEquals( 1, bp_activity_get_meta( $a, 'favorite_count' ) );
+
+		$this->set_current_user( $current_user );
+	}
+
+	/**
+	 * @group bp_activity_post_update
+	 */
+	public function test_bp_activity_post_update_empty_content() {
+		$this->assertFalse( bp_activity_post_update( array( 'user_id' => 3, ) ) );
+	}
+
+	/**
+	 * @group bp_activity_post_update
+	 */
+	public function test_bp_activity_post_update_inactive_user() {
+		$this->assertFalse( bp_activity_post_update( array(
+			'user_id' => 3456,
+			'content' => 'foo',
+		) ) );
+	}
+
+	/**
+	 * @group bp_activity_post_update
+	 */
+	public function test_bp_activity_post_update_success() {
+		$u = $this->factory->user->create();
+
+		$a = bp_activity_post_update( array(
+			'user_id' => $u,
+			'content' => 'foo',
+		) );
+
+		$this->assertNotEmpty( $a );
+	}
+
+	/**
+	 * @group bp_activity_get_activity_id
+	 */
+	public function test_bp_activity_get_activity_id() {
+		$args = array(
+			'user_id' => 5,
+			'component' => 'foo',
+			'type' => 'bar',
+			'item_id' => 12,
+			'secondary_item_id' => 44,
+		);
+
+		$a = $this->factory->activity->create( $args );
+
+		$this->assertEquals( $a, bp_activity_get_activity_id( $args ) );
+	}
+
+	/**
+	 * @group bp_activity_delete_by_item_id
+	 */
+	public function test_bp_activity_delete_by_item_id() {
+		$args = array(
+			'user_id' => 5,
+			'component' => 'foo',
+			'type' => 'bar',
+			'item_id' => 12,
+			'secondary_item_id' => 44,
+		);
+
+		$a = $this->factory->activity->create( $args );
+
+		$this->assertTrue( bp_activity_delete_by_item_id( $args ) );
+
+		$found = bp_activity_get_specific( array(
+			'activity_ids' => array( $a ),
+		) );
+
+		$this->assertSame( array(), $found['activities'] );
+	}
+
+	public function check_activity_caches() {
+		foreach ( $this->acaches as $k => $v ) {
+			$this->acaches[ $k ] = wp_cache_get( $k, 'bp_activity' );
+		}
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/activity/notifications.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/activity/notifications.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/activity/notifications.php	(revision 10410)
@@ -0,0 +1,263 @@
+<?php
+
+/**
+ * @group activity
+ * @group notifications
+ */
+class BP_Tests_Activity_Notifications extends BP_UnitTestCase {
+	protected $current_user;
+	protected $u1;
+	protected $u2;
+	protected $a1;
+	protected $a2;
+
+	public function setUp() {
+		parent::setUp();
+		$this->current_user = get_current_user_id();
+		$this->u1 = $this->factory->user->create();
+		$this->u2 = $this->factory->user->create();
+		$this->set_current_user( $this->u1 );
+
+		/**
+		 * Tests suite in WP < 4.0 does not include the WP_UnitTestCase->_restore_hooks() function
+		 * When updating an activity, the following filter is fired to prevent sending more than one
+		 * notification. Once we've reached this filter all at_mentions tests fails so we need to
+		 * temporarly remove it and restore it in $this->tearDown()
+		 */
+		remove_filter( 'bp_activity_at_name_do_notifications', '__return_false' );
+	}
+
+	public function tearDown() {
+		$this->set_current_user( $this->current_user );
+		parent::tearDown();
+
+		// Restore the filter
+		add_filter( 'bp_activity_at_name_do_notifications', '__return_false' );
+	}
+
+	/**
+	 * @group bp_activity_remove_screen_notifications
+	 * @group mentions
+	 */
+	public function test_bp_activity_remove_screen_notifications_on_single_activity_permalink() {
+		$this->create_notifications();
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Double check it's there
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		// Go to the activity permalink page
+		$this->go_to( bp_activity_get_permalink( $this->a1 ) );
+		$activity = bp_activity_get_specific( array( 'activity_ids' => $this->a1, 'show_hidden' => true, 'spam' => 'ham_only', ) );
+		do_action( 'bp_activity_screen_single_activity_permalink', $activity['activities'][0] );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Should be empty
+		$this->assertEquals( array(), $notifications );
+	}
+
+	/**
+	 * @group bp_activity_remove_screen_notifications
+	 * @group mentions
+	 */
+	public function test_bp_activity_remove_screen_notifications_on_single_activity_permalink_logged_out() {
+		$this->create_notifications();
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Double check it's there
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		// Log out
+		$this->set_current_user( 0 );
+
+		// Go to the activity permalink page
+		$this->go_to( bp_activity_get_permalink( $this->a1 ) );
+		$activity = bp_activity_get_specific( array( 'activity_ids' => $this->a1, 'show_hidden' => true, 'spam' => 'ham_only', ) );
+		do_action( 'bp_activity_screen_single_activity_permalink', $activity['activities'][0] );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Should be untouched
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		$this->set_current_user( $this->u1 );
+	}
+
+	/**
+	 * @group bp_activity_remove_screen_notifications
+	 * @group mentions
+	 */
+	public function test_bp_activity_remove_screen_notifications_on_single_activity_permalink_wrong_user() {
+		$this->create_notifications();
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Double check it's there
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		// Switch user
+		$this->set_current_user( $this->u2 );
+
+		// Go to the activity permalink page
+		$this->go_to( bp_activity_get_permalink( $this->a1 ) );
+		$activity = bp_activity_get_specific( array( 'activity_ids' => $this->a1, 'show_hidden' => true, 'spam' => 'ham_only', ) );
+		do_action( 'bp_activity_screen_single_activity_permalink', $activity['activities'][0] );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Should be untouched
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		$this->set_current_user( $this->u1 );
+	}
+
+	/**
+	 * @group bp_activity_remove_screen_notifications
+	 * @group mentions
+	 */
+	public function test_bp_activity_remove_screen_notifications_on_mentions() {
+		$this->create_notifications();
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Double check it's there
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		// Go to the My Activity page
+		$this->go_to( bp_core_get_user_domain( $this->u1 ) . bp_get_activity_slug() . '/mentions/' );
+		do_action( 'bp_activity_screen_mentions' );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Should be empty
+		$this->assertEquals( array(), $notifications );
+	}
+
+	/**
+	 * @group bp_activity_remove_screen_notifications
+	 * @group mentions
+	 */
+	public function test_bp_activity_remove_screen_notifications_on_mentions_logged_out() {
+		$this->create_notifications();
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Double check it's there
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		// Log out
+		$this->set_current_user( 0 );
+
+		// Go to the My Activity page
+		$this->go_to( bp_core_get_user_domain( $this->u1 ) . bp_get_activity_slug() . '/mentions/' );
+		do_action( 'bp_activity_screen_mentions' );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Should be untouched
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		// clean up
+		$this->set_current_user( $this->u1 );
+	}
+
+	/**
+	 * @group bp_activity_remove_screen_notifications
+	 * @group mentions
+	 */
+	public function test_bp_activity_remove_screen_notifications_on_mentions_wrong_user() {
+		$this->create_notifications();
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Double check it's there
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		// Log out
+		$this->set_current_user( $this->u2 );
+
+		// Go to the My Activity page
+		$this->go_to( bp_core_get_user_domain( $this->u1 ) . bp_get_activity_slug() . '/mentions/' );
+		do_action( 'bp_activity_screen_mentions' );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $this->u1,
+		) );
+
+		// Should be untouched
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		// clean up
+		$this->set_current_user( $this->u1 );
+	}
+
+	/**
+	 * @group bp_notifications_delete_all_notifications_by_type
+	 * @group bp_activity_at_mention_delete_notification
+	 */
+	public function test_bp_activity_at_mention_delete_notification() {
+		$this->create_notifications();
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'item_id' => $this->a1,
+		) );
+
+		// Double check it's there
+		$this->assertEquals( array( $this->a1 ), wp_list_pluck( $notifications, 'item_id' ) );
+
+		bp_activity_delete( array(
+			'id' => $this->a1,
+		) );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'item_id' => $this->a1,
+		) );
+
+		$this->assertEmpty( $notifications );
+	}
+
+	/**
+	 * Creates two notifications for $u1, one of which is for mentions
+	 */
+	protected function create_notifications() {
+		$u1_mentionname = bp_activity_get_user_mentionname( $this->u1 );
+		$this->a1 = $this->factory->activity->create( array(
+			'user_id' => $this->u2,
+			'component' => buddypress()->activity->id,
+			'type' => 'activity_update',
+			'content' => sprintf( 'Hello! @%s', $u1_mentionname ),
+		) );
+		$u2_mentionname = bp_activity_get_user_mentionname( $this->u2 );
+		$this->a2 = $this->factory->activity->create( array(
+			'user_id' => $this->u1,
+			'component' => buddypress()->activity->id,
+			'type' => 'activity_update',
+			'content' => sprintf( 'Hello! @%s', $u2_mentionname ),
+		) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/activity/template.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/activity/template.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/activity/template.php	(revision 10410)
@@ -0,0 +1,1526 @@
+<?php
+/**
+ * @group activity
+ */
+class BP_Tests_Activity_Template extends BP_UnitTestCase {
+
+	/**
+	 * @ticket BP4735
+	 */
+	public function test_user_can_delete() {
+		$bp = buddypress();
+		$u = $this->factory->user->create();
+		$original_user = bp_loggedin_user_id();
+		$this->set_current_user( $u );
+
+		$a = $this->factory->activity->create( array(
+			'type' => 'activity_update',
+			'user_id' => $u,
+		) );
+
+		// User can delete his own items
+		$activity = $this->factory->activity->get_object_by_id( $a );
+		$this->assertTrue( bp_activity_user_can_delete( $activity ) );
+
+		// Logged-out user can't delete
+		$this->set_current_user( 0 );
+		$this->assertFalse( bp_activity_user_can_delete( $activity ) );
+
+		// Miscellaneous user can't delete
+		$misc_user = $this->factory->user->create( array( 'role' => 'subscriber' ) );
+		$this->set_current_user( $misc_user );
+		$this->assertFalse( bp_activity_user_can_delete( $activity ) );
+
+		// Item admin can delete
+		$is_single_item = $bp->is_single_item;
+		$bp->is_single_item = true;
+
+		$is_item_admin = $bp->is_item_admin;
+		$bp->is_item_admin = true;
+
+		$this->assertTrue( bp_activity_user_can_delete( $activity ) );
+
+		$bp->is_single_item = $is_single_item;
+		$bp->is_item_admin = $is_item_admin;
+		$this->set_current_user( $original_user );
+	}
+
+	/**
+	 * Test if a non-admin can delete their own activity.
+	 */
+	public function test_user_can_delete_for_nonadmin() {
+		// save the current user and override logged-in user
+		$old_user = get_current_user_id();
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		// create an activity update for the user
+		$this->factory->activity->create( array(
+			'component' => buddypress()->activity->id,
+			'type' => 'activity_update',
+			'user_id' => $u,
+		) );
+
+		// start the activity loop
+		bp_has_activities( array( 'user_id' => $u ) );
+		while ( bp_activities() ) : bp_the_activity();
+			// assert!
+			$this->assertTrue( bp_activity_user_can_delete() );
+		endwhile;
+
+		// reset
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * Make sure that action filters ('activity_update', etc) work when
+	 * limiting query to user favorites
+	 *
+	 * @ticket BP4872
+	 * @group scope
+	 */
+	public function test_bp_has_activities_favorites_action_filter() {
+		$user_id = $this->factory->user->create( array( 'role' => 'subscriber' ) );
+
+		$now = time();
+
+		$a1 = $this->factory->activity->create( array(
+			'type' => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+
+		$a2 = $this->factory->activity->create( array(
+			'type' => 'joined_group',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		$current_user = bp_loggedin_user_id();
+		$this->set_current_user( $user_id );
+
+		bp_activity_add_user_favorite( $a1, $user_id );
+		bp_activity_add_user_favorite( $a2, $user_id );
+
+		$this->set_current_user( $current_user );
+
+		// groan. It sucks that you have to invoke the global
+		global $activities_template;
+
+		// Case 1: no action filter
+		bp_has_activities( array(
+			'user_id' => $user_id,
+			'scope' => 'favorites',
+		) );
+
+		// The formatting of $activities_template->activities is messed
+		// up, so we're just going to look at the IDs. This should be
+		// fixed in BP at some point
+		$ids = wp_list_pluck( $activities_template->activities, 'id' );
+
+		$this->assertEquals( array( $a1, $a2 ), $ids );
+
+		$activities_template = null;
+
+		// Case 2: action filter
+		bp_has_activities( array(
+			'user_id' => $user_id,
+			'scope' => 'favorites',
+			'action' => 'activity_update',
+		) );
+
+		$ids = wp_list_pluck( $activities_template->activities, 'id' );
+
+		$this->assertEquals( array( $a1 ), $ids );
+
+		$activities_template = null;
+	}
+
+	/**
+	 * @group scope
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_just_me_scope_with_no_user_id() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// save the current user and override logged-in user
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u1 );
+
+		$now = time();
+
+		// activity item
+		$a1 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		// misc activity items
+
+		$this->factory->activity->create( array(
+			'user_id'   => $u2,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$this->factory->activity->create( array(
+			'user_id'   => $u2,
+			'component' => 'groups',
+			'item_id'   => 324,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		global $activities_template;
+
+		// grab just-me scope with no user ID
+		// user ID should fallback to logged-in user ID
+		bp_has_activities( array(
+			'user_id' => false,
+			'scope' => 'just-me',
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a1 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group scope
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_mentions_scope() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$now = time();
+
+		// mentioned activity item
+		$mention_username = '@' . bp_activity_get_user_mentionname( $u1 );
+		$a1 = $this->factory->activity->create( array(
+			'user_id' => $u2,
+			'type'    => 'activity_update',
+			'content' => "{$mention_username} - You rule, dude!",
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+
+		// misc activity items
+		$this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'blogs',
+			'item_id'   => 1,
+			'type'      => 'new_blog_post',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$this->factory->activity->create( array(
+			'user_id'   => $u2,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$this->factory->activity->create( array(
+			'user_id'   => $u2,
+			'component' => 'groups',
+			'item_id'   => 324,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		global $activities_template;
+
+		// grab activities from multiple scopes
+		bp_has_activities( array(
+			'user_id' => $u1,
+			'scope' => 'mentions',
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a1 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+	}
+
+	/**
+	 * @group scope
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_friends_and_mentions_scope() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		// user 1 becomes friends with user 2
+		friends_add_friend( $u1, $u2, true );
+
+		$now = time();
+
+		// friend status update
+		$a1 = $this->factory->activity->create( array(
+			'user_id' => $u2,
+			'type' => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+
+		// mentioned item by non-friend
+		$mention_username = '@' . bp_activity_get_user_mentionname( $u1 );
+		$a2 = $this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'content'   => "{$mention_username} - Oy!",
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		// misc activity items
+		$this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'blogs',
+			'item_id'   => 1,
+			'type'      => 'new_blog_post',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'groups',
+			'item_id'   => 324,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		global $activities_template;
+
+		// grab activities from multiple scopes
+		bp_has_activities( array(
+			'user_id' => $u1,
+			'scope' => 'mentions,friends',
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a1, $a2 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+	}
+
+	/**
+	 * @group scope
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_groups_and_friends_scope() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		// user 1 becomes friends with user 2
+		friends_add_friend( $u1, $u2, true );
+
+		// user 1 joins a group
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		$now = time();
+
+		// friend status update
+		$a1 = $this->factory->activity->create( array(
+			'user_id' => $u2,
+			'type' => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+
+		// group activity
+		$a2 = $this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'groups',
+			'item_id'   => $g1,
+			'type'      => 'joined_group',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		// misc activity items
+		$this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'blogs',
+			'item_id'   => 1,
+			'type'      => 'new_blog_post',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		// Make sure we get a fake group ID.
+		global $wpdb, $bp;
+		$max_group_id = $wpdb->get_var( "SELECT id FROM {$bp->groups->table_name} ORDER BY id DESC LIMIT 1" );
+		$this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'groups',
+			'item_id'   => $max_group_id + 1,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		global $activities_template;
+
+		// grab activities from multiple scopes
+		bp_has_activities( array(
+			'user_id' => $u1,
+			'scope' => 'groups,friends',
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a1, $a2 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+	}
+
+	/**
+	 * @group scope
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_scope_friends_no_items() {
+		$u1 = $this->factory->user->create();
+
+		$now = time();
+
+		// Create a random activity
+		$this->factory->activity->create( array(
+			'user_id' => $u1,
+			'type' => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+
+		global $activities_template;
+		$reset_activities_template = $activities_template;
+
+		// grab activities from friends scope
+		bp_has_activities( array(
+			'user_id' => $u1,
+			'scope' => 'friends',
+		) );
+
+		// assert!
+		$this->assertEmpty( $activities_template->activities, 'When a user does not have any friendship, no activities should be fetched when on friends scope' );
+
+		// clean up!
+		$activities_template = $reset_activities_template;
+	}
+
+	/**
+	 * @group scope
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_scope_favorites_no_items() {
+		$u1 = $this->factory->user->create();
+
+		$now = time();
+
+		// Create a random activity
+		$this->factory->activity->create( array(
+			'user_id' => $u1,
+			'type' => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+
+		global $activities_template;
+		$reset_activities_template = $activities_template;
+
+		// grab activities from favorites scope
+		bp_has_activities( array(
+			'user_id' => $u1,
+			'scope' => 'favorites',
+		) );
+
+		// assert!
+		$this->assertEmpty( $activities_template->activities, 'When a user has not favorited any activity, no activities should be fetched when on favorites scope' );
+
+		// clean up!
+		$activities_template = $reset_activities_template;
+	}
+
+	/**
+	 * @group scope
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_scope_groups_no_items() {
+		$u1 = $this->factory->user->create();
+
+		$now = time();
+
+		// Create a random activity
+		$this->factory->activity->create( array(
+			'user_id' => $u1,
+			'type' => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+
+		global $activities_template;
+		$reset_activities_template = $activities_template;
+
+		// grab activities from groups scope
+		bp_has_activities( array(
+			'user_id' => $u1,
+			'scope' => 'groups',
+		) );
+
+		// assert!
+		$this->assertEmpty( $activities_template->activities, 'When a user is not a member of any group, no activities should be fetched when on groups scope' );
+
+		// clean up!
+		$activities_template = $reset_activities_template;
+	}
+
+	/**
+	 * @group scope
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_scope_mentions_no_items() {
+		$u1 = $this->factory->user->create();
+
+		$now = time();
+
+		// Create a random activity
+		$this->factory->activity->create( array(
+			'user_id' => $u1,
+			'type' => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+
+		global $activities_template;
+		$reset_activities_template = $activities_template;
+
+		// grab activities from mentions scope
+		bp_has_activities( array(
+			'user_id' => $u1,
+			'scope' => 'mentions',
+		) );
+
+		// assert!
+		$this->assertEmpty( $activities_template->activities, 'When a user has no mention, no activities should be fetched when on the mentions scope' );
+
+		// clean up!
+		$activities_template = $reset_activities_template;
+	}
+
+	/**
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_with_filter_query_nested_conditions() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$now = time();
+
+		$a1 = $this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'blogs',
+			'item_id'   => 1,
+			'type'      => 'new_blog_post',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'user_id'   => $u2,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		// misc activity items
+		$this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'groups',
+			'item_id'   => 324,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		global $activities_template;
+
+		bp_has_activities( array(
+			'filter_query' => array(
+				'relation' => 'OR',
+				array(
+					'column' => 'component',
+					'value'  => 'blogs',
+				),
+				array(
+					'relation' => 'AND',
+					array(
+						'column' => 'type',
+						'value'  => 'activity_update',
+					),
+					array(
+						'column' => 'user_id',
+						'value'  => $u2,
+					),
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a1, $a2 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+	}
+
+	/**
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_with_filter_query_compare_not_in_operator() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$now = time();
+
+		// misc activity items
+		$a1 = $this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'blogs',
+			'item_id'   => 1,
+			'type'      => 'new_blog_post',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'user_id'   => $u2,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a4 = $this->factory->activity->create( array(
+			'user_id'   => $u3,
+			'component' => 'groups',
+			'item_id'   => 324,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		global $activities_template;
+
+		bp_has_activities( array(
+			'filter_query' => array(
+				array(
+					'column'  => 'id',
+					'compare' => 'NOT IN',
+					'value'   => array( $a1, $a4 ),
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a2, $a3 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+	}
+
+	/**
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_with_filter_query_compare_between_operator() {
+		$u1 = $this->factory->user->create();
+
+		$now = time();
+
+		// misc activity items
+		$a1 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'blogs',
+			'item_id'   => 1,
+			'type'      => 'new_blog_post',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a4 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'groups',
+			'item_id'   => 324,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		global $activities_template;
+
+		bp_has_activities( array(
+			'filter_query' => array(
+				array(
+					'column'  => 'id',
+					'compare' => 'BETWEEN',
+					'value'   => array( $a3, $a4 ),
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a3, $a4 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+	}
+
+	/**
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_with_filter_query_compare_arithmetic_comparisons() {
+		$u1 = $this->factory->user->create();
+
+		$now = time();
+
+		// misc activity items
+		$a1 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'activity',
+			'item_id'   => 1,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'activity',
+			'item_id'   => 10,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'activity',
+			'item_id'   => 25,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a4 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'activity',
+			'item_id'   => 100,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		global $activities_template;
+
+		// greater than
+		bp_has_activities( array(
+			'filter_query' => array(
+				array(
+					'column'  => 'item_id',
+					'compare' => '>',
+					'value'   => 10,
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a3, $a4 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// greater or equal than
+		bp_has_activities( array(
+			'filter_query' => array(
+				array(
+					'column'  => 'item_id',
+					'compare' => '>=',
+					'value'   => 10,
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a2, $a3, $a4 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// less than
+		bp_has_activities( array(
+			'filter_query' => array(
+				array(
+					'column'  => 'item_id',
+					'compare' => '<',
+					'value'   => 10,
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a1 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// less or equal than
+		bp_has_activities( array(
+			'filter_query' => array(
+				array(
+					'column'  => 'item_id',
+					'compare' => '<=',
+					'value'   => 10,
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a1, $a2 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// not equal to
+		bp_has_activities( array(
+			'filter_query' => array(
+				array(
+					'column'  => 'item_id',
+					'compare' => '!=',
+					'value'   => 10,
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a1, $a3, $a4 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+	}
+
+	/**
+	 * @group filter_query
+	 * @group BP_Activity_Query
+	 */
+	function test_bp_has_activities_with_filter_query_compare_regex() {
+		$u1 = $this->factory->user->create();
+
+		$now = time();
+
+		// misc activity items
+		$a1 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'blogs',
+			'item_id'   => 1,
+			'type'      => 'new_blog_post',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'blogs',
+			'type'      => 'new_blog_comment',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'activity',
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a4 = $this->factory->activity->create( array(
+			'user_id'   => $u1,
+			'component' => 'groups',
+			'item_id'   => 324,
+			'type'      => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		global $activities_template;
+
+		// REGEXP
+		bp_has_activities( array(
+			'filter_query' => array(
+				array(
+					'column'  => 'type',
+					'compare' => 'REGEXP',
+					'value'   => '^new_blog_',
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a1, $a2 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// RLIKE is a synonym for REGEXP
+		bp_has_activities( array(
+			'filter_query' => array(
+				array(
+					'column'  => 'type',
+					'compare' => 'RLIKE',
+					'value'   => '^new_blog_',
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a1, $a2 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// NOT REGEXP
+		bp_has_activities( array(
+			'filter_query' => array(
+				array(
+					'column'  => 'type',
+					'compare' => 'NOT REGEXP',
+					'value'   => '^new_blog_',
+				),
+			)
+		) );
+
+		// assert!
+		$this->assertEqualSets( array( $a3, $a4 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+	}
+
+	/**
+	 * @ticket BP6169
+	 * @group bp_has_activities
+	 */
+	public function test_bp_has_activities_private_group_home_scope() {
+		global $activities_template;
+		$bp = buddypress();
+		$reset_current_group = $bp->groups->current_group;
+		$reset_current_action = $bp->current_action;
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$this->set_current_user( $u1 );
+
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		groups_join_group( $g, $u2 );
+		groups_join_group( $g, $u3 );
+
+		$a1 = $this->factory->activity->create( array(
+			'component' => $bp->groups->id,
+			'item_id'   => $g,
+			'type'      => 'activity_update',
+			'user_id'   => $u2,
+			'content'   => 'foo bar',
+		) );
+
+		$a2 = $this->factory->activity->create( array(
+			'component' => $bp->groups->id,
+			'item_id'   => $g,
+			'type'      => 'activity_update',
+			'user_id'   => $u3,
+			'content'   => 'bar foo',
+		) );
+
+		$bp->groups->current_group = groups_get_group( array(
+			'group_id'        => $g,
+			'populate_extras' => true,
+		) );
+
+		// On group's home the scope is set to 'home'
+		$bp->current_action = 'home';
+
+		bp_has_activities( array( 'action' => 'activity_update' ) );
+
+		$this->assertEqualSets( array( $a1, $a2 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+		$bp->groups->current_group = $reset_current_group;
+		$bp->current_action = $reset_current_action;
+	}
+
+	/**
+	 * @ticket BP6169
+	 * @group bp_has_activities
+	 */
+	public function test_bp_has_activities_hidden_group_home_scope() {
+		global $activities_template;
+		$bp = buddypress();
+		$reset_current_group = $bp->groups->current_group;
+		$reset_current_action = $bp->current_action;
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$this->set_current_user( $u1 );
+
+		$g = $this->factory->group->create( array(
+			'status' => 'hidden',
+		) );
+
+		groups_join_group( $g, $u2 );
+		groups_join_group( $g, $u3 );
+
+		$a1 = $this->factory->activity->create( array(
+			'component' => $bp->groups->id,
+			'item_id'   => $g,
+			'type'      => 'activity_update',
+			'user_id'   => $u2,
+			'content'   => 'foo bar',
+		) );
+
+		$a2 = $this->factory->activity->create( array(
+			'component' => $bp->groups->id,
+			'item_id'   => $g,
+			'type'      => 'activity_update',
+			'user_id'   => $u3,
+			'content'   => 'bar foo',
+		) );
+
+		$bp->groups->current_group = groups_get_group( array(
+			'group_id'        => $g,
+			'populate_extras' => true,
+		) );
+
+		// On group's home the scope is set to 'home'
+		$bp->current_action = 'home';
+
+		bp_has_activities( array( 'action' => 'activity_update' ) );
+
+		$this->assertEqualSets( array( $a1, $a2 ), wp_list_pluck( $activities_template->activities, 'id' ) );
+
+		// clean up!
+		$activities_template = null;
+		$bp->groups->current_group = $reset_current_group;
+		$bp->current_action = $reset_current_action;
+	}
+
+	/**
+	 * Integration test for 'meta_query' param
+	 */
+	function test_bp_has_activities_with_meta_query() {
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+		bp_activity_update_meta( $a1, 'foo', 'bar' );
+
+		global $activities_template;
+		bp_has_activities( array(
+			'meta_query' => array(
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+				),
+			),
+		) );
+
+		$ids = wp_list_pluck( $activities_template->activities, 'id' );
+		$this->assertEquals( $ids, array( $a1 ) );
+	}
+
+	/**
+	 * @ticket BP5029
+	 * @group bp_has_activities
+	 */
+	public function test_bp_has_activities_with_display_comments_false() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'content' => 'Candy is good',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+
+		global $activities_template;
+		bp_has_activities( array(
+			'display_comments' => false,
+		) );
+		$ids = wp_list_pluck( $activities_template->activities, 'id' );
+
+		$this->assertEquals( array( $a1, $a2 ), wp_parse_id_list( $ids ) );
+
+	}
+
+	/**
+	 * @ticket BP5029
+	 * @group bp_has_activities
+	 */
+	public function test_bp_has_activities_with_display_comments_0() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'content' => 'Candy is good',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+
+		global $activities_template;
+		bp_has_activities( array(
+			'display_comments' => 0,
+		) );
+		$ids = wp_list_pluck( $activities_template->activities, 'id' );
+
+		$this->assertEquals( array( $a1, $a2 ), wp_parse_id_list( $ids ) );
+
+	}
+
+	/**
+	 * @ticket BP5029
+	 * @group bp_has_activities
+	 */
+	public function test_bp_has_activities_with_display_comments_0_querystring() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'content' => 'Candy is good',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+
+		global $activities_template;
+		bp_has_activities( 'display_comments=0' );
+		$ids = wp_list_pluck( $activities_template->activities, 'id' );
+
+		$this->assertEquals( array( $a1, $a2 ), $ids );
+
+	}
+
+	/**
+	 * @ticket BP5029
+	 * @group bp_has_activities
+	 */
+	public function test_bp_has_activities_with_display_comments_none_querystring() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$a3 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'content' => 'Candy is good',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+
+		global $activities_template;
+		bp_has_activities( 'display_comments=none' );
+		$ids = wp_list_pluck( $activities_template->activities, 'id' );
+
+		$this->assertEquals( array( $a1, $a2 ), $ids );
+
+	}
+
+	/**
+	 * @group bp_has_activities
+	 * @group cache
+	 */
+	public function test_bp_has_activities_with_update_meta_cache_false() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		bp_activity_add_meta( $a1, 'foo', 'bar' );
+		bp_activity_add_meta( $a2, 'foo1', 'bar2' );
+
+		// prime
+		bp_has_activities( array(
+			'update_meta_cache' => false,
+		) );
+
+		$this->assertFalse( wp_cache_get( $a1, 'activity_meta' ) );
+		$this->assertFalse( wp_cache_get( $a2, 'activity_meta' ) );
+	}
+
+	/**
+	 * @group bp_has_activities
+	 * @group cache
+	 */
+	public function test_bp_has_activities_with_update_meta_cache_true() {
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		bp_activity_add_meta( $a1, 'foo', 'bar' );
+		bp_activity_add_meta( $a2, 'foo1', 'bar2' );
+
+		// prime
+		bp_has_activities( array(
+			'update_meta_cache' => true,
+		) );
+
+		$this->assertNotEmpty( wp_cache_get( $a1, 'activity_meta' ) );
+		$this->assertNotEmpty( wp_cache_get( $a2, 'activity_meta' ) );
+	}
+
+	/**
+	 * @group bp_has_activities
+	 */
+	public function test_bp_has_activities_with_type_new_blog_comments() {
+		add_filter( 'bp_disable_blogforum_comments', '__return_false' );
+
+		$u = $this->factory->user->create();
+
+		$now = time();
+		$a1 = $this->factory->activity->create( array(
+			'content' => 'Life Rules',
+			'component' => 'blogs',
+			'type' => 'new_blog_post',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now ),
+			'user_id' => $u,
+		) );
+		$a2 = $this->factory->activity->create( array(
+			'content' => 'Life Drools',
+			'component' => 'blogs',
+			'type' => 'new_blog_comment',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 100 ),
+			'user_id' => $u,
+		) );
+
+		// This one will show up in the stream because it's a comment
+		// on a blog post
+		$a3 = bp_activity_new_comment( array(
+			'activity_id' => $a1,
+			'content' => 'Candy is good',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 200 ),
+			'user_id' => $u,
+		) );
+
+		$a4 = $this->factory->activity->create( array(
+			'content' => 'Life Rulez',
+			'component' => 'activity',
+			'type' => 'activity_update',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 300 ),
+			'user_id' => $u,
+		) );
+
+		// This one should not show up in the stream because it's a
+		// comment on an activity item
+		$a5 = bp_activity_new_comment( array(
+			'activity_id' => $a4,
+			'content' => 'Candy is great',
+			'recorded_time' => date( 'Y-m-d H:i:s', $now - 400 ),
+			'user_id' => $u,
+		) );
+		global $activities_template;
+
+		// prime
+		bp_has_activities( array(
+			'component' => 'blogs',
+			'action' => 'new_blog_comment',
+		) );
+
+		$this->assertEquals( array( $a3, $a2 ), wp_parse_id_list( wp_list_pluck( $activities_template->activities, 'id' ) ) );
+
+		// Clean up
+		$activities_template = null;
+		remove_filter( 'bp_disable_blogforum_comments', '__return_false' );
+	}
+
+	/**
+	 * @group bp_activity_can_comment_reply
+	 */
+	public function test_bp_activity_can_comment_reply_thread_comments_on() {
+		$tc = get_option( 'thread_comments' );
+		update_option( 'thread_comments', '1' );
+
+		$tcd = get_option( 'thread_comments_depth' );
+		update_option( 'thread_comments_depth', '4' );
+
+		// Fake the global
+		global $activities_template;
+		$activities_template = new stdClass;
+		$activities_template->activity = new stdClass;
+		$activities_template->activity->current_comment = new stdClass;
+
+		$comment = new stdClass;
+		$comment->item_id = 4;
+
+		$activities_template->activity->current_comment->depth = 1;
+		$this->assertTrue( bp_activity_can_comment_reply( $comment ) );
+
+		$activities_template->activity->current_comment->depth = 3;
+		$this->assertTrue( bp_activity_can_comment_reply( $comment ) );
+
+		$activities_template->activity->current_comment->depth = 4;
+		$this->assertFalse( bp_activity_can_comment_reply( $comment ) );
+
+		$activities_template->activity->current_comment->depth = 5;
+		$this->assertFalse( bp_activity_can_comment_reply( $comment ) );
+
+		// Set right what once went wrong
+		update_option( 'thread_comments', $tc );
+		update_option( 'thread_comments_depth', $tcd );
+		$activities_template = null;
+	}
+
+	/**
+	 * @group bp_activity_can_comment_reply
+	 */
+	public function test_bp_activity_can_comment_reply_thread_comments_off() {
+		$tc = get_option( 'thread_comments' );
+		update_option( 'thread_comments', '0' );
+
+		$tcd = get_option( 'thread_comments_depth' );
+		update_option( 'thread_comments_depth', '4' );
+
+		// Fake the global
+		global $activities_template;
+		$activities_template = new stdClass;
+		$activities_template->activity = new stdClass;
+		$activities_template->activity->current_comment = new stdClass;
+
+		$comment = new stdClass;
+		$comment->item_id = 4;
+
+		$activities_template->activity->current_comment->depth = 1;
+		$this->assertFalse( bp_activity_can_comment_reply( $comment ) );
+
+		$activities_template->activity->current_comment->depth = 2;
+		$this->assertFalse( bp_activity_can_comment_reply( $comment ) );
+
+		// Set right what once went wrong
+		update_option( 'thread_comments', $tc );
+		update_option( 'thread_comments_depth', $tcd );
+		$activities_template = null;
+	}
+
+	/**
+	 * @group bp_activity_has_more_items
+	 */
+	public function test_bp_activity_has_more_items_no_count_total_false() {
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+
+		$args = array(
+			'count_total' => false,
+		);
+
+		if ( bp_has_activities( $args ) ) {
+			global $activities_template;
+			$this->assertFalse( bp_activity_has_more_items() );
+			$activities_template = null;
+		}
+	}
+
+	/**
+	 * @group bp_activity_has_more_items
+	 */
+	public function test_bp_activity_has_more_items_no_count_total_true() {
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+		$a3 = $this->factory->activity->create();
+		$a4 = $this->factory->activity->create();
+
+		$args = array(
+			'count_total' => false,
+			'per_page' => 2,
+		);
+
+		if ( bp_has_activities( $args ) ) {
+			global $activities_template;
+			$this->assertTrue( bp_activity_has_more_items() );
+			$activities_template = null;
+		}
+	}
+
+	/**
+	 * @group bp_activity_has_more_items
+	 */
+	public function test_bp_activity_has_more_items_count_total_false() {
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+		$a3 = $this->factory->activity->create();
+		$a4 = $this->factory->activity->create();
+
+		$args = array(
+			'count_total' => 'count_query',
+		);
+
+		if ( bp_has_activities( $args ) ) {
+			global $activities_template;
+			$this->assertFalse( bp_activity_has_more_items() );
+			$activities_template = null;
+		}
+	}
+
+	/**
+	 * @group bp_activity_has_more_items
+	 */
+	public function test_bp_activity_has_more_items_count_total_true() {
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create();
+		$a3 = $this->factory->activity->create();
+		$a4 = $this->factory->activity->create();
+
+		$args = array(
+			'count_total' => 'count_query',
+			'per_page' => 2,
+		);
+
+		if ( bp_has_activities( $args ) ) {
+			global $activities_template;
+			$this->assertTrue( bp_activity_has_more_items() );
+			$activities_template = null;
+		}
+	}
+
+	/**
+	 * Integration test for 'date_query' param
+	 *
+	 * @group date_query
+	 * @requires PHP 5.3
+	 */
+	function test_bp_has_activities_with_date_query() {
+		if ( ! class_exists( 'WP_Date_Query' ) ) {
+			return;
+		}
+
+		$a1 = $this->factory->activity->create();
+		$a2 = $this->factory->activity->create( array(
+			'recorded_time' => '2001-01-01 12:00'
+		) );
+		$a3 = $this->factory->activity->create( array(
+			'recorded_time' => '2005-01-01 12:00'
+		) );
+
+		global $activities_template;
+		bp_has_activities( array(
+			'date_query' => array( array(
+				'after' => '1 day ago'
+			) )
+		) );
+
+		$ids = wp_list_pluck( $activities_template->activities, 'id' );
+		$this->assertEquals( $ids, array( $a1 ) );
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Activity_Template
+	 */
+	public function test_bp_activity_template_should_give_precedence_to_acpage_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['acpage'] = '5';
+
+		$at = new BP_Activity_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 5, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Activity_Template
+	 */
+	public function test_bp_activity_template_should_reset_0_pag_page_URL_param_to_default_pag_page_value() {
+		$request = $_REQUEST;
+		$_REQUEST['acpage'] = '0';
+
+		$at = new BP_Activity_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 8, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Activity_Template
+	 */
+	public function test_bp_activity_template_should_give_precedence_to_num_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '14';
+
+		$at = new BP_Activity_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 14, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Activity_Template
+	 */
+	public function test_bp_activity_template_should_reset_0_pag_num_URL_param_to_default_pag_num_value() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '0';
+
+		$at = new BP_Activity_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 13, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/admin/functions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/admin/functions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/admin/functions.php	(revision 10410)
@@ -0,0 +1,221 @@
+<?php
+/**
+ * @group admin
+ */
+class BP_Tests_Admin_Functions extends BP_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' ) ) );
+
+		if ( ! function_exists( 'bp_admin' ) ) {
+			require_once( BP_PLUGIN_DIR . 'bp-core/bp-core-admin.php' );
+		}
+
+		if ( ! function_exists( 'bp_new_site' ) ) {
+			bp_admin();
+		}
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	public function test_bp_admin_list_table_current_bulk_action() {
+		$_REQUEST['action'] = 'foo';
+		$_REQUEST['action2'] = '-1';
+		$this->assertEquals( bp_admin_list_table_current_bulk_action(), 'foo' );
+
+		$_REQUEST['action'] = '-1';
+		$_REQUEST['action2'] = 'foo';
+		$this->assertEquals( bp_admin_list_table_current_bulk_action(), 'foo' );
+
+		$_REQUEST['action'] = 'bar';
+		$_REQUEST['action2'] = 'foo';
+		$this->assertEquals( bp_admin_list_table_current_bulk_action(), 'foo' );
+	}
+
+	/**
+	 * @group bp_core_admin_get_active_components_from_submitted_settings
+	 */
+	public function test_bp_core_admin_get_active_components_from_submitted_settings() {
+		$get_action = isset( $_GET['action'] ) ? $_GET['action'] : null;
+		$ac = buddypress()->active_components;
+
+		// Standard deactivation from All screen
+		unset( $_GET['action'] );
+		buddypress()->active_components = array(
+			'activity' => 1,
+			'friends' => 1,
+			'groups' => 1,
+			'members' => 1,
+			'messages' => 1,
+			'settings' => 1,
+			'xprofile' => 1,
+		);
+
+		$submitted = array(
+			'groups' => 1,
+			'members' => 1,
+			'messages' => 1,
+			'settings' => 1,
+			'xprofile' => 1,
+		);
+
+		$this->assertEquals( bp_core_admin_get_active_components_from_submitted_settings( $submitted ), array( 'groups' => 1, 'members' => 1, 'messages' => 1, 'settings' => 1, 'xprofile' => 1 ) );
+
+		// Activating deactivated components from the Inactive screen
+		$_GET['action'] = 'inactive';
+		buddypress()->active_components = array(
+			'activity' => 1,
+			'members' => 1,
+			'messages' => 1,
+			'settings' => 1,
+			'xprofile' => 1,
+		);
+
+		$submitted2 = array(
+			'groups' => 1,
+		);
+
+		$this->assertEquals( bp_core_admin_get_active_components_from_submitted_settings( $submitted2 ), array( 'activity' => 1, 'groups' => 1, 'members' => 1, 'messages' => 1, 'settings' => 1, 'xprofile' => 1 ) );
+
+		// Activating from the Retired screen
+		$_GET['action'] = 'retired';
+		buddypress()->active_components = array(
+			'activity' => 1,
+			'members' => 1,
+			'messages' => 1,
+			'settings' => 1,
+			'xprofile' => 1,
+		);
+
+		$submitted3 = array(
+			'forums' => 1,
+		);
+
+		$this->assertEquals( bp_core_admin_get_active_components_from_submitted_settings( $submitted3 ), array( 'activity' => 1, 'forums' => 1, 'members' => 1, 'messages' => 1, 'settings' => 1, 'xprofile' => 1 ) );
+
+		// Deactivating from the Retired screen
+		$_GET['action'] = 'retired';
+		buddypress()->active_components = array(
+			'activity' => 1,
+			'forums' => 1,
+			'members' => 1,
+			'messages' => 1,
+			'settings' => 1,
+			'xprofile' => 1,
+		);
+
+		$submitted4 = array();
+
+		$this->assertEquals( bp_core_admin_get_active_components_from_submitted_settings( $submitted4 ), array( 'activity' => 1, 'members' => 1, 'messages' => 1, 'settings' => 1, 'xprofile' => 1 ) );
+
+		// reset
+		if ( $get_action ) {
+			$_GET['action'] = $get_action;
+		} else {
+			unset( $_GET['action'] );
+		}
+
+		buddypress()->active_components = $ac;
+	}
+
+	/**
+	 * @group BP6244
+	 * @group bp_core_admin_get_active_components_from_submitted_settings
+	 */
+	public function test_bp_core_admin_get_active_components_from_submitted_settings_should_keep_custom_component_directory_page() {
+		$bp = buddypress();
+		$reset_active_components = $bp->active_components;
+
+		// Create and activate the foo component
+		$bp->foo = new BP_Component;
+		$bp->foo->id   = 'foo';
+		$bp->foo->slug = 'foo';
+		$bp->foo->name = 'Foo';
+		$bp->active_components[ $bp->foo->id ] = 1;
+		$new_page_ids = array( $bp->foo->id => $this->factory->post->create( array(
+			'post_type'  => 'page',
+			'post_title' => $bp->foo->name,
+			'post_name'  => $bp->foo->slug,
+		) ) );
+
+		$page_ids = array_merge( $new_page_ids, (array) bp_core_get_directory_page_ids( 'all' ) );
+		bp_core_update_directory_page_ids( $page_ids );
+
+		$bp->active_components = bp_core_admin_get_active_components_from_submitted_settings( $reset_active_components );
+		bp_core_add_page_mappings( $bp->active_components );
+
+		$this->assertContains( $bp->foo->id, array_keys( bp_core_get_directory_page_ids( 'all' ) ) );
+
+		// Reset buddypress() vars
+		$bp->active_components = $reset_active_components;
+	}
+
+	/**
+	 * @group bp_core_activation_notice
+	 */
+	public function test_bp_core_activation_notice_register_activate_pages_notcreated_signup_allowed() {
+		$bp = buddypress();
+		$reset_bp_pages = $bp->pages;
+		$reset_admin_notices = $bp->admin->notices;
+
+		// Reset pages
+		$bp->pages = bp_core_get_directory_pages();
+
+		add_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		bp_core_activation_notice();
+
+		remove_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		$missing_pages = array();
+		foreach( buddypress()->admin->notices as $notice ) {
+			preg_match_all( '/<strong>(.+?)<\/strong>/', $notice['message'], $missing_pages );
+		}
+
+		$this->assertContains( 'Register', $missing_pages[1] );
+		$this->assertContains( 'Activate', $missing_pages[1] );
+
+		// Reset buddypress() vars
+		$bp->pages = $reset_bp_pages;
+		$bp->admin->notices = $reset_admin_notices;
+	}
+
+	/**
+	 * @group bp_core_activation_notice
+	 */
+	public function test_bp_core_activation_notice_register_activate_pages_created_signup_allowed() {
+		$bp = buddypress();
+		$reset_bp_pages = $bp->pages;
+		$reset_admin_notices = $bp->admin->notices;
+
+		add_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		$ac = buddypress()->active_components;
+		bp_core_add_page_mappings( array_keys( $ac ) );
+
+		// Reset pages
+		$bp->pages = bp_core_get_directory_pages();
+
+		bp_core_activation_notice();
+
+		remove_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		$missing_pages = array();
+		foreach( buddypress()->admin->notices as $notice ) {
+			preg_match_all( '/<strong>(.+?)<\/strong>/', $notice['message'], $missing_pages );
+		}
+
+		$this->assertNotContains( 'Register', $missing_pages[1] );
+		$this->assertNotContains( 'Activate', $missing_pages[1] );
+
+		// Reset buddypress() vars
+		$bp->pages = $reset_bp_pages;
+		$bp->admin->notices = $reset_admin_notices;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/blogs/activity.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/blogs/activity.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/blogs/activity.php	(revision 10410)
@@ -0,0 +1,442 @@
+<?php
+
+class BP_Tests_Blogs_Activity extends BP_UnitTestCase {
+	/**
+	 * @group bp_blogs_register_activity_actions
+	 * @group activity_tracking
+	 */
+	public function test_bp_blogs_loader_post_tracking_args_filter() {
+		$bp = buddypress();
+
+		$expected = array( 'new_blog_post', 'new_blog_comment' );
+
+		if ( is_multisite() ) {
+			$expected = array_merge( array( 'new_blog' ), $expected );
+		}
+
+		$actions = bp_activity_get_actions();
+		$actions = array_keys( (array) $actions->blogs );
+
+		$this->assertEquals( $expected, $actions );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_blogs_format_activity_action_new_blog
+	 */
+	public function test_bp_blogs_format_activity_action_new_blog() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$b = $this->factory->blog->create();
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->blogs->id,
+			'type' => 'new_blog',
+			'user_id' => $u,
+			'item_id' => $b,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$expected = sprintf( '%s created the site %s', bp_core_get_userlink( $u ), '<a href="' . get_blog_option( $b, 'home' ) . '">' . get_blog_option( $b, 'blogname' ) . '</a>' );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_blogs_format_activity_action_new_blog_post
+	 */
+	public function test_bp_blogs_format_activity_action_new_blog_post_nonms() {
+		if ( is_multisite() ) {
+			return;
+		}
+
+		$u = $this->factory->user->create();
+		$p = $this->factory->post->create( array(
+			'post_author' => $u,
+		) );
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->blogs->id,
+			'type' => 'new_blog_post',
+			'user_id' => $u,
+			'item_id' => 1,
+			'secondary_item_id' => $p,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$user_link = bp_core_get_userlink( $u );
+		$blog_url = get_home_url();
+		$post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
+		$post_title = bp_activity_get_meta( $a, 'post_title' );
+		$post_link = '<a href="' . $post_url . '">' . $post_title . '</a>';
+
+		$expected = sprintf( '%s wrote a new post, %s', $user_link, $post_link );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_blogs_format_activity_action_new_blog_post
+	 */
+	public function test_bp_blogs_format_activity_action_new_blog_post_ms_rootblog() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$u = $this->factory->user->create();
+		$p = $this->factory->post->create( array(
+			'post_author' => $u,
+		) );
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->blogs->id,
+			'type' => 'new_blog_post',
+			'user_id' => $u,
+			'item_id' => 1,
+			'secondary_item_id' => $p,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$user_link = bp_core_get_userlink( $u );
+		$blog_url = get_home_url();
+		$post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
+		$post_title = bp_activity_get_meta( $a, 'post_title' );
+		$post_link = '<a href="' . $post_url . '">' . $post_title . '</a>';
+
+		$expected = sprintf( '%s wrote a new post, %s, on the site %s', $user_link, $post_link, '<a href="' . $blog_url . '">' . bp_blogs_get_blogmeta( $a_obj->item_id, 'name' ) . '</a>' );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_blogs_format_activity_action_new_blog_post
+	 */
+	public function test_bp_blogs_format_activity_action_new_blog_post_ms_nonrootblog() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$b = $this->factory->blog->create();
+		$u = $this->factory->user->create();
+
+		switch_to_blog( $b );
+		$p = $this->factory->post->create( array(
+			'post_author' => $u,
+		) );
+		$p_obj = get_post( $p );
+		restore_current_blog();
+
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->blogs->id,
+			'type' => 'new_blog_post',
+			'user_id' => $u,
+			'item_id' => $b,
+			'secondary_item_id' => $p,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$user_link = bp_core_get_userlink( $u );
+		$blog_url = get_blog_option( $a_obj->item_id, 'home' );
+		$post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
+		$post_title = $p_obj->post_title;
+		$post_link = '<a href="' . $post_url . '">' . $post_title . '</a>';
+
+		$expected = sprintf( '%s wrote a new post, %s, on the site %s', $user_link, $post_link, '<a href="' . $blog_url . '">' . get_blog_option( $a_obj->item_id, 'blogname' ) . '</a>' );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_blogs_format_activity_action_new_blog_comment
+	 */
+	public function test_bp_blogs_format_activity_action_new_blog_comment_ms_nonrootblog() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$b = $this->factory->blog->create();
+		$u = $this->factory->user->create();
+
+		switch_to_blog( $b );
+		$p = $this->factory->post->create( array(
+			'post_author' => $u,
+		) );
+		$p_obj = get_post( $p );
+		$c = $this->factory->comment->create( array(
+			'comment_post_ID' => $p,
+		) );
+		$c_obj = get_comment( $c );
+		restore_current_blog();
+
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->blogs->id,
+			'type' => 'new_blog_comment',
+			'user_id' => $u,
+			'item_id' => $b,
+			'secondary_item_id' => $c,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$user_link = bp_core_get_userlink( $u );
+		$blog_url = get_blog_option( $a_obj->item_id, 'home' );
+		$post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
+		$post_title = $p_obj->post_title;
+		$post_link = '<a href="' . $post_url . '">' . $post_title . '</a>';
+
+		$expected = sprintf( '%s commented on the post, %s, on the site %s', $user_link, $post_link, '<a href="' . $blog_url . '">' . get_blog_option( $a_obj->item_id, 'blogname' ) . '</a>' );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group bp_blogs_format_activity_action_new_blog
+	 */
+	public function test_bp_activity_format_activity_action_new_blog_backpat() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		add_filter( 'bp_blogs_activity_created_blog_action', array( $this, 'created_blog_passthrough' ), 10, 2 );
+
+		$b = $this->factory->blog->create();
+		$u = $this->factory->user->create();
+
+		$recorded_blog          = new BP_Blogs_Blog;
+		$recorded_blog->user_id = $u;
+		$recorded_blog->blog_id = $b;
+		$recorded_blog_id       = $recorded_blog->save();
+
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->blogs->id,
+			'type' => 'new_blog',
+			'user_id' => $u,
+			'item_id' => $b,
+		) );
+
+		$this->assertEquals( $this->userblog_id, $recorded_blog_id );
+	}
+
+	/**
+	 * @group bp_blogs_format_activity_action_new_blog_post
+	 */
+	public function test_bp_activity_format_activity_action_new_blog_post_backpat() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		add_filter( 'bp_blogs_activity_new_post_action', array( $this, 'new_post_passthrough' ), 10, 2 );
+
+		$b = $this->factory->blog->create();
+
+		switch_to_blog( $b );
+		$p = $this->factory->post->create();
+		restore_current_blog();
+
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->blogs->id,
+			'type' => 'new_blog_post',
+			'user_id' => $u,
+			'item_id' => $b,
+			'secondary_item_id' => $p,
+		) );
+
+		$this->assertEquals( $this->post_id, $p );
+	}
+
+	/**
+	 * @group bp_blogs_format_activity_action_new_blog_comment
+	 */
+	public function test_bp_activity_format_activity_action_new_blog_comment_backpat() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		add_filter( 'bp_blogs_activity_new_comment_action', array( $this, 'new_comment_passthrough' ), 10, 2 );
+
+		$b = $this->factory->blog->create();
+
+		switch_to_blog( $b );
+		$p = $this->factory->post->create();
+		$c = $this->factory->comment->create( array(
+			'comment_post_ID' => $p,
+		) );
+		restore_current_blog();
+
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->blogs->id,
+			'type' => 'new_blog_comment',
+			'user_id' => $u,
+			'item_id' => $b,
+			'secondary_item_id' => $c,
+		) );
+
+		$this->assertEquals( $this->comment_post_id, $p );
+	}
+
+	/**
+	 * @ticket BP6126
+	 */
+	public function test_check_activity_actions_are_set_when_creating_activity_object() {
+		$bp = buddypress();
+
+		if ( isset( $bp->activity->actions ) ) {
+			unset( $bp->activity->actions );
+		}
+
+		$u = $this->factory->user->create();
+		$p = $this->factory->post->create( array( 'post_author' => $u, ) );
+		$a = $this->factory->activity->create( array(
+			'component'         => buddypress()->blogs->id,
+			'item_id'           => 1,
+			'secondary_item_id' => $p,
+			'type'              => 'new_blog_post',
+			'user_id'           => $u,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+		$this->assertTrue( ! empty( $a_obj->action ) );
+
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_blogs_format_activity_action_new_blog_post
+	 */
+	public function test_bp_blogs_format_activity_action_new_blog_post_no_title() {
+		if ( is_multisite() ) {
+			return;
+		}
+
+		$bp = buddypress();
+		$activity_actions = $bp->activity->actions;
+		$bp->activity->actions = new stdClass();
+
+		$u = $this->factory->user->create();
+		$p = wp_insert_post( array(
+			'post_author' => $u,
+			'post_title'  => '', // no title: the object of the test
+			'post_status' => 'publish',
+			'post_content' => 'foo bar',
+		) );
+
+		$user_link = bp_core_get_userlink( $u );
+		$blog_url = get_home_url();
+		$post_url = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
+		$post_link = '<a href="' . $post_url . '">(no title)</a>';
+
+		// Set activity actions
+		bp_activity_get_actions();
+
+		$a_obj = bp_activity_get( array(
+			'item_id'           => 1,
+			'secondary_item_id' => $p,
+		) );
+
+		$expected = sprintf( '%s wrote a new post, %s', $user_link, $post_link );
+
+		$this->assertSame( $expected, $a_obj['activities'][0]->action );
+
+		// Reset activity actions
+		$bp->activity->actions = $activity_actions;
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_blogs_format_activity_action_new_blog_post
+	 */
+	public function test_bp_blogs_format_activity_action_new_blog_post_updated_without_title() {
+		if ( is_multisite() ) {
+			return;
+		}
+
+		$bp = buddypress();
+		$activity_actions = $bp->activity->actions;
+		$bp->activity->actions = new stdClass();
+
+		$u = $this->factory->user->create();
+		$p = wp_insert_post( array(
+			'post_author' => $u,
+			'post_title'  => 'foo',
+			'post_status' => 'publish',
+			'post_content' => 'foo bar',
+		) );
+
+		$user_link  = bp_core_get_userlink( $u );
+		$blog_url   = get_home_url();
+		$post_url   = add_query_arg( 'p', $p, trailingslashit( $blog_url ) );
+		$post_title = get_the_title( $p );
+		$post_link  = '<a href="' . $post_url . '">' . $post_title . '</a>';
+
+		// Set actions
+		bp_activity_get_actions();
+
+		$a_obj = bp_activity_get( array(
+			'item_id'           => 1,
+			'secondary_item_id' => $p,
+		) );
+
+		$expected = sprintf( '%s wrote a new post, %s', $user_link, $post_link );
+
+		$this->assertSame( $expected, $a_obj['activities'][0]->action );
+
+		// Update the post by removing its title
+		wp_update_post( array(
+			'ID'         => $p,
+			'post_title' => '',
+		) );
+
+		// we now expect the (no title) post link
+		$post_link = '<a href="' . $post_url . '">(no title)</a>';
+		$expected = sprintf( '%s wrote a new post, %s', $user_link, $post_link );
+
+		$a_obj = bp_activity_get( array(
+			'item_id'           => 1,
+			'secondary_item_id' => $p,
+		) );
+
+		$this->assertSame( $expected, $a_obj['activities'][0]->action );
+
+		// Reset activity actions
+		$bp->activity->actions = $activity_actions;
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * Dopey passthrough method so we can check that the correct values
+	 * are being passed to the filter
+	 */
+	public function created_blog_passthrough( $a, $b ) {
+		$this->userblog_id = isset( $b->id ) ? $b->id : '';
+		return $a;
+	}
+
+	/**
+	 * Dopey passthrough method so we can check that the correct values
+	 * are being passed to the filter
+	 */
+	public function new_post_passthrough( $a, $b ) {
+		$this->post_id = isset( $b->ID ) ? $b->ID : '';
+		return $a;
+	}
+
+	/**
+	 * Dopey passthrough method so we can check that the correct values
+	 * are being passed to the filter
+	 */
+	public function new_comment_passthrough( $a, $b ) {
+		$this->comment_post_id = isset( $b->comment_post_ID ) ? $b->comment_post_ID : '';
+		return $a;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/blogs/cache.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/blogs/cache.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/blogs/cache.php	(revision 10410)
@@ -0,0 +1,317 @@
+<?php
+
+/**
+ * @group blogs
+ * @group cache
+ */
+class BP_Tests_Blogs_Cache extends BP_UnitTestCase {
+	/**
+	 * @group bp_blogs_update_meta_cache
+	 */
+	public function test_bp_blogs_update_meta_cache() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$b1 = $this->factory->blog->create();
+		$b2 = $this->factory->blog->create();
+
+		bp_blogs_add_blogmeta( $b1, 'foo', 'bar' );
+		bp_blogs_add_blogmeta( $b1, 'foo2', 'bar2' );
+		bp_blogs_add_blogmeta( $b2, 'foo', 'bar' );
+
+		// Need this info
+		$b1_name = bp_blogs_get_blogmeta( $b1, 'name' );
+		$b1_description = bp_blogs_get_blogmeta( $b1, 'description' );
+		$b1_last_activity = bp_blogs_get_blogmeta( $b1, 'last_activity' );
+
+		$b2_name = bp_blogs_get_blogmeta( $b2, 'name' );
+		$b2_description = bp_blogs_get_blogmeta( $b2, 'description' );
+		$b2_last_activity = bp_blogs_get_blogmeta( $b2, 'last_activity' );
+
+		// Clear caches (due to _get_)
+		wp_cache_delete( $b1, 'blog_meta' );
+		wp_cache_delete( $b2, 'blog_meta' );
+
+		// Caches should be empty
+		$this->assertFalse( wp_cache_get( $b1, 'blog_meta' ) );
+		$this->assertFalse( wp_cache_get( $b2, 'blog_meta' ) );
+
+		bp_blogs_update_meta_cache( array( $b1, $b2 ) );
+
+		$b1_expected = array(
+			'name' => array(
+				$b1_name,
+			),
+			'description' => array(
+				$b1_description,
+			),
+			'last_activity' => array(
+				$b1_last_activity,
+			),
+			'foo' => array(
+				'bar',
+			),
+			'foo2' => array(
+				'bar2',
+			),
+		);
+
+		$b2_expected = array(
+			'name' => array(
+				$b2_name,
+			),
+			'description' => array(
+				$b2_description,
+			),
+			'last_activity' => array(
+				$b2_last_activity,
+			),
+			'foo' => array(
+				'bar',
+			),
+		);
+
+		// The cache may contain more than just this, so loop through
+		// and check only relevant keys
+		$b1_found = wp_cache_get( $b1, 'blog_meta' );
+		foreach ( $b1_expected as $k => $v ) {
+			$this->assertSame( $v, $b1_found[ $k ] );
+		}
+
+		$b2_found = wp_cache_get( $b2, 'blog_meta' );
+		foreach ( $b2_expected as $k => $v ) {
+			$this->assertSame( $v, $b2_found[ $k ] );
+		}
+	}
+
+	/**
+	 * @group bp_blogs_update_meta_cache
+	 * @group bp_has_blogs
+	 */
+	public function test_bp_blogs_update_meta_cache_bp_has_blogs() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$u = $this->factory->user->create();
+
+		// Switch user so we have access to non-public blogs
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$b1 = $this->factory->blog->create();
+		$b2 = $this->factory->blog->create();
+
+		bp_blogs_record_blog( $b1, $u, true );
+		bp_blogs_record_blog( $b2, $u, true );
+
+		bp_blogs_add_blogmeta( $b1, 'foo', 'bar' );
+		bp_blogs_add_blogmeta( $b1, 'foo2', 'bar2' );
+		bp_blogs_add_blogmeta( $b2, 'foo', 'bar' );
+
+		// Need this info
+		$b1_name = bp_blogs_get_blogmeta( $b1, 'name' );
+		$b1_description = bp_blogs_get_blogmeta( $b1, 'description' );
+		$b1_last_activity = bp_blogs_get_blogmeta( $b1, 'last_activity' );
+
+		$b2_name = bp_blogs_get_blogmeta( $b2, 'name' );
+		$b2_description = bp_blogs_get_blogmeta( $b2, 'description' );
+		$b2_last_activity = bp_blogs_get_blogmeta( $b2, 'last_activity' );
+
+		// Clear caches (due to _get_)
+		wp_cache_delete( $b1, 'blog_meta' );
+		wp_cache_delete( $b2, 'blog_meta' );
+
+		// Caches should be empty
+		$this->assertFalse( wp_cache_get( $b1, 'blog_meta' ) );
+		$this->assertFalse( wp_cache_get( $b2, 'blog_meta' ) );
+
+		bp_has_blogs( array(
+			'user_id' => $u,
+		) );
+
+		$b1_expected = array(
+			'name' => array(
+				$b1_name,
+			),
+			'description' => array(
+				$b1_description,
+			),
+			'last_activity' => array(
+				$b1_last_activity,
+			),
+			'foo' => array(
+				'bar',
+			),
+			'foo2' => array(
+				'bar2',
+			),
+		);
+
+		$b2_expected = array(
+			'name' => array(
+				$b2_name,
+			),
+			'description' => array(
+				$b2_description,
+			),
+			'last_activity' => array(
+				$b2_last_activity,
+			),
+			'foo' => array(
+				'bar',
+			),
+		);
+
+		// The cache may contain more than just this, so loop through
+		// and check only relevant keys
+		$b1_found = wp_cache_get( $b1, 'blog_meta' );
+		foreach ( $b1_expected as $k => $v ) {
+			$this->assertSame( $v, $b1_found[ $k ] );
+		}
+
+		$b2_found = wp_cache_get( $b2, 'blog_meta' );
+		foreach ( $b2_expected as $k => $v ) {
+			$this->assertSame( $v, $b2_found[ $k ] );
+		}
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group bp_blogs_update_meta_cache
+	 * @group bp_has_blogs
+	 */
+	public function test_bp_blogs_update_meta_cache_bp_has_blogs_false() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$u = $this->factory->user->create();
+
+		// Switch user so we have access to non-public blogs
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$b1 = $this->factory->blog->create();
+		$b2 = $this->factory->blog->create();
+
+		bp_blogs_record_blog( $b1, $u, true );
+		bp_blogs_record_blog( $b2, $u, true );
+
+		bp_blogs_add_blogmeta( $b1, 'foo', 'bar' );
+		bp_blogs_add_blogmeta( $b1, 'foo2', 'bar2' );
+		bp_blogs_add_blogmeta( $b2, 'foo', 'bar' );
+
+		// Need this info
+		$b1_name = bp_blogs_get_blogmeta( $b1, 'name' );
+		$b1_description = bp_blogs_get_blogmeta( $b1, 'description' );
+		$b1_last_activity = bp_blogs_get_blogmeta( $b1, 'last_activity' );
+
+		$b2_name = bp_blogs_get_blogmeta( $b2, 'name' );
+		$b2_description = bp_blogs_get_blogmeta( $b2, 'description' );
+		$b2_last_activity = bp_blogs_get_blogmeta( $b2, 'last_activity' );
+
+		// Clear caches (due to _get_)
+		wp_cache_delete( $b1, 'blog_meta' );
+		wp_cache_delete( $b2, 'blog_meta' );
+
+		// Caches should be empty
+		$this->assertFalse( wp_cache_get( $b1, 'blog_meta' ) );
+		$this->assertFalse( wp_cache_get( $b2, 'blog_meta' ) );
+
+		bp_has_blogs( array(
+			'update_meta_cache' => false,
+		) );
+
+		// Caches should be empty
+		$this->assertFalse( wp_cache_get( $b1, 'blog_meta' ) );
+		$this->assertFalse( wp_cache_get( $b2, 'blog_meta' ) );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group bp_blogs_total_blogs
+	 * @group counts
+	 */
+	public function test_bp_blogs_total_count_should_respect_cached_value_of_0() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		global $wpdb;
+
+		// prime cache
+		// no blogs are created by default, so count is zero
+		bp_blogs_total_blogs();
+		$first_query_count = $wpdb->num_queries;
+
+		// run function again
+		bp_blogs_total_blogs();
+
+		// check if function references cache or hits the DB by comparing query count
+		$this->assertEquals( $first_query_count, $wpdb->num_queries );
+	}
+
+	/**
+	 * @group bp_blogs_total_blogs
+	 */
+	public function test_bp_blogs_total_blogs_count_after_delete_blog() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$u = $this->factory->user->create();
+
+		// need to make sure we set the 'public' flag due to how BP_Blogs_Blogs:get_all() works
+		$b1 = $this->factory->blog->create( array(
+			'meta' => array(
+				'public' => 1
+			)
+		) );
+		$b2 = $this->factory->blog->create( array(
+			'meta' => array(
+				'public' => 1
+			)
+		) );
+
+		bp_blogs_record_blog( $b1, $u );
+		bp_blogs_record_blog( $b2, $u );
+
+		// prime total blog count
+		bp_blogs_total_blogs();
+
+		// delete a blog
+		wpmu_delete_blog( $b2 );
+
+		$this->assertEquals( 1, bp_blogs_total_blogs() );
+	}
+
+	/**
+	 * @group update_blog_details
+	 */
+	public function test_update_blog_details_should_purge_blogmeta_cache() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$u = $this->factory->user->create();
+
+		$b1 = $this->factory->blog->create();
+		bp_blogs_record_blog( $b1, $u, true );
+
+		// prime cache
+		bp_blogs_get_blogmeta( $b1, 'url' );
+		$this->assertNotEmpty( wp_cache_get( $b1, 'blog_meta' ) );
+
+		// updating blog details should purge cache
+		update_blog_details( $b1, array(
+			'domain' => 'awesome.com'
+		) );
+
+		// assert cache is purged
+		$this->assertEmpty( wp_cache_get( $b1, 'blog_meta' ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/blogs/class-bp-blogs-blog.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/blogs/class-bp-blogs-blog.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/blogs/class-bp-blogs-blog.php	(revision 10410)
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * @group blogs
+ * @group BP_Blogs_Blog
+ */
+class BP_Tests_BP_Blogs_Blog_TestCases extends BP_UnitTestCase {
+	public function test_get_with_search_terms() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$old_user = get_current_user_id();
+
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+		$b = $this->factory->blog->create( array(
+			'title' => 'The Foo Bar Blog',
+			'user_id' => $u,
+		) );
+		bp_blogs_record_existing_blogs();
+
+		// make the blog public or it won't turn up in generic results
+		update_blog_option( $b, 'blog_public', '1' );
+
+		$blogs = BP_Blogs_Blog::get( 'active', false, false, 0, 'Foo' );
+		$blog_ids = wp_list_pluck( $blogs['blogs'], 'blog_id' );
+
+		$this->assertEquals( array( $b ), $blog_ids );
+	}
+
+	/**
+	 * @ticket BP5858
+	 */
+	public function test_get_with_search_terms_should_match_description() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$old_user = get_current_user_id();
+
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+		$b = $this->factory->blog->create( array(
+			'title' => 'The Foo Bar Blog',
+			'domain' => __METHOD__,
+			'user_id' => $u,
+		) );
+		update_blog_option( $b, 'blogdescription', 'Full of foorificness' );
+		bp_blogs_record_existing_blogs();
+
+		// make the blog public or it won't turn up in generic results
+		update_blog_option( $b, 'blog_public', '1' );
+
+		$blogs = BP_Blogs_Blog::get( 'active', false, false, 0, 'Full' );
+		$blog_ids = wp_list_pluck( $blogs['blogs'], 'blog_id' );
+
+		$this->assertEquals( array( $b ), $blog_ids );
+		$this->assertEquals( 1, $blogs['total'] );
+	}
+
+	public function test_search_blogs() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$old_user = get_current_user_id();
+
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+		$b = $this->factory->blog->create( array(
+			'title' => 'The Foo Bar Blog',
+			'user_id' => $u,
+			'path' => '/path' . rand() . time() . '/',
+		) );
+		bp_blogs_record_existing_blogs();
+
+		// make the blog public or it won't turn up in generic results
+		update_blog_option( $b, 'blog_public', '1' );
+
+		$blogs = BP_Blogs_Blog::search_blogs( 'Foo' );
+		$blog_ids = wp_list_pluck( $blogs['blogs'], 'blog_id' );
+
+		$this->assertEquals( array( $b ), $blog_ids );
+	}
+
+	/**
+	 * @group get_by_letter
+	 */
+	public function test_get_by_letter() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$old_user = get_current_user_id();
+
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+		$b = $this->factory->blog->create( array(
+			'title' => 'Foo Bar Blog',
+			'user_id' => $u,
+			'path' => '/path' . rand() . time() . '/',
+		) );
+		bp_blogs_record_existing_blogs();
+
+		// make the blog public or it won't turn up in generic results
+		update_blog_option( $b, 'blog_public', '1' );
+
+		$blogs = BP_Blogs_Blog::get_by_letter( 'F' );
+		$blog_ids = wp_list_pluck( $blogs['blogs'], 'blog_id' );
+
+		$this->assertEquals( array( $b ), $blog_ids );
+	}
+
+	/**
+	 * @group get_order_by
+	 */
+	public function test_get_order_by() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$old_user = get_current_user_id();
+
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+		$bs = array(
+			'foobar' => $this->factory->blog->create( array(
+				'title' => 'Foo Bar Blog',
+				'user_id' => $u,
+				'path' => '/path' . rand() . time() . '/',
+			) ),
+			'barfoo' => $this->factory->blog->create( array(
+				'title' => 'Bar foo Blog',
+				'user_id' => $u,
+				'path' => '/path' . rand() . time() . '/',
+			) ),
+		);
+
+		bp_blogs_record_existing_blogs();
+
+		// make the blog public or it won't turn up in generic results
+		foreach ( $bs as $b ) {
+			update_blog_option( $b, 'blog_public', '1' );
+		}
+
+		// Used to make sure barfoo is older than foobar
+		$b_time = date_i18n( 'Y-m-d H:i:s', strtotime( '-5 minutes' ) );
+
+		/* Alphabetical */
+		$blogs = BP_Blogs_Blog::get( 'alphabetical', false, false, $u );
+		$blog_ids = wp_list_pluck( $blogs['blogs'], 'blog_id' );
+		$this->assertEquals( array( $bs['barfoo'], $bs['foobar'] ), $blog_ids );
+
+		/* Newest */
+		update_blog_details( $bs['barfoo'], array( 'registered' => $b_time ) );
+		$blogs = BP_Blogs_Blog::get( 'newest', false, false, $u );
+		$blog_ids = wp_list_pluck( $blogs['blogs'], 'blog_id' );
+		$this->assertEquals( array( $bs['foobar'], $bs['barfoo'] ), $blog_ids );
+
+		/* Active */
+		bp_blogs_update_blogmeta( $bs['barfoo'], 'last_activity', $b_time );
+		$blogs = BP_Blogs_Blog::get( 'active', false, false, $u );
+		$blog_ids = wp_list_pluck( $blogs['blogs'], 'blog_id' );
+		$this->assertEquals( array( $bs['foobar'],$bs['barfoo'] ), $blog_ids );
+
+		/* Random */
+		$blogs = BP_Blogs_Blog::get( 'random', false, false, $u );
+		$this->assertTrue( 2 == count( $blogs['blogs'] ) );
+
+		$this->set_current_user( $old_user );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/blogs/filters.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/blogs/filters.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/blogs/filters.php	(revision 10410)
@@ -0,0 +1,142 @@
+<?php
+/**
+ * @group blogs
+ * @ticket BP6306
+ */
+class BP_Tests_Blogs_Filters extends BP_UnitTestCase {
+	protected $activity_actions;
+	protected $custom_post_types;
+
+	public function setUp() {
+		parent::setUp();
+
+		$bp = buddypress();
+
+		$this->activity_actions = $bp->activity->actions;
+		$bp->activity->actions = new stdClass();
+
+		$this->custom_post_types = array( 'using_old_filter' );
+
+		register_post_type( 'using_old_filter', array(
+			'label'   => 'using_old_filter',
+			'public'   => true,
+			'supports' => array( 'comments' ),
+		) );
+
+		add_filter( 'bp_blogs_record_post_post_types',    array( $this, 'filter_post_types'), 10, 1 );
+		add_filter( 'bp_blogs_record_comment_post_types', array( $this, 'filter_post_types'), 10, 1 );
+	}
+
+	function tearDown() {
+		parent::tearDown();
+
+		$bp = buddypress();
+
+		_unregister_post_type( 'using_old_filter' );
+		remove_filter( 'bp_blogs_record_post_post_types',    array( $this, 'filter_post_types'), 10, 1 );
+		remove_filter( 'bp_blogs_record_comment_post_types', array( $this, 'filter_post_types'), 10, 1 );
+
+		// Reset activity actions
+		$bp->activity->actions = $this->activity_actions;
+		$bp->activity->track = array();
+	}
+
+	/**
+	 * @goup bp_activity_get_actions
+	 */
+	public function test_bp_activity_get_actions() {
+		$activity_actions = bp_activity_get_actions();
+
+		$this->assertTrue( ! isset( $activity_actions->activity->new_using_old_filter ), 'Post types registering using the bp_blogs_record_post_post_types filter should not have a specific action' );
+	}
+
+	/**
+	 * @goup bp_activity_catch_transition_post_type_status
+	 */
+	public function test_bp_activity_catch_transition_post_type_status() {
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type'   => 'using_old_filter',
+		) );
+
+		$this->assertTrue( $this->activity_exists_for_post_type( get_current_blog_id(), $post_id, 'new_blog_post' ), 'Generated activity for a post type registering using the bp_blogs_record_post_post_types filter should have a new_blog_post action' );
+	}
+
+	/**
+	 * @goup bp_blogs_record_comment
+	 */
+	public function test_bp_blogs_record_comment() {
+		$u = $this->factory->user->create();
+		$user = $this->factory->user->get_object_by_id( $u );
+
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type'   => 'using_old_filter',
+			'post_author' => $u,
+		) );
+
+		$comment_id = $this->factory->comment->create( array(
+			'user_id'              => $u,
+			'comment_author_email' => $user->user_email,
+			'comment_post_ID'      => $post_id,
+		) );
+
+		// Approve the comment
+		$this->factory->comment->update_object( $comment_id, array( 'comment_approved' => 1 ) );
+
+		$this->assertTrue( $this->activity_exists_for_post_type( get_current_blog_id(), $comment_id, 'new_blog_comment' ), 'Generated activity for comments about a post type registering using the bp_blogs_record_post_post_types filter should have a new_blog_comment action' );
+	}
+
+	/**
+	 * @goup bp_blogs_record_comment_sync_activity_comment
+	 */
+	public function test_bp_blogs_record_comment_sync_activity_comment() {
+		$u = $this->factory->user->create();
+		$user = $this->factory->user->get_object_by_id( $u );
+
+		add_filter( 'bp_disable_blogforum_comments', '__return_false' );
+
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type'   => 'using_old_filter',
+			'post_author' => $u,
+		) );
+
+		$parent_activity_id = bp_activity_get_activity_id( array(
+			'component'         => 'blogs',
+			'type'              => 'new_blog_post',
+			'item_id'           => get_current_blog_id(),
+			'secondary_item_id' => $post_id
+		) );
+
+		$comment_id = $this->factory->comment->create( array(
+			'user_id'              => $u,
+			'comment_author_email' => $user->user_email,
+			'comment_post_ID'      => $post_id,
+		) );
+
+		// Approve the comment
+		$this->factory->comment->update_object( $comment_id, array( 'comment_approved' => 1 ) );
+
+		$this->assertTrue( $this->activity_exists_for_post_type( $parent_activity_id, '', 'activity_comment', 'stream' ), 'Generated activity for comments about a post type registering using the bp_blogs_record_post_post_types filter having sync on should have a activity_comment action' );
+
+		remove_filter( 'bp_disable_blogforum_comments', '__return_false' );
+	}
+
+	public function filter_post_types( $post_types ) {
+		$post_types = array_merge( $post_types, $this->custom_post_types );
+		return $post_types;
+	}
+
+	protected function activity_exists_for_post_type( $item_id, $secondary_item_id, $action, $display_comments = false ) {
+		$a = bp_activity_get( array(
+			'display_comments'  => $display_comments,
+			'filter'            => array(
+				'action'        => $action,
+				'primary_id'    => $item_id,
+				'secondary_id'  => $secondary_item_id,
+		) ) );
+
+		return ! empty( $a['activities'] );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/blogs/functions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/blogs/functions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/blogs/functions.php	(revision 10410)
@@ -0,0 +1,853 @@
+<?php
+
+/**
+ * @group blogs
+ */
+class BP_Tests_Blogs_Functions extends BP_UnitTestCase {
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_delete_blogmeta
+	 */
+	public function test_bp_blogs_delete_blogmeta_non_numeric_blog_id() {
+		$this->assertFalse( bp_blogs_delete_blogmeta( 'foo' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_delete_blogmeta
+	 * @ticket BP5399
+	 */
+	public function test_bp_blogs_delete_blogmeta_illegal_characters() {
+		$this->assertNotEmpty( bp_blogs_update_blogmeta( 1, 'foo', 'bar' ) );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo' ) );
+		$krazy_key = ' f!@#$%^o *(){}o?+';
+		$this->assertFalse( bp_blogs_delete_blogmeta( 1, $krazy_key ) );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_delete_blogmeta
+	 * @ticket BP5399
+	 */
+	public function test_bp_blogs_delete_blogmeta_trim_meta_value() {
+		$this->assertNotEmpty( bp_blogs_update_blogmeta( 1, 'foo', 'bar' ) );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo' ) );
+		bp_blogs_delete_blogmeta( 1, 'foo', '   bar  ' );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_delete_blogmeta
+	 */
+	public function test_bp_blogs_delete_blogmeta_no_meta_key() {
+		bp_blogs_update_blogmeta( 1, 'foo', 'bar' );
+		bp_blogs_update_blogmeta( 1, 'foo2', 'bar2' );
+		$this->assertNotEmpty( bp_blogs_get_blogmeta( 1 ) );
+		$this->assertTrue( bp_blogs_delete_blogmeta( 1 ) );
+		$this->assertSame( array(), bp_blogs_get_blogmeta( 1 ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_delete_blogmeta
+	 */
+	public function test_bp_blogs_delete_blogmeta_with_meta_value() {
+		bp_blogs_update_blogmeta( 1, 'foo', 'bar' );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo' ) );
+		bp_blogs_delete_blogmeta( 1, 'foo', 'baz' );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo' ) );
+		$this->assertTrue( bp_blogs_delete_blogmeta( 1, 'foo', 'bar' ) );
+		$this->assertSame( '', bp_blogs_get_blogmeta( 1, 'foo' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_delete_blogmeta
+	 */
+	public function test_bp_blogs_delete_blogmeta_with_delete_all_but_no_meta_key() {
+		// With no meta key, don't delete for all items - just delete
+		// all for a single item
+		bp_blogs_add_blogmeta( 1, 'foo', 'bar' );
+		bp_blogs_add_blogmeta( 1, 'foo1', 'bar1' );
+		bp_blogs_add_blogmeta( 2, 'foo', 'bar' );
+		bp_blogs_add_blogmeta( 2, 'foo1', 'bar1' );
+
+		$this->assertTrue( bp_blogs_delete_blogmeta( 1, '', '', true ) );
+		$this->assertEmpty( bp_blogs_get_blogmeta( 1 ) );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 2, 'foo' ) );
+		$this->assertSame( 'bar1', bp_blogs_get_blogmeta( 2, 'foo1' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_delete_blogmeta
+	 */
+	public function test_bp_blogs_delete_blogmeta_with_delete_all() {
+		// With no meta key, don't delete for all items - just delete
+		// all for a single item
+		bp_blogs_add_blogmeta( 1, 'foo', 'bar' );
+		bp_blogs_add_blogmeta( 1, 'foo1', 'bar1' );
+		bp_blogs_add_blogmeta( 2, 'foo', 'bar' );
+		bp_blogs_add_blogmeta( 2, 'foo1', 'bar1' );
+
+		$this->assertTrue( bp_blogs_delete_blogmeta( 1, 'foo', '', true ) );
+		$this->assertSame( '', bp_blogs_get_blogmeta( 1, 'foo' ) );
+		$this->assertSame( '', bp_blogs_get_blogmeta( 2, 'foo' ) );
+		$this->assertSame( 'bar1', bp_blogs_get_blogmeta( 1, 'foo1' ) );
+		$this->assertSame( 'bar1', bp_blogs_get_blogmeta( 2, 'foo1' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_get_blogmeta
+	 */
+	public function test_bp_blogs_get_blogmeta_empty_blog_id() {
+		$this->assertFalse( bp_blogs_get_blogmeta( 0 ) );
+		$this->assertFalse( bp_blogs_get_blogmeta( '' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_get_blogmeta
+	 * @ticket BP5399
+	 */
+	public function test_bp_blogs_get_blogmeta_illegal_characters() {
+		bp_blogs_update_blogmeta( 1, 'foo', 'bar' );
+		$krazy_key = ' f!@#$%^o *(){}o?+';
+		$this->assertSame( '', bp_blogs_get_blogmeta( 1, $krazy_key ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_get_blogmeta
+	 */
+	public function test_bp_blogs_get_blogmeta_no_meta_key() {
+		bp_blogs_update_blogmeta( 1, 'foo', 'bar' );
+		bp_blogs_update_blogmeta( 1, 'foo2', 'bar2' );
+
+		$expected = array(
+			'foo' => array(
+				'bar',
+			),
+			'foo2' => array(
+				'bar2',
+			),
+		);
+
+		$this->assertSame( $expected, bp_blogs_get_blogmeta( 1 ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_get_blogmeta
+	 */
+	public function test_bp_blogs_get_blogmeta_no_meta_key_empty() {
+		$this->assertSame( array(), bp_blogs_get_blogmeta( 1 ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_get_blogmeta
+	 */
+	public function test_bp_blogs_get_blogmeta_single_true() {
+		bp_blogs_add_blogmeta( 1, 'foo', 'bar' );
+		bp_blogs_add_blogmeta( 1, 'foo', 'baz' );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo' ) ); // default is true
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo', true ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_get_blogmeta
+	 */
+	public function test_bp_blogs_get_blogmeta_single_false() {
+		bp_blogs_add_blogmeta( 1, 'foo', 'bar' );
+		bp_blogs_add_blogmeta( 1, 'foo', 'baz' );
+		$this->assertSame( array( 'bar', 'baz' ), bp_blogs_get_blogmeta( 1, 'foo', false ) );
+	}
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_update_blogmeta
+	 */
+	public function test_bp_blogs_update_blogmeta_non_numeric_blog_id() {
+		$this->assertFalse( bp_blogs_update_blogmeta( 'foo', 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_update_blogmeta
+	 * @ticket BP5399
+	 */
+	public function test_bp_blogs_update_blogmeta_illegal_characters() {
+		$krazy_key = ' f!@#$%^o *(){}o?+';
+		bp_blogs_update_blogmeta( 1, $krazy_key, 'bar' );
+		$this->assertSame( '', bp_blogs_get_blogmeta( 1, 'foo' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_update_blogmeta
+	 */
+	public function test_bp_blogs_update_blogmeta_stripslashes() {
+		$slashed = 'This \"string\" is cool';
+		bp_blogs_update_blogmeta( 1, 'foo', $slashed );
+		$this->assertSame( 'This "string" is cool', bp_blogs_get_blogmeta( 1, 'foo' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_update_blogmeta
+	 */
+	public function test_bp_blogs_update_blogmeta_new() {
+		$this->assertNotEmpty( bp_blogs_update_blogmeta( 1, 'foo', 'bar' ) );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_update_blogmeta
+	 */
+	public function test_bp_blogs_update_blogmeta_existing() {
+		bp_blogs_update_blogmeta( 1, 'foo', 'bar' );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo' ) );
+		$this->assertTrue( bp_blogs_update_blogmeta( 1, 'foo', 'baz' ) );
+		$this->assertSame( 'baz', bp_blogs_get_blogmeta( 1, 'foo' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_update_blogmeta
+	 */
+	public function test_bp_blogs_update_blogmeta_existing_no_change() {
+		bp_blogs_update_blogmeta( 1, 'foo', 'bar' );
+		$this->assertSame( 'bar', bp_blogs_get_blogmeta( 1, 'foo' ) );
+		$this->assertFalse( bp_blogs_update_blogmeta( 1, 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_update_blogmeta
+	 */
+	public function test_bp_blogs_update_meta_prev_value() {
+		bp_blogs_add_blogmeta( 1, 'foo', 'bar' );
+
+		// In earlier versions of WordPress, bp_activity_update_meta()
+		// returns true even on failure. However, we know that in these
+		// cases the update is failing as expected, so we skip this
+		// assertion just to keep our tests passing
+		// See https://core.trac.wordpress.org/ticket/24933
+		if ( version_compare( $GLOBALS['wp_version'], '3.7', '>=' ) ) {
+			$this->assertFalse( bp_blogs_update_blogmeta( 1, 'foo', 'bar2', 'baz' ) );
+		}
+
+		$this->assertTrue( bp_blogs_update_blogmeta( 1, 'foo', 'bar2', 'bar' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_add_blogmeta
+	 */
+	public function test_bp_blogs_add_blogmeta_no_meta_key() {
+		$this->assertFalse( bp_blogs_add_blogmeta( 1, '', 'bar' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_add_blogmeta
+	 */
+	public function test_bp_blogs_add_blogmeta_empty_object_id() {
+		$this->assertFalse( bp_blogs_add_blogmeta( 0, 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_add_blogmeta
+	 */
+	public function test_bp_blogs_add_blogmeta_existing_unique() {
+		bp_blogs_add_blogmeta( 1, 'foo', 'bar' );
+		$this->assertFalse( bp_blogs_add_blogmeta( 1, 'foo', 'baz', true ) );
+	}
+
+	/**
+	 * @group blogmeta
+	 * @group bp_blogs_add_blogmeta
+	 */
+	public function test_bp_blogs_add_blogmeta_existing_not_unique() {
+		bp_blogs_add_blogmeta( 1, 'foo', 'bar' );
+		$this->assertNotEmpty( bp_blogs_add_blogmeta( 1, 'foo', 'baz' ) );
+	}
+
+	/**
+	 * @group bp_blogs_restore_data
+	 */
+	public function test_bp_blogs_restore_data() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		// Create a regular member
+		$u = $this->factory->user->create();
+
+		// Create blogs
+		$b1 = $this->factory->blog->create( array( 'user_id' => $u ) );
+		$b2 = $this->factory->blog->create( array( 'user_id' => $u ) );
+
+		$expected = array(
+			$b1 => $b1,
+			$b2 => $b2
+		);
+
+		// Mark the user as spam
+		bp_core_process_spammer_status( $u, 'spam' );
+
+		// get all blogs for user
+		$blogs = bp_blogs_get_blogs_for_user( $u, true );
+		$blog_ids = wp_list_pluck( $blogs['blogs'], 'blog_id' );
+
+		$this->assertNotEquals( $expected, array_map( 'intval', $blog_ids ), 'User marked as spam should not have any blog registered' );
+
+		// Ham the user
+		bp_core_process_spammer_status( $u, 'ham' );
+
+		// get all blogs for user
+		$blogs = bp_blogs_get_blogs_for_user( $u, true );
+		$blog_ids = wp_list_pluck( $blogs['blogs'], 'blog_id' );
+
+		$this->assertEquals( $expected, array_map( 'intval', $blog_ids ) );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_transition_post_status_publish_to_publish() {
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type' => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		// 'publish' => 'publish'
+		$this->assertTrue( $this->activity_exists_for_post( $post_id ), 'Published post should have activity' );
+
+		$post->post_status = 'publish';
+		$post->post_content .= ' foo';
+
+		wp_update_post( $post );
+
+		$this->assertTrue( $this->activity_exists_for_post( $post_id ), 'Published post should have activity (no change)' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_transition_post_status_password_publish() {
+		$post_id = $this->factory->post->create( array(
+			'post_status'   => 'publish',
+			'post_type'     => 'post',
+			'post_password' => 'pass',
+		) );
+		$post = get_post( $post_id );
+
+		// 'new' => 'publish with password'
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Published with password post should not have activity' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_transition_post_status_publish_update_password() {
+		$post_id = $this->factory->post->create( array(
+			'post_status'   => 'publish',
+			'post_type'     => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		// 'publish' => 'publish'
+		$this->assertTrue( $this->activity_exists_for_post( $post_id ), 'Published post should have activity' );
+
+		$post->post_content .= ' foo';
+		$post->post_password = 'pass';
+
+		wp_update_post( $post );
+
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Updated with password post should not have activity' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_transition_post_status_private_publish() {
+		$post_id = $this->factory->post->create( array(
+			'post_status'   => 'private',
+			'post_type'     => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		// 'new' => 'private'
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Private post should not have activity' );
+
+		$post->post_status = 'publish';
+
+		wp_update_post( $post );
+
+		// 'private' => 'publish'
+		$this->assertTrue( $this->activity_exists_for_post( $post_id ), 'Published post should have activity' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_transition_post_status_publish_private() {
+		$post_id = $this->factory->post->create( array(
+			'post_status'   => 'publish',
+			'post_type'     => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		// 'new' => 'publish'
+		$this->assertTrue( $this->activity_exists_for_post( $post_id ), 'Published post should have activity' );
+
+		$post->post_status = 'private';
+
+		wp_update_post( $post );
+
+		// 'publish' => 'private'
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Private post should not have activity' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_transition_post_status_draft_to_draft() {
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'draft',
+			'post_type' => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Unpublished post should not have activity' );
+
+		$post->post_status = 'draft';
+		$post->post_content .= ' foo';
+
+		wp_update_post( $post );
+
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Unpublished post should not have activity (no change)' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_transition_post_status_draft_to_publish() {
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'draft',
+			'post_type' => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Unpublished post should not have activity' );
+
+		$post->post_status = 'publish';
+		$post->post_content .= ' foo';
+
+		wp_update_post( $post );
+
+		$this->assertTrue( $this->activity_exists_for_post( $post_id ), 'Published post should have activity' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_transition_post_status_publish_to_draft() {
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type' => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		$this->assertTrue( $this->activity_exists_for_post( $post_id ), 'Published post should have activity' );
+
+		$post->post_status = 'draft';
+		$post->post_content .= ' foo';
+
+		wp_update_post( $post );
+
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Unpublished post should not have activity' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_transition_post_status_wp_delete_post() {
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type' => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		$this->assertTrue( $this->activity_exists_for_post( $post_id ), 'Published post should have activity' );
+
+		wp_delete_post( $post->ID );
+
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Unpublished post should not have activity' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_transition_post_status_wp_trash_post() {
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type' => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		$this->assertTrue( $this->activity_exists_for_post( $post_id ), 'Published post should have activity' );
+
+		wp_trash_post( $post->ID );
+
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Unpublished post should not have activity' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_update_blog_post_and_new_blog_comment_and_activity_comment_meta() {
+		// save the current user and override logged-in user
+		$old_user = get_current_user_id();
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+		$userdata = get_userdata( $u );
+
+		// create the blog post
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type' => 'post',
+			'post_title' => 'First title',
+		) );
+
+		// remove comment flood protection temporarily
+		add_filter( 'comment_flood_filter', '__return_false' );
+
+		// let's use activity comments instead of single "new_blog_comment" activity items
+		add_filter( 'bp_disable_blogforum_comments', '__return_false' );
+		$c1 = wp_new_comment( array(
+			'comment_post_ID'      => $post_id,
+			'comment_author'       => $userdata->user_nicename,
+			'comment_author_url'   => 'http://buddypress.org',
+			'comment_author_email' => $userdata->user_email,
+			'comment_content'      => 'this is a blog comment',
+			'comment_type'         => '',
+			'comment_parent'       => 0,
+			'user_id'              => $u,
+		) );
+		remove_filter( 'bp_disable_blogforum_comments', '__return_false' );
+
+		// let's also add a "new_blog_comment" activity entry
+		$c2 = wp_new_comment( array(
+			'comment_post_ID'      => $post_id,
+			'comment_author'       => $userdata->user_nicename,
+			'comment_author_url'   => 'http://buddypress.org',
+			'comment_author_email' => $userdata->user_email,
+			'comment_content'      => 'this is another blog comment',
+			'comment_type'         => '',
+			'comment_parent'       => 0,
+			'user_id'              => $u,
+		) );
+
+		// bring back flood protection
+		remove_filter( 'comment_flood_filter', '__return_false' );
+
+		// update the initial blog post
+		wp_update_post( array(
+			'ID'        => $post_id,
+			'post_title' => 'Second title',
+		) );
+
+		// grab the activity ID for the activity comment
+		$a1 = bp_activity_get_activity_id( array(
+			'type'              => 'activity_comment',
+			'display_comments'  => 'stream',
+			'meta_query'        => array( array(
+				'key'     => 'bp_blogs_post_comment_id',
+				'value'   => $c1,
+			) )
+		) );
+
+		// grab the activity ID for the blog comment
+		$a2 = bp_activity_get_activity_id( array(
+			'component'         => buddypress()->blogs->id,
+			'type'              => 'new_blog_comment',
+			'secondary_item_id' => $c2,
+		) );
+
+		// see if blog comment activity meta matches the post items
+		$this->assertEquals( 'Second title', bp_activity_get_meta( $a1, 'post_title' ) );
+		$this->assertEquals( add_query_arg( 'p', $post_id, home_url( '/' ) ), bp_activity_get_meta( $a1, 'post_url' ) );
+
+		$this->assertEquals( 'Second title', bp_activity_get_meta( $a2, 'post_title' ) );
+		$this->assertEquals( add_query_arg( 'p', $post_id, home_url( '/' ) ), bp_activity_get_meta( $a2, 'post_url' ) );
+
+		// reset
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group bp_blogs_transition_activity_status
+	 * @group bp_blogs_remove_comment
+	 */
+	public function test_bp_blogs_remove_comment_should_remove_spammed_activity_comment() {
+		// save the current user and override logged-in user
+		$old_user = get_current_user_id();
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+		$userdata = get_userdata( $u );
+
+		// create the blog post
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type' => 'post',
+			'post_title' => 'First title',
+		) );
+
+		// let's use activity comments instead of single "new_blog_comment" activity items
+		add_filter( 'bp_disable_blogforum_comments', '__return_false' );
+		$c1 = wp_new_comment( array(
+			'comment_post_ID'      => $post_id,
+			'comment_author'       => $userdata->user_nicename,
+			'comment_author_url'   => 'http://buddypress.org',
+			'comment_author_email' => $userdata->user_email,
+			'comment_content'      => 'this is a blog comment',
+			'comment_type'         => '',
+			'comment_parent'       => 0,
+			'user_id'              => $u,
+		) );
+
+		// save the corresponding activity comment ID
+		$a1 = bp_activity_get_activity_id( array(
+			'type'              => 'activity_comment',
+			'display_comments'  => 'stream',
+			'meta_query'        => array( array(
+				'key'     => 'bp_blogs_post_comment_id',
+				'value'   => $c1,
+			) )
+		) );
+
+		// trash the parent comment.
+		// corresponding activity comment should now be marked as spam
+		// @see bp_blogs_transition_activity_status()
+		wp_trash_comment( $c1 );
+
+		// now permanently delete the comment
+		wp_delete_comment( $c1, true );
+
+		// activity comment should no longer exist
+		$a = bp_activity_get( array(
+			'in'               => $a1,
+			'display_comments' => 'stream',
+			'spam'             => 'all'
+		) );
+		// this is a convoluted way of testing if the activity comment still exists
+		$this->assertTrue( empty( $a['activities'][0] ) );
+
+		// reset
+		$this->set_current_user( $old_user );
+		remove_filter( 'bp_disable_blogforum_comments', '__return_false' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_bp_blogs_is_blog_trackable_false_publish_post() {
+		add_filter( 'bp_blogs_is_blog_trackable', '__return_false' );
+
+		$post_id = $this->factory->post->create( array(
+			'post_status'   => 'publish',
+			'post_type'     => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		// 'new' => 'publish'
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Not trackable blog post should not have activity' );
+
+		$post->post_content .= ' foo';
+
+		wp_update_post( $post );
+
+		// 'publish' => 'publish'
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Not trackable blog post should not have activity' );
+
+		remove_filter( 'bp_blogs_is_blog_trackable', '__return_false' );
+	}
+
+	/**
+	 * @group bp_blogs_catch_transition_post_status
+	 */
+	public function test_bp_is_blog_public_zero_publish_post() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		add_filter( 'bp_is_blog_public', '__return_zero' );
+
+		$post_id = $this->factory->post->create( array(
+			'post_status'   => 'publish',
+			'post_type'     => 'post',
+		) );
+		$post = get_post( $post_id );
+
+		// 'new' => 'publish'
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Not public blog post should not have activity' );
+
+		$post->post_content .= ' foo';
+
+		wp_update_post( $post );
+
+		// 'publish' => 'publish'
+		$this->assertFalse( $this->activity_exists_for_post( $post_id ), 'Not public blog post should not have activity' );
+
+		remove_filter( 'bp_is_blog_public', '__return_zero' );
+	}
+
+	/**
+	 * @group bp_blogs_record_comment
+	 * @group unique
+	 */
+	public function test_bp_blogs_record_comment_no_duplicate_activity_comments() {
+		// save the current user and override logged-in user
+		$old_user = get_current_user_id();
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+		$userdata = get_userdata( $u );
+		$this->activity_saved_comment_count = 0;
+		$this->comment_saved_count = 0;
+
+		// let's use activity comments instead of single "new_blog_comment" activity items
+		add_filter( 'bp_disable_blogforum_comments', '__return_false' );
+		add_action( 'bp_activity_add', array( $this, 'count_activity_comment_saved' ) );
+		add_action( 'wp_insert_comment', array( $this, 'count_post_comment_saved' ) );
+		add_action( 'edit_comment', array( $this, 'count_post_comment_saved' ) );
+
+		// create the blog post
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type'   => 'post',
+			'post_title'  => 'Test Duplicate activity comments',
+		) );
+
+		// grab the activity ID for the activity comment
+		$a1 = bp_activity_get_activity_id( array(
+			'type'      => 'new_blog_post',
+			'component' => buddypress()->blogs->id,
+			'filter'    => array(
+				'item_id' => get_current_blog_id(),
+				'secondary_item_id' => $post_id
+			),
+		) );
+
+		$a2 = bp_activity_new_comment( array(
+			'content'     => 'activity comment should be unique',
+			'user_id'     => $u,
+			'activity_id' => $a1,
+		) );
+
+		$activities = bp_activity_get( array(
+			'type'             => 'activity_comment',
+			'display_comments' => 'stream',
+			'search_terms'     => 'activity comment should be unique',
+		) );
+
+		$this->assertTrue( count( $activities['activities'] ) === 1, 'An activity comment should be unique' );
+
+		$this->assertTrue( 2 === $this->activity_saved_comment_count, 'An activity comment should be saved only twice' );
+		$this->assertTrue( 1 === $this->comment_saved_count, 'A comment should be saved only once' );
+
+		// reset
+		remove_filter( 'bp_disable_blogforum_comments', '__return_false' );
+		remove_action( 'bp_activity_add', array( $this, 'count_activity_comment_saved' ) );
+		remove_action( 'wp_insert_comment', array( $this, 'count_post_comment_saved' ) );
+		remove_action( 'edit_comment', array( $this, 'count_post_comment_saved' ) );
+
+		$this->activity_saved_comment_count = 0;
+		$this->comment_saved_count = 0;
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group bp_blogs_record_comment
+	 */
+	public function test_bp_blogs_record_comment_should_record_parent_blog_post_activity_if_not_found() {
+		// Save the current user and override logged-in user
+		$old_user = get_current_user_id();
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		// Get user details
+		$user = get_userdata( $u );
+
+		// Let's use activity comments instead of single "new_blog_comment" activity items
+		add_filter( 'bp_disable_blogforum_comments', '__return_false' );
+
+		// Create the blog post
+		$post_id = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type'   => 'post',
+		) );
+
+		// Now, delete the activity item for the blog post
+		bp_activity_delete( array(
+			'component'         => buddypress()->blogs->id,
+			'type'              => 'new_blog_post',
+			'item_id'           => get_current_blog_id(),
+			'secondary_item_id' => $post_id,
+		) );
+
+		// Add a comment to blog post
+		wp_new_comment( array(
+			'comment_post_ID' => $post_id,
+			'user_id' => $u,
+			'comment_content' => 'Dummy comment',
+			'comment_author' => 'Dumbo',
+			'comment_author_url' => 'http://buddypress.org',
+
+			// Important to pass check in bp_blogs_record_comment()
+			'comment_author_email' => $user->user_email
+		) );
+
+		// Fetch the activity ID for the blog post to see if it exists
+		$a1 = bp_activity_get_activity_id( array(
+			'type'      => 'new_blog_post',
+			'component' => buddypress()->blogs->id,
+			'filter'    => array(
+				'item_id' => get_current_blog_id(),
+				'secondary_item_id' => $post_id
+			),
+		) );
+
+		// Assert that activity item for blog post was created after adding a comment
+		$this->assertNotNull( $a1, 'Activity item was not created for existing blog post when recording post comment.' );
+
+		$this->set_current_user( $old_user );
+	}
+
+	public function count_activity_comment_saved() {
+		$this->activity_saved_comment_count += 1;
+	}
+
+	public function count_post_comment_saved() {
+		$this->comment_saved_count += 1;
+	}
+
+	protected function activity_exists_for_post( $post_id ) {
+		$a = bp_activity_get( array(
+			'component' => buddypress()->blogs->id,
+			'action' => 'new_blog_post',
+			'item_id' => get_current_blog_id(),
+			'secondary_item_id' => $post_id,
+		) );
+
+		return ! empty( $a['activities'] );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/blogs/template.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/blogs/template.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/blogs/template.php	(revision 10410)
@@ -0,0 +1,429 @@
+<?php
+
+/**
+ * @group blogs
+ */
+class BP_Tests_Blogs_Template extends BP_UnitTestCase {
+	/**
+	 * @group bp_get_blog_last_active
+	 */
+	public function test_bp_get_blog_last_active_default_params() {
+		// Fake the global
+		global $blogs_template;
+
+		$time = date( 'Y-m-d h:i:s', time() - 24 * 60 * 60 );
+		$blogs_template = new stdClass;
+		$blogs_template->blog = new stdClass;
+		$blogs_template->blog->last_activity = $time;
+
+		$this->assertEquals( bp_core_get_last_activity( $time, __( 'active %s', 'buddypress' ) ), bp_get_blog_last_active() );
+
+		$blogs_template->blog = null;
+	}
+
+	/**
+	 * @group bp_get_blog_last_active
+	 */
+	public function test_bp_get_blog_last_active_active_format_true() {
+		// Fake the global
+		global $blogs_template;
+
+		$time = date( 'Y-m-d h:i:s', time() - 24 * 60 * 60 );
+		$blogs_template = new stdClass;
+		$blogs_template->blog = new stdClass;
+		$blogs_template->blog->last_activity = $time;
+
+		$this->assertEquals( bp_core_get_last_activity( $time, __( 'active %s', 'buddypress' ) ), bp_get_blog_last_active( array( 'active_format' => true, ) ) );
+
+		$blogs_template->blog = null;
+	}
+
+	/**
+	 * @group bp_get_blog_last_active
+	 */
+	public function test_bp_get_blog_last_active_active_format_false() {
+		// Fake the global
+		global $blogs_template;
+
+		$time = date( 'Y-m-d h:i:s', time() - 24 * 60 * 60 );
+		$blogs_template = new stdClass;
+		$blogs_template->blog = new stdClass;
+		$blogs_template->blog->last_activity = $time;
+
+		$this->assertEquals( bp_core_time_since( $time ), bp_get_blog_last_active( array( 'active_format' => false, ) ) );
+
+		$blogs_template->blog = null;
+	}
+
+	/**
+	 * @group bp_get_blog_last_active
+	 */
+	public function test_bp_get_blog_last_active_active_no_last_activity() {
+		$this->assertEquals( __( 'Never active', 'buddypress' ), bp_get_blog_last_active() );
+	}
+
+	/**
+	 * @group bp_get_blog_latest_post
+	 */
+	public function test_bp_get_blog_latest_post_default_params() {
+		// Fake the global
+		global $blogs_template;
+
+		$blogs_template = new stdClass;
+		$blogs_template->blog = new stdClass;
+		$blogs_template->blog->latest_post = new stdClass;
+		$blogs_template->blog->latest_post->guid = 'foo';
+		$blogs_template->blog->latest_post->post_title = 'bar';
+
+		$this->assertSame( sprintf( __( 'Latest Post: %s', 'buddypress' ), '<a href="foo">bar</a>' ), bp_get_blog_latest_post() );
+
+		$blogs_template->blog = null;
+	}
+
+	/**
+	 * @group bp_get_blog_latest_post
+	 */
+	public function test_bp_get_blog_latest_post_latest_format_true() {
+		// Fake the global
+		global $blogs_template;
+
+		$blogs_template = new stdClass;
+		$blogs_template->blog = new stdClass;
+		$blogs_template->blog->latest_post = new stdClass;
+		$blogs_template->blog->latest_post->guid = 'foo';
+		$blogs_template->blog->latest_post->post_title = 'bar';
+
+		$this->assertSame( sprintf( __( 'Latest Post: %s', 'buddypress' ), '<a href="foo">bar</a>' ), bp_get_blog_latest_post( array( 'latest_format' => true, ) ) );
+
+		$blogs_template->blog = null;
+	}
+
+	/**
+	 * @group bp_get_blog_latest_post
+	 */
+	public function test_bp_get_blog_latest_post_latest_format_false() {
+		// Fake the global
+		global $blogs_template;
+
+		$blogs_template = new stdClass;
+		$blogs_template->blog = new stdClass;
+		$blogs_template->blog->latest_post = new stdClass;
+		$blogs_template->blog->latest_post->guid = 'foo';
+		$blogs_template->blog->latest_post->post_title = 'bar';
+
+		$this->assertSame( '<a href="foo">bar</a>', bp_get_blog_latest_post( array( 'latest_format' => false, ) ) );
+
+		$blogs_template->blog = null;
+	}
+
+	/**
+	 * @group bp_blog_signup_enabled
+	 */
+	public function test_bp_signup_enabled_when_registration_setting_does_not_exist_should_default_to_true() {
+		$old_settings = $settings = buddypress()->site_options;
+		if ( is_array( $settings ) && isset( $settings['registration'] ) ) {
+			unset( $settings['registration'] );
+		}
+		buddypress()->site_options = $settings;
+
+		$this->assertTrue( bp_blog_signup_enabled() );
+
+		buddypress()->site_options = $old_settings;
+	}
+
+	/**
+	 * @group bp_blog_signup_enabled
+	 */
+	public function test_bp_signup_enabled_when_registration_setting_is_all_should_return_true() {
+		$old_settings = $settings = buddypress()->site_options;
+
+		if ( ! is_array( $settings ) ) {
+			$settings = array();
+		}
+
+		$settings['registration'] = 'all';
+		buddypress()->site_options = $settings;
+
+		$this->assertTrue( bp_blog_signup_enabled() );
+
+		buddypress()->site_options = $old_settings;
+	}
+
+	/**
+	 * @group bp_blog_signup_enabled
+	 */
+	public function test_bp_signup_enabled_when_registration_setting_is_blog_should_return_true() {
+		$old_settings = $settings = buddypress()->site_options;
+
+		if ( ! is_array( $settings ) ) {
+			$settings = array();
+		}
+
+		$settings['registration'] = 'blog';
+		buddypress()->site_options = $settings;
+
+		$this->assertTrue( bp_blog_signup_enabled() );
+
+		buddypress()->site_options = $old_settings;
+	}
+
+	/**
+	 * @group bp_blog_signup_enabled
+	 */
+	public function test_bp_signup_enabled_when_registration_setting_is_user_should_return_false() {
+		$old_settings = $settings = buddypress()->site_options;
+
+		if ( ! is_array( $settings ) ) {
+			$settings = array();
+		}
+
+		$settings['registration'] = 'user';
+		buddypress()->site_options = $settings;
+
+		$this->assertFalse( bp_blog_signup_enabled() );
+
+		buddypress()->site_options = $old_settings;
+	}
+
+	/**
+	 * @group bp_blog_signup_enabled
+	 */
+	public function test_bp_signup_enabled_when_registration_setting_is_none_should_return_false() {
+		$old_settings = $settings = buddypress()->site_options;
+
+		if ( ! is_array( $settings ) ) {
+			$settings = array();
+		}
+
+		$settings['registration'] = 'none';
+		buddypress()->site_options = $settings;
+
+		$this->assertFalse( bp_blog_signup_enabled() );
+
+		buddypress()->site_options = $old_settings;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Blogs_Template
+	 */
+	public function test_bp_blogs_template_should_give_precedence_to_bpage_URL_param() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$request = $_REQUEST;
+		$_REQUEST['bpage'] = '5';
+
+		$r = array(
+			'type'              => 'active',
+			'page_arg'          => 'bpage',
+			'page'              => 8,
+			'per_page'          => 20,
+			'max'               => false,
+			'user_id'           => 0,
+			'include_blog_ids'  => false,
+			'search_terms'      => '',
+			'update_meta_cache' => true
+		);
+
+		$at = new BP_Blogs_Template(
+			$r['type'],
+			$r['page'],
+			$r['per_page'],
+			$r['max'],
+			$r['user_id'],
+			$r['search_terms'],
+			$r['page_arg'],
+			$r['update_meta_cache'],
+			$r['include_blog_ids']
+		);
+
+		$this->assertEquals( 5, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Blogs_Template
+	 */
+	public function test_bp_blogs_template_should_reset_0_pag_page_URL_param_to_default_pag_page_value() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$request = $_REQUEST;
+		$_REQUEST['bpage'] = '0';
+
+		$r = array(
+			'type'              => 'active',
+			'page_arg'          => 'bpage',
+			'page'              => 8,
+			'per_page'          => 20,
+			'max'               => false,
+			'user_id'           => 0,
+			'include_blog_ids'  => false,
+			'search_terms'      => '',
+			'update_meta_cache' => true
+		);
+
+		$at = new BP_Blogs_Template(
+			$r['type'],
+			$r['page'],
+			$r['per_page'],
+			$r['max'],
+			$r['user_id'],
+			$r['search_terms'],
+			$r['page_arg'],
+			$r['update_meta_cache'],
+			$r['include_blog_ids']
+		);
+
+		$this->assertEquals( 8, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Blogs_Template
+	 */
+	public function test_bp_blogs_template_should_give_precedence_to_num_URL_param() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '14';
+
+		$r = array(
+			'type'              => 'active',
+			'page_arg'          => 'bpage',
+			'page'              => 1,
+			'per_page'          => 13,
+			'max'               => false,
+			'user_id'           => 0,
+			'include_blog_ids'  => false,
+			'search_terms'      => '',
+			'update_meta_cache' => true
+		);
+
+		$at = new BP_Blogs_Template(
+			$r['type'],
+			$r['page'],
+			$r['per_page'],
+			$r['max'],
+			$r['user_id'],
+			$r['search_terms'],
+			$r['page_arg'],
+			$r['update_meta_cache'],
+			$r['include_blog_ids']
+		);
+
+		$this->assertEquals( 14, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Blogs_Template
+	 */
+	public function test_bp_blogs_template_should_reset_0_pag_num_URL_param_to_default_pag_num_value() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '0';
+
+		$r = array(
+			'type'              => 'active',
+			'page_arg'          => 'bpage',
+			'page'              => 1,
+			'per_page'          => 13,
+			'max'               => false,
+			'user_id'           => 0,
+			'include_blog_ids'  => false,
+			'search_terms'      => '',
+			'update_meta_cache' => true
+		);
+
+		$at = new BP_Blogs_Template(
+			$r['type'],
+			$r['page'],
+			$r['per_page'],
+			$r['max'],
+			$r['user_id'],
+			$r['search_terms'],
+			$r['page_arg'],
+			$r['update_meta_cache'],
+			$r['include_blog_ids']
+		);
+
+		$this->assertEquals( 13, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @ticket BP6519
+	 */
+	public function test_bp_blog_get_avatar_should_respect_title_parameter() {
+		global $blogs_template;
+
+		if ( isset( $blogs_template ) ) {
+			$_blogs_template = $blogs_template;
+		}
+
+		$user = $this->factory->user->create_and_get();
+
+		$blogs_template = new stdClass;
+		$blogs_template->blog = new stdClass;
+		$blogs_template->blog->blog_id = get_current_blog_id();
+		$blogs_template->blog->admin_user_id = $user->ID;
+		$blogs_template->blog->admin_user_email = $user->user_email;
+
+		$actual = bp_get_blog_avatar( array(
+			'title' => 'Foo',
+		) );
+
+		if ( isset( $_blogs_template ) ) {
+			$blogs_template = $_blogs_template;
+		} else {
+			unset( $blogs_template );
+		}
+
+		$this->assertContains( 'title="Foo"', $actual );
+	}
+
+	/**
+	 * @ticket BP6519
+	 */
+	public function test_bp_blog_get_avatar_title_attribute_should_default_to_user_displayname() {
+		global $blogs_template;
+
+		if ( isset( $blogs_template ) ) {
+			$_blogs_template = $blogs_template;
+		}
+
+		$user = $this->factory->user->create_and_get();
+
+		$blogs_template = new stdClass;
+		$blogs_template->blog = new stdClass;
+		$blogs_template->blog->blog_id = get_current_blog_id();
+		$blogs_template->blog->admin_user_id = $user->ID;
+		$blogs_template->blog->admin_user_email = $user->user_email;
+
+		$actual = bp_get_blog_avatar();
+
+		if ( isset( $_blogs_template ) ) {
+			$blogs_template = $_blogs_template;
+		} else {
+			unset( $blogs_template );
+		}
+
+		$this->assertContains( 'title="Profile picture of site author ' . bp_core_get_user_displayname( $user->ID ) . '"', $actual );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/avatars.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/avatars.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/avatars.php	(revision 10410)
@@ -0,0 +1,317 @@
+<?php
+/**
+ * @group core
+ * @group avatars
+ */
+class BP_Tests_Avatars extends BP_UnitTestCase {
+	private $params = array();
+
+	private function clean_existing_avatars( $type = 'user' ) {
+		if ( 'user' === $type ) {
+			$avatar_dir = 'avatars';
+		} elseif ( 'group' === $object ) {
+			$avatar_dir = 'group-avatars';
+		}
+
+		$this->rrmdir( bp_core_avatar_upload_path() . '/' . $avatar_dir );
+	}
+
+	/**
+	 * @ticket BP4948
+	 */
+	function test_avatars_on_non_root_blog() {
+		// Do not pass 'Go', do not collect $200
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$u = $this->factory->user->create();
+
+		// get BP root blog's upload directory data
+		$upload_dir = wp_upload_dir();
+
+		// create new subsite
+		$blog_id = $this->factory->blog->create( array(
+			'user_id' => $u,
+			'title'   => 'Test Title',
+			'path'    => '/path' . rand() . time() . '/',
+		) );
+
+		// emulate a page load on the new sub-site
+		$this->go_to( get_blog_option( $blog_id, 'siteurl' ) );
+
+		// test to see if the upload dir is correct
+		$this->assertEquals( $upload_dir['baseurl'], bp_core_avatar_url() );
+
+		// reset globals
+		$this->go_to( '/' );
+	}
+
+	/**
+	 * @group bp_get_user_has_avatar
+	 */
+	public function test_bp_get_user_has_avatar_no_avatar_uploaded() {
+		$this->clean_existing_avatars();
+
+		$u = $this->factory->user->create();
+		$this->assertFalse( bp_get_user_has_avatar( $u ) );
+	}
+
+	/**
+	 * @group bp_get_user_has_avatar
+	 */
+	public function test_bp_get_user_has_avatar_has_avatar_uploaded() {
+		$u = $this->factory->user->create();
+
+		// Fake it
+		add_filter( 'bp_core_fetch_avatar_url', array( $this, 'avatar_cb' ) );
+
+		$this->assertTrue( bp_get_user_has_avatar( $u ) );
+
+		remove_filter( 'bp_core_fetch_avatar_url', array( $this, 'avatar_cb' ) );
+	}
+
+	public function avatar_cb() {
+		return 'foo';
+	}
+
+	/**
+	 * @group bp_core_fetch_avatar
+	 */
+	public function test_bp_core_fetch_avatar_parameter_conservation() {
+		// First, run the check with custom parameters, specifying no gravatar.
+		$this->params = array(
+			'item_id'       => 1406,
+			'object'        => 'custom_object',
+			'type'          => 'full',
+			'avatar_dir'    => 'custom-dir',
+			'width'         => 48,
+			'height'        => 54,
+			'class'         => 'custom-class',
+			'css_id'        => 'custom-css-id',
+			'alt'           => 'custom alt',
+			'email'         => 'avatar@avatar.org',
+			'no_grav'       => true,
+			'html'          => true,
+			'title'         => 'custom-title',
+			'extra_attr'    => 'data-testing="buddypress"',
+			'scheme'        => 'http',
+			'rating'        => get_option( 'avatar_rating' ),
+			'force_default' => false,
+		);
+
+		// Check to make sure the custom parameters survived the function all the way up to output
+		add_filter( 'bp_core_fetch_avatar', array( $this, 'bp_core_fetch_avatar_filter_check' ), 12, 2 );
+		$avatar = bp_core_fetch_avatar( $this->params );
+
+		// Re-run check, allowing gravatars.
+		$this->params['no_grav'] = false;
+		$avatar = bp_core_fetch_avatar( $this->params );
+
+		remove_filter( 'bp_core_fetch_avatar', array( $this, 'bp_core_fetch_avatar_filter_check' ), 12, 2 );
+
+		unset( $this->params );
+	}
+
+	public function bp_core_fetch_avatar_filter_check( $html, $params ) {
+		// Check that the passed parameters match the original custom parameters.
+		$this->assertEmpty( array_merge( array_diff( $params, $this->params ), array_diff( $this->params, $params ) ) );
+
+		// Check the returned html to see that it matches an expected value.
+		// Get the correct default avatar, based on whether gravatars are allowed.
+		if ( $params['no_grav'] ) {
+			$avatar_url = bp_core_avatar_default( 'local' );
+		} else {
+			// This test has the slight odor of hokum since it recreates so much code that could be changed at any time.
+			$bp = buddypress();
+			$host = '//www.gravatar.com/avatar/';
+
+			// Set expected gravatar type
+			if ( empty( $bp->grav_default->{$this->params['object']} ) ) {
+				$default_grav = 'wavatar';
+			} elseif ( 'mystery' == $bp->grav_default->{$this->params['object']} ) {
+				$default_grav = apply_filters( 'bp_core_mysteryman_src', 'mm', $this->params['width'] );
+			} else {
+				$default_grav = $bp->grav_default->{$this->params['object']};
+			}
+
+			$avatar_url = $host . md5( strtolower( $this->params['email'] ) );
+
+			// Main Gravatar URL args.
+			$url_args = array(
+				's' => $this->params['width']
+			);
+
+			// Force default.
+			if ( ! empty( $this->params['force_default'] ) ) {
+				$url_args['f'] = 'y';
+			}
+
+			// Gravatar rating; http://bit.ly/89QxZA
+			$rating = strtolower( get_option( 'avatar_rating' ) );
+			if ( ! empty( $rating ) ) {
+				$url_args['r'] = $rating;
+			}
+
+			// Default avatar.
+			if ( 'gravatar_default' !== $default_grav ) {
+				$url_args['d'] = $default_grav;
+			}
+
+			// Set up the Gravatar URL.
+			$avatar_url = esc_url( add_query_arg(
+				rawurlencode_deep( array_filter( $url_args ) ),
+				$avatar_url
+			) );
+
+		}
+
+		$expected_html = '<img src="' . $avatar_url . '" id="' . $this->params['css_id'] . '" class="' . $this->params['class'] . ' ' . $this->params['object'] . '-' . $this->params['item_id'] . '-avatar avatar-' . $this->params['width'] . ' photo" width="' . $this->params['width'] . '" height="' . $this->params['height'] . '" alt="' . $this->params['alt'] . '" title="' . $this->params['title'] . '" ' . $this->params['extra_attr'] . ' />';
+
+		$this->assertEquals( $html, $expected_html );
+	}
+
+	/**
+	 * @group bp_core_fetch_avatar
+	 */
+	public function test_bp_core_fetch_avatar_class_attribute() {
+		$u = $this->factory->user->create();
+
+		$hw = 100;
+		$args = array(
+			'item_id'    => $u,
+			'object'     => 'user',
+			'type'       => 'full',
+			'width'      => $hw,
+			'height'     => $hw,
+			'class'      => '',
+			'no_grav'    => true,
+			'html'       => true,
+		);
+
+		// Class attribute is empty
+		$avatar = bp_core_fetch_avatar( $args );
+		$expected = array( 'avatar', 'user-' . $u . '-avatar', 'avatar-' . $hw );
+		preg_match( '/class=["\']?([^"\']*)["\' ]/is', $avatar, $matches );
+		$classes = explode( ' ', $matches[1] );
+		$this->assertSame( $expected, array_intersect_key( $expected, $classes ) );
+
+		// Class attribute is a String
+		$args['class'] = 'custom-class class-custom';
+		$avatar = bp_core_fetch_avatar( $args );
+		$expected = array_merge( explode( ' ', $args['class'] ), array( 'user-' . $u . '-avatar', 'avatar-' . $hw ) );
+		preg_match( '/class=["\']?([^"\']*)["\' ]/is', $avatar, $matches );
+		$classes = explode( ' ', $matches[1] );
+		$this->assertSame( $expected, array_intersect_key( $expected, $classes ) );
+
+		// Class attribute is an Array
+		$args['class'] = array( 'custom-class', 'class-custom' );
+		$avatar = bp_core_fetch_avatar( $args );
+		$expected = array_merge( $args['class'], array( 'user-' . $u . '-avatar', 'avatar-' . $hw ) );
+		preg_match( '/class=["\']?([^"\']*)["\' ]/is', $avatar, $matches );
+		$classes = explode( ' ', $matches[1] );
+		$this->assertSame( $expected, array_intersect_key( $expected, $classes ) );
+	}
+
+	/**
+	 * @group bp_core_check_avatar_type
+	 */
+	public function test_bp_core_check_avatar_type() {
+		$plugin_dir = trailingslashit( buddypress()->plugin_dir );
+
+		$file = array(
+			'file' => array(
+				'name' => 'humans.txt',
+				'type' => 'text/plain',
+				'tmp_name' => $plugin_dir . 'humans.txt',
+			)
+		);
+
+		$this->assertFalse( bp_core_check_avatar_type( $file ) );
+
+		$file = array(
+			'file' => array(
+				'name' => 'mystery-man.jpg',
+				'type' => 'image/jpeg',
+				'tmp_name' => $plugin_dir . 'bp-core/images/mystery-man.jpg',
+			)
+		);
+
+		$this->assertTrue( bp_core_check_avatar_type( $file ) );
+
+		$file = array(
+			'file' => array(
+				'name' => 'mystery-man.jpg',
+				'type' => 'application/octet-stream',
+				'tmp_name' => $plugin_dir . 'bp-core/images/mystery-man.jpg',
+			)
+		);
+
+		$this->assertTrue( bp_core_check_avatar_type( $file ), 'flash is using application/octet-stream for image uploads' );
+	}
+
+	/**
+	 * @group bp_core_check_avatar_type
+	 * @group bp_core_get_allowed_avatar_types
+	 */
+	public function test_bp_core_get_allowed_avatar_types_filter() {
+		add_filter( 'bp_core_get_allowed_avatar_types', array( $this, 'avatar_types_filter_add_type' ) );
+
+		$this->assertEquals( array( 'jpeg', 'gif', 'png' ), bp_core_get_allowed_avatar_types() );
+
+		remove_filter( 'bp_core_get_allowed_avatar_types', array( $this, 'avatar_types_filter_add_type' ) );
+
+		add_filter( 'bp_core_get_allowed_avatar_types', array( $this, 'avatar_types_filter_remove_type' ) );
+
+		$this->assertEquals( array( 'gif', 'png' ), bp_core_get_allowed_avatar_types() );
+
+		remove_filter( 'bp_core_get_allowed_avatar_types', array( $this, 'avatar_types_filter_remove_type' ) );
+
+		add_filter( 'bp_core_get_allowed_avatar_types', '__return_empty_array' );
+
+		$this->assertEquals( array( 'jpeg', 'gif', 'png' ), bp_core_get_allowed_avatar_types() );
+
+		remove_filter( 'bp_core_get_allowed_avatar_types', '__return_empty_array' );
+	}
+
+	/**
+	 * @group bp_core_check_avatar_type
+	 * @group bp_core_get_allowed_avatar_mimes
+	 */
+	public function test_bp_core_get_allowed_avatar_mimes() {
+		$mimes = bp_core_get_allowed_avatar_mimes();
+
+		$this->assertEqualSets( array( 'jpeg', 'gif', 'png', 'jpg' ), array_keys( $mimes ) );
+		$this->assertEqualSets( array( 'image/jpeg', 'image/gif', 'image/png', 'image/jpeg' ), array_values( $mimes ) );
+
+		add_filter( 'bp_core_get_allowed_avatar_types', array( $this, 'avatar_types_filter_add_type' ) );
+
+		$this->assertEqualSets( array( 'image/jpeg', 'image/gif', 'image/png', 'image/jpeg' ), array_values( bp_core_get_allowed_avatar_mimes() ) );
+
+		remove_filter( 'bp_core_get_allowed_avatar_types', array( $this, 'avatar_types_filter_add_type' ) );
+
+		add_filter( 'bp_core_get_allowed_avatar_types', array( $this, 'avatar_types_filter_remove_type' ) );
+
+		$this->assertEqualSets( array( 'image/gif', 'image/png' ), array_values( bp_core_get_allowed_avatar_mimes() ) );
+
+		remove_filter( 'bp_core_get_allowed_avatar_types', array( $this, 'avatar_types_filter_remove_type' ) );
+
+		add_filter( 'bp_core_get_allowed_avatar_types', '__return_empty_array' );
+
+		$this->assertEqualSets( array( 'image/jpeg', 'image/gif', 'image/png', 'image/jpeg' ), array_values( bp_core_get_allowed_avatar_mimes() ) );
+
+		remove_filter( 'bp_core_get_allowed_avatar_types', '__return_empty_array' );
+	}
+
+	public function avatar_types_filter_add_type( $types ) {
+		$types[] = 'bmp';
+
+		return $types;
+	}
+
+	public function avatar_types_filter_remove_type( $types ) {
+		$jpeg = array_shift( $types );
+
+		return $types;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/cache.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/cache.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/cache.php	(revision 10410)
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @group cache
+ * @group core
+ */
+class BP_Tests_Core_Cache extends BP_UnitTestCase {
+	/**
+	 * @group bp_update_meta_cache
+	 */
+	public function test_bp_update_meta_cache_with_cache_misses() {
+		// Use activity just because
+		$a1 = $this->factory->activity->create();
+
+		// Confirm that all activitymeta is deleted
+		global $wpdb;
+
+		$bp = buddypress();
+
+		$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->activity->table_name_meta} WHERE activity_id = %d", $a1 ) );
+
+		bp_update_meta_cache( array(
+			'object_ids' => array( $a1 ),
+			'object_type' => 'activity',
+			'cache_group' => 'activity_meta',
+			'meta_table' => buddypress()->activity->table_name_meta,
+			'object_column' => 'activity_id',
+		) );
+
+		$this->assertSame( array(), wp_cache_get( $a1, 'activity_meta' ) );
+	}
+}
+
Index: /tags/2.4.3/tests/phpunit/testcases/core/caps.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/caps.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/caps.php	(revision 10410)
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @group core
+ * @group caps
+ */
+class BP_Tests_Core_Caps extends BP_UnitTestCase {
+	public function test_bp_current_user_can_should_interpret_integer_second_param_as_a_blog_id() {
+		if ( ! is_multisite() ) {
+			$this->markTestSkipped( __METHOD__ . ' requires multisite.' );
+		}
+
+		$b = $this->factory->blog->create();
+		$u = $this->factory->user->create();
+
+		$this->set_current_user( $u );
+
+		add_filter( 'user_has_cap', array( $this, 'grant_cap_foo' ), 10, 2 );
+		$can  = bp_current_user_can( 'foo', bp_get_root_blog_id() );
+		$cant = bp_current_user_can( 'foo', $b );
+		remove_filter( 'user_has_cap', array( $this, 'grant_cap_foo' ), 10, 2 );
+
+		$this->assertTrue( $can );
+		$this->assertFalse( $cant );
+	}
+
+	/**
+	 * @ticket BP6501
+	 */
+	public function test_bp_current_user_can_should_respect_blog_id_passed_in_args_array() {
+		if ( ! is_multisite() ) {
+			$this->markTestSkipped( __METHOD__ . ' requires multisite.' );
+		}
+
+		$b = $this->factory->blog->create();
+		$u = $this->factory->user->create();
+
+		$this->set_current_user( $u );
+
+		add_filter( 'user_has_cap', array( $this, 'grant_cap_foo' ), 10, 2 );
+		$can  = bp_current_user_can( 'foo', array( 'blog_id' => bp_get_root_blog_id() ) );
+		$cant = bp_current_user_can( 'foo', array( 'blog_id' => $b ) );
+		remove_filter( 'user_has_cap', array( $this, 'grant_cap_foo' ), 10, 2 );
+
+		$this->assertTrue( $can );
+		$this->assertFalse( $cant );
+	}
+
+	/**
+	 * @group bp_xprofile_change_field_visibility
+	 */
+	public function test_bp_current_user_can_should_pass_null_in_args_parameter_if_empty() {
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		/**
+		 * Fake bp_get_the_profile_field_id() to pretend we're in the field loop and
+		 * to avoid notices when checking 'bp_xprofile_change_field_visibility' cap
+		 */
+		$GLOBALS['field'] = new stdClass;
+		$GLOBALS['field']->id = 1;
+
+		// Capture the cap's $args
+		add_filter( 'bp_xprofile_map_meta_caps', array( $this, 'check_cap_args' ), 10, 4 );
+
+		// Use a cap check that depends on a null value for a cap's args
+		bp_current_user_can( 'bp_xprofile_change_field_visibility' );
+
+		// Assert!
+		$this->assertEquals( null, $this->test_args[0] );
+
+		// Reset
+		remove_filter( 'bp_xprofile_map_meta_caps', array( $this, 'check_cap_args' ), 10, 4 );
+		unset( $GLOBALS['field'], $this->test_args );
+	}
+
+	public function grant_cap_foo( $allcaps, $caps ) {
+		if ( bp_is_root_blog() ) {
+			$allcaps['foo'] = 1;
+		}
+
+		return $allcaps;
+	}
+
+	public function check_cap_args( $caps, $cap, $user_id, $args ) {
+		$this->test_args = $args;
+		return $caps;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/class-bp-attachment.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/class-bp-attachment.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/class-bp-attachment.php	(revision 10410)
@@ -0,0 +1,517 @@
+<?php
+
+include_once BP_TESTS_DIR . 'assets/attachment-extensions.php';
+
+/**
+ * @group bp_attachments
+ * @group BP_Attachment
+ */
+class BP_Tests_BP_Attachment_TestCases extends BP_UnitTestCase {
+	private $upload_results;
+	private $image_file;
+
+	public function setUp() {
+		parent::setUp();
+		add_filter( 'bp_attachment_upload_overrides',        array( $this, 'filter_overrides' ),       10, 1 );
+		add_filter( 'upload_dir',                            array( $this, 'filter_upload_dir' ),      20, 1 );
+		add_filter( 'bp_attachments_cover_image_upload_dir', array( $this, 'filter_cover_image_dir' ), 10, 2 );
+		$this->upload_results = array();
+		$this->image_file = trailingslashit( buddypress()->plugin_dir ) . 'bp-core/images/mystery-man.jpg';
+		$this->original_upload_dir = array();
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		remove_filter( 'bp_attachment_upload_overrides',     array( $this, 'filter_overrides' ),       10, 1 );
+		remove_filter( 'upload_dir',                         array( $this, 'filter_upload_dir' ),      20, 1 );
+		add_filter( 'bp_attachments_cover_image_upload_dir', array( $this, 'filter_cover_image_dir' ), 10, 2 );
+		$this->upload_results = array();
+		$this->image_file = '';
+		$this->original_upload_dir = array();
+	}
+
+	public function filter_overrides( $overrides ) {
+		$overrides['upload_error_handler'] = array( $this, 'upload_error_handler' );
+
+		// Don't test upload for WordPress < 4.0
+		$overrides['test_upload'] = false;
+		return $overrides;
+	}
+
+	public function filter_upload_dir( $upload_dir ) {
+		$upload_dir['error'] = 'fake_upload_success';
+
+		$this->upload_results = array(
+			'new_file' => $upload_dir['path'] . '/mystery-man.jpg',
+			'url'      => $upload_dir['url'] . '/mystery-man.jpg',
+		);
+
+		return $upload_dir;
+	}
+
+	public function filter_cover_image_dir( $cover_dir, $upload_dir ) {
+		$this->original_upload_dir = $upload_dir;
+
+		return $cover_dir;
+	}
+
+	/**
+	 * To avoid copying files in tests, we're faking a succesfull uploads
+	 * as soon as all the test_form have been executed in _wp_handle_upload
+	 */
+	public function upload_error_handler( &$file, $message ) {
+		if ( 'fake_upload_success' !== $message ) {
+			return array( 'error' => $message );
+		} else {
+			return array(
+				'file' => $this->upload_results['new_file'],
+				'url'  => $this->upload_results['url'],
+				'type' => 'image/jpeg',
+			);
+		}
+	}
+
+	private function clean_files( $basedir = 'attachment_base_dir' ) {
+		$upload_dir = bp_upload_dir();
+
+		$this->rrmdir( $upload_dir['basedir'] . '/' . $basedir );
+	}
+
+	private function clean_avatars( $type = 'user' ) {
+		if ( 'user' === $type ) {
+			$avatar_dir = 'avatars';
+		} elseif ( 'group' === $type ) {
+			$avatar_dir = 'group-avatars';
+		}
+
+		$this->rrmdir( bp_core_avatar_upload_path() . '/' . $avatar_dir );
+	}
+
+	public function max_filesize() {
+		return 1000;
+	}
+
+	public function test_bp_attachment_construct_missing_required_parameter() {
+		$reset_files = $_FILES;
+		$reset_post = $_POST;
+
+		$_FILES['file'] = array(
+			'name'     => 'mystery-man.jpg',
+			'type'     => 'image/jpeg',
+			'error'    => 0,
+			'size'     => 1000
+		);
+
+		$attachment_class = new BPTest_Attachment_Extension();
+		$upload = $attachment_class->upload( $_FILES );
+
+		$this->assertTrue( empty( $upload ) );
+
+		$_FILES = $reset_files;
+		$_POST = $reset_post;
+	}
+
+	public function test_bp_attachment_set_upload_dir() {
+		$upload_dir = bp_upload_dir();
+
+		$attachment_class = new BPTest_Attachment_Extension( array(
+			'action'     => 'attachment_action',
+			'file_input' => 'attachment_file_input'
+		) );
+
+		$this->assertSame( $attachment_class->upload_dir, bp_upload_dir() );
+
+		$attachment_class = new BPTest_Attachment_Extension( array(
+			'action'     => 'attachment_action',
+			'file_input' => 'attachment_file_input',
+			'base_dir'   => 'attachment_base_dir',
+		) );
+
+		$this->assertTrue( file_exists( $upload_dir['basedir'] . '/attachment_base_dir'  ) );
+
+		// clean up
+		$this->clean_files();
+	}
+
+	/**
+	 * @group upload
+	 */
+	public function test_bp_attachment_upload() {
+		$reset_files = $_FILES;
+		$reset_post = $_POST;
+
+		$attachment_class = new BPTest_Attachment_Extension( array(
+			'action'                => 'attachment_action',
+			'file_input'            => 'attachment_file_input',
+			'base_dir'   		    => 'attachment_base_dir',
+			'original_max_filesize' => 1000,
+		) );
+
+		$_POST['action'] = $attachment_class->action;
+		$_FILES[ $attachment_class->file_input ] = array(
+			'tmp_name' => $this->image_file,
+			'name'     => 'mystery-man.jpg',
+			'type'     => 'image/jpeg',
+			'error'    => 0,
+			'size'     => filesize( $this->image_file ),
+		);
+
+		// Error: file size
+		$upload = $attachment_class->upload( $_FILES );
+		$this->assertFalse( empty( $upload['error'] ) );
+
+		$attachment_class->allowed_mime_types    = array( 'pdf' );
+		$attachment_class->original_max_filesize = false;
+
+		// Error: file type
+		$upload = $attachment_class->upload( $_FILES );
+		$this->assertFalse( empty( $upload['error'] ) );
+
+		$attachment_class->allowed_mime_types = array();
+
+		// Success
+		$upload = $attachment_class->upload( $_FILES );
+		$this->assertEquals( $upload['file'], $attachment_class->upload_path . '/mystery-man.jpg' );
+
+		// clean up!
+		$_FILES = $reset_files;
+		$_POST = $reset_post;
+		$this->clean_files();
+	}
+
+	/**
+	 * @group upload
+	 */
+	public function test_bp_attachment_upload_no_base_dir_specific_time() {
+		$reset_files = $_FILES;
+		$reset_post = $_POST;
+
+		$attachment_class = new BPTest_Attachment_Extension( array(
+			'action'                => 'attachment_action',
+			'file_input'            => 'attachment_file_input',
+		) );
+
+		$_POST['action'] = $attachment_class->action;
+		$_FILES[ $attachment_class->file_input ] = array(
+			'tmp_name' => $this->image_file,
+			'name'     => 'mystery-man.jpg',
+			'type'     => 'image/jpeg',
+			'error'    => 0,
+			'size'     => filesize( $this->image_file ),
+		);
+
+		$time = '2015/01';
+
+		$upload = $attachment_class->upload( $_FILES, '', $time );
+
+		// If no base_dir was provided, default WordPress uploads dir should be used.
+		$this->assertEquals( $upload['file'], $attachment_class->upload_path . '/' . $time . '/mystery-man.jpg' );
+
+		// clean up!
+		$_FILES = $reset_files;
+		$_POST = $reset_post;
+	}
+
+	/**
+	 * @group upload
+	 * @group avatar
+	 */
+	public function test_bp_attachment_avatar_user_upload() {
+		$reset_files = $_FILES;
+		$reset_post = $_POST;
+		$bp = buddypress();
+		$displayed_user = $bp->displayed_user;
+		$bp->displayed_user = new stdClass;
+
+		$u1 = $this->factory->user->create();
+		$bp->displayed_user->id = $u1;
+
+		// Upload the file
+		$avatar_attachment = new BP_Attachment_Avatar();
+		$_POST['action'] = $avatar_attachment->action;
+		$_FILES[ $avatar_attachment->file_input ] = array(
+			'tmp_name' => $this->image_file,
+			'name'     => 'mystery-man.jpg',
+			'type'     => 'image/jpeg',
+			'error'    => 0,
+			'size'     => filesize( $this->image_file )
+		);
+
+		/* No error */
+		$user_avatar = $avatar_attachment->upload( $_FILES, 'xprofile_avatar_upload_dir' );
+		$this->assertEquals( $user_avatar['file'], $bp->avatar->upload_path . '/avatars/' . $u1 .'/mystery-man.jpg' );
+
+		/* File size error */
+		add_filter( 'bp_core_avatar_original_max_filesize', array( $this, 'max_filesize' ) );
+
+		$user_avatar = $avatar_attachment->upload( $_FILES, 'xprofile_avatar_upload_dir' );
+
+		remove_filter( 'bp_core_avatar_original_max_filesize', array( $this, 'max_filesize' ) );
+		$this->assertFalse( empty( $user_avatar['error'] ) );
+
+		/* File type error */
+		$_FILES[ $avatar_attachment->file_input ]['name'] = 'buddypress_logo.pdf';
+		$_FILES[ $avatar_attachment->file_input ]['type'] = 'application/pdf';
+
+		$user_avatar = $avatar_attachment->upload( $_FILES, 'xprofile_avatar_upload_dir' );
+		$this->assertFalse( empty( $user_avatar['error'] ) );
+
+		// clean up!
+		$bp->displayed_user = $displayed_user;
+		$this->clean_avatars();
+		$_FILES = $reset_files;
+		$_POST = $reset_post;
+	}
+
+	/**
+	 * @group upload
+	 * @group avatar
+	 */
+	public function test_bp_attachment_avatar_group_upload() {
+		$bp = buddypress();
+		$reset_files = $_FILES;
+		$reset_post = $_POST;
+		$reset_current_group = $bp->groups->current_group;
+
+		$g = $this->factory->group->create();
+
+		$bp->groups->current_group = groups_get_group( array(
+			'group_id'        => $g,
+			'populate_extras' => true,
+		) );
+
+		// Upload the file
+		$avatar_attachment = new BP_Attachment_Avatar();
+		$_POST['action'] = $avatar_attachment->action;
+		$_FILES[ $avatar_attachment->file_input ] = array(
+			'tmp_name' => $this->image_file,
+			'name'     => 'mystery-man.jpg',
+			'type'     => 'image/jpeg',
+			'error'    => 0,
+			'size'     => filesize( $this->image_file )
+		);
+
+		$group_avatar = $avatar_attachment->upload( $_FILES, 'groups_avatar_upload_dir' );
+		$this->assertEquals( $group_avatar['file'], $bp->avatar->upload_path . '/group-avatars/' . $g .'/mystery-man.jpg' );
+
+		// clean up!
+		$this->clean_avatars( 'group' );
+		$bp->groups->current_group = $reset_current_group;
+		$_FILES = $reset_files;
+		$_POST = $reset_post;
+	}
+
+	/**
+	 * @group crop
+	 */
+	public function test_bp_attachment_crop() {
+		$crop_args = array(
+			'original_file' => $this->image_file,
+			'crop_x'        => 0,
+			'crop_y'        => 0,
+			'crop_w'        => 150,
+			'crop_h'        => 150,
+			'dst_w'         => 150,
+			'dst_h'         => 150,
+		);
+
+		$attachment_class = new BPTest_Attachment_Extension( array(
+			'action'                => 'attachment_action',
+			'file_input'            => 'attachment_file_input',
+			'base_dir'   		    => 'attachment_base_dir',
+		) );
+
+		$cropped = $attachment_class->crop( $crop_args );
+
+		// Image must come from the upload basedir
+		$this->assertTrue( is_wp_error( $cropped ) );
+
+		$crop_args['original_file'] = $attachment_class->upload_path . '/mystery-man.jpg';
+
+		// Image must stay in the upload basedir
+		$crop_args['dst_file'] = BP_TESTS_DIR . 'assets/error.jpg';
+		$cropped = $attachment_class->crop( $crop_args );
+
+		// Image must stay in the upload basedir
+		$this->assertTrue( is_wp_error( $cropped ) );
+
+		// clean up!
+		$this->clean_files();
+	}
+
+	/**
+	 * @group upload
+	 * @group cover_image
+	 */
+	public function test_bp_attachment_cover_image_user_upload() {
+		$reset_files = $_FILES;
+		$reset_post = $_POST;
+		$bp = buddypress();
+		$displayed_user = $bp->displayed_user;
+		$bp->displayed_user = new stdClass;
+
+		$u1 = $this->factory->user->create();
+		$bp->displayed_user->id = $u1;
+
+		// Upload the file
+		$cover_image_attachment = new BP_Attachment_Cover_Image();
+		$_POST['action'] = $cover_image_attachment->action;
+		$_FILES[ $cover_image_attachment->file_input ] = array(
+			'tmp_name' => $this->image_file,
+			'name'     => 'mystery-man.jpg',
+			'type'     => 'image/jpeg',
+			'error'    => 0,
+			'size'     => filesize( $this->image_file )
+		);
+
+		/* No error */
+		$cover_image = $cover_image_attachment->upload( $_FILES );
+		$this->assertEquals( $cover_image['file'], $bp->avatar->upload_path . '/buddypress/members/' . $u1 .'/cover-image/mystery-man.jpg' );
+
+		// clean up!
+		$bp->displayed_user = $displayed_user;
+		$this->clean_files( 'buddypress' );
+		$_FILES = $reset_files;
+		$_POST = $reset_post;
+	}
+
+	/**
+	 * @group shrink
+	 * @group avatars
+	 */
+	public function test_bp_attachment_avatar_shrink() {
+		$image = BP_TESTS_DIR . 'assets/upside-down.jpg';
+
+		$dir_copy = bp_upload_dir();
+
+		// in case cleaning files fails
+		if ( ! is_dir( $dir_copy['basedir'] . '/shrink' ) ) {
+			mkdir( $dir_copy['basedir'] . '/shrink' );
+		}
+
+		$abs_path_copy = $dir_copy['basedir'] . '/shrink/upside-down.jpg';
+
+		copy( $image, $abs_path_copy );
+
+		add_filter( 'bp_core_avatar_original_max_width', array( $this, 'limit_to_50px' ) );
+
+		$shrink = BP_Attachment_Avatar::shrink( $abs_path_copy );
+
+		remove_filter( 'bp_core_avatar_original_max_width', array( $this, 'limit_to_50px' ) );
+
+		$this->assertTrue( 50 === $shrink['width'] && 50 === $shrink['height'] );
+
+		// Cleanup
+		$this->clean_files( 'shrink' );
+	}
+
+	public function limit_to_50px( $max_width ) {
+		return 50;
+	}
+
+	/**
+	 * @group shrink
+	 * @group avatars
+	 */
+	public function test_bp_attachment_avatar_shrink_not_needed() {
+		$shrink = BP_Attachment_Avatar::shrink( $this->image_file );
+
+		$this->assertTrue( empty( $shrink ) );
+	}
+
+	/**
+	 * @group shrink
+	 * @group cover_images
+	 */
+	public function test_bp_attachment_cover_image_fit() {
+		$image = BP_TESTS_DIR . 'assets/upside-down.jpg';
+
+		$cover_image_class = new BP_Attachment_Cover_Image();
+
+		$abs_path_copy = $cover_image_class->upload_path . '/upside-down.jpg';
+
+		copy( $image, $abs_path_copy );
+
+		$fit = $cover_image_class->fit( $abs_path_copy, array( 'width' => 50, 'height' => 50 ) );
+
+		$this->assertTrue( 50 === $fit['width'] && 50 === $fit['height'] );
+
+		// Cleanup
+		$this->clean_files( 'buddypress' );
+	}
+
+	/**
+	 * @group shrink
+	 * @group cover_images
+	 */
+	public function test_bp_attachment_cover_image_fit_not_needed() {
+		$cover_image_class = new BP_Attachment_Cover_Image();
+		$fit = $cover_image_class->fit( $this->image_file, array( 'width' => 1300, 'height' => 225 ) );
+
+		$this->assertTrue( empty( $fit ) );
+
+		// Cleanup
+		$this->clean_files( 'buddypress' );
+	}
+
+	/**
+	 * @group avatars
+	 * @group cover_images
+	 */
+	public function test_bp_attachment_get_image_data() {
+		$image_data = BP_Attachment::get_image_data( BP_TESTS_DIR . 'assets/upside-down.jpg' );
+
+		$this->assertTrue( 3 === $image_data['meta']['orientation'] );
+	}
+
+	/**
+	 * @group upload
+	 * @group cover_images
+	 */
+	public function test_bp_attachment_upload_dir_filter_arg() {
+		$reset_files = $_FILES;
+		$reset_post = $_POST;
+
+		$attachment_class = new BPTest_Attachment_Extension( array(
+			'action'                 => 'attachment_action',
+			'file_input'             => 'attachment_file_input',
+			'base_dir'               => 'attachment_base_dir',
+			'upload_dir_filter_args' => 1,
+		) );
+
+		$_POST['action'] = $attachment_class->action;
+		$_FILES[ $attachment_class->file_input ] = array(
+			'tmp_name' => $this->image_file,
+			'name'     => 'mystery-man.jpg',
+			'type'     => 'image/jpeg',
+			'error'    => 0,
+			'size'     => filesize( $this->image_file ),
+		);
+
+		// Simulate an upload
+		$attachment_class->upload( $_FILES );
+
+		// Remove the filter used to fake uploads
+		remove_filter( 'upload_dir', array( $this, 'filter_upload_dir' ), 20, 1 );
+
+		$this->assertSame( $attachment_class->original_upload_dir, wp_upload_dir() );
+
+		// Restore the filter used to fake uploads
+		add_filter( 'upload_dir', array( $this, 'filter_upload_dir' ), 20, 1 );
+
+		$this->assertTrue( 1 === $attachment_class->upload_dir_filter_args );
+
+		$cover_image_class = new BP_Attachment_Cover_Image();
+
+		// Simulate an upload
+		$cover_image_class->upload( $_FILES );
+
+		// Should be empty
+		$this->assertEmpty( $this->original_upload_dir );
+
+		$this->assertTrue( 0 === $cover_image_class->upload_dir_filter_args );
+
+		$_FILES = $reset_files;
+		$_POST = $reset_post;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/class-bp-button.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/class-bp-button.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/class-bp-button.php	(revision 10410)
@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * @group core
+ * @group BP_Button
+ */
+class BP_Tests_BP_Button extends BP_UnitTestCase {
+	/**
+	 * @group block_self
+	 */
+	public function test_block_self_own_profile() {
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		$this->go_to( bp_core_get_user_domain( $u ) );
+
+		$b = new BP_Button( array(
+			'id' => 'foo',
+			'component' => 'members',
+			'block_self' => true,
+		) );
+
+		$this->assertEquals( '', $b->contents );
+	}
+
+	/**
+	 * @group block_self
+	 */
+	public function test_block_self_others_profile() {
+		$u1 = $this->factory->user->create();
+		$this->set_current_user( $u1 );
+
+		$u2 = $this->factory->user->create();
+		$this->go_to( bp_core_get_user_domain( $u2 ) );
+
+		$b = new BP_Button( array(
+			'id' => 'foo',
+			'component' => 'members',
+			'block_self' => true,
+		) );
+
+		$this->assertNotEmpty( $b->contents );
+	}
+
+	/**
+	 * @group block_self
+	 */
+	public function test_block_self_inside_members_loop() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		$this->set_current_user( $u1 );
+
+		$found = array();
+		if ( bp_has_members() ) {
+			while ( bp_members() ) {
+				bp_the_member();
+
+				$b = new BP_Button( array(
+					'id' => 'foo',
+					'component' => 'members',
+					'block_self' => true,
+				) );
+
+				$found[ bp_get_member_user_id() ] = empty( $b->contents );
+			}
+		}
+
+		$expected = array(
+			$u1 => true,
+			$u2 => false,
+		);
+
+		$this->assertSame( $expected, $found );
+
+		// clean up
+		$GLOBALS['members_template'] = null;
+	}
+
+	/**
+	 * @group block_self
+	 */
+	public function test_block_self_false_inside_members_loop() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		$this->set_current_user( $u1 );
+
+		$found = array();
+		if ( bp_has_members() ) {
+			while ( bp_members() ) {
+				bp_the_member();
+
+				$b = new BP_Button( array(
+					'id' => 'foo',
+					'component' => 'members',
+					'block_self' => false,
+				) );
+
+				$found[ bp_get_member_user_id() ] = empty( $b->contents );
+			}
+		}
+
+		$expected = array(
+			$u1 => false,
+			$u2 => false,
+		);
+
+		$this->assertSame( $expected, $found );
+
+		// clean up
+		$GLOBALS['members_template'] = null;
+	}
+
+	/**
+	 * @group block_self
+	 */
+	public function test_block_self_inside_members_loop_on_my_profile_page() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		$this->set_current_user( $u1 );
+		$this->go_to( bp_core_get_user_domain( $u1 ) );
+
+		$found = array();
+		if ( bp_has_members() ) {
+			while ( bp_members() ) {
+				bp_the_member();
+
+				$b = new BP_Button( array(
+					'id' => 'foo',
+					'component' => 'members',
+					'block_self' => true,
+				) );
+
+				$found[ bp_get_member_user_id() ] = empty( $b->contents );
+			}
+		}
+
+		$expected = array(
+			$u1 => true,
+			$u2 => false,
+		);
+
+		$this->assertSame( $expected, $found );
+
+		// clean up
+		$GLOBALS['members_template'] = null;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/class-bp-core-user.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/class-bp-core-user.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/class-bp-core-user.php	(revision 10410)
@@ -0,0 +1,307 @@
+<?php
+
+/**
+ * @group core
+ * @group BP_Core_User
+ */
+class BP_Tests_BP_Core_User_TestCases extends BP_UnitTestCase {
+	/**
+	 * @expectedDeprecated BP_Core_User::get_users
+	 */
+	public function test_get_users_with_exclude_querystring() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$exclude_qs = $u1 . ',junkstring,' . $u3;
+
+		$users = BP_Core_User::get_users( 'active', 0, 1, 0, false, false, true, $exclude_qs );
+		$user_ids = wp_parse_id_list( wp_list_pluck( $users['users'], 'id' ) );
+
+		$this->assertEquals( array( $u2 ), $user_ids );
+	}
+
+	/**
+	 * @expectedDeprecated BP_Core_User::get_users
+	 */
+	public function test_get_users_with_exclude_array() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$exclude_array = array(
+			$u1,
+			'junkstring',
+			$u3,
+		);
+
+		$users = BP_Core_User::get_users( 'active', 0, 1, 0, false, false, true, $exclude_array );
+		$user_ids = wp_parse_id_list( wp_list_pluck( $users['users'], 'id' ) );
+
+		$this->assertEquals( array( $u2 ), $user_ids );
+	}
+
+	/**
+	 * @expectedDeprecated BP_Core_User::get_users
+	 */
+	public function test_get_users_with_include_querystring() {
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s' ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', time() - 1000 ),
+		) );
+		$u3 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', time() - 50 ),
+		) );
+
+		$include_qs = $u1 . ',junkstring,' . $u3;
+
+		$users = BP_Core_User::get_users( 'active', 0, 1, 0, $include_qs );
+		$user_ids = wp_parse_id_list( wp_list_pluck( $users['users'], 'id' ) );
+
+		$this->assertEquals( array( $u1, $u3 ), $user_ids );
+	}
+
+	/**
+	 * @expectedDeprecated BP_Core_User::get_users
+	 */
+	public function test_get_users_with_include_array() {
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s' ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', time() - 1000 ),
+		) );
+		$u3 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', time() - 50 ),
+		) );
+
+
+		$include_array = array(
+			$u1,
+			'junkstring',
+			$u3,
+		);
+
+		$users = BP_Core_User::get_users( 'active', 0, 1, 0, $include_array );
+		$user_ids = wp_list_pluck( $users['users'], 'id' );
+
+		// typecast...ugh
+		$user_ids = array_map( 'intval', $user_ids );
+
+		$this->assertEquals( array( $u1, $u3 ), $user_ids );
+	}
+
+	/**
+	 * @expectedDeprecated BP_Core_User::get_users
+	 * @group get_users
+	 * @group type
+	 */
+	public function test_type_alphabetical() {
+		$u1 = $this->factory->user->create( array(
+			'display_name' => 'foo',
+		) );
+		$u2 = $this->factory->user->create( array(
+			'display_name' => 'bar',
+		) );
+
+		global $wpdb;
+
+		$q = BP_Core_User::get_users( 'alphabetical' );
+		$found = array_map( 'intval', wp_list_pluck( $q['users'], 'id' ) );
+
+		$this->assertEquals( array( $u2, $u1 ), $found );
+	}
+
+	/**
+	 * @group get_users_by_letter
+	 */
+	public function test_get_users_by_letter() {
+		$u1 = $this->factory->user->create( array(
+			'display_name' => 'foo',
+		) );
+		$u2 = $this->factory->user->create( array(
+			'display_name' => 'bar',
+		) );
+
+		$q = BP_Core_User::get_users_by_letter( 'b' );
+		$found = array_map( 'intval', wp_list_pluck( $q['users'], 'id' ) );
+
+		$this->assertEquals( array( $u2 ), $found );
+	}
+
+	/**
+	 * @group search_users
+	 */
+	public function test_search_users() {
+		$u1 = $this->factory->user->create( array(
+			'display_name' => 'foo',
+		) );
+		$u2 = $this->factory->user->create( array(
+			'display_name' => 'bar',
+		) );
+
+		$q = BP_Core_User::search_users( 'ar' );
+		$found = array_map( 'intval', wp_list_pluck( $q['users'], 'id' ) );
+
+		$this->assertEquals( array( $u2 ), $found );
+	}
+
+	public function test_get_specific_users() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$include_array = array(
+			$u1,
+			'junkstring',
+			$u3,
+		);
+
+		$users = BP_Core_User::get_specific_users( $include_array );
+		$user_ids = wp_parse_id_list( wp_list_pluck( $users['users'], 'id' ) );
+
+		$this->assertEquals( array( $u1, $u3 ), $user_ids );
+	}
+
+	/**
+	 * @group last_activity
+	 */
+	public function test_get_last_activity() {
+		$u = $this->factory->user->create();
+		$time = bp_core_current_time();
+
+		BP_Core_User::update_last_activity( $u, $time );
+
+		$a = BP_Core_User::get_last_activity( $u );
+		$found = isset( $a[ $u ]['date_recorded'] ) ? $a[ $u ]['date_recorded'] : '';
+
+		$this->assertEquals( $time, $found );
+	}
+
+	/**
+	 * @group last_activity
+	 * @group cache
+	 */
+	public function test_get_last_activity_store_in_cache() {
+		$u = $this->factory->user->create();
+		$time = bp_core_current_time();
+
+		// Cache is set during user creation. Clear to reflect actual
+		// pageload
+		wp_cache_delete( $u, 'bp_last_activity' );
+
+		// prime cache
+		$a = BP_Core_User::get_last_activity( $u );
+
+		$this->assertSame( $a[ $u ], wp_cache_get( $u, 'bp_last_activity' ) );
+	}
+
+	/**
+	 * @group last_activity
+	 * @group cache
+	 */
+	public function test_get_last_activity_store_in_cache_multiple_users() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$time = bp_core_current_time();
+
+		// Cache is set during user creation. Clear to reflect actual
+		// pageload
+		wp_cache_delete( $u1, 'bp_last_activity' );
+		wp_cache_delete( $u2, 'bp_last_activity' );
+
+		// prime cache
+		$a = BP_Core_User::get_last_activity( array( $u1, $u2 ) );
+
+		$this->assertSame( $a[ $u1 ], wp_cache_get( $u1, 'bp_last_activity' ) );
+		$this->assertSame( $a[ $u2 ], wp_cache_get( $u2, 'bp_last_activity' ) );
+	}
+
+	/**
+	 * @group last_activity
+	 * @group cache
+	 */
+	public function test_get_last_activity_from_cache_single_user() {
+		$u    = $this->factory->user->create();
+		$time = bp_core_current_time();
+
+		BP_Core_User::update_last_activity( $u, $time );
+
+		// Cache is set during user creation. Clear to reflect actual
+		// pageload
+		wp_cache_delete( $u, 'bp_last_activity' );
+
+		// Prime cache
+		$uncached = BP_Core_User::get_last_activity( $u );
+
+		// Fetch again to get from the cache
+		$cached = BP_Core_User::get_last_activity( $u );
+
+		$this->assertSame( $uncached, $cached );
+	}
+
+	/**
+	 * @group last_activity
+	 * @group cache
+	 */
+	public function test_get_last_activity_in_cache_multiple_users() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$time = bp_core_current_time();
+
+		BP_Core_User::update_last_activity( $u1, $time );
+		BP_Core_User::update_last_activity( $u2, $time );
+
+		// Cache is set during user creation. Clear to reflect actual pageload
+		wp_cache_delete( $u1, 'bp_last_activity' );
+		wp_cache_delete( $u2, 'bp_last_activity' );
+
+		// Prime cache
+		$uncached = BP_Core_User::get_last_activity( array( $u1, $u2 ) );
+
+		// Second grab will be from the cache
+		$cached = BP_Core_User::get_last_activity( array( $u1, $u2 ) );
+		$cached_u1 = wp_cache_get( $u1, 'bp_last_activity' );
+
+		$this->assertSame( $cached, $uncached );
+	}
+
+	/**
+	 * @group last_activity
+	 */
+	public function test_update_last_activity() {
+		$u = $this->factory->user->create();
+		$time = bp_core_current_time();
+		$time2 = '1968-12-25 01:23:45';
+
+		BP_Core_User::update_last_activity( $u, $time );
+		$a = BP_Core_User::get_last_activity( $u );
+		$found = isset( $a[ $u ]['date_recorded'] ) ? $a[ $u ]['date_recorded'] : '';
+		$this->assertEquals( $time, $found );
+
+		BP_Core_User::update_last_activity( $u, $time2 );
+		$a = BP_Core_User::get_last_activity( $u );
+		$found = isset( $a[ $u ]['date_recorded'] ) ? $a[ $u ]['date_recorded'] : '';
+		$this->assertEquals( $time2, $found );
+	}
+
+	/**
+	 * @group last_activity
+	 */
+	public function test_delete_last_activity() {
+		$u = $this->factory->user->create();
+		$time = bp_core_current_time();
+
+		BP_Core_User::update_last_activity( $u, $time );
+		$a = BP_Core_User::get_last_activity( $u );
+		$found = isset( $a[ $u ]['date_recorded'] ) ? $a[ $u ]['date_recorded'] : '';
+		$this->assertEquals( $time, $found );
+
+		BP_Core_User::delete_last_activity( $u );
+		$a = BP_Core_User::get_last_activity( $u );
+		$found = isset( $a[ $u ]['date_recorded'] ) ? $a[ $u ]['date_recorded'] : '';
+		$this->assertEquals( '', $found );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/class-bp-media-extractor.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/class-bp-media-extractor.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/class-bp-media-extractor.php	(revision 10410)
@@ -0,0 +1,486 @@
+<?php
+/**
+ * @group core
+ * @group BP_Media_Extractor
+ */
+class BP_Tests_Media_Extractor extends BP_UnitTestCase {
+	public static $media_extractor = null;
+	public static $richtext        = '';
+
+
+	public static function setUpBeforeClass() {
+		parent::setUpBeforeClass();
+
+		self::$media_extractor = new BP_Media_Extractor();
+		self::$richtext        = "Hello world.
+
+		This sample text is used to test the media extractor parsing class. @paulgibbs thinks it's pretty cool.
+		Another thing really cool is this @youtube:
+
+		https://www.youtube.com/watch?v=2mjvfnUAfyo
+
+		This video is literally out of the world, but uses a different protocol to the embed above:
+
+		http://www.youtube.com/watch?v=KaOC9danxNo
+
+		<a href='https://example.com'>Testing a regular link.</a>
+		<strong>But we should throw in some markup and maybe even an <img src='http://example.com/image.gif'>.
+		<a href='http://example.com'><img src='http://example.com/image-in-a-link.gif' /></a></strong>.
+		It definitely does not like <img src='data:1234567890A'>data URIs</img>. @
+
+		The parser only extracts wp_allowed_protocols() protocols, not something like <a href='phone:004400'>phone</a>.
+
+		[caption id='example']Here is a caption shortcode.[/caption]
+
+		There are two types of [gallery] shortcodes; one like that, and another with IDs specified.
+
+		Audio shortcodes:
+		[audio src='http://example.com/source.mp3']
+		[audio src='http://example.com/source.wav' loop='on' autoplay='off' preload='metadata'].
+
+		The following shortcode should be picked up by the shortcode extractor, but not the audio extractor, because
+		it has an unrecognised file extension (for an audio file). [audio src='http://example.com/not_audio.gif']
+		<a href='http://example.com/more_audio.mp3'>This should be picked up, too</a>.
+
+		Video shortcodes:
+		[video src='http://example.com/source.ogv']
+		[video src='http://example.com/source.webm' loop='on' autoplay='off' preload='metadata']
+
+		The following shortcode should be picked up by the shortcode extractor, but not the video extractor, because
+		it has an unrecognised file extension (for a video file). [video src='http://example.com/not_video.mp3']
+		";
+	}
+
+
+	/**
+	 * General.
+	 */
+
+	public function test_check_media_extraction_return_types() {
+		$media = self::$media_extractor->extract( self::$richtext );
+
+		foreach ( array( 'has', 'embeds', 'images', 'links', 'mentions', 'shortcodes', 'audio' ) as $key ) {
+			$this->assertArrayHasKey( $key, $media );
+			$this->assertInternalType( 'array', $media[ $key ] );
+		}
+
+		foreach ( $media['has'] as $item ) {
+			$this->assertInternalType( 'int', $item );
+		}
+
+		foreach ( $media['links'] as $item ) {
+			$this->assertArrayHasKey( 'url', $item );
+			$this->assertInternalType( 'string', $item['url'] );
+			$this->assertNotEmpty( $item['url'] );
+		}
+
+		foreach ( $media['mentions'] as $item ) {
+			$this->assertArrayHasKey( 'name', $item );
+			$this->assertInternalType( 'string', $item['name'] );
+			$this->assertNotEmpty( $item['name'] );
+		}
+
+		foreach ( $media['images'] as $item ) {
+			$this->assertArrayHasKey( 'height', $item );
+			$this->assertInternalType( 'int', $item['height'] );
+
+			$this->assertArrayHasKey( 'width', $item );
+			$this->assertInternalType( 'int', $item['width'] );
+
+			$this->assertArrayHasKey( 'source', $item );
+			$this->assertInternalType( 'string', $item['source'] );
+			$this->assertNotEmpty( $item['source'] );
+
+			$this->assertArrayHasKey( 'url', $item );
+			$this->assertInternalType( 'string', $item['url'] );
+			$this->assertNotEmpty( $item['url'] );
+		}
+
+		foreach ( $media['shortcodes'] as $shortcode_type => $item ) {
+			$this->assertArrayHasKey( 'attributes', $item );
+			$this->assertInternalType( 'array', $item['attributes'] );
+
+			$this->assertArrayHasKey( 'content', $item );
+			$this->assertInternalType( 'string', $item['content'] );
+
+			$this->assertArrayHasKey( 'type', $item );
+			$this->assertInternalType( 'string', $item['type'] );
+
+			$this->assertArrayHasKey( 'original', $item );
+			$this->assertInternalType( 'string', $item['original'] );
+		}
+
+		foreach ( $media['embeds'] as $item ) {
+			$this->assertArrayHasKey( 'url', $item );
+			$this->assertInternalType( 'string', $item['url'] );
+			$this->assertNotEmpty( $item['url'] );
+		}
+
+		foreach ( $media['audio'] as $item ) {
+			$this->assertArrayHasKey( 'url', $item );
+			$this->assertInternalType( 'string', $item['url'] );
+			$this->assertNotEmpty( $item['url'] );
+
+			$this->assertArrayHasKey( 'source', $item );
+			$this->assertInternalType( 'string', $item['source'] );
+			$this->assertNotEmpty( $item['source'] );
+		}
+	}
+
+	public function test_check_media_extraction_counts_are_correct() {
+		$media = self::$media_extractor->extract( self::$richtext );
+		$types = array_keys( $media );
+
+		foreach ( $types as $type ) {
+			if ( $type === 'has' ) {
+				continue;
+			}
+
+			$this->assertArrayHasKey( $type, $media['has'] );
+			$this->assertSame( count( $media[ $type ] ), $media['has'][ $type ], "Difference with the 'has' count for {$type}." );
+		}
+	}
+
+
+	public function test_extract_multiple_media_types_from_content() {
+		$this->factory->user->create( array( 'user_login' => 'paulgibbs' ) );
+		$media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::LINKS | BP_Media_Extractor::MENTIONS );
+
+		$this->assertNotEmpty( $media['links'] );
+		$this->assertNotEmpty( $media['mentions'] );
+		$this->assertArrayNotHasKey( 'shortcodes', $media );
+	}
+
+	public function test_extract_media_from_a_wp_post() {
+		$post_id = $this->factory->post->create( array( 'post_content' => self::$richtext ) );
+		$media   = self::$media_extractor->extract( get_post( $post_id ), BP_Media_Extractor::LINKS );
+
+		$this->assertArrayHasKey( 'links', $media );
+		$this->assertSame( 'https://example.com', $media['links'][0]['url'] );
+		$this->assertSame( 'http://example.com',  $media['links'][1]['url'] );
+	}
+
+
+	/**
+	 * Link extraction.
+	 */
+
+	public function test_extract_links_from_content() {
+		$media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::LINKS );
+
+		$this->assertArrayHasKey( 'links', $media );
+		$this->assertSame( 'https://example.com', $media['links'][0]['url'] );
+		$this->assertSame( 'http://example.com',  $media['links'][1]['url'] );
+	}
+
+	public function test_extract_no_links_from_content_with_invalid_links() {
+		$richtext = "This is some sample text, with links, but not the kinds we want.
+		<a href=''>Empty links should be ignore<a/> and
+		<a href='phone:004400'>weird protocols should be ignored, too</a>.
+		";
+
+		$media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::LINKS );
+		$this->assertSame( 0, $media['has']['links'] );
+	}
+
+
+	/**
+	 * at-mentions extraction.
+	 */
+
+	public function test_extract_mentions_from_content_with_activity_enabled() {
+		$this->factory->user->create( array( 'user_login' => 'paulgibbs' ) );
+		$media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::MENTIONS );
+
+		$this->assertArrayHasKey( 'user_id', $media['mentions'][0] );
+		$this->assertSame( 'paulgibbs', $media['mentions'][0]['name'] );
+	}
+
+	public function test_extract_mentions_from_content_with_activity_disabled() {
+		$this->factory->user->create( array( 'user_login' => 'paulgibbs' ) );
+		$was_activity_enabled = false;
+
+		// Turn activity off.
+		if ( isset( buddypress()->active_components['activity'] ) ) {
+			unset( buddypress()->active_components['activity'] );
+			$was_activity_enabled = true;
+		}
+
+
+		$media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::MENTIONS );
+
+		$this->assertArrayNotHasKey( 'user_id', $media['mentions'][0] );
+		$this->assertSame( 'paulgibbs', $media['mentions'][0]['name'] );
+
+
+		// Turn activity on.
+		if ( $was_activity_enabled ) {
+			buddypress()->active_components['activity'] = 1;
+		}
+	}
+
+
+	/**
+	 * Shortcodes extraction.
+	 */
+
+	public function test_extract_shortcodes_from_content() {
+		$media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::SHORTCODES );
+
+		$this->assertArrayHasKey( 'shortcodes', $media );
+
+		$this->assertSame( 'caption', $media['shortcodes'][0]['type'] );
+		$this->assertSame( 'Here is a caption shortcode.', $media['shortcodes'][0]['content'] );
+		$this->assertSame( 'example', $media['shortcodes'][0]['attributes']['id'] );
+
+		$this->assertSame( 'gallery', $media['shortcodes'][1]['type'] );
+		$this->assertEmpty( $media['shortcodes'][1]['content'] );
+
+		$this->assertSame( 'audio', $media['shortcodes'][2]['type'] );
+		$this->assertEmpty( $media['shortcodes'][2]['content'] );
+		$this->assertSame( 'http://example.com/source.mp3', $media['shortcodes'][2]['attributes']['src'] );
+
+		$this->assertSame( 'audio', $media['shortcodes'][3]['type'] );
+		$this->assertEmpty( $media['shortcodes'][3]['content'] );
+		$this->assertSame( 'http://example.com/source.wav', $media['shortcodes'][3]['attributes']['src'] );
+		$this->assertSame( 'on', $media['shortcodes'][3]['attributes']['loop'] );
+		$this->assertSame( 'off', $media['shortcodes'][3]['attributes']['autoplay'] );
+		$this->assertSame( 'metadata', $media['shortcodes'][3]['attributes']['preload'] );
+	}
+
+	public function test_extract_no_shortcodes_from_content_with_unregistered_shortcodes() {
+		$richtext = 'This sample text has some made-up [fake]shortcodes[/fake].';
+
+		$media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::SHORTCODES );
+		$this->assertSame( 0, $media['has']['shortcodes'] );
+	}
+
+
+	/**
+	 * oEmbeds extraction.
+	 */
+
+	public function test_extract_oembeds_from_content() {
+		$media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::EMBEDS );
+
+		$this->assertArrayHasKey( 'embeds', $media );
+		$this->assertSame( 'https://www.youtube.com/watch?v=2mjvfnUAfyo', $media['embeds'][0]['url'] );
+		$this->assertSame( 'http://www.youtube.com/watch?v=KaOC9danxNo',  $media['embeds'][1]['url'] );
+	}
+
+
+	/**
+	 * Images extraction (src tags).
+	 */
+
+	// both quote styles
+	public function test_extract_images_from_content_with_src_tags() {
+		$media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::IMAGES );
+
+		$this->assertArrayHasKey( 'images', $media );
+		$media = array_values( wp_list_filter( $media['images'], array( 'source' => 'html' ) ) );
+
+		$this->assertSame( 'http://example.com/image.gif',           $media[0]['url'] );
+		$this->assertSame( 'http://example.com/image-in-a-link.gif', $media[1]['url'] );
+	}
+
+	// empty src attributes, data: URIs
+	public function test_extract_no_images_from_content_with_invalid_src_tags() {
+		$richtext = 'This sample text will contain images with invalid src tags, like this:
+		<img src="data://abcd"> or <img src="phone://0123" />.
+		';
+
+		$media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::IMAGES );
+
+		$this->assertArrayHasKey( 'images', $media );
+		$this->assertSame( 0, $media['has']['images'] );
+	}
+
+
+	/**
+	 * Images extraction (galleries).
+	 */
+
+	public function test_extract_images_from_content_with_galleries_variant_no_ids() {
+		// To test the [gallery] shortcode, we need to create a post and an attachment.
+		$post_id       = $this->factory->post->create( array( 'post_content' => self::$richtext ) );
+		$attachment_id = $this->factory->attachment->create_object( 'image.jpg', $post_id, array(
+			'post_mime_type' => 'image/jpeg',
+			'post_type'      => 'attachment'
+		) );
+		wp_update_attachment_metadata( $attachment_id, array( 'width' => 100, 'height' => 100 ) );
+
+
+		// Extract the gallery images.
+		$media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::IMAGES, array(
+			'post' => get_post( $post_id ),
+		) );
+
+		$this->assertArrayHasKey( 'images', $media );
+		$media = array_values( wp_list_filter( $media['images'], array( 'source' => 'galleries' ) ) );
+		$this->assertCount( 1, $media );
+
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/image.jpg', $media[0]['url'] );
+	}
+
+	public function test_extract_images_from_content_with_galleries_variant_ids() {
+		$attachment_ids   = array();
+		$attachment_ids[] = $this->fake_attachment_upload( DIR_TESTDATA . '/images/test-image-large.png' );
+		$attachment_ids[] = $this->fake_attachment_upload( DIR_TESTDATA . '/images/canola.jpg' );
+		$attachment_ids   = join( ',', $attachment_ids );
+		$post_id          = $this->factory->post->create( array( 'post_content' => "[gallery ids='{$attachment_ids}']" ) );
+
+		// Extract the gallery images.
+		$media = self::$media_extractor->extract( '', BP_Media_Extractor::IMAGES, array(
+			'post' => get_post( $post_id ),
+		) );
+
+		$this->assertArrayHasKey( 'images', $media );
+		$media = array_values( wp_list_filter( $media['images'], array( 'source' => 'galleries' ) ) );
+		$this->assertCount( 2, $media );
+
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/test-image-large.png', $media[0]['url'] );
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/canola.jpg', $media[1]['url'] );
+	}
+
+	public function test_extract_no_images_from_content_with_invalid_galleries_variant_no_ids() {
+		$post_id = $this->factory->post->create( array( 'post_content' => self::$richtext ) );
+		$media   = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::IMAGES, array(
+			'post' => get_post( $post_id ),
+		) );
+
+		$this->assertArrayHasKey( 'images', $media );
+		$media = array_values( wp_list_filter( $media['images'], array( 'source' => 'galleries' ) ) );
+		$this->assertCount( 0, $media );
+	}
+
+	public function test_extract_no_images_from_content_with_invalid_galleries_variant_ids() {
+		$post_id = $this->factory->post->create( array( 'post_content' => '[gallery ids="117,4529"]' ) );
+		$media   = self::$media_extractor->extract( '', BP_Media_Extractor::IMAGES, array(
+			'post' => get_post( $post_id ),
+		) );
+
+		$this->assertArrayHasKey( 'images', $media );
+		$media = array_values( wp_list_filter( $media['images'], array( 'source' => 'galleries' ) ) );
+		$this->assertCount( 0, $media );
+	}
+
+
+	/**
+	 * Images extraction (thumbnail).
+	 */
+
+	public function test_extract_no_images_from_content_with_featured_image() {
+		$post_id      = $this->factory->post->create( array( 'post_content' => self::$richtext ) );
+		$thumbnail_id = $this->factory->attachment->create_object( 'image.jpg', $post_id, array(
+			'post_mime_type' => 'image/jpeg',
+			'post_type'      => 'attachment'
+		) );
+		set_post_thumbnail( $post_id, $thumbnail_id );
+
+
+		// Extract the gallery images.
+		$media = self::$media_extractor->extract( '', BP_Media_Extractor::IMAGES, array(
+			'post' => get_post( $post_id ),
+		) );
+
+		$this->assertArrayHasKey( 'images', $media );
+		$media = array_values( wp_list_filter( $media['images'], array( 'source' => 'featured_images' ) ) );
+		$this->assertCount( 1, $media );
+
+		$this->assertSame( 'http://' . WP_TESTS_DOMAIN . '/wp-content/uploads/image.jpg', $media[0]['url'] );
+	}
+
+	public function test_extract_images_from_content_without_featured_image() {
+		$post_id = $this->factory->post->create( array( 'post_content' => self::$richtext ) );
+		$media   = self::$media_extractor->extract( '', BP_Media_Extractor::IMAGES, array(
+			'post' => get_post( $post_id ),
+		) );
+
+		$this->assertArrayHasKey( 'images', $media );
+		$media = array_values( wp_list_filter( $media['images'], array( 'source' => 'featured_images' ) ) );
+		$this->assertCount( 0, $media );
+	}
+
+
+	/**
+	 * Audio extraction.
+	 */
+
+	public function test_extract_audio_from_content() {
+		$media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::AUDIO );
+
+		$this->assertArrayHasKey( 'audio', $media );
+		$this->assertCount( 3, $media['audio'] );
+
+		$this->assertSame( 'shortcodes', $media['audio'][0]['source'] );
+		$this->assertSame( 'shortcodes', $media['audio'][1]['source'] );
+		$this->assertSame( 'html',       $media['audio'][2]['source'] );
+
+		$this->assertSame( 'http://example.com/source.mp3',     $media['audio'][0]['url'] );
+		$this->assertSame( 'http://example.com/source.wav',     $media['audio'][1]['url'] );
+		$this->assertSame( 'http://example.com/more_audio.mp3', $media['audio'][2]['url'] );
+	}
+
+	public function test_extract_audio_shortcode_with_no_src_param() {
+		$richtext = '[audio http://example.com/a-song.mp3]';
+		$media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::AUDIO );
+
+		$this->assertArrayHasKey( 'audio', $media );
+		$this->assertCount( 1, $media['audio'] );
+		$this->assertSame( 'http://example.com/a-song.mp3', $media['audio'][0]['url'] );
+	}
+
+	public function test_extract_no_audio_from_invalid_content() {
+		$richtext = '[audio src="http://example.com/not_audio.gif"]
+		<a href="http://example.com/more_not_audio.mp33">Hello</a>.';
+
+		$media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::AUDIO );
+		$this->assertSame( 0, $media['has']['audio'] );
+	}
+
+	public function test_extract_no_audio_from_empty_audio_shortcode() {
+		$media = self::$media_extractor->extract( '[audio]', BP_Media_Extractor::AUDIO );
+		$this->assertSame( 0, $media['has']['audio'] );
+	}
+
+
+	/**
+	 * Video extraction.
+	 */
+
+	public function test_extract_video_from_content() {
+		$media = self::$media_extractor->extract( self::$richtext, BP_Media_Extractor::VIDEOS );
+
+		$this->assertArrayHasKey( 'videos', $media );
+		$this->assertCount( 2, $media['videos'] );
+
+		$this->assertSame( 'shortcodes', $media['videos'][0]['source'] );
+		$this->assertSame( 'shortcodes', $media['videos'][1]['source'] );
+
+		$this->assertSame( 'http://example.com/source.ogv',  $media['videos'][0]['url'] );
+		$this->assertSame( 'http://example.com/source.webm', $media['videos'][1]['url'] );
+	}
+
+
+	public function test_extract_video_shortcode_with_no_src_param() {
+		$richtext = '[video http://example.com/source.ogv]';
+		$media = self::$media_extractor->extract( $richtext, BP_Media_Extractor::VIDEOS );
+
+		$this->assertArrayHasKey( 'videos', $media );
+		$this->assertCount( 1, $media['videos'] );
+		$this->assertSame( 'http://example.com/source.ogv', $media['videos'][0]['url'] );
+	}
+
+	public function test_extract_no_video_from_invalid_content() {
+		$richtext = '[video src="http://example.com/not_video.mp3"]';
+		$media    = self::$media_extractor->extract( $richtext, BP_Media_Extractor::VIDEOS );
+
+		$this->assertSame( 0, $media['has']['videos'] );
+	}
+
+	public function test_extract_no_videos_from_empty_video_shortcodes() {
+		$media = self::$media_extractor->extract( '[video]', BP_Media_Extractor::VIDEOS );
+		$this->assertSame( 0, $media['has']['videos'] );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/class-bp-user-query.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/class-bp-user-query.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/class-bp-user-query.php	(revision 10410)
@@ -0,0 +1,742 @@
+<?php
+
+/**
+ * @group core
+ * @group BP_User_Query
+ */
+class BP_Tests_BP_User_Query_TestCases extends BP_UnitTestCase {
+	/**
+	 * Checks that user_id returns friends
+	 */
+	public function test_bp_user_query_friends() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		friends_add_friend( $u1, $u2, true );
+
+		$q = new BP_User_Query( array(
+			'user_id' => $u2,
+		) );
+
+		$friends = is_array( $q->results ) ? array_values( $q->results ) : array();
+		$friend_ids = wp_list_pluck( $friends, 'ID' );
+		$this->assertEquals( $friend_ids, array( $u1 ) );
+	}
+
+	/**
+	 * @ticket BP4938
+	 */
+	public function test_bp_user_query_friends_with_include() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$u4 = $this->factory->user->create();
+		friends_add_friend( $u1, $u2, true );
+		friends_add_friend( $u1, $u3, true );
+
+		$q = new BP_User_Query( array(
+			'user_id' => $u1,
+
+			// Represents an independent filter passed by a plugin
+			// u4 is not a friend of u1 and should not be returned
+			'include' => array( $u2, $u4 ),
+		) );
+
+		$friends = is_array( $q->results ) ? array_values( $q->results ) : array();
+		$friend_ids = wp_list_pluck( $friends, 'ID' );
+		$this->assertEquals( $friend_ids, array( $u2 ) );
+	}
+
+	public function test_bp_user_query_friends_with_include_but_zero_friends() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$u4 = $this->factory->user->create();
+
+		$q = new BP_User_Query( array(
+			'user_id' => $u1,
+
+			// Represents an independent filter passed by a plugin
+			// u4 is not a friend of u1 and should not be returned
+			'include' => array( $u2, $u4 ),
+		) );
+
+		$friends = is_array( $q->results ) ? array_values( $q->results ) : array();
+		$friend_ids = wp_list_pluck( $friends, 'ID' );
+		$this->assertEquals( $friend_ids, array() );
+	}
+
+	/**
+	 * @group user_ids
+	 */
+	public function test_bp_user_query_user_ids_with_invalid_user_id() {
+		$now = time();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// invalid user ID
+		$u3 = $u2 + 1;
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u1 );
+
+		// pass 'user_ids' to user query to trigger this bug
+		$q = new BP_User_Query( array(
+			'user_ids' => array( $u2, $u3 )
+		) );
+
+		// $q->user_ids property should now not contain invalid user IDs
+		$this->assertNotContains( $u3, $q->user_ids );
+
+		// clean up
+		$this->set_current_user( $old_user );
+	}
+
+	public function test_bp_user_query_sort_by_popular() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$u4 = $this->factory->user->create();
+
+		bp_update_user_meta( $u1, bp_get_user_meta_key( 'total_friend_count' ), '5' );
+		bp_update_user_meta( $u2, bp_get_user_meta_key( 'total_friend_count' ), '90' );
+		bp_update_user_meta( $u3, bp_get_user_meta_key( 'total_friend_count' ), '101' );
+		bp_update_user_meta( $u4, bp_get_user_meta_key( 'total_friend_count' ), '3002' );
+
+		$q = new BP_User_Query( array(
+			'type' => 'popular',
+		) );
+
+		$users = is_array( $q->results ) ? array_values( $q->results ) : array();
+		$user_ids = wp_parse_id_list( wp_list_pluck( $users, 'ID' ) );
+
+		$expected = array( $u4, $u3, $u2, $u1 );
+		$this->assertEquals( $expected, $user_ids );
+	}
+
+	/**
+	 * @group online
+	 */
+	public function test_bp_user_query_type_online() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*13 ),
+		) );
+		$u3 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*16 ),
+		) );
+
+		$q = new BP_User_Query( array(
+			'type' => 'online',
+		) );
+
+		$users = is_array( $q->results ) ? array_values( $q->results ) : array();
+		$user_ids = wp_parse_id_list( wp_list_pluck( $users, 'ID' ) );
+		$this->assertEquals( array( $u1, $u2 ), $user_ids );
+	}
+
+	/**
+	 * @group online
+	 */
+	public function test_bp_user_query_type_online_five_minute_interval() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*4 ),
+		) );
+		$u3 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*6 ),
+		) );
+
+		add_filter( 'bp_user_query_online_interval', create_function( '', 'return 5;' ) );
+
+		$q = new BP_User_Query( array(
+			'type' => 'online',
+		) );
+
+		$users = is_array( $q->results ) ? array_values( $q->results ) : array();
+		$user_ids = wp_parse_id_list( wp_list_pluck( $users, 'ID' ) );
+		$this->assertEquals( array( $u1, $u2 ), $user_ids );
+	}
+
+
+	public function test_bp_user_query_search_with_apostrophe() {
+		// Apostrophe. Search_terms must escaped to mimic POST payload
+		$user_id = $this->factory->user->create();
+		xprofile_set_field_data( 1, $user_id, "Foo'Bar" );
+		$q = new BP_User_Query( array( 'search_terms' => "oo\'Ba", ) );
+
+		$found_user_id = null;
+		if ( ! empty( $q->results ) ) {
+			$found_user = array_pop( $q->results );
+			$found_user_id = $found_user->ID;
+		}
+
+		$this->assertEquals( $user_id, $found_user_id );
+	}
+
+	public function test_bp_user_query_search_with_percent_sign() {
+
+		// LIKE special character: %
+		$user_id = $this->factory->user->create();
+		xprofile_set_field_data( 1, $user_id, "Foo%Bar" );
+		$q = new BP_User_Query( array( 'search_terms' => "oo%Bar", ) );
+
+		$found_user_id = null;
+		if ( ! empty( $q->results ) ) {
+			$found_user = array_pop( $q->results );
+			$found_user_id = $found_user->ID;
+		}
+
+		$this->assertEquals( $user_id, $found_user_id );
+
+	}
+
+	public function test_bp_user_query_search_with_underscore() {
+
+		// LIKE special character: _
+		$user_id = $this->factory->user->create();
+		xprofile_set_field_data( 1, $user_id, "Foo_Bar" );
+		$q = new BP_User_Query( array( 'search_terms' => "oo_Bar", ) );
+
+		$found_user_id = null;
+		if ( ! empty( $q->results ) ) {
+			$found_user = array_pop( $q->results );
+			$found_user_id = $found_user->ID;
+		}
+
+		$this->assertEquals( $user_id, $found_user_id );
+	}
+
+	public function test_bp_user_query_search_with_ampersand_sign() {
+
+		// LIKE special character: &
+		$user_id = $this->factory->user->create();
+		xprofile_set_field_data( 1, $user_id, "a&mpersand" );
+		$q = new BP_User_Query( array( 'search_terms' => "a&m", ) );
+
+		$found_user_id = null;
+		if ( ! empty( $q->results ) ) {
+			$found_user = array_pop( $q->results );
+			$found_user_id = $found_user->ID;
+		}
+
+		$this->assertEquals( $user_id, $found_user_id );
+
+	}
+
+	/**
+	 * @group search_terms
+	 */
+	public function test_bp_user_query_search_core_fields() {
+		$user_id = $this->factory->user->create( array(
+			'user_login' => 'foo',
+		) );
+		xprofile_set_field_data( 1, $user_id, "Bar" );
+		$q = new BP_User_Query( array( 'search_terms' => 'foo', ) );
+
+		$found_user_id = null;
+		if ( ! empty( $q->results ) ) {
+			$found_user = array_pop( $q->results );
+			$found_user_id = $found_user->ID;
+		}
+
+		$this->assertEquals( $user_id, $found_user_id );
+	}
+
+	public function test_bp_user_query_search_wildcards() {
+		$u1 = $this->factory->user->create( array(
+			'user_login' => 'xfoo',
+		) );
+		xprofile_set_field_data( 1, $u1, "Bar" );
+		$q1 = new BP_User_Query( array( 'search_terms' => 'foo', 'search_wildcard' => 'left' ) );
+
+		$u2 = $this->factory->user->create( array(
+			'user_login' => 'foox',
+		) );
+		xprofile_set_field_data( 1, $u2, "Bar" );
+		$q2 = new BP_User_Query( array( 'search_terms' => 'foo', 'search_wildcard' => 'right' ) );
+
+		$u3 = $this->factory->user->create( array(
+			'user_login' => 'xfoox',
+		) );
+		xprofile_set_field_data( 1, $u3, "Bar" );
+		$q3 = new BP_User_Query( array( 'search_terms' => 'foo', 'search_wildcard' => 'both' ) );
+
+		$this->assertNotEmpty( $q1->results );
+		$q1 = array_pop( $q1->results );
+		$this->assertEquals( $u1, $q1->ID );
+
+		$this->assertNotEmpty( $q2->results );
+		$q2 = array_pop( $q2->results );
+		$this->assertEquals( $u2, $q2->ID );
+
+		$this->assertNotEmpty( $q3->results );
+		foreach ( $q3->results as $user ) {
+			$this->assertTrue( in_array( $user->ID, array( $u1, $u2, $u3 ) ) );
+		}
+	}
+
+	/**
+	 * @group exclude
+	 */
+	public function test_bp_user_query_with_exclude() {
+		// Grab list of existing users who should also be excluded
+		global $wpdb;
+		$existing_users = $wpdb->get_col( "SELECT ID FROM {$wpdb->users}" );
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$exclude = array_merge( array( $u1 ), $existing_users );
+		$q = new BP_User_Query( array( 'exclude' => $exclude, ) );
+
+		$found_user_ids = null;
+		if ( ! empty( $q->results ) ) {
+			$found_user_ids = array_values( wp_parse_id_list( wp_list_pluck( $q->results, 'ID' ) ) );
+		}
+
+		$this->assertEquals( array( $u2 ), $found_user_ids );
+	}
+
+	/**
+	 * @group type
+	 * @group spam
+	 */
+	public function test_bp_user_query_type_alphabetical_spam_xprofileon() {
+		if ( is_multisite() ) {
+			return;
+		}
+
+		// Make sure xprofile is on
+		$xprofile_toggle = isset( buddypress()->active_components['xprofile'] );
+		buddypress()->active_components['xprofile'] = 1;
+		add_filter( 'bp_disable_profile_sync', '__return_false' );
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		global $wpdb;
+		bp_core_process_spammer_status( $u1, 'spam' );
+
+		$q = new BP_User_Query( array( 'type' => 'alphabetical', ) );
+
+		// Restore xprofile setting
+		if ( $xprofile_toggle ) {
+			buddypress()->active_components['xprofile'] = 1;
+		} else {
+			unset( buddypress()->active_components['xprofile'] );
+		}
+		remove_filter( 'bp_disable_profile_sync', '__return_false' );
+
+		$found_user_ids = null;
+
+		if ( ! empty( $q->results ) ) {
+			$found_user_ids = array_values( wp_parse_id_list( wp_list_pluck( $q->results, 'ID' ) ) );
+		}
+
+		// Do a assertNotContains because there are weird issues with user #1 as created by WP
+		$this->assertNotContains( $u1, $found_user_ids );
+	}
+
+	/**
+	 * @group type
+	 * @group spam
+	 */
+	public function test_bp_user_query_type_alphabetical_spam_xprofileoff() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// Make sure xprofile and profile sync are off
+		$xprofile_toggle = isset( buddypress()->active_components['xprofile'] );
+		buddypress()->active_components['xprofile'] = 0;
+		add_filter( 'bp_disable_profile_sync', '__return_false' );
+
+		bp_core_process_spammer_status( $u1, 'spam' );
+
+		$q = new BP_User_Query( array( 'type' => 'alphabetical', ) );
+
+		// Restore xprofile setting
+		if ( $xprofile_toggle ) {
+			buddypress()->active_components['xprofile'] = 1;
+		} else {
+			unset( buddypress()->active_components['xprofile'] );
+		}
+		remove_filter( 'bp_disable_profile_sync', '__return_false' );
+
+		$found_user_ids = null;
+
+		if ( ! empty( $q->results ) ) {
+			$found_user_ids = array_values( wp_parse_id_list( wp_list_pluck( $q->results, 'ID' ) ) );
+		}
+
+		// Do a assertNotContains because there are weird issues with user #1 as created by WP
+		$this->assertNotContains( $u1, $found_user_ids );
+	}
+
+	/**
+	 * @group meta
+	 * @group BP5904
+	 */
+	public function test_bp_user_query_with_user_meta_argument() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		bp_update_user_meta( $u2, 'foo', 'bar' );
+
+		$q = new BP_User_Query( array(
+			'meta_key'        => 'foo',
+			'meta_value'      => 'bar',
+		) );
+
+		$found_user_ids = array_values( wp_parse_id_list( wp_list_pluck( $q->results, 'ID' ) ) );
+
+		// Do a assertNotContains because there are weird issues with user #1 as created by WP
+		$this->assertNotContains( $u1, $found_user_ids );
+		$this->assertEquals( array( $u2 ), $found_user_ids );
+	}
+
+	/**
+	 * @group meta
+	 * @group BP5904
+	 */
+	public function test_bp_user_query_with_user_meta_argument_no_user() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$q = new BP_User_Query( array(
+			'meta_key'        => 'foo',
+			'meta_value'      => 'bar',
+		) );
+
+		$found_user_ids = array_values( wp_parse_id_list( wp_list_pluck( $q->results, 'ID' ) ) );
+
+		$this->assertEmpty( $found_user_ids );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_single_value() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type' => 'bar',
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEquals( array( $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_array_with_single_value() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type' => array( 'bar' ),
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEquals( array( $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_comma_separated_values() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type' => 'foo, bar',
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[0], $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_array_with_multiple_values() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type' => array( 'foo', 'bar' ),
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[0], $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_comma_separated_values_should_discard_non_existent_taxonomies() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type' => 'foo, baz',
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[0] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 * @ticket BP6334
+	 */
+	public function test_should_return_no_results_when_no_users_match_the_specified_member_type() {
+		bp_register_member_type( 'foo' );
+		$users = $this->factory->user->create_many( 3 );
+
+		$q = new BP_User_Query( array(
+			'member_type' => 'foo, baz',
+		) );
+
+		$this->assertEmpty( $q->results );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type__in_single_value() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type__in' => 'bar',
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEquals( array( $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type__in_array_with_single_value() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type__in' => array( 'bar' ),
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEquals( array( $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type__in_comma_separated_values() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type__in' => 'foo, bar',
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[0], $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type__in_array_with_multiple_values() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type__in' => array( 'foo', 'bar' ),
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[0], $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type__in_comma_separated_values_should_discard_non_existent_taxonomies() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type__in' => 'foo, baz',
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[0] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_should_return_no_results_when_no_users_match_the_specified_member_type__in() {
+		bp_register_member_type( 'foo' );
+		$users = $this->factory->user->create_many( 3 );
+
+		$q = new BP_User_Query( array(
+			'member_type__in' => 'foo, baz',
+		) );
+
+		$this->assertEmpty( $q->results );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_should_take_precedence_over_member_type__in() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type__in' => 'foo',
+			'member_type' => 'bar'
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type__not_in_returns_members_from_other_types_and_members_with_no_types() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type__not_in' => 'foo',
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[1], $users[2] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_should_return_no_results_when_all_users_match_the_specified_member_type__not_in() {
+		bp_register_member_type( 'foo' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'foo' );
+		bp_set_member_type( $users[2], 'foo' );
+
+		$q = new BP_User_Query( array(
+			'member_type__not_in' => 'foo',
+		) );
+
+		$this->assertEmpty( $q->results );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type__not_in_takes_precedence_over_member_type() {
+		bp_register_member_type( 'foo' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'foo' );
+		bp_set_member_type( $users[2], 'foo' );
+
+		$q = new BP_User_Query( array(
+			'member_type__not_in' => 'foo',
+			'member_type' => 'foo'
+		) );
+
+		$this->assertEmpty( $q->results );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type__not_in_takes_precedence_over_member_type__in() {
+		bp_register_member_type( 'foo' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'foo' );
+		bp_set_member_type( $users[2], 'foo' );
+
+		$q = new BP_User_Query( array(
+			'member_type__not_in' => 'foo',
+			'member_type__in' => 'foo'
+		) );
+
+		$this->assertEmpty( $q->results );
+	}
+
+	/**
+	 * @group cache
+	 * @group member_types
+	 */
+	public function test_member_type_should_be_prefetched_into_cache_during_user_query() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 4 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+		bp_set_member_type( $users[2], 'foo' );
+
+		$q = new BP_User_Query( array(
+			'include' => $users,
+		) );
+
+		$this->assertSame( array( 'foo' ), wp_cache_get( $users[0], 'bp_member_member_type' ) );
+		$this->assertSame( array( 'bar' ), wp_cache_get( $users[1], 'bp_member_member_type' ) );
+		$this->assertSame( array( 'foo' ), wp_cache_get( $users[2], 'bp_member_member_type' ) );
+		$this->assertSame( '', wp_cache_get( $users[3], 'bp_member_member_type' ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/functions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/functions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/functions.php	(revision 10410)
@@ -0,0 +1,710 @@
+<?php
+
+/**
+ * @group core
+ */
+
+class BP_Tests_Core_Functions extends BP_UnitTestCase {
+	/**
+	 * @group bp_esc_sql_order
+	 */
+	public function test_bp_esc_sql_order_ASC() {
+		$this->assertEquals( 'ASC', bp_esc_sql_order( 'ASC' ) );
+	}
+
+	/**
+	 * @group bp_esc_sql_order
+	 */
+	public function test_bp_esc_sql_order_DESC() {
+		$this->assertEquals( 'DESC', bp_esc_sql_order( 'DESC' ) );
+	}
+
+	/**
+	 * @group bp_esc_sql_order
+	 */
+	public function test_bp_esc_sql_order_desc_lowercase() {
+		$this->assertEquals( 'DESC', bp_esc_sql_order( 'desc' ) );
+	}
+
+	/**
+	 * @group bp_esc_sql_order
+	 */
+	public function test_bp_esc_sql_order_desc_whitespace() {
+		$this->assertEquals( 'DESC', bp_esc_sql_order( ' desc ' ) );
+	}
+
+	/**
+	 * @group bp_esc_sql_order
+	 */
+	public function test_bp_esc_sql_order_invalid() {
+		$this->assertEquals( 'ASC', bp_esc_sql_order( 'In ur base killin ur d00dz' ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_years_months() {
+		$now = time();
+		$then = $now - ( 3 * YEAR_IN_SECONDS ) - ( 3 * 30 * DAY_IN_SECONDS );
+		$this->assertEquals( '3 years, 3 months ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_years_nomonths() {
+		$now = time();
+		$then = $now - ( 3 * YEAR_IN_SECONDS );
+		$this->assertEquals( '3 years ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_months_weeks() {
+		$now = time();
+		$then = $now - ( 3 * 30 * DAY_IN_SECONDS ) - ( 3 * WEEK_IN_SECONDS );
+		$this->assertEquals( '3 months, 3 weeks ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_months_noweeks() {
+		$now = time();
+		$then = $now - ( 3 * 30 * DAY_IN_SECONDS );
+		$this->assertEquals( '3 months ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_weeks_days() {
+		$now = time();
+		$then = $now - ( 3 * WEEK_IN_SECONDS ) - ( 3 * DAY_IN_SECONDS );
+		$this->assertEquals( '3 weeks, 3 days ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_weeks_nodays() {
+		$now = time();
+		$then = $now - ( 3 * WEEK_IN_SECONDS );
+		$this->assertEquals( '3 weeks ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_days_hours() {
+		$now = time();
+		$then = $now - ( 3 * DAY_IN_SECONDS ) - ( 3 * HOUR_IN_SECONDS );
+		$this->assertEquals( '3 days, 3 hours ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_days_nohours() {
+		$now = time();
+		$then = $now - ( 3 * DAY_IN_SECONDS );
+		$this->assertEquals( '3 days ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_hours_minutes() {
+		$now = time();
+		$then = $now - ( 3 * HOUR_IN_SECONDS ) - ( 3 * MINUTE_IN_SECONDS );
+		$this->assertEquals( '3 hours, 3 minutes ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_hours_nominutes() {
+		$now = time();
+		$then = $now - ( 3 * HOUR_IN_SECONDS );
+		$this->assertEquals( '3 hours ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 * @ticket BP5017
+	 */
+	public function test_bp_core_time_since_minutes_seconds() {
+		$now = time();
+		$then = $now - ( 3 * MINUTE_IN_SECONDS ) - 3;
+		$this->assertEquals( '3 minutes ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_minutes_noseconds() {
+		$now = time();
+		$then = $now - ( 3 * MINUTE_IN_SECONDS );
+		$this->assertEquals( '3 minutes ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_seconds() {
+		$now = time();
+		$then = $now - 3;
+		$this->assertEquals( '3 seconds ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * Sanity check for the singular version of 'year'
+	 *
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_year() {
+		$now = time();
+		$then = $now - YEAR_IN_SECONDS;
+		$this->assertEquals( '1 year ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_rightnow() {
+		$now = time();
+		$then = $now;
+		$this->assertEquals( 'right now', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 */
+	public function test_bp_core_time_since_future() {
+		$now = time();
+		$then = $now + 100;
+		$this->assertEquals( 'sometime ago', bp_core_time_since( $then, $now ) );
+	}
+
+	/**
+	 * @group bp_format_time
+	 */
+	public function test_bp_format_time_gmt_offset_no_timezone_string() {
+		$time          = 1395169200;
+		$gmt_offset    = '-6';
+		$just_date     = false;
+		$localize_time = true;
+
+		update_option( 'date_format', 'F j, Y' );
+		update_option( 'time_format', 'g:i a' );
+		update_option( 'gmt_offset', $gmt_offset );
+		update_option( 'timezone_string', '' );
+
+		$this->assertEquals( 'March 18, 2014 at 1:00 pm', bp_format_time( $time, $just_date, $localize_time ) );
+	}
+
+	/**
+	 * @group bp_format_time
+	 */
+	public function test_bp_format_time_timezone_string_no_gmt_offset() {
+		$time           = 1395169200;
+		$timzone_string = 'America/Chicago';
+		$just_date      = false;
+		$localize_time  = true;
+
+		update_option( 'date_format', 'F j, Y' );
+		update_option( 'time_format', 'g:i a' );
+		update_option( 'timezone_string', $timzone_string );
+		update_option( 'gmt_offset', '0' );
+
+		$this->assertEquals( 'March 18, 2014 at 2:00 pm', bp_format_time( $time, $just_date, $localize_time ) );
+	}
+
+	/**
+	 * @group bp_format_time
+	 */
+	public function test_bp_format_time_gmt_offset_no_localize() {
+		$time          = 1395169200;
+		$gmt_offset    = '-6';
+		$just_date     = false;
+		$localize_time = false;
+
+		update_option( 'date_format', 'F j, Y' );
+		update_option( 'time_format', 'g:i a' );
+		update_option( 'gmt_offset', $gmt_offset );
+		update_option( 'timezone_string', '' );
+
+		$this->assertEquals( 'March 18, 2014 at 7:00 pm', bp_format_time( $time, $just_date, $localize_time ) );
+	}
+
+	/**
+	 * @group bp_format_time
+	 */
+	public function test_bp_format_time_timezone_string_no_localize() {
+		$time           = 1395169200;
+		$timzone_string = 'America/Chicago';
+		$just_date      = false;
+		$localize_time  = false;
+
+		update_option( 'date_format', 'F j, Y' );
+		update_option( 'time_format', 'g:i a' );
+		update_option( 'timezone_string', $timzone_string );
+		update_option( 'gmt_offset', '0' );
+
+		$this->assertEquals( 'March 18, 2014 at 7:00 pm', bp_format_time( $time, $just_date, $localize_time ) );
+	}
+
+	/**
+	 * @group bp_sort_by_key
+	 */
+	public function test_bp_sort_by_key_arrays_num() {
+		$items = array(
+			array(
+				'foo' => 'bar',
+				'value' => 5,
+			),
+			array(
+				'foo' => 'bar',
+				'value' => 10,
+			),
+			array(
+				'foo' => 'bar',
+				'value' => 1,
+			),
+		);
+
+		$expected = array(
+			array(
+				'foo' => 'bar',
+				'value' => 1,
+			),
+			array(
+				'foo' => 'bar',
+				'value' => 5,
+			),
+			array(
+				'foo' => 'bar',
+				'value' => 10,
+			),
+		);
+
+		$this->assertEquals( $expected, bp_sort_by_key( $items, 'value', 'num' ) );
+	}
+
+	/**
+	 * @group bp_sort_by_key
+	 */
+	public function test_bp_sort_by_key_objects_num() {
+		$items = array(
+			new stdClass,
+			new stdClass,
+			new stdClass,
+		);
+		$items[0]->foo = 'bar';
+		$items[0]->value = 5;
+		$items[1]->foo = 'bar';
+		$items[1]->value = 10;
+		$items[2]->foo = 'bar';
+		$items[2]->value = 1;
+
+		$expected = array(
+			new stdClass,
+			new stdClass,
+			new stdClass,
+		);
+		$expected[0]->foo = 'bar';
+		$expected[0]->value = 1;
+		$expected[1]->foo = 'bar';
+		$expected[1]->value = 5;
+		$expected[2]->foo = 'bar';
+		$expected[2]->value = 10;
+
+		$this->assertEquals( $expected, bp_sort_by_key( $items, 'value', 'num' ) );
+	}
+
+	/**
+	 * @group bp_sort_by_key
+	 */
+	public function test_bp_sort_by_key_num_should_respect_0() {
+		$items = array(
+			array(
+				'foo' => 'bar',
+				'value' => 2,
+			),
+			array(
+				'foo' => 'bar',
+				'value' => 0,
+			),
+			array(
+				'foo' => 'bar',
+				'value' => 4,
+			),
+		);
+
+		$expected = array(
+			array(
+				'foo' => 'bar',
+				'value' => 0,
+			),
+			array(
+				'foo' => 'bar',
+				'value' => 2,
+			),
+			array(
+				'foo' => 'bar',
+				'value' => 4,
+			),
+		);
+
+		$this->assertEquals( $expected, bp_sort_by_key( $items, 'value', 'num' ) );
+	}
+
+	/**
+	 * @group bp_alpha_sort_by_key
+	 */
+	public function test_bp_alpha_sort_by_key_arrays() {
+		$items = array(
+			array(
+				'foo' => 'bar',
+				'name' => 'alpha',
+			),
+			array(
+				'foo' => 'bar',
+				'name' => 'charlie',
+			),
+			array(
+				'foo' => 'bar',
+				'name' => 'beta',
+			),
+		);
+
+		$expected = array(
+			array(
+				'foo' => 'bar',
+				'name' => 'alpha',
+			),
+			array(
+				'foo' => 'bar',
+				'name' => 'beta',
+			),
+			array(
+				'foo' => 'bar',
+				'name' => 'charlie',
+			),
+		);
+
+		$this->assertEquals( $expected, bp_alpha_sort_by_key( $items, 'name' ) );
+	}
+
+	/**
+	 * @group bp_alpha_sort_by_key
+	 */
+	public function test_bp_alpha_sort_by_key_objects() {
+		$items = array(
+			new stdClass,
+			new stdClass,
+			new stdClass,
+		);
+		$items[0]->foo = 'bar';
+		$items[0]->name = 'alpha';
+		$items[1]->foo = 'bar';
+		$items[1]->name = 'charlie';
+		$items[2]->foo = 'bar';
+		$items[2]->name = 'beta';
+
+		$expected = array(
+			new stdClass,
+			new stdClass,
+			new stdClass,
+		);
+		$expected[0]->foo = 'bar';
+		$expected[0]->name = 'alpha';
+		$expected[1]->foo = 'bar';
+		$expected[1]->name = 'beta';
+		$expected[2]->foo = 'bar';
+		$expected[2]->name = 'charlie';
+
+		$this->assertEquals( $expected, bp_alpha_sort_by_key( $items, 'name' ) );
+	}
+
+	/**
+	 * @group pagination
+	 * @group bp_sanitize_pagination_arg
+	 */
+	public function test_bp_sanitize_pagination_arg_zero() {
+		$request          = $_REQUEST;
+		$arg              = 'bp_pagination_test';
+		$page             = 1;
+		$_REQUEST[ $arg ] = '0';
+		$value            = bp_sanitize_pagination_arg( $arg, $page );
+
+		$this->assertEquals( $value, $page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group bp_sanitize_pagination_arg
+	 */
+	public function test_bp_sanitize_pagination_arg_negative() {
+		$request          = $_REQUEST;
+		$arg              = 'bp_pagination_test';
+		$page             = 25;
+		$_REQUEST[ $arg ] = '-25';
+		$value            = bp_sanitize_pagination_arg( $arg, $page );
+
+		$this->assertEquals( $value, $page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group bp_core_get_directory_pages
+	 */
+	public function test_bp_core_get_directory_pages_after_page_edit() {
+		// Set the cache
+		$pages = bp_core_get_directory_pages();
+
+		// Update one of the posts
+		switch_to_blog( bp_get_root_blog_id() );
+
+		// grab the first one
+		foreach ( $pages as $page ) {
+			$p = $page;
+			break;
+		}
+
+		$post = get_post( $p->id );
+		$post->post_title .= ' Foo';
+		wp_update_post( $post );
+
+		restore_current_blog();
+
+		$this->assertFalse( wp_cache_get( 'directory_pages', 'bp' ) );
+	}
+
+	/**
+	 * @group bp_core_get_root_options
+	 */
+	public function test_bp_core_get_root_options_cache_invalidate() {
+		$keys = array_keys( bp_get_default_options() );
+		$keys[] = 'registration';
+		$keys[] = 'avatar_default';
+
+		foreach ( $keys as $key ) {
+			// prime cache
+			$root_options = bp_core_get_root_options();
+
+			bp_update_option( $key, 'foo' );
+
+			$this->assertFalse( wp_cache_get( 'root_blog_options', 'bp' ), 'Cache not invalidated after updating "' . $key . '"' );
+		}
+
+		if ( is_multisite() ) {
+			$ms_keys = array(
+				'tags_blog_id',
+				'sitewide_tags_blog',
+				'registration',
+				'fileupload_mask',
+			);
+
+			foreach ( $ms_keys as $ms_key ) {
+				$root_options = bp_core_get_root_options();
+
+				update_site_option( $ms_key, 'foooooooo' );
+
+				$this->assertFalse( wp_cache_get( 'root_blog_options', 'bp' ), 'Cache not invalidated after updating "' . $ms_key . '"' );
+			}
+		}
+	}
+
+	/**
+	 * @group bp_core_get_root_option
+	 */
+	public function test_bp_core_get_root_option_with_unpopulated_cache() {
+		// Back up and unset global cache.
+		$old_options = buddypress()->site_options;
+		unset( buddypress()->site_options );
+
+		$this->assertSame( $old_options['avatar_default'], bp_core_get_root_option( 'avatar_default' ) );
+
+		// Clean up.
+		buddypress()->site_options = $old_options;
+	}
+
+	/**
+	 * @group bp_core_get_root_option
+	 */
+	public function test_bp_core_get_root_option_with_populated_cache() {
+		// Back up and unset global cache.
+		$old_options = buddypress()->site_options;
+		buddypress()->site_options = bp_core_get_root_options();
+		$expected = buddypress()->site_options['avatar_default'];
+
+		$this->assertSame( $expected, bp_core_get_root_option( 'avatar_default' ) );
+	}
+
+	/**
+	 * @group bp_core_add_root_component
+	 */
+	public function test_add_root_component_not_in_bp_pages() {
+		buddypress()->foo = new stdClass;
+		buddypress()->foo->id = 'foo';
+		buddypress()->foo->slug = 'foo';
+
+		bp_core_add_root_component( 'foo' );
+
+		$this->assertTrue( in_array( 'foo', buddypress()->add_root ) );
+		$this->assertTrue( buddypress()->foo->has_directory );
+		$this->assertNotEmpty( buddypress()->loaded_components['foo'] );
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 * @group bp_core_current_time
+	 */
+	public function test_bp_core_time_since_timezone_minute_ago() {
+		// backup timezone
+		$tz_backup = date_default_timezone_get();
+
+		// set timezone to something other than UTC
+		date_default_timezone_set( 'Europe/Paris' );
+
+		$this->assertSame( '1 minute ago', bp_core_time_since( time() - 60 ) );
+
+		// revert timezone back to normal
+		if ( $tz_backup ) {
+			date_default_timezone_set( $tz_backup );
+		}
+	}
+
+	/**
+	 * @group bp_core_time_since
+	 * @group bp_core_current_time
+	 */
+	public function test_bp_core_time_since_timezone() {
+		// backup timezone
+		$tz_backup = date_default_timezone_get();
+
+		// set timezone to something other than UTC
+		date_default_timezone_set( 'Europe/Paris' );
+
+		$this->assertSame( '1 hour ago', bp_core_time_since( time() - 60*60 ) );
+
+		// revert timezone back to normal
+		if ( $tz_backup ) {
+			date_default_timezone_set( $tz_backup );
+		}
+	}
+
+	/**
+	 * @group bp_attachments
+	 * @group bp_upload_dir
+	 */
+	public function test_bp_upload_dir() {
+		$expected_upload_dir = wp_upload_dir();
+
+		if ( is_multisite() ) {
+			$b = $this->factory->blog->create();
+			switch_to_blog( $b );
+		}
+
+		$tested_upload_dir = bp_upload_dir();
+
+		if ( is_multisite() ) {
+			restore_current_blog();
+		}
+
+		$this->assertSame( $expected_upload_dir, $tested_upload_dir );
+	}
+
+	/**
+	 * @group bp_is_active
+	 */
+	public function test_bp_is_active_component() {
+		$bp = buddypress();
+		$reset_active_components = $bp->active_components;
+
+		$this->assertTrue( bp_is_active( 'members' ) );
+
+		$this->assertFalse( bp_is_active( 'foo' ) );
+
+		// Create and activate the foo component
+		$bp->foo = new BP_Component;
+		$bp->foo->id   = 'foo';
+		$bp->foo->slug = 'foo';
+		$bp->foo->name = 'Foo';
+		$bp->active_components[ $bp->foo->id ] = 1;
+
+		$this->assertTrue( bp_is_active( 'foo' ) );
+
+		add_filter( 'bp_is_active', '__return_false' );
+
+		$this->assertFalse( bp_is_active( 'foo' ) );
+
+		remove_filter( 'bp_is_active', '__return_false' );
+
+		// Reset buddypress() vars
+		$bp->active_components = $reset_active_components;
+	}
+
+	/**
+	 * @group bp_is_active
+	 */
+	public function test_bp_is_active_feature() {
+		$bp = buddypress();
+		$reset_active_components = $bp->active_components;
+
+		// Create and activate the foo component
+		$bp->foo = new BP_Component;
+		$bp->foo->id   = 'foo';
+		$bp->foo->slug = 'foo';
+		$bp->foo->name = 'Foo';
+		$bp->active_components[ $bp->foo->id ] = 1;
+
+		// foo did not register 'bar' as a feature
+		$this->assertFalse( bp_is_active( 'foo', 'bar' ) );
+
+		// fake registering the 'bar' feature
+		$bp->foo->features = array( 'bar' );
+		$this->assertTrue( bp_is_active( 'foo', 'bar' ) );
+
+		// test the feature filter
+		add_filter( 'bp_is_foo_bar_active', '__return_false' );
+		$this->assertFalse( bp_is_active( 'foo', 'bar' ) );
+		remove_filter( 'bp_is_foo_bar_active', '__return_false' );
+
+		// test the main component filter
+		add_filter( 'bp_is_active', '__return_false' );
+		$this->assertFalse( bp_is_active( 'foo', 'bar' ) );
+		remove_filter( 'bp_is_active', '__return_false' );
+
+		// Reset buddypress() vars
+		$bp->active_components = $reset_active_components;
+	}
+
+	/**
+	 * @group bp_attachments
+	 */
+	public function test_bp_attachments_get_allowed_types() {
+		$supported = array( 'jpeg', 'gif', 'png' );
+
+		$avatar = bp_attachments_get_allowed_types( 'avatar' );
+		$this->assertSame( $supported, $avatar );
+
+		$cover_image = bp_attachments_get_allowed_types( 'cover_image' );
+		$this->assertSame( $supported, $cover_image );
+
+		$images = bp_attachments_get_allowed_types( 'image/' );
+
+		foreach ( $images as $image ) {
+			if ( 'image' !== wp_ext2type( $image ) ) {
+				$not_image = $image;
+			}
+		}
+
+		$this->assertTrue( empty( $not_image ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/functions/bpCoreGetDirectoryPageIds.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/functions/bpCoreGetDirectoryPageIds.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/functions/bpCoreGetDirectoryPageIds.php	(revision 10410)
@@ -0,0 +1,288 @@
+<?php
+
+/**
+ * @group core
+ * @covers ::bp_core_get_directory_page_ids
+ */
+class BP_Tests_Core_Functions_BpCoreGetDirectoryPageIds extends BP_UnitTestCase {
+	public function test_bp_core_get_directory_page_ids_on_directory_page_to_trash() {
+		$old_page_ids = bp_core_get_directory_page_ids();
+
+		// Grab the and remove the first page.
+		foreach ( $old_page_ids as $component => $page_id ) {
+			$p = $page_id;
+			unset( $old_page_ids[ $component ] );
+			break;
+		}
+
+		// Move page to trash.
+		wp_delete_post( $p, false );
+
+		$new_page_ids = bp_core_get_directory_page_ids();
+
+		$this->assertEquals( $old_page_ids, $new_page_ids );
+	}
+
+	public function test_bp_core_get_directory_page_ids_on_directory_page_delete() {
+		$old_page_ids = bp_core_get_directory_page_ids();
+
+		// Grab the and remove the first page.
+		foreach ( $old_page_ids as $component => $page_id ) {
+			$p = $page_id;
+			unset( $old_page_ids[ $component ] );
+			break;
+		}
+
+		// Force delete page.
+		wp_delete_post( $p, true );
+
+		$new_page_ids = bp_core_get_directory_page_ids();
+
+		$this->assertEquals( $old_page_ids, $new_page_ids );
+	}
+
+	public function test_bp_core_get_directory_page_ids_on_non_directory_page_delete() {
+		$old_page_ids = bp_core_get_directory_page_ids();
+
+		$p = $this->factory->post->create( array(
+			'post_status' => 'publish',
+			'post_type' => 'page',
+		) );
+
+		// Force delete page.
+		wp_delete_post( $p, true );
+
+		$new_page_ids = bp_core_get_directory_page_ids();
+
+		$this->assertEquals( $old_page_ids, $new_page_ids );
+	}
+
+	public function test_bp_core_get_directory_page_ids_non_active_component() {
+		$old_page_ids = bp_core_get_directory_page_ids();
+		$bp = buddypress();
+
+		// Grab the and remove the first page.
+		foreach ( $old_page_ids as $component => $page_id ) {
+			$p = $page_id;
+			$c = $component;
+			unset( $old_page_ids[ $component ] );
+			break;
+		}
+
+		// Deactivate component.
+		unset( $bp->active_components[ $c ] );
+
+		$new_page_ids = bp_core_get_directory_page_ids();
+
+		// Restore components.
+		$bp->active_components[ $c ] = 1;
+
+		$this->assertEquals( $old_page_ids, $new_page_ids );
+	}
+
+	/**
+	 * @ticket BP6280
+	 */
+	public function test_inactive_components_should_not_be_removed_if_status_is_all() {
+		$old_page_ids = bp_core_get_directory_page_ids( 'all' );
+
+		$page_ids = $old_page_ids;
+		$page_ids['foo'] = 12345;
+
+		bp_core_update_directory_page_ids( $page_ids );
+		$found = bp_core_get_directory_page_ids( 'all' );
+
+		$this->assertEquals( 12345, $found['foo'] );
+	}
+
+	/**
+	 * @ticket BP6280
+	 */
+	public function test_inactive_components_should_be_removed_if_status_is_active() {
+		$old_page_ids = bp_core_get_directory_page_ids( 'all' );
+
+		$page_ids = $old_page_ids;
+		$page_ids['foo'] = 12345;
+
+		bp_core_update_directory_page_ids( $page_ids );
+		$found = bp_core_get_directory_page_ids( 'active' );
+
+		$this->assertFalse( isset( $found['foo'] ) );
+	}
+
+	/**
+	 * @ticket BP6280
+	 */
+	public function test_inactive_components_should_be_removed_if_status_is_unspecified() {
+		$old_page_ids = bp_core_get_directory_page_ids( 'all' );
+
+		$page_ids = $old_page_ids;
+		$page_ids['foo'] = 12345;
+
+		bp_core_update_directory_page_ids( $page_ids );
+		$found = bp_core_get_directory_page_ids( 'active' );
+
+		$this->assertFalse( isset( $found['foo'] ) );
+	}
+
+	public function test_bp_core_get_directory_page_ids_should_contain_register_and_activet_pages_when_registration_is_open() {
+		add_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		$ac = buddypress()->active_components;
+		bp_core_add_page_mappings( array_keys( $ac ) );
+
+		$page_ids = bp_core_get_directory_page_ids();
+		$page_names = array_keys( $page_ids );
+
+		$this->assertContains( 'register', $page_names );
+		$this->assertContains( 'activate', $page_names );
+
+		remove_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+	}
+
+	public function test_bp_core_get_directory_page_ids_should_not_contain_register_and_activet_pages_when_registration_is_closed() {
+
+		// Make sure the pages exist, to verify they're filtered out.
+		add_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+		$ac = buddypress()->active_components;
+		bp_core_add_page_mappings( array_keys( $ac ) );
+		remove_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		// Get page ids
+		$page_ids = bp_core_get_directory_page_ids();
+
+		// Need to delete these pages as previously created.
+		wp_delete_post( $page_ids['register'], true );
+		wp_delete_post( $page_ids['activate'], true );
+
+		add_filter( 'bp_get_signup_allowed', '__return_false', 999 );
+		bp_core_add_page_mappings( array_keys( $ac ) );
+		$page_ids = bp_core_get_directory_page_ids();
+		remove_filter( 'bp_get_signup_allowed', '__return_false', 999 );
+
+		$page_names = array_keys( $page_ids );
+
+		$this->assertNotContains( 'register', $page_names );
+		$this->assertNotContains( 'activate', $page_names );
+	}
+
+	public function test_bp_core_get_directory_pages_register_activate_page_created_signups_allowed() {
+		add_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		$ac = buddypress()->active_components;
+		bp_core_add_page_mappings( array_keys( $ac ) );
+		$directory_pages = bp_core_get_directory_pages();
+
+		remove_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		$this->assertTrue( isset( $directory_pages->register ) );
+		$this->assertTrue( isset( $directory_pages->activate ) );
+
+		$r = get_post( $directory_pages->register->id );
+		$this->assertTrue( 'publish' == $r->post_status );
+
+		$a = get_post( $directory_pages->activate->id );
+		$this->assertTrue( 'publish' == $a->post_status );
+	}
+
+	public function test_bp_core_get_directory_pages_register_activate_page_notcreated_signups_allowed() {
+		add_filter( 'bp_get_signup_allowed', '__return_false', 999 );
+
+		$ac = buddypress()->active_components;
+		bp_core_add_page_mappings( array_keys( $ac ) );
+
+		remove_filter( 'bp_get_signup_allowed', '__return_false', 999 );
+
+		add_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		$directory_pages = bp_core_get_directory_pages();
+
+		remove_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		$this->assertFalse( isset( $directory_pages->register ) );
+		$this->assertFalse( isset( $directory_pages->activate ) );
+	}
+
+	public function test_bp_core_get_directory_pages_register_activate_page_created_signups_notallowed() {
+		add_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		$ac = buddypress()->active_components;
+		bp_core_add_page_mappings( array_keys( $ac ) );
+
+		remove_filter( 'bp_get_signup_allowed', '__return_true', 999 );
+
+		add_filter( 'bp_get_signup_allowed', '__return_false', 999 );
+
+		$directory_pages = bp_core_get_directory_pages();
+
+		remove_filter( 'bp_get_signup_allowed', '__return_false', 999 );
+
+		$this->assertTrue( isset( $directory_pages->register ) );
+		$this->assertTrue( isset( $directory_pages->activate ) );
+
+		$r = get_post( $directory_pages->register->id );
+		$this->assertTrue( 'publish' == $r->post_status );
+
+		$a = get_post( $directory_pages->activate->id );
+		$this->assertTrue( 'publish' == $a->post_status );
+	}
+
+	public function test_bp_core_get_directory_pages_register_activate_page_notcreated_signups_notallowed() {
+
+		add_filter( 'bp_get_signup_allowed', '__return_false', 999 );
+
+		$ac = buddypress()->active_components;
+		bp_core_add_page_mappings( array_keys( $ac ) );
+		$directory_pages = bp_core_get_directory_pages();
+
+		remove_filter( 'bp_get_signup_allowed', '__return_false', 999 );
+
+		$this->assertFalse( isset( $directory_pages->register ) );
+		$this->assertFalse( isset( $directory_pages->activate ) );
+	}
+
+	public function test_bp_core_get_directory_pages_pages_settings_update() {
+		// Set the cache
+		$pages = bp_core_get_directory_pages();
+
+		// Mess with it but put it back
+		$v = bp_get_option( 'bp-pages' );
+		bp_update_option( 'bp-pages', 'foo' );
+
+		$this->assertFalse( wp_cache_get( 'directory_pages', 'bp' ) );
+
+		bp_update_option( 'bp-pages', $v );
+	}
+
+	public function test_bp_core_get_directory_pages_multisite_delete_post_with_same_bp_page_id() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$dir_pages = bp_core_get_directory_pages();
+
+		// create a blog
+		$u = $this->factory->user->create();
+		$b1 = $this->factory->blog->create( array( 'user_id' => $u ) );
+
+		// switch to blog and create some dummy posts until we reach a post ID that
+		// matches our BP activity page ID
+		switch_to_blog( $b1 );
+		$p = $this->factory->post->create();
+		while( $p <= $dir_pages->activity->id ) {
+			$p = $this->factory->post->create();
+		}
+
+		// delete the post that matches the BP activity page ID on this sub-site
+		wp_delete_post( $dir_pages->activity->id, true );
+
+		// restore blog
+		restore_current_blog();
+
+		// refetch BP directory pages
+		$dir_pages = bp_core_get_directory_pages();
+
+		// Now verify that our BP activity page was not wiped out
+		$this->assertNotEmpty( $dir_pages->activity );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/functions/bpGetRefererPath.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/functions/bpGetRefererPath.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/functions/bpGetRefererPath.php	(revision 10410)
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @group core
+ * @group functions
+ * @group bp_get_referer_path
+ */
+class BP_Tests_Core_Functions_BPGetRefererPath extends BP_UnitTestCase {
+	private $_wp_http_referer = '';
+	private $http_referer = '';
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->_wp_http_referer = '';
+		$this->http_referer = '';
+
+		if ( isset( $_REQUEST['_wp_http_referer'] ) ) {
+			$this->_wp_http_referer = $_REQUEST['_wp_http_referer'];
+		}
+
+		if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
+			$this->http_referer = $_SERVER['HTTP_REFERER'];
+		}
+	}
+
+	public function tearDown() {
+		if ( isset( $_REQUEST['_wp_http_referer'] ) ) {
+			unset( $_REQUEST['_wp_http_referer'] );
+		}
+
+		if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
+			unset( $_SERVER['HTTP_REFERER'] );
+		}
+
+		if ( $this->_wp_http_referer ) {
+			$_REQUEST['_wp_http_referer'] = $this->_wp_http_referer;
+		}
+
+		if ( $this->http_referer ) {
+			$_SERVER['HTTP_REFERER'] = $this->http_referer;
+		}
+
+		parent::tearDown();
+	}
+
+	public function test_from__wp_http_referer_fully_qualified_uri() {
+		$home = get_option( 'home' );
+		$_REQUEST['_wp_http_referer'] = trailingslashit( $home ) . 'foo/';
+		$found = bp_get_referer_path();
+		$this->assertSame( '/foo/', bp_get_referer_path() );
+	}
+
+	public function test_from__wp_http_referer_absolute_path() {
+		$_REQUEST['_wp_http_referer'] = '/foo/';
+		$found = bp_get_referer_path();
+		$this->assertSame( '/foo/', bp_get_referer_path() );
+	}
+
+	public function test_from_server_request_uri_fully_qualified_uri() {
+		$home = get_option( 'home' );
+		$_SERVER['HTTP_REFERER'] = trailingslashit( $home ) . 'foo/';
+		$found = bp_get_referer_path();
+		$this->assertSame( '/foo/', bp_get_referer_path() );
+	}
+
+	public function test_from_server_request_uri_absolute_path() {
+		$_SERVER['HTTP_REFERER'] = '/foo/';
+		$found = bp_get_referer_path();
+		$this->assertSame( '/foo/', bp_get_referer_path() );
+	}
+
+	public function test__wp_http_referer_should_take_precedence_over_server_superglobal() {
+		$_SERVER['HTTP_REFERER'] = '/foo/';
+		$_REQUEST['_wp_http_referer'] = '/bar/';
+		$found = bp_get_referer_path();
+		$this->assertSame( '/bar/', bp_get_referer_path() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/functions/bpVerifyNonceRequest.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/functions/bpVerifyNonceRequest.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/functions/bpVerifyNonceRequest.php	(revision 10410)
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @group core
+ * @group functions
+ * @group bp_verify_nonce_request
+ */
+class BP_Tests_Core_Functions_BPVerifyNonceRequest extends BP_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() {
+		if ( '' !== $this->http_host ) {
+			$_SERVER['HTTP_HOST'] = $this->http_host;
+		}
+
+		if ( '' !== $this->server_port ) {
+			$_SERVER['SERVER_PORT'] = $this->server_port;
+		}
+
+		if ( '' !== $this->request_uri ) {
+			$_SERVER['REQUEST_URI'] = $this->request_uri;
+		}
+
+		parent::tearDown();
+	}
+
+	public function test_bp_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 bp_verify_nonce_request()
+		$action = 'verify-this';
+		$_REQUEST[$action] = wp_create_nonce( $action );
+		$test = bp_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 $orig_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.4.3/tests/phpunit/testcases/core/nav.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/nav.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/nav.php	(revision 10410)
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * @group core
+ * @group nav
+ */
+class BP_Tests_Core_Nav extends BP_UnitTestCase {
+
+	/**
+	 * @group bp_core_sort_nav_items
+	 */
+	public function test_bp_core_sort_nav_items() {
+		$bp_nav = buddypress()->bp_nav;
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->go_to( bp_core_get_user_domain( $u ) );
+
+		bp_core_new_nav_item( array(
+			'name'                    => 'Foo',
+			'slug'                    => 'foo',
+			'position'                => 25,
+			'screen_function'         => 'foo_screen_function',
+			'default_subnav_slug'     => 'foo-sub'
+		) );
+
+		$expected = array(
+			'name'                    => 'Foo',
+			'slug'                    => 'foo',
+			'link'                    => trailingslashit( bp_core_get_user_domain( $u ) . 'foo' ),
+			'css_id'                  => 'foo',
+			'show_for_displayed_user' => true,
+			'position'                => 25,
+			'screen_function'         => 'foo_screen_function',
+			'default_subnav_slug'     => 'foo-sub'
+		);
+
+		bp_core_sort_nav_items();
+
+		$this->assertSame( buddypress()->bp_nav[25], $expected );
+
+		// Clean up
+		buddypress()->bp_nav = $bp_nav;
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group bp_core_sort_subnav_items
+	 */
+	public function test_bp_core_sort_subnav_items() {
+		$bp_options_nav = buddypress()->bp_options_nav;
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$user_domain = bp_core_get_user_domain( $u );
+
+		$this->go_to( $user_domain );
+
+		bp_core_new_subnav_item( array(
+			'name'            => 'Foo',
+			'slug'            => 'foo',
+			'parent_url'      => trailingslashit( $user_domain . 'foo' ),
+			'parent_slug'     => 'foo',
+			'screen_function' => 'foo_screen_function',
+			'position'        => 10
+		) );
+
+		bp_core_sort_subnav_items();
+
+		$expected = array(
+			'name'              => 'Foo',
+			'link'              => trailingslashit( $user_domain . 'foo/foo' ),
+			'slug'              => 'foo',
+			'css_id'            => 'foo',
+			'position'          => 10,
+			'user_has_access'   => true,
+			'no_access_url'     => '',
+			'screen_function'   => 'foo_screen_function',
+			'show_in_admin_bar' => false,
+		);
+
+		$this->assertSame( buddypress()->bp_options_nav['foo'][10], $expected );
+
+		// Clean up
+		buddypress()->bp_options_nav = $bp_options_nav;
+		$this->set_current_user( $old_current_user );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/nav/bpCoreMaybeHookNewSubnavScreenFunction.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/nav/bpCoreMaybeHookNewSubnavScreenFunction.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/nav/bpCoreMaybeHookNewSubnavScreenFunction.php	(revision 10410)
@@ -0,0 +1,188 @@
+<?php
+/**
+ * @group core
+ * @group nav
+ */
+class BP_Tests_Core_Nav_BpCoreMaybeHookNewSubnavScreenFunction extends BP_UnitTestCase {
+
+	public function test_user_has_access_true_no_callable_function() {
+		$subnav_item = array(
+			'user_has_access' => true,
+			'screen_function' => '123foo456',
+		);
+
+		$expected = array(
+			'status' => 'failure',
+		);
+
+		$this->assertSame( $expected, bp_core_maybe_hook_new_subnav_screen_function( $subnav_item ) );
+	}
+
+	public function test_user_has_access_true_callable_function() {
+		$subnav_item = array(
+			'user_has_access' => true,
+			'screen_function' => 'wptexturize', // any old callable function
+		);
+
+		$expected = array(
+			'status' => 'success',
+		);
+
+		$this->assertSame( $expected, bp_core_maybe_hook_new_subnav_screen_function( $subnav_item ) );
+
+		// clean up
+		remove_action( 'bp_screens', 'wptexturize', 3 );
+	}
+
+	public function test_user_has_access_false_user_logged_out() {
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( 0 );
+
+		$subnav_item = array(
+			'user_has_access' => false,
+		);
+
+		$expected = array(
+			'status' => 'failure',
+			'redirect_args' => array(),
+		);
+
+		$this->assertSame( $expected, bp_core_maybe_hook_new_subnav_screen_function( $subnav_item ) );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_user_has_access_false_user_logged_in_my_profile() {
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->go_to( bp_core_get_user_domain( $u ) );
+
+		$subnav_item = array(
+			'user_has_access' => false,
+		);
+
+		// Just test relevant info
+		$found = bp_core_maybe_hook_new_subnav_screen_function( $subnav_item );
+		$this->assertSame( 'failure', $found['status'] );
+		$this->assertSame( bp_core_get_user_domain( $u ), $found['redirect_args']['root'] );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_user_has_access_false_user_logged_in_others_profile_default_component_accessible() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u1 );
+
+		$this->go_to( bp_core_get_user_domain( $u2 ) );
+
+		$old_bp_nav = buddypress()->bp_nav;
+		$old_default_component = buddypress()->default_component;
+		buddypress()->default_component = 'foo';
+		buddypress()->bp_nav = array(
+			'foo' => array(
+				'show_for_displayed_user' => true,
+			),
+		);
+
+		$subnav_item = array(
+			'user_has_access' => false,
+		);
+
+		// Just test relevant info
+		$found = bp_core_maybe_hook_new_subnav_screen_function( $subnav_item );
+		$this->assertSame( 'failure', $found['status'] );
+		$this->assertSame( bp_core_get_user_domain( $u2 ), $found['redirect_args']['root'] );
+
+		// Clean up
+		$this->set_current_user( $old_current_user );
+		buddypress()->default_component = $old_default_component;
+		buddypress()->bp_nav = $old_bp_nav;
+	}
+
+	public function test_user_has_access_false_user_logged_in_others_profile_default_component_not_accessible() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u1 );
+
+		$this->go_to( bp_core_get_user_domain( $u2 ) );
+
+		$old_bp_nav = buddypress()->bp_nav;
+		$old_default_component = buddypress()->default_component;
+		buddypress()->default_component = 'foo';
+		buddypress()->bp_nav = array(
+			'foo' => array(
+				'show_for_displayed_user' => false,
+			),
+		);
+
+		$subnav_item = array(
+			'user_has_access' => false,
+		);
+
+		// Just test relevant info
+		$found = bp_core_maybe_hook_new_subnav_screen_function( $subnav_item );
+		$this->assertSame( 'failure', $found['status'] );
+		$this->assertSame( bp_core_get_user_domain( $u2 ) . bp_get_activity_slug() . '/', $found['redirect_args']['root'] );
+
+		// Clean up
+		$this->set_current_user( $old_current_user );
+		buddypress()->default_component = $old_default_component;
+		buddypress()->bp_nav = $old_bp_nav;
+	}
+
+	public function test_user_has_access_false_user_logged_in_group() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$group = groups_get_group( array(
+			'group_id' => $g,
+		) );
+
+		$this->go_to( bp_get_group_permalink( $group ) );
+
+		$subnav_item = array(
+			'user_has_access' => false,
+			'no_access_url' => bp_get_group_permalink( $group ),
+		);
+
+		// Just test relevant info
+		$found = bp_core_maybe_hook_new_subnav_screen_function( $subnav_item );
+		$this->assertSame( 'failure', $found['status'] );
+		$this->assertSame( bp_get_group_permalink( $group ), $found['redirect_args']['root'] );
+
+		// Clean up
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_user_has_access_false_user_logged_in_group_no_redirect_url_provided() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$group = groups_get_group( array(
+			'group_id' => $g,
+		) );
+
+		$this->go_to( bp_get_group_permalink( $group ) );
+
+		$subnav_item = array(
+			'user_has_access' => false,
+		);
+
+		// Just test relevant info
+		$found = bp_core_maybe_hook_new_subnav_screen_function( $subnav_item );
+		$this->assertSame( 'failure', $found['status'] );
+		$this->assertSame( bp_get_root_domain(), $found['redirect_args']['root'] );
+
+		// Clean up
+		$this->set_current_user( $old_current_user );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/nav/bpCoreNewNavItem.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/nav/bpCoreNewNavItem.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/nav/bpCoreNewNavItem.php	(revision 10410)
@@ -0,0 +1,193 @@
+<?php
+/**
+ * @group core
+ * @group nav
+ */
+class BP_Tests_Core_Nav_BpCoreNewNavItem extends BP_UnitTestCase {
+
+	public function test_user_nav() {
+		$bp_nav = buddypress()->bp_nav;
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->go_to( bp_core_get_user_domain( $u ) );
+
+		bp_core_new_nav_item( array(
+			'name'                    => 'Foo',
+			'slug'                    => 'foo',
+			'position'                => 25,
+			'screen_function'         => 'foo_screen_function',
+			'default_subnav_slug'     => 'foo-sub'
+		) );
+
+		$expected = array(
+			'name'                    => 'Foo',
+			'slug'                    => 'foo',
+			'link'                    => trailingslashit( bp_core_get_user_domain( $u ) . 'foo' ),
+			'css_id'                  => 'foo',
+			'show_for_displayed_user' => true,
+			'position'                => 25,
+			'screen_function'         => 'foo_screen_function',
+			'default_subnav_slug'     => 'foo-sub'
+		);
+
+		$this->assertSame( buddypress()->bp_nav['foo'], $expected );
+
+		// Clean up
+		buddypress()->bp_nav = $bp_nav;
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_group_nav() {
+		$bp_nav = buddypress()->bp_nav;
+
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$group = groups_get_group( array(
+			'group_id' => $g,
+		) );
+
+		$this->go_to( bp_get_group_permalink( $group ) );
+
+		$this->assertTrue( buddypress()->bp_nav[ $group->slug ]['position'] === -1 );
+
+		// Clean up
+		buddypress()->bp_nav = $bp_nav;
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_should_return_false_if_name_is_not_provided() {
+		$args = array(
+			'slug' => 'foo',
+		);
+
+		$this->assertFalse( bp_core_new_nav_item( $args ) );
+	}
+
+	public function test_should_return_false_if_slug_is_not_provided() {
+		$args = array(
+			'name' => 'foo',
+		);
+
+		$this->assertFalse( bp_core_new_nav_item( $args ) );
+	}
+
+	public function test_should_return_false_if_site_admin_only_and_current_user_cannot_bp_moderate() {
+		// Should already be set to a 0 user.
+		$this->assertFalse( bp_current_user_can( 'bp_moderate' ) );
+		$args = array(
+			'name' => 'Foo',
+			'slug' => 'foo',
+			'site_admin_only' => true,
+		);
+
+		$this->assertFalse( bp_core_new_nav_item( $args ) );
+	}
+
+	public function test_css_id_should_fall_back_on_slug() {
+		$args = array(
+			'name' => 'Foo',
+			'slug' => 'foo',
+		);
+		bp_core_new_nav_item( $args );
+
+		$this->assertSame( 'foo', buddypress()->bp_nav['foo']['css_id'] );
+	}
+
+	public function test_css_id_should_be_respected() {
+		$args = array(
+			'name' => 'Foo',
+			'slug' => 'foo',
+			'item_css_id' => 'bar',
+		);
+		bp_core_new_nav_item( $args );
+
+		$this->assertSame( 'bar', buddypress()->bp_nav['foo']['css_id'] );
+	}
+
+	public function test_show_for_displayed_user_false_should_force_function_to_return_false_when_bp_user_has_access_is_also_false() {
+		$args = array(
+			'name' => 'Foo',
+			'slug' => 'foo',
+			'show_for_displayed_user' => false,
+		);
+
+		add_filter( 'bp_user_has_access', '__return_false' );
+		$retval = bp_core_new_nav_item( $args );
+		remove_filter( 'bp_user_has_access', '__return_false' );
+
+		$this->assertFalse( $retval );
+	}
+
+	public function test_existence_of_access_protected_user_nav() {
+		$bp_nav = buddypress()->bp_nav;
+
+		$u = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u2 );
+
+		$this->go_to( bp_core_get_user_domain( $u ) );
+
+		$expected = array(
+			'name'                    => 'Settings',
+			'slug'                    => 'settings',
+			'link'                    => trailingslashit( bp_loggedin_user_domain() . 'settings' ),
+			'css_id'                  => 'settings',
+			'show_for_displayed_user' => false,
+			'position'                => 100,
+			'screen_function'         => 'bp_settings_screen_general',
+			'default_subnav_slug'     => 'general'
+		);
+
+		$this->assertSame( buddypress()->bp_nav['settings'], $expected );
+
+		// Clean up
+		buddypress()->bp_nav = $bp_nav;
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_creation_of_access_protected_user_nav() {
+		// The nav item must be added to bp_nav, even if the current user
+		// can't visit that nav item.
+		$bp_nav = buddypress()->bp_nav;
+
+		$u = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u2 );
+
+		$this->go_to( bp_core_get_user_domain( $u ) );
+
+		bp_core_new_nav_item( array(
+			'name'                    => 'Woof',
+			'slug'                    => 'woof',
+			'show_for_displayed_user' => false,
+			'position'                => 35,
+			'screen_function'         => 'woof_screen_function',
+			'default_subnav_slug'     => 'woof-one'
+		) );
+
+		$expected = array(
+			'name'                    => 'Woof',
+			'slug'                    => 'woof',
+			'link'                    => trailingslashit( bp_loggedin_user_domain() . 'woof' ),
+			'css_id'                  => 'woof',
+			'show_for_displayed_user' => false,
+			'position'                => 35,
+			'screen_function'         => 'woof_screen_function',
+			'default_subnav_slug'     => 'woof-one'
+		);
+
+		$this->assertSame( buddypress()->bp_nav['woof'], $expected );
+
+		// Clean up
+		buddypress()->bp_nav = $bp_nav;
+		$this->set_current_user( $old_current_user );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/nav/bpCoreNewSubnavItem.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/nav/bpCoreNewSubnavItem.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/nav/bpCoreNewSubnavItem.php	(revision 10410)
@@ -0,0 +1,257 @@
+<?php
+/**
+ * @group core
+ * @group nav
+ */
+class BP_Tests_Core_Nav_BpCoreNewSubnavItem extends BP_UnitTestCase {
+
+	public function test_user_subnav() {
+		$bp_options_nav = buddypress()->bp_options_nav;
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$user_domain = bp_core_get_user_domain( $u );
+
+		$this->go_to( $user_domain );
+
+		bp_core_new_subnav_item( array(
+			'name'            => 'Foo',
+			'slug'            => 'foo',
+			'parent_url'      => trailingslashit( $user_domain . 'foo' ),
+			'parent_slug'     => 'foo',
+			'screen_function' => 'foo_screen_function',
+			'position'        => 10
+		) );
+
+		$expected = array(
+			'name'              => 'Foo',
+			'link'              => trailingslashit( $user_domain . 'foo/foo' ),
+			'slug'              => 'foo',
+			'css_id'            => 'foo',
+			'position'          => 10,
+			'user_has_access'   => true,
+			'no_access_url'     => '',
+			'screen_function'   => 'foo_screen_function',
+			'show_in_admin_bar' => false,
+		);
+
+		$this->assertSame( buddypress()->bp_options_nav['foo']['foo'], $expected );
+
+		// Clean up
+		buddypress()->bp_options_nav = $bp_options_nav;
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_required_params() {
+		// 'name'
+		$this->assertFalse( bp_core_new_subnav_item( array(
+			'slug' => 'foo',
+			'parent_slug' => 'foo',
+			'parent_url' => 'foo',
+			'screen_function' => 'foo',
+		) ) );
+
+		// 'slug'
+		$this->assertFalse( bp_core_new_subnav_item( array(
+			'name' => 'foo',
+			'parent_slug' => 'foo',
+			'parent_url' => 'foo',
+			'screen_function' => 'foo',
+		) ) );
+
+		// 'parent_slug'
+		$this->assertFalse( bp_core_new_subnav_item( array(
+			'name' => 'foo',
+			'slug' => 'foo',
+			'parent_url' => 'foo',
+			'screen_function' => 'foo',
+		) ) );
+
+		// 'parent_url'
+		$this->assertFalse( bp_core_new_subnav_item( array(
+			'name' => 'foo',
+			'slug' => 'foo',
+			'parent_slug' => 'foo',
+			'screen_function' => 'foo',
+		) ) );
+
+		// 'screen_function'
+		$this->assertFalse( bp_core_new_subnav_item( array(
+			'name' => 'foo',
+			'slug' => 'foo',
+			'parent_slug' => 'foo',
+			'parent_url' => 'foo',
+		) ) );
+	}
+
+	public function test_site_admin_only() {
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( 0 );
+
+		$this->assertFalse( bp_core_new_subnav_item( array(
+			'name' => 'foo',
+			'slug' => 'foo',
+			'parent_slug' => 'foo',
+			'parent_url' => 'foo',
+			'screen_function' => 'foo',
+			'site_admin_only' => true,
+		) ) );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_link_provided() {
+		$bp_options_nav = buddypress()->bp_options_nav;
+
+		bp_core_new_subnav_item( array(
+			'name' => 'bar',
+			'slug' => 'bar',
+			'parent_slug' => 'foo',
+			'parent_url' => 'foo',
+			'screen_function' => 'foo',
+			'link' => 'https://buddypress.org/',
+		) );
+
+		$this->assertSame( 'https://buddypress.org/', buddypress()->bp_options_nav['foo']['bar']['link'] );
+
+		buddypress()->bp_options_nav = $bp_options_nav;
+	}
+
+	public function test_link_built_from_parent_url_and_slug() {
+		$bp_options_nav = buddypress()->bp_options_nav;
+
+		bp_core_new_subnav_item( array(
+			'name' => 'bar',
+			'slug' => 'bar',
+			'parent_slug' => 'foo',
+			'parent_url' => 'http://example.com/foo/',
+			'screen_function' => 'foo',
+		) );
+
+		$this->assertSame( 'http://example.com/foo/bar/', buddypress()->bp_options_nav['foo']['bar']['link'] );
+
+		buddypress()->bp_options_nav = $bp_options_nav;
+	}
+
+	public function test_link_built_from_parent_url_and_slug_where_slug_is_default() {
+		$bp_nav = buddypress()->bp_nav;
+		$bp_options_nav = buddypress()->bp_options_nav;
+
+		// fake the parent
+		buddypress()->bp_nav = array(
+			'foo' => array(
+				'default_subnav_slug' => 'bar',
+			),
+		);
+
+		bp_core_new_subnav_item( array(
+			'name' => 'bar',
+			'slug' => 'bar',
+			'parent_slug' => 'foo',
+			'parent_url' => 'http://example.com/foo/',
+			'screen_function' => 'foo',
+		) );
+
+		$this->assertSame( 'http://example.com/foo/', buddypress()->bp_options_nav['foo']['bar']['link'] );
+
+		// clean up
+		buddypress()->bp_nav = $bp_nav;
+		buddypress()->bp_options_nav = $bp_options_nav;
+	}
+
+	public function test_should_trailingslash_link_when_link_is_autogenerated_using_slug() {
+		bp_core_new_subnav_item( array(
+			'name' => 'bar',
+			'slug' => 'bar',
+			'parent_slug' => 'foo',
+			'parent_url' => bp_get_root_domain() . 'foo/',
+			'screen_function' => 'foo',
+		) );
+
+		$expected = bp_get_root_domain() . 'foo/bar/';
+		$this->assertSame( $expected, buddypress()->bp_options_nav['foo']['bar']['link'] );
+	}
+
+	public function test_should_trailingslash_link_when_link_is_autogenerated_not_using_slug() {
+		bp_core_new_nav_item( array(
+			'name' => 'foo',
+			'slug' => 'foo-parent',
+			'link' => bp_get_root_domain() . 'foo-parent/',
+			'default_subnav_slug' => 'bar',
+			'screen_function' => 'foo',
+		) );
+
+		bp_core_new_subnav_item( array(
+			'name' => 'bar',
+			'slug' => 'bar',
+			'parent_slug' => 'foo-parent',
+			'parent_url' => bp_get_root_domain() . 'foo-parent/',
+			'screen_function' => 'bar',
+		) );
+
+		$expected = bp_get_root_domain() . 'foo-parent/';
+		$this->assertSame( $expected, buddypress()->bp_options_nav['foo-parent']['bar']['link'] );
+	}
+
+	/**
+	 * @ticket BP6353
+	 */
+	public function test_link_should_not_trailingslash_link_explicit_link() {
+		$link = 'http://example.com/foo/bar/blah/?action=edit&id=30';
+
+		bp_core_new_subnav_item( array(
+			'name' => 'bar',
+			'slug' => 'bar',
+			'parent_slug' => 'foo',
+			'parent_url' => 'http://example.com/foo/',
+			'screen_function' => 'foo',
+			'link' => $link,
+		) );
+
+		$this->assertSame( $link, buddypress()->bp_options_nav['foo']['bar']['link'] );
+	}
+
+	public function test_should_return_false_if_site_admin_only_and_current_user_cannot_bp_moderate() {
+		// Should already be set to a 0 user.
+		$this->assertFalse( bp_current_user_can( 'bp_moderate' ) );
+		$args = array(
+			'name' => 'Foo',
+			'slug' => 'foo',
+			'parent_slug' => 'parent',
+			'parent_url' => bp_get_root_domain() . '/parent/',
+			'screen_function' => 'foo',
+			'site_admin_only' => true,
+		);
+
+		$this->assertFalse( bp_core_new_subnav_item( $args ) );
+	}
+
+	public function test_css_id_should_fall_back_on_slug() {
+		$args = array(
+			'name' => 'Foo',
+			'slug' => 'foo',
+			'parent_slug' => 'parent',
+			'parent_url' => bp_get_root_domain() . '/parent/',
+			'screen_function' => 'foo',
+		);
+		bp_core_new_subnav_item( $args );
+
+		$this->assertSame( 'foo', buddypress()->bp_options_nav['parent']['foo']['css_id'] );
+	}
+
+	public function test_css_id_should_be_respected() {
+		$args = array(
+			'name' => 'Foo',
+			'slug' => 'foo',
+			'parent_slug' => 'parent',
+			'parent_url' => bp_get_root_domain() . '/parent/',
+			'screen_function' => 'foo',
+			'item_css_id' => 'bar',
+		);
+		bp_core_new_subnav_item( $args );
+
+		$this->assertSame( 'bar', buddypress()->bp_options_nav['parent']['foo']['css_id'] );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/suggestions-nonauth.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/suggestions-nonauth.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/suggestions-nonauth.php	(revision 10410)
@@ -0,0 +1,180 @@
+<?php
+/**
+ * Suggestions API tests specifically for non-authenticated (anonymous) users.
+ *
+ * @group api
+ * @group suggestions
+ */
+class BP_Tests_Suggestions_Non_Authenticated extends BP_UnitTestCase {
+	protected static $group_ids    = array();
+	protected static $group_slugs  = array();
+	protected static $user_ids     = array();
+
+	public static function setUpBeforeClass() {
+		parent::setUpBeforeClass();
+
+		$users = array(
+			// user_login, display_name
+			array( 'aardvark',    'Bob Smith' ),
+			array( 'alpaca red',  'William Quinn' ),
+			array( 'cat',         'Lauren Curtis' ),
+			array( 'caterpillar', 'Eldon Burrows' ),
+			array( 'dog green',   'Reece Thornton' ),
+			array( 'pig',         'Joshua Barton' ),
+			array( 'rabbit blue', 'Amber Hooper' ),
+			array( 'smith',       'Robert Bar' ),
+			array( 'snake',       'Eleanor Moore' ),
+			array( 'xylo',        'Silver McFadden' ),
+			array( 'zoom',        'Lisa Smithy' ),
+		);
+
+		$factory = new BP_UnitTest_Factory();
+
+		// Create some dummy users.
+		foreach( $users as $user_index => $user ) {
+			$new_user = $factory->user->create( array(
+				'display_name' => $user[1],
+				'user_login'   => $user[0],
+				'user_email'   => "test-$user_index@example.com",
+			) );
+
+			self::$user_ids[ $user[0] ] = $new_user;
+		}
+
+		self::$group_slugs['hidden']  = 'the-maw';
+		self::$group_slugs['public']  = 'the-great-journey';
+		self::$group_slugs['private'] = 'tsavo-highway';
+
+		// Create dummy groups.
+		self::$group_ids['hidden'] = $factory->group->create( array(
+			'creator_id' => self::$user_ids['xylo'],
+			'slug'       => self::$group_slugs['hidden'],
+			'status'     => 'hidden',
+		) );
+		self::$group_ids['public'] = $factory->group->create( array(
+			'creator_id' => self::$user_ids['xylo'],
+			'slug'       => self::$group_slugs['public'],
+			'status'     => 'public',
+		) );
+		self::$group_ids['private'] = $factory->group->create( array(
+			'creator_id' => self::$user_ids['xylo'],
+			'slug'       => self::$group_slugs['private'],
+			'status'     => 'private',
+		) );
+
+		// Add dummy users to dummy hidden groups.
+		groups_join_group( self::$group_ids['hidden'], self::$user_ids['pig'] );
+		groups_join_group( self::$group_ids['hidden'], self::$user_ids['alpaca red'] );
+
+		// Add dummy users to dummy public groups.
+		groups_join_group( self::$group_ids['public'], self::$user_ids['aardvark'] );
+		groups_join_group( self::$group_ids['public'], self::$user_ids['alpaca red'] );
+		groups_join_group( self::$group_ids['public'], self::$user_ids['cat'] );
+		groups_join_group( self::$group_ids['public'], self::$user_ids['smith'] );
+
+		// Add dummy users to dummy private groups.
+		groups_join_group( self::$group_ids['private'], self::$user_ids['cat'] );
+		groups_join_group( self::$group_ids['private'], self::$user_ids['caterpillar'] );
+
+		self::commit_transaction();
+	}
+
+	public static function tearDownAfterClass() {
+		foreach ( self::$group_ids as $group_id ) {
+			groups_delete_group( $group_id );
+		}
+
+		foreach ( self::$user_ids as $user_id ) {
+			if ( is_multisite() ) {
+				wpmu_delete_user( $user_id );
+			} else {
+				wp_delete_user( $user_id );
+			}
+		}
+
+		self::commit_transaction();
+	}
+
+	/**
+	 * Tests below this point are expected to fail.
+	 */
+
+	public function test_suggestions_with_type_members_and_only_friends() {
+		// only_friends requires authenticated requests
+		$suggestions = bp_core_get_suggestions( array(
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'smith',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_and_only_friends() {
+		// only_friends requires authenticated requests
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id'     => self::$group_ids['public'],
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'smith',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_private() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -self::$group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoom
+	}
+
+	public function test_suggestions_with_type_groupmembers_private_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -self::$group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );  // no access to group.
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );  // no access to group.
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/suggestions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/suggestions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/suggestions.php	(revision 10410)
@@ -0,0 +1,507 @@
+<?php
+/**
+ * Suggestions API tests for authenticated (logged in) users.
+ *
+ * @group api
+ * @group suggestions
+ */
+class BP_Tests_Suggestions_Authenticated extends BP_UnitTestCase {
+	protected static $current_user = null;
+	protected static $group_ids    = array();
+	protected static $group_slugs  = array();
+	protected static $old_user_id  = 0;
+	protected static $user_ids     = array();
+
+	public static function setUpBeforeClass() {
+		parent::setUpBeforeClass();
+
+		$factory = new BP_UnitTest_Factory();
+
+		self::$old_user_id  = get_current_user_id();
+		self::$current_user = $factory->user->create( array(
+			'display_name' => 'Katie Parker',
+			'user_login'   => 'katie',
+			'user_email'   => 'test-katie@example.com',
+		) );
+
+		$users = array(
+			// user_login, display_name
+			array( 'aardvark',    'Bob Smith' ),
+			array( 'alpaca red',  'William Quinn' ),
+			array( 'cat',         'Lauren Curtis' ),
+			array( 'caterpillar', 'Eldon Burrows' ),
+			array( 'dog green',   'Reece Thornton' ),
+			array( 'pig',         'Joshua Barton' ),
+			array( 'rabbit blue', 'Amber Hooper' ),
+			array( 'smith',       'Robert Bar' ),
+			array( 'snake',       'Eleanor Moore' ),
+			array( 'xylo',        'Silver McFadden' ),
+			array( 'zoom',        'Lisa Smithy' ),
+		);
+
+		// Create some dummy users.
+		foreach ( $users as $user_index => $user ) {
+			$new_user = $factory->user->create( array(
+				'display_name' => $user[1],
+				'user_login'   => $user[0],
+				'user_email'   => "test-$user_index@example.com",
+			) );
+
+			self::$user_ids[ $user[0] ] = $new_user;
+		}
+
+		// Create some dummy friendships (but not the corresponding activity items).
+		remove_action( 'friends_friendship_accepted', 'bp_friends_friendship_accepted_activity', 10, 4 );
+		friends_add_friend( self::$current_user, self::$user_ids['aardvark'], true );
+		friends_add_friend( self::$current_user, self::$user_ids['cat'], true );
+		friends_add_friend( self::$current_user, self::$user_ids['caterpillar'], true );
+		friends_add_friend( self::$current_user, self::$user_ids['pig'], true );
+		add_action( 'friends_friendship_accepted', 'bp_friends_friendship_accepted_activity', 10, 4 );
+
+		self::$group_slugs['hidden']  = 'the-maw';
+		self::$group_slugs['public']  = 'the-great-journey';
+		self::$group_slugs['private'] = 'tsavo-highway';
+
+		// Create dummy groups.
+		self::$group_ids['hidden'] = $factory->group->create( array(
+			'creator_id' => self::$user_ids['xylo'],
+			'slug'       => self::$group_slugs['hidden'],
+			'status'     => 'hidden',
+		) );
+		self::$group_ids['public'] = $factory->group->create( array(
+			'creator_id' => self::$user_ids['xylo'],
+			'slug'       => self::$group_slugs['public'],
+			'status'     => 'public',
+		) );
+		self::$group_ids['private'] = $factory->group->create( array(
+			'creator_id' => self::$user_ids['xylo'],
+			'slug'       => self::$group_slugs['private'],
+			'status'     => 'private',
+		) );
+
+		// Add dummy users to dummy hidden groups.
+		groups_join_group( self::$group_ids['hidden'], self::$user_ids['pig'] );
+		groups_join_group( self::$group_ids['hidden'], self::$user_ids['alpaca red'] );
+
+		// Add dummy users to dummy public groups.
+		groups_join_group( self::$group_ids['public'], self::$current_user );
+		groups_join_group( self::$group_ids['public'], self::$user_ids['aardvark'] );
+		groups_join_group( self::$group_ids['public'], self::$user_ids['alpaca red'] );
+		groups_join_group( self::$group_ids['public'], self::$user_ids['cat'] );
+		groups_join_group( self::$group_ids['public'], self::$user_ids['smith'] );
+
+		// Add dummy users to dummy private groups.
+		groups_join_group( self::$group_ids['private'], self::$user_ids['cat'] );
+		groups_join_group( self::$group_ids['private'], self::$user_ids['caterpillar'] );
+
+		self::commit_transaction();
+	}
+
+	public static function tearDownAfterClass() {
+		foreach ( self::$group_ids as $group_id ) {
+			groups_delete_group( $group_id );
+		}
+
+		foreach ( array_merge( self::$user_ids, array( self::$current_user ) ) as $user_id ) {
+			if ( is_multisite() ) {
+				wpmu_delete_user( $user_id );
+			} else {
+				wp_delete_user( $user_id );
+			}
+		}
+
+		self::commit_transaction();
+	}
+
+	public function setUp() {
+		parent::setUp();
+		$this->set_current_user( self::$current_user );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( self::$old_user_id );
+	}
+
+	public function test_suggestions_with_type_members() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 3, count( $suggestions ) );  // aardvark, smith, zoom.
+	}
+
+	public function test_suggestions_with_type_members_and_limit() {
+		$suggestions = bp_core_get_suggestions( array(
+			'limit' => 2,
+			'type'  => 'members',
+			'term'  => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // two of: aardvark, smith, zoom.
+	}
+
+	public function test_suggestions_with_type_members_and_only_friends() {
+		$suggestions = bp_core_get_suggestions( array(
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+
+		$suggestions = bp_core_get_suggestions( array(
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'cat',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // cat, caterpillar.
+	}
+
+	public function test_suggestions_with_type_members_and_term_as_displayname() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'aardvark',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+	}
+
+	public function test_suggestions_with_type_members_and_term_as_usernicename() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'eleanor',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // snake.
+	}
+
+	public function test_suggestions_with_term_as_current_user() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'members',
+			'term' => 'katie',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );
+		$this->assertSame( 'katie', $suggestions[0]->ID );
+	}
+
+
+	public function test_suggestions_with_type_groupmembers_public() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_limit() {
+		$suggestions = bp_core_get_suggestions( array(
+			'limit'    => 1,
+			'group_id' => self::$group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // one of: aardvark, smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_only_friends() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id'     => self::$group_ids['public'],
+			'only_friends' => true,
+			'type'         => 'members',
+			'term'         => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_term_as_displayname() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'aardvark',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // aardvark.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_and_term_as_usernicename() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'robert',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_public_as_id() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden() {
+		// current_user isn't a member of the hidden group
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+		// "alpaca red" is in the hidden group
+		$this->set_current_user( self::$user_ids['alpaca red'] );
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // pig
+	}
+
+	public function test_suggestions_with_type_groupmembers_private() {
+		// current_user isn't a member of the private group.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+		// "caterpillar" is in the private group
+		$this->set_current_user( self::$user_ids['caterpillar'] );
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // cat, caterpillar
+	}
+
+
+	public function test_suggestions_with_type_groupmembers_public_and_exclude_group_from_results() {
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 2, count( $suggestions ) );  // aardvark, smith.
+
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -self::$group_ids['public'],
+			'type'     => 'members',
+			'term'     => 'smith',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoom
+	}
+
+	public function test_suggestions_with_type_groupmembers_private_and_exclude_group_from_results() {
+		// current_user isn't a member of the private group.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -self::$group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+
+		$this->set_current_user( self::$user_ids['caterpillar'] );
+
+		// "cat" is in the private group, so won't show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -self::$group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'cat',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEmpty( $suggestions );
+
+		// "zoo" is not the private group, so will show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -self::$group_ids['private'],
+			'type'     => 'members',
+			'term'     => 'zoo',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoo
+	}
+
+	public function test_suggestions_with_type_groupmembers_hidden_and_exclude_group_from_results() {
+		// current_user isn't a member of the hidden group.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => self::$group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'pig',
+		) );
+		$this->assertTrue( is_wp_error( $suggestions ) );
+
+
+		$this->set_current_user( self::$user_ids['alpaca red'] );
+
+		// "alpaca red" is in the hidden group, so won't show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -self::$group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'alpaca red',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEmpty( $suggestions );
+
+		// "zoo" is not the hidden group, so will show up here.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => -self::$group_ids['hidden'],
+			'type'     => 'members',
+			'term'     => 'zoo',
+		) );
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertEquals( 1, count( $suggestions ) );  // zoo
+	}
+
+
+	/**
+	 * These next tests check the format of the response from the Suggestions API.
+	 */
+
+	public function test_suggestions_response_no_matches() {
+		$suggestions = bp_core_get_suggestions( array(
+			'term' => 'abcdefghijklmnopqrstuvwxyz',
+			'type' => 'members',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertInternalType( 'array', $suggestions );
+		$this->assertEmpty( $suggestions );
+	}
+
+	public function test_suggestions_response_single_match() {
+		$suggestion = bp_core_get_suggestions( array(
+			'term' => 'zoom',
+			'type' => 'members',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestion ) );
+		$this->assertInternalType( 'array', $suggestion );
+		$this->assertNotEmpty( $suggestion );
+
+		$suggestion = array_shift( $suggestion );
+
+		$this->assertInternalType( 'object', $suggestion );
+		$this->assertAttributeNotEmpty( 'image', $suggestion );
+		$this->assertAttributeNotEmpty( 'ID', $suggestion );
+		$this->assertAttributeNotEmpty( 'name', $suggestion );
+	}
+
+	public function test_suggestions_response_multiple_matches() {
+		$suggestions = bp_core_get_suggestions( array(
+			'term' => 'cat',
+			'type' => 'members',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestions ) );
+		$this->assertInternalType( 'array', $suggestions );
+		$this->assertNotEmpty( $suggestions );
+
+		foreach ( $suggestions as $suggestion ) {
+			$this->assertInternalType( 'object', $suggestion );
+			$this->assertAttributeNotEmpty( 'image', $suggestion );
+			$this->assertAttributeNotEmpty( 'ID', $suggestion );
+			$this->assertAttributeNotEmpty( 'name', $suggestion );
+		}
+	}
+
+	public function test_suggestions_term_is_case_insensitive() {
+		$lowercase = bp_core_get_suggestions( array(
+			'term' => 'lisa',
+			'type' => 'members',
+		) );
+		$this->assertFalse( is_wp_error( $lowercase ) );
+		$this->assertEquals( 1, count( $lowercase ) );
+
+		$uppercase = bp_core_get_suggestions( array(
+			'term' => 'LISA',
+			'type' => 'members',
+		) );
+		$this->assertFalse( is_wp_error( $uppercase ) );
+		$this->assertEquals( 1, count( $uppercase ) );
+
+		$this->assertSame( $lowercase[0]->ID, $uppercase[0]->ID );
+		$this->assertSame( 'zoom', $lowercase[0]->ID );
+	}
+
+	public function test_suggestions_response_property_types() {
+		$suggestion = bp_core_get_suggestions( array(
+			'term' => 'zoom',
+			'type' => 'members',
+		) );
+
+		$this->assertFalse( is_wp_error( $suggestion ) );
+		$this->assertInternalType( 'array', $suggestion );
+		$this->assertNotEmpty( $suggestion );
+
+		$suggestion = array_shift( $suggestion );
+
+		$this->assertInternalType( 'object', $suggestion );
+		$this->assertAttributeInternalType( 'string', 'image', $suggestion );
+		$this->assertAttributeInternalType( 'string', 'ID', $suggestion );
+		$this->assertAttributeInternalType( 'string', 'name', $suggestion );
+	}
+
+
+	/**
+	 * Tests below this point are expected to fail.
+	 */
+
+	public function test_suggestions_with_bad_type() {
+		$suggestions = bp_core_get_suggestions( array(
+			'type' => 'fake_type',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_type_groupmembers_and_bad_group_ids() {
+		// group_ids can't be a group slug.
+		$suggestions = bp_core_get_suggestions( array(
+			'group_id' => 'fake-group-slug',
+			'type'     => 'members',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+
+	public function test_suggestions_with_bad_term() {
+		// a non-empty term is mandatory
+		$suggestions = bp_core_get_suggestions( array(
+			'term' => '',
+			'type' => 'members',
+		) );
+
+		$this->assertTrue( is_wp_error( $suggestions ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/template/bpCreateExcerpt.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/template/bpCreateExcerpt.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/template/bpCreateExcerpt.php	(revision 10410)
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @group core
+ * @group bp_create_excerpt
+ */
+class BP_Tests_Core_Template_BPCreateExcerpt extends BP_UnitTestCase {
+	public function test_should_ignore_html_tag_when_html_true() {
+		$text = 'foo <hr> bar baz';
+		$expected = 'foo <hr /> bar';
+		$this->assertSame( $expected, bp_create_excerpt( $text, 8, array(
+			'ending' => '',
+			'html' => true,
+			'exact' => true,
+		) ) );
+	}
+
+	/**
+	 * @ticket BP3680
+	 */
+	public function test_should_ignore_single_word_html_comments_when_html_true() {
+		$text = 'foo <!--more--> bar baz';
+		$expected = 'foo <!--more--> bar';
+		$this->assertSame( $expected, bp_create_excerpt( $text, 8, array(
+			'ending' => '',
+			'html' => true,
+			'exact' => true,
+		) ) );
+	}
+
+	/**
+	 * @ticket BP3680
+	 */
+	public function test_should_ignore_multiple_word_html_comments_when_html_true() {
+		$text = 'foo <!--one two three--> bar baz';
+		$expected = 'foo <!--one two three--> bar';
+		$this->assertSame( $expected, bp_create_excerpt( $text, 8, array(
+			'ending' => '',
+			'html' => true,
+			'exact' => true,
+		) ) );
+	}
+
+	public function test_should_break_on_prior_word_boundary_when_exact_is_false() {
+		$text = 'aaaaa aaaaaa';
+		$expected = 'aaaaa';
+		$this->assertSame( $expected, bp_create_excerpt( $text, 7, array(
+			'exact' => false,
+			'ending' => '',
+		) ) );
+	}
+
+	/**
+	 * @ticket BP6517
+	 */
+	public function test_exact_false_should_properly_account_for_accented_characters() {
+		$text = 'Toutes les connaissances que les hommes avaient mis sur Internet lui étaient accessible. Les grandes bibliothèques du monde entier n’avaient plus de secret pour lui. Il pouvait apprendre très vite, beaucoup plus vite que n’importe quel humain.
+Il avait appris toutes les connaissances du monde entier, visiter tout les pays. C’est lui qui avait fait en sorte qu’Internet se déploie ainsi.';
+		$expected = 'Toutes les connaissances que les hommes avaient mis sur Internet lui étaient accessible. Les';
+		$this->assertSame( $expected, bp_create_excerpt( $text, 98, array(
+			'ending' => '',
+			'exact' => false,
+		) ) );
+	}
+
+	/**
+	 * @ticket BP6254
+	 */
+	public function test_should_trim_too_long_first_word_to_max_characters_even_when_exact_is_false() {
+		$text = 'aaaaaaaaaaa';
+		$expected = 'aaa';
+		$this->assertSame( $expected, bp_create_excerpt( $text, 3, array(
+			'exact' => false,
+			'ending' => '',
+		) ) );
+	}
+
+	/**
+	 * @ticket BP6517
+	 */
+	public function test_string_should_not_be_cut_mid_tag_when_exact_is_false() {
+		$text = '<p><span>Foo</span> <a href="http://example.com">Bar</a> Baz.</p><p>Foo Bar Baz</p>';
+		$actual = bp_create_excerpt( $text, 7, array(
+			'html' => true,
+			'ending' => '',
+			'exact' => false,
+		) );
+		$this->assertSame( '<p><span>Foo</span> <a href="http://example.com">Bar</a></p>', $actual );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/core/template/bpUserHasAccess.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/core/template/bpUserHasAccess.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/core/template/bpUserHasAccess.php	(revision 10410)
@@ -0,0 +1,37 @@
+<?php
+/**
+ * @group core
+ * @group template
+ */
+class BP_Tests_Core_Template_BpUserHasAccess extends BP_UnitTestCase {
+	public function test_should_return_true_for_bp_moderate_user() {
+		$users = $this->factory->user->create_many( 2 );
+
+		$this->grant_bp_moderate( $users[0] );
+		$this->set_current_user( $users[0] );
+
+		$this->go_to( bp_core_get_user_domain( $users[1] ) );
+
+		$this->assertTrue( bp_user_has_access( $users[0] ) );
+	}
+
+	public function test_should_return_false_on_anothers_profile_for_user_without_bp_moderate() {
+		$users = $this->factory->user->create_many( 2 );
+
+		$this->set_current_user( $users[0] );
+
+		$this->go_to( bp_core_get_user_domain( $users[1] ) );
+
+		$this->assertFalse( bp_user_has_access( $users[0] ) );
+	}
+
+	public function test_should_return_true_on_own_profile() {
+		$users = $this->factory->user->create_many( 2 );
+
+		$this->set_current_user( $users[0] );
+
+		$this->go_to( bp_core_get_user_domain( $users[0] ) );
+
+		$this->assertTrue( bp_user_has_access( $users[0] ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/friends/activity.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/friends/activity.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/friends/activity.php	(revision 10410)
@@ -0,0 +1,188 @@
+<?php
+
+/**
+ * @group friends
+ * @group activity
+ */
+class BP_Tests_Friends_Activity extends BP_UnitTestCase {
+	/**
+	 * @group activity_action
+	 * @group bp_friends_format_activity_action_friendship_accepted
+	 */
+	public function test_bp_friends_format_activity_action_friendship_accepted() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->friends->id,
+			'type' => 'friendship_accepted',
+			'user_id' => $u1,
+			'secondary_item_id' => $u2,
+		) );
+
+		$expected = sprintf( __( '%1$s and %2$s are now friends', 'buddypress' ), bp_core_get_userlink( $u1 ), bp_core_get_userlink( $u2 ) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_friends_format_activity_action_friendship_created
+	 */
+	public function test_bp_friends_format_activity_action_friendship_created() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->friends->id,
+			'type' => 'friendship_created',
+			'user_id' => $u1,
+			'secondary_item_id' => $u2,
+		) );
+
+		$expected = sprintf( __( '%1$s and %2$s are now friends', 'buddypress' ), bp_core_get_userlink( $u1 ), bp_core_get_userlink( $u2 ) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group friends_delete_activity
+	 */
+	public function test_friends_delete_activity() {
+		$old_user = get_current_user_id();
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		friends_add_friend( $u2, $u1 );
+		$friendship_id = friends_get_friendship_id( $u2, $u1 );
+
+		// Set current user to u1 to accepte the friendship
+		$this->set_current_user( $u1 );
+		friends_accept_friendship( $friendship_id );
+
+		// Reset the current user
+		$this->set_current_user( $old_user );
+
+		// Random activities
+		$au1 = $this->factory->activity->create( array( 'user_id' => $u1 ) );
+		$au2 = $this->factory->activity->create( array( 'user_id' => $u2 ) );
+
+		$fc_act = bp_activity_get( array(
+			'component'   => buddypress()->friends->id,
+			'item_id'     => $friendship_id,
+			'filter'      => array( 'action' => array( 'friendship_created' ) ),
+			'show_hidden' => false
+		) );
+
+		$this->assertTrue( count( $fc_act['activities'] ) == 1, '1 public activity should be created when a friendship is confirmed' );
+
+		// Remove the friendship
+		friends_remove_friend( $u2, $u1 );
+
+		$this->assertFalse( friends_check_friendship( $u2, $u1 ), '2 users should not be friend once the friendship is removed' );
+
+		$fd_act = bp_activity_get( array(
+			'component'   => buddypress()->friends->id,
+			'item_id'     => $friendship_id,
+			'filter'      => array( 'action' => array( 'friendship_created' ) ),
+			'show_hidden' => true
+		) );
+
+		$this->assertTrue( count( $fd_act['activities'] ) == 0, 'friends_delete_activity() should remove "friendship_created" activities about a deleted friendship' );
+	}
+
+	/**
+	 * @group bp_friends_friendship_accepted_activity
+	 */
+	public function test_bp_friends_friendship_accepted_activity() {
+		$old_user = get_current_user_id();
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		friends_add_friend( $u2, $u1 );
+		$friendship_id = friends_get_friendship_id( $u2, $u1 );
+
+		// Set current user to u1 to accepte the friendship
+		$this->set_current_user( $u1 );
+		friends_accept_friendship( $friendship_id );
+
+		// Reset the current user
+		$this->set_current_user( $old_user );
+
+		$u1_act = bp_activity_get( array(
+			'component'   => buddypress()->friends->id,
+			'item_id'     => $friendship_id,
+			'scope'       => 'just-me',
+			'filter'      => array( 'action' => array( 'friendship_created' ), 'user_id' => $u1 ),
+		) );
+
+		$this->assertTrue( count( $u1_act['activities'] ) == 1, 'a public activity should be listed in the friend stream' );
+
+		$u2_act = bp_activity_get( array(
+			'component'   => buddypress()->friends->id,
+			'item_id'     => $friendship_id,
+			'scope'       => 'just-me',
+			'filter'      => array( 'action' => array( 'friendship_created' ), 'user_id' => $u2 ),
+		) );
+
+		$this->assertTrue( count( $u2_act['activities'] ) == 1, 'a public activity should be listed in the initiator stream' );
+	}
+
+	/**
+	 * @group bp_cleanup_friendship_activities
+	 */
+	public function test_bp_cleanup_friendship_activities() {
+		$old_user = get_current_user_id();
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$users = array( $u1, $u2 );
+
+		friends_add_friend( $u2, $u1 );
+		$friendship_id = friends_get_friendship_id( $u2, $u1 );
+
+		// Set current user to u1 to accepte the friendship and generate a public activity
+		$this->set_current_user( $u1 );
+		friends_accept_friendship( $friendship_id );
+
+		// Reset the current user
+		$this->set_current_user( $old_user );
+
+		$users[] = $this->factory->user->create();
+		$users[] = $this->factory->user->create();
+
+		foreach( $users as $u ) {
+			bp_activity_add( array(
+				'user_id'       => $u,
+				'item_id'       => $friendship_id,
+				'type'          => 'friendship_created',
+				'component'     => buddypress()->friends->id,
+				'hide_sitewide' => true,
+			) );
+		}
+
+		$hidden = bp_activity_get( array(
+			'component'   => buddypress()->friends->id,
+			'filter'      => array( 'action' => array( 'friendship_created' ) ),
+			'show_hidden' => true,
+		) );
+
+		bp_cleanup_friendship_activities();
+
+		$check = bp_activity_get( array(
+			'component'   => buddypress()->friends->id,
+			'item_id'     => $friendship_id,
+			'filter'      => array( 'action' => array( 'friendship_created' ) ),
+			'show_hidden' => true,
+		) );
+
+		$this->assertTrue( count( $check['activities'] ) == 1 );
+	}
+}
+
Index: /tags/2.4.3/tests/phpunit/testcases/friends/class-bp-friends-friendship.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/friends/class-bp-friends-friendship.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/friends/class-bp-friends-friendship.php	(revision 10410)
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @group friends
+ */
+class BP_Tests_BP_Friends_Friendship_TestCases extends BP_UnitTestCase {
+	public function test_search_friends() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		xprofile_set_field_data( 1, $u2, 'Cool Dude' );
+		xprofile_set_field_data( 1, $u3, 'Rock And Roll America Yeah' );
+
+		friends_add_friend( $u1, $u2, true );
+		friends_add_friend( $u1, $u3, true );
+
+		$friends = BP_Friends_Friendship::search_friends( 'Coo', $u1 );
+		$this->assertEquals( array( $u2 ), $friends['friends'] );
+	}
+
+	/**
+	 * @ticket BP6546
+	 */
+	public function test_search_friends_with_xprofile_inactive() {
+		$this->deactivate_component( 'xprofile' );
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		add_user_meta( $u2, 'nickname', 'Cool Dude' );
+		add_user_meta( $u3, 'nickname', 'Rock And Roll America Yeah' );
+
+		friends_add_friend( $u1, $u2, true );
+		friends_add_friend( $u1, $u3, true );
+
+		$friends = BP_Friends_Friendship::search_friends( 'Coo', $u1 );
+		$this->assertEquals( array( $u2 ), $friends['friends'] );
+	}
+
+	public function test_get_bulk_last_active() {
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s' ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', time() - 1000 ),
+		) );
+		$u3 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', time() - 50 ),
+		) );
+
+		$friends = BP_Friends_Friendship::get_bulk_last_active( array( $u1, $u2, $u3, 'junk' ) );
+		$friend_ids = wp_list_pluck( $friends, 'user_id' );
+		$this->assertEquals( array( $u1, $u3, $u2 ), $friend_ids );
+	}
+
+	public function test_search_users() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		xprofile_set_field_data( 1, $u1, 'Freedom Isn\'t Free' );
+		xprofile_set_field_data( 1, $u2, 'Cool Dude' );
+		xprofile_set_field_data( 1, $u3, 'Rock And Roll America Yeah' );
+
+		// Needs a user_id param though it does nothing
+		$friends = BP_Friends_Friendship::search_users( 'Coo', 1 );
+		$this->assertEquals( array( $u2 ), $friends );
+	}
+
+	public function test_search_users_count() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		xprofile_set_field_data( 1, $u1, 'Freedom Isn\'t Free' );
+		xprofile_set_field_data( 1, $u2, 'Cool Dude' );
+		xprofile_set_field_data( 1, $u3, 'Rock And Roll America Yeah' );
+
+		// Needs a user_id param though it does nothing
+		$friends = BP_Friends_Friendship::search_users_count( 'Coo' );
+		$this->assertEquals( 1, $friends );
+	}
+
+	/**
+	 * @group check_is_friend
+	 */
+	public function test_check_is_friend_not_friends() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$this->assertEquals( 'not_friends', BP_Friends_Friendship::check_is_friend( $u1, $u2 ) );
+	}
+
+	/**
+	 * @group check_is_friend
+	 */
+	public function test_check_is_friend_pending() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		friends_add_friend( $u1, $u2, false );
+		$this->assertEquals( 'pending', BP_Friends_Friendship::check_is_friend( $u1, $u2 ) );
+	}
+
+	/**
+	 * @group check_is_friend
+	 */
+	public function test_check_is_friend_awaiting_response() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		friends_add_friend( $u1, $u2, false );
+		$this->assertEquals( 'awaiting_response', BP_Friends_Friendship::check_is_friend( $u2, $u1 ) );
+	}
+
+	/**
+	 * @group check_is_friend
+	 */
+	public function test_check_is_friend_is_friend() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		friends_add_friend( $u1, $u2, true );
+		$this->assertEquals( 'is_friend', BP_Friends_Friendship::check_is_friend( $u1, $u2 ) );
+	}
+
+	/**
+	 * @group BP6247
+	 */
+	public function test_save_method_should_update_existing_row() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$friendship = new BP_Friends_Friendship();
+		$friendship->initiator_user_id = $u1;
+		$friendship->friend_user_id = $u2;
+		$friendship->is_confirmed = 0;
+		$friendship->is_limited = 0;
+		$friendship->date_created = bp_core_current_time();
+		$friendship->is_confirmed = 1;
+		$friendship->save();
+
+		$fid = $friendship->id;
+
+		$f = new BP_Friends_Friendship( $fid );
+		$f->is_confirmed = 1;
+		$f->save();
+
+		$f2 = new BP_Friends_Friendship( $fid );
+		$this->assertEquals( 1, $f2->is_confirmed );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/friends/functions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/friends/functions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/friends/functions.php	(revision 10410)
@@ -0,0 +1,270 @@
+<?php
+
+/**
+ * @group friends
+ * @group functions
+ */
+class BP_Tests_Friends_Functions extends BP_UnitTestCase {
+
+	protected $filter_fired;
+
+	public function setUp() {
+		parent::setUp();
+		$this->filter_fired = '';
+	}
+
+	/**
+	 * @group friends_get_friendship_request_user_ids
+	 * @group friends_add_friend
+	 * @group friends_accept_friendship
+	 */
+	public function test_requests_on_accept() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		// request friendship
+		friends_add_friend( $u2, $u1 );
+		friends_add_friend( $u3, $u1 );
+
+		// get request count for user 1 and assert
+		$requests = friends_get_friendship_request_user_ids( $u1 );
+		$this->assertEquals( array( $u2, $u3 ), $requests );
+
+		// accept friendship
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u1 );
+		friends_accept_friendship( friends_get_friendship_id( $u2, $u1 ) );
+
+		// refetch request count for user 1 and assert
+		$requests = friends_get_friendship_request_user_ids( $u1 );
+		$this->assertEquals( array( $u3 ), $requests );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group friends_get_friendship_request_user_ids
+	 * @group friends_add_friend
+	 */
+	public function test_requests_on_request() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		// request friendship
+		friends_add_friend( $u2, $u1 );
+
+		// get request count for user 1 and assert
+		$requests = friends_get_friendship_request_user_ids( $u1 );
+		$this->assertEquals( array( $u2 ), $requests );
+
+		// request another friendship
+		friends_add_friend( $u3, $u1 );
+
+		// refetch request count for user 1 and assert
+		$requests = friends_get_friendship_request_user_ids( $u1 );
+		$this->assertEquals( array( $u2, $u3 ), $requests );
+	}
+
+	/**
+	 * @group friends_get_friendship_request_user_ids
+	 * @group friends_add_friend
+	 * @group friends_withdraw_friendship
+	 */
+	public function test_requests_on_withdraw() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// request friendship
+		friends_add_friend( $u2, $u1 );
+
+		// get request count for user 1 and assert
+		$requests = friends_get_friendship_request_user_ids( $u1 );
+		$this->assertEquals( array( $u2 ), $requests );
+
+		// user 2 withdraws friendship
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u2 );
+		friends_withdraw_friendship( $u2, $u1 );
+
+		// refetch request count for user 1 and assert
+		$requests = friends_get_friendship_request_user_ids( $u1 );
+		$this->assertEquals( array(), $requests );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group friends_get_friendship_request_user_ids
+	 * @group friends_add_friend
+	 * @group friends_reject_friendship
+	 */
+	public function test_requests_on_reject() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// request friendship
+		friends_add_friend( $u2, $u1 );
+
+		// get request count for user 1 and assert
+		$requests = friends_get_friendship_request_user_ids( $u1 );
+		$this->assertEquals( array( $u2 ), $requests );
+
+		// user 1 rejects friendship
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u1 );
+		friends_reject_friendship( friends_get_friendship_id( $u2, $u1 ) );
+
+		// refetch request count for user 1 and assert
+		$requests = friends_get_friendship_request_user_ids( $u1 );
+		$this->assertEquals( array(), $requests );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group friends_add_friend
+	 */
+	public function test_friends_add_friend_fail_on_self() {
+		$u1 = $this->factory->user->create();
+		$this->assertFalse( friends_add_friend( $u1, $u1 ) );
+	}
+
+	/**
+	 * @group friends_add_friend
+	 */
+	public function test_friends_add_friend_already_friends() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		friends_add_friend( $u1, $u2, true );
+
+		$this->assertTrue( friends_add_friend( $u1, $u2 ) );
+	}
+
+	/**
+	 * @group friends_check_friendship_status
+	 */
+	public function test_friends_check_friendship_status_in_members_loop() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$u3 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 200 ),
+		) );
+		$u4 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 300 ),
+		) );
+		$u5 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 400 ),
+		) );
+
+		friends_add_friend( $u1, $u2, true );
+		friends_add_friend( $u1, $u3, false );
+		friends_add_friend( $u4, $u1, false );
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u1 );
+
+		$found = array();
+		if ( bp_has_members() ) : while ( bp_members() ) : bp_the_member();
+			$found[ bp_get_member_user_id() ] = friends_check_friendship_status( $u1, bp_get_member_user_id() );
+		endwhile; endif;
+
+		$expected = array(
+			$u1 => 'not_friends',
+			$u2 => 'is_friend',
+			$u3 => 'pending',
+			$u4 => 'awaiting_response',
+			$u5 => 'not_friends',
+		);
+
+		$this->assertSame( $expected, $found );
+
+		// clean up
+		$GLOBALS['members_template'] = null;
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group friends_check_friendship_status
+	 */
+	public function test_friends_check_friendship_status_not_in_members_loop() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$u3 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 200 ),
+		) );
+		$u4 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 300 ),
+		) );
+		$u5 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 400 ),
+		) );
+
+		friends_add_friend( $u1, $u2, true );
+		friends_add_friend( $u1, $u3, false );
+		friends_add_friend( $u4, $u1, false );
+
+		$found = array(
+			$u1 => friends_check_friendship_status( $u1, $u1 ),
+			$u2 => friends_check_friendship_status( $u1, $u2 ),
+			$u3 => friends_check_friendship_status( $u1, $u3 ),
+			$u4 => friends_check_friendship_status( $u1, $u4 ),
+			$u5 => friends_check_friendship_status( $u1, $u5 ),
+		);
+
+		$expected = array(
+			$u1 => 'not_friends',
+			$u2 => 'is_friend',
+			$u3 => 'pending',
+			$u4 => 'awaiting_response',
+			$u5 => 'not_friends',
+		);
+
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group friends_add_friend
+	 */
+	public function test_friends_add_friend_friends_friendship_requested() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		add_filter( 'friends_friendship_requested', array( $this, 'friends_friendship_filter_callback' ) );
+		$n = friends_add_friend( $u1, $u2, false );
+		remove_filter( 'friends_friendship_requested', array( $this, 'friends_friendship_filter_callback' ) );
+
+		$this->assertSame( 'friends_friendship_requested', $this->filter_fired );
+	}
+
+	/**
+	 * @group friends_add_friend
+	 */
+	public function test_friends_add_friend_friends_friendship_accepted() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		add_filter( 'friends_friendship_accepted', array( $this, 'friends_friendship_filter_callback' ) );
+		$n = friends_add_friend( $u1, $u2, true );
+		remove_filter( 'friends_friendship_accepted', array( $this, 'friends_friendship_filter_callback' ) );
+
+		$this->assertSame( 'friends_friendship_accepted', $this->filter_fired );
+	}
+
+	public function friends_friendship_filter_callback( $value ) {
+		$this->filter_fired = current_filter();
+		return $value;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/friends/notifications.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/friends/notifications.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/friends/notifications.php	(revision 10410)
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @group friends
+ */
+class BP_Tests_Friends_Notifications extends BP_UnitTestcase {
+	protected $filter_fired;
+	protected $current_user;
+	protected $friend;
+
+	public function setUp() {
+		parent::setUp();
+		$this->current_user = get_current_user_id();
+		$this->set_current_user( $this->factory->user->create() );
+
+		$this->friend = $this->factory->user->create();
+		$this->filter_fired = '';
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->current_user );
+	}
+
+	/**
+	 * @group friends_format_notifications
+	 */
+	public function test_friends_format_notifications_bp_friends_multiple_friendship_accepted_notification_filter() {
+		add_filter( 'bp_friends_multiple_friendship_accepted_notification', array( $this, 'notification_filter_callback' ) );
+		$n = friends_format_notifications( 'friendship_accepted', $this->friend, 0, 5 );
+		remove_filter( 'bp_friends_multiple_friendship_accepted_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_friends_multiple_friendship_accepted_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group friends_format_notifications
+	 */
+	public function test_friends_format_notifications_bp_friends_single_friendship_accepted_notification_filter() {
+		add_filter( 'bp_friends_single_friendship_accepted_notification', array( $this, 'notification_filter_callback' ) );
+		$n = friends_format_notifications( 'friendship_accepted', $this->friend, 0, 1 );
+		remove_filter( 'bp_friends_single_friendship_accepted_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_friends_single_friendship_accepted_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group friends_format_notifications
+	 */
+	public function test_friends_format_notifications_bp_friends_multiple_friendship_request_notification_filter() {
+		add_filter( 'bp_friends_multiple_friendship_request_notification', array( $this, 'notification_filter_callback' ) );
+		$n = friends_format_notifications( 'friendship_request', $this->friend, 0, 5 );
+		remove_filter( 'bp_friends_multiple_friendship_request_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_friends_multiple_friendship_request_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group friends_format_notifications
+	 */
+	public function test_friends_format_notifications_bp_friends_single_friendship_request_notification_filter() {
+		add_filter( 'bp_friends_single_friendship_request_notification', array( $this, 'notification_filter_callback' ) );
+		$n = friends_format_notifications( 'friendship_request', $this->friend, 0, 1 );
+		remove_filter( 'bp_friends_single_friendship_request_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_friends_single_friendship_request_notification', $this->filter_fired );
+	}
+
+	public function notification_filter_callback( $value ) {
+		$this->filter_fired = current_filter();
+		return $value;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/activity.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/activity.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/activity.php	(revision 10410)
@@ -0,0 +1,163 @@
+<?php
+
+/**
+ * @group groups
+ * @group activity
+ */
+class BP_Tests_Groups_Activity extends BP_UnitTestCase {
+	/**
+	 * @group activity_action
+	 * @group bp_groups_format_activity_action_created_group
+	 */
+	public function test_bp_groups_format_activity_action_created_group() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->groups->id,
+			'type' => 'created_group',
+			'user_id' => $u,
+			'item_id' => $g,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+		$g_obj = groups_get_group( array( 'group_id' => $g, ) );
+
+		$expected = sprintf( __( '%s created the group %s', 'buddypress' ), bp_core_get_userlink( $u ),  '<a href="' . bp_get_group_permalink( $g_obj ) . '">' . $g_obj->name . '</a>' );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_groups_format_activity_action_joined_group
+	 */
+	public function test_bp_groups_format_activity_action_joined_group() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->groups->id,
+			'type' => 'joined_group',
+			'user_id' => $u,
+			'item_id' => $g,
+		) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+		$g_obj = groups_get_group( array( 'group_id' => $g, ) );
+
+		$expected = sprintf( __( '%s joined the group %s', 'buddypress' ), bp_core_get_userlink( $u ),  '<a href="' . bp_get_group_permalink( $g_obj ) . '">' . $g_obj->name . '</a>' );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_groups_format_activity_action_group_details_updated
+	 */
+	public function test_bp_groups_format_activity_action_group_details_updated_with_no_change() {
+		$group = $this->factory->group->create_and_get();
+		groups_edit_base_group_details( $group->id, $group->name, $group->description, true );
+
+		$a = bp_activity_get( array(
+			'component' => buddypress()->groups->id,
+			'action' => 'group_details_updated',
+			'item_id' => $group->id,
+		) );
+
+		$this->assertTrue( empty( $a['activities'] ) );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_groups_format_activity_action_group_details_updated
+	 */
+	public function test_bp_groups_format_activity_action_group_details_updated_with_notify_members_false() {
+		$group = $this->factory->group->create_and_get();
+		groups_edit_base_group_details( $group->id, 'Foo', $group->description, false );
+
+		$a = bp_activity_get( array(
+			'component' => buddypress()->groups->id,
+			'action' => 'group_details_updated',
+			'item_id' => $group->id,
+		) );
+
+		$this->assertTrue( empty( $a['activities'] ) );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_groups_format_activity_action_group_details_updated
+	 */
+	public function test_bp_groups_format_activity_action_group_details_updated_with_updated_name() {
+		$old_user = get_current_user_id();
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		$group = $this->factory->group->create_and_get();
+		groups_edit_base_group_details( $group->id, 'Foo', $group->description, true );
+
+		$a = bp_activity_get( array(
+			'component' => buddypress()->groups->id,
+			'action' => 'group_details_updated',
+			'item_id' => $group->id,
+		) );
+
+		$this->assertNotEmpty( $a['activities'] );
+
+		$expected = sprintf( __( '%s changed the name of the group %s from "%s" to "%s"', 'buddypress' ), bp_core_get_userlink( $u ),  '<a href="' . bp_get_group_permalink( $group ) . '">Foo</a>', $group->name, 'Foo' );
+		$this->assertSame( $expected, $a['activities'][0]->action );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_groups_format_activity_action_group_details_updated
+	 */
+	public function test_bp_groups_format_activity_action_group_details_updated_with_updated_description() {
+		$old_user = get_current_user_id();
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		$group = $this->factory->group->create_and_get();
+		groups_edit_base_group_details( $group->id, $group->name, 'Bar', true );
+
+		$a = bp_activity_get( array(
+			'component' => buddypress()->groups->id,
+			'action' => 'group_details_updated',
+			'item_id' => $group->id,
+		) );
+
+		$this->assertNotEmpty( $a['activities'] );
+
+		$expected = sprintf( __( '%s changed the description of the group %s from "%s" to "%s"', 'buddypress' ), bp_core_get_userlink( $u ),  '<a href="' . bp_get_group_permalink( $group ) . '">' . $group->name . '</a>', $group->description, 'Bar' );
+		$this->assertSame( $expected, $a['activities'][0]->action );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_groups_format_activity_action_group_details_updated
+	 */
+	public function test_bp_groups_format_activity_action_group_details_updated_with_updated_name_and_description() {
+		$old_user = get_current_user_id();
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		$group = $this->factory->group->create_and_get();
+		groups_edit_base_group_details( $group->id, 'Foo', 'Bar', true );
+
+		$a = bp_activity_get( array(
+			'component' => buddypress()->groups->id,
+			'action' => 'group_details_updated',
+			'item_id' => $group->id,
+		) );
+
+		$this->assertNotEmpty( $a['activities'] );
+
+		$expected = sprintf( __( '%s changed the name and description of the group %s', 'buddypress' ), bp_core_get_userlink( $u ),  '<a href="' . bp_get_group_permalink( $group ) . '">Foo</a>' );
+		$this->assertSame( $expected, $a['activities'][0]->action );
+
+		$this->set_current_user( $old_user );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/avatars.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/avatars.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/avatars.php	(revision 10410)
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @group avatars
+ * @group groups
+ */
+class BP_Tests_Groups_Avatars extends BP_UnitTestCase {
+	/**
+	 * @group bp_get_group_has_avatar
+	 */
+	public function test_bp_get_group_has_avatar_no_avatar_uploaded() {
+		$g = $this->factory->group->create();
+		$this->assertFalse( bp_get_group_has_avatar( $g ) );
+	}
+
+	/**
+	 * @group bp_get_group_has_avatar
+	 */
+	public function test_bp_get_group_has_avatar_has_avatar_uploaded() {
+		$g = $this->factory->group->create();
+
+		// Fake it
+		add_filter( 'bp_core_fetch_avatar_url', array( $this, 'avatar_cb' ) );
+
+		$this->assertTrue( bp_get_group_has_avatar( $g ) );
+
+		remove_filter( 'bp_core_fetch_avatar_url', array( $this, 'avatar_cb' ) );
+	}
+
+	public function avatar_cb() {
+		return 'foo';
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/cache.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/cache.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/cache.php	(revision 10410)
@@ -0,0 +1,222 @@
+<?php
+
+/**
+ * @group groups
+ * @group cache
+ */
+class BP_Tests_Group_Cache extends BP_UnitTestCase {
+	/**
+	 * @group bp_groups_update_meta_cache
+	 */
+	public function test_bp_groups_update_meta_cache() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+
+		$time = bp_core_current_time();
+
+		// Set up some data
+		groups_update_groupmeta( $g1, 'total_member_count', 4 );
+		groups_update_groupmeta( $g1, 'last_activity', $time );
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+
+		groups_update_groupmeta( $g2, 'total_member_count', 81 );
+		groups_update_groupmeta( $g2, 'last_activity', $time );
+		groups_update_groupmeta( $g2, 'foo', 'baz' );
+
+		// Prime the cache for $g1
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+		groups_get_groupmeta( $g1, 'foo' );
+
+		// Ensure an empty cache for $g2
+		wp_cache_delete( $g2, 'group_meta' );
+
+		bp_groups_update_meta_cache( array( $g1, $g2 ) );
+
+		$expected = array(
+			$g1 => array(
+				'total_member_count' => array(
+					4,
+				),
+				'last_activity' => array(
+					$time,
+				),
+				'foo' => array(
+					'bar',
+				),
+			),
+			$g2 => array(
+				'total_member_count' => array(
+					81,
+				),
+				'last_activity' => array(
+					$time,
+				),
+				'foo' => array(
+					'baz',
+				),
+			),
+		);
+
+		$found = array(
+			$g1 => wp_cache_get( $g1, 'group_meta' ),
+			$g2 => wp_cache_get( $g2, 'group_meta' ),
+		);
+
+		$this->assertEquals( $expected, $found );
+	}
+
+	/**
+	 * @group groups_update_groupmeta
+	 * @group groups_delete_group_cache_on_metadata_change
+	 */
+	public function test_bp_groups_delete_group_cache_on_metadata_add() {
+		$g = $this->factory->group->create();
+
+		// Prime cache
+		groups_get_group( array( 'group_id' => $g, ) );
+
+		$this->assertNotEmpty( wp_cache_get( $g, 'bp_groups' ) );
+
+		// Trigger flush
+		groups_update_groupmeta( $g, 'foo', 'bar' );
+
+		$this->assertFalse( wp_cache_get( $g, 'bp_groups' ) );
+	}
+
+	/**
+	 * @group groups_update_groupmeta
+	 * @group groups_delete_group_cache_on_metadata_change
+	 */
+	public function test_bp_groups_delete_group_cache_on_metadata_change() {
+		$g = $this->factory->group->create();
+
+		// Prime cache
+		groups_update_groupmeta( $g, 'foo', 'bar' );
+		groups_get_group( array( 'group_id' => $g ) );
+
+		$this->assertNotEmpty( wp_cache_get( $g, 'bp_groups' ) );
+
+		// Trigger flush
+		groups_update_groupmeta( $g, 'foo', 'baz' );
+		$this->assertFalse( wp_cache_get( $g, 'bp_groups' ) );
+	}
+
+	/**
+	 * @group bp_groups_prefetch_activity_object_data
+	 */
+	public function test_bp_groups_prefetch_activity_object_data_all_cached() {
+		$g = $this->factory->group->create();
+
+		// Prime cache
+		groups_get_group( array( 'group_id' => $g ) );
+
+		// fake an activity
+		$a = new stdClass;
+		$a->component = buddypress()->groups->id;
+		$a->item_id = $g;
+		$activities = array(
+			$a,
+		);
+
+		bp_groups_prefetch_activity_object_data( $activities );
+
+		// This assertion is not really necessary - just checks to see
+		// whether a fatal error has occurred above
+		$this->assertNotEmpty( wp_cache_get( $g, 'bp_groups' ) );
+	}
+
+	/**
+	 * @group bp_groups_prefetch_activity_object_data
+	 */
+	public function test_bp_groups_prefetch_activity_object_data_some_cached() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+
+		// Prime cache
+		groups_get_group( array( 'group_id' => $g1 ) );
+
+		// fake activities
+		$a1 = new stdClass;
+		$a1->component = buddypress()->groups->id;
+		$a1->item_id = $g1;
+
+		$a2 = new stdClass;
+		$a2->component = buddypress()->groups->id;
+		$a2->item_id = $g2;
+
+		$activities = array(
+			$a1,
+			$a2,
+		);
+
+		bp_groups_prefetch_activity_object_data( $activities );
+
+		$this->assertNotEmpty( wp_cache_get( $g1, 'bp_groups' ) );
+		$this->assertNotEmpty( wp_cache_get( $g2, 'bp_groups' ) );
+	}
+
+	/**
+	 * @group groups_get_group_admins
+	 */
+	public function test_groups_get_group_admins_cache() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		// User 2 joins the group
+		groups_join_group( $g, $u2 );
+
+		// prime cache
+		groups_get_group_admins( $g );
+
+		// promote user 2 to an admin
+		bp_update_is_item_admin( true );
+		groups_promote_member( $u2, $g, 'admin' );
+
+		// assert that cache is invalidated
+		$this->assertEmpty( wp_cache_get( $g, 'bp_group_admins' ) );
+
+		// assert new cached value
+		$this->assertEquals( 2, count( groups_get_group_admins( $g ) ) );
+	}
+
+	/**
+	 * @group groups_get_group_admins
+	 */
+	public function test_groups_get_group_admins_cache_on_member_save() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		// prime cache
+		groups_get_group_admins( $g );
+
+		// promote user 2 to an admin via BP_Groups_Member::save()
+		self::add_user_to_group( $u2, $g, array( 'is_admin' => 1 ) );
+
+		// assert that cache is invalidated
+		$this->assertEmpty( wp_cache_get( $g, 'bp_group_admins' ) );
+
+		// assert new cached value
+		$this->assertEquals( 2, count( groups_get_group_admins( $g ) ) );
+	}
+
+	/**
+	 * @group groups_get_total_group_count
+	 * @group counts
+	 */
+	public function test_groups_get_total_group_count_should_respect_cached_value_of_0() {
+		global $wpdb;
+
+		// prime cache
+		// no groups are created by default, so count is zero
+		groups_get_total_group_count();
+		$first_query_count = $wpdb->num_queries;
+
+		// run function again
+		groups_get_total_group_count();
+
+		// check if function references cache or hits the DB by comparing query count
+		$this->assertEquals( $first_query_count, $wpdb->num_queries );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-group-extension.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-group-extension.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-group-extension.php	(revision 10410)
@@ -0,0 +1,844 @@
+<?php
+
+include_once BP_TESTS_DIR . '/assets/group-extensions.php';
+
+/**
+ * @group groups
+ * @group BP_Group_Extension
+ */
+class BP_Tests_Group_Extension_TestCases extends BP_UnitTestCase {
+	public function test_parse_legacy_properties() {
+		$class_name = 'BPTest_Group_Extension_Parse_Legacy_Properties';
+		$class_slug = sanitize_title( $class_name );
+		$e = new $class_name();
+		$e->_register();
+
+		// Test most items separately so we can ignore irrelevant props
+		$l = $e->_get_legacy_properties_converted();
+		$this->assertEquals( $l['name'], $class_name );
+		$this->assertEquals( $l['slug'], $class_slug );
+		$this->assertEquals( $l['visibility'], 'private' );
+		$this->assertEquals( $l['nav_item_position'], 63 );
+		$this->assertEquals( $l['enable_nav_item'], true );
+		$this->assertEquals( $l['nav_item_name'], $class_name . ' Nav' );
+		$this->assertEquals( $l['display_hook'], 'foo_hook' );
+		$this->assertEquals( $l['template_file'], 'foo_template' );
+
+		// Build the screens array manually
+		$expected = array(
+			'create' => array(
+				'name' => $class_name . ' Create',
+				'slug' => $class_slug . '-create',
+				'position' => 58,
+				'enabled' => false,
+			),
+			'edit' => array(
+				'name' => $class_name . ' Edit',
+				'slug' => $class_slug . '-edit',
+				'enabled' => false,
+			),
+			'admin' => array(
+				'enabled' => true,
+				'metabox_context' => 'high',
+				'metabox_priority' => 'side',
+			),
+		);
+
+		$this->assertEquals( $expected, $l['screens'] );
+	}
+
+	public function test_setup_screens_use_global_fallbacks() {
+		$class_name = 'BPTest_Group_Extension_Setup_Screens_Use_Global_Fallbacks';
+		$e = new $class_name();
+		$e->_setup_class_info();
+		$screens = $e->_get_default_screens();
+
+		$fallback = array(
+			'screen_callback' => array( $e, 'settings_screen' ),
+			'screen_save_callback' => array( $e, 'settings_screen_save' ),
+		);
+		$fallbacks = array(
+			'create' => $fallback,
+			'edit' => $fallback,
+			'admin' => $fallback,
+		);
+
+		// strip everything from the screens array but what we we're
+		// testing
+		foreach ( $screens as &$screen ) {
+			foreach ( $screen as $k => $v ) {
+				if ( ! in_array( $k, array( 'screen_callback', 'screen_save_callback' ) ) ) {
+					unset( $screen[ $k ] );
+				}
+			}
+		}
+
+		$this->assertEquals( $fallbacks, $screens );
+	}
+
+	public function test_setup_screens_define_edit_screens_locally() {
+		$class_name = 'BPTest_Group_Extension_Setup_Screens_Define_Edit_Screens_Locally';
+		$e = new $class_name();
+		$e->_setup_class_info();
+		$screens = $e->_get_default_screens();
+
+		$fallback = array(
+			'screen_callback' => array( $e, 'settings_screen' ),
+			'screen_save_callback' => array( $e, 'settings_screen_save' ),
+		);
+		$expected = array(
+			'create' => $fallback,
+			'edit' => array(
+				'screen_callback' => array( $e, 'edit_screen' ),
+				'screen_save_callback' => array( $e, 'edit_screen_save' ),
+			),
+			'admin' => $fallback,
+		);
+
+		// strip everything from the screens array but what we we're
+		// testing
+		foreach ( $screens as &$screen ) {
+			foreach ( $screen as $k => $v ) {
+				if ( ! in_array( $k, array( 'screen_callback', 'screen_save_callback' ) ) ) {
+					unset( $screen[ $k ] );
+				}
+			}
+		}
+
+		$this->assertEquals( $screens, $expected );
+	}
+
+	public function test_parse_args_r() {
+		$a = array(
+			'veggies' => 'yes',
+			'ice_cream' => 'dope',
+			'fruit' => array(
+				'apple' => 'gross',
+				'berries' => array(
+					'blueberries' => array(
+						'in_season' => 'never',
+						'oh' => 'boy',
+					),
+					'cherries' => 'sometimes',
+				),
+			),
+		);
+
+		$b = array(
+			'veggies' => 'no',
+			'cheese' => array(
+				'cheddar' => 'good',
+			),
+			'fruit' => array(
+				'apple' => 'yum',
+				'berries' => array(
+					'strawberries' => 'awesome',
+					'blueberries' => array(
+						'in_season' => 'yes',
+						'out_of_season' => 'no',
+					),
+				),
+			),
+		);
+
+		$expected = array(
+			'veggies' => 'yes',
+			'ice_cream' => 'dope',
+			'cheese' => array(
+				'cheddar' => 'good',
+			),
+			'fruit' => array(
+				'apple' => 'gross',
+				'berries' => array(
+					'strawberries' => 'awesome',
+					'blueberries' => array(
+						'in_season' => 'never',
+						'out_of_season' => 'no',
+						'oh' => 'boy',
+					),
+					'cherries' => 'sometimes',
+				),
+			),
+		);
+
+		$this->assertEquals( $expected, BP_Group_Extension::parse_args_r( $a, $b ) );
+	}
+
+	/**
+	 * Config that gets intentionally stored as a direct property of object
+	 */
+	public function test_access_root_property() {
+		$class_name = 'BPTest_Group_Extension_Access_Root_Property';
+		$e = new $class_name();
+		$e->_register();
+
+		$this->assertEquals( 39, $e->nav_item_position );
+	}
+
+	/**
+	 * Config that gets registered using init(), but is then accessed via
+	 * the legacy location
+	 */
+	public function test_access_init_property_using_legacy_location() {
+		$class_name = 'BPTest_Group_Extension_Access_Init_Property_Using_Legacy_Location';
+		$e = new $class_name();
+		$e->_register();
+
+		$this->assertEquals( 18, $e->create_step_position );
+	}
+
+	/**
+	 * Provides settings_screen* and edit_screen*
+	 */
+	public function test_get_screen_callback_fallbacks() {
+		$class_name = 'BPTest_Group_Extension_Get_Screen_Callback_Fallbacks';
+		$e = new $class_name();
+		$e->_register();
+
+		$this->assertEquals( array( $e, 'settings_screen' ), $e->screens['create']['screen_callback'] );
+		$this->assertEquals( array( $e, 'settings_screen_save' ), $e->screens['create']['screen_save_callback'] );
+		$this->assertEquals( array( $e, 'settings_screen' ), $e->screens['admin']['screen_callback'] );
+		$this->assertEquals( array( $e, 'settings_screen_save' ), $e->screens['admin']['screen_save_callback'] );
+		$this->assertEquals( array( $e, 'edit_screen' ), $e->screens['edit']['screen_callback'] );
+		$this->assertEquals( array( $e, 'edit_screen_save' ), $e->screens['edit']['screen_save_callback'] );
+	}
+
+	public function test_has_submit_button() {
+		$a = '<p>Foo bar</p><input type="text" name="awesome" /><input name="save" type="submit" id="saverrrr" />sweet';
+		$this->assertTrue( BP_Group_Extension::has_submit_button( $a ) );
+
+		$b = '<p>Foo bar</p><input type="text" name="awesome" />sweet';
+		$this->assertFalse( BP_Group_Extension::has_submit_button( $b ) );
+
+		// switch the quotation marks
+		$c = "<p>Foo bar</p><input type='text' name='awesome' /><input name='save' type='submit' id='saverrrr' />sweet";
+		$this->assertTrue( BP_Group_Extension::has_submit_button( $c ) );
+	}
+
+	/**
+	 * @group enable_nav_item
+	 */
+	public function test_enable_nav_item_true() {
+		$old_options_nav = buddypress()->bp_options_nav;
+
+		$g = $this->factory->group->create();
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$class_name = 'BPTest_Group_Extension_Enable_Nav_Item_True';
+		$e = new $class_name();
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e->_register();
+
+		$this->assertTrue( isset( buddypress()->bp_options_nav[ $g_obj->slug ][ $e->slug ] ) );
+
+		// Clean up
+		buddypress()->bp_options_nav = $old_options_nav;
+	}
+
+	/**
+	 * @group enable_nav_item
+	 */
+	public function test_enable_nav_item_false() {
+		$old_options_nav = buddypress()->bp_options_nav;
+
+		$g = $this->factory->group->create();
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$class_name = 'BPTest_Group_Extension_Enable_Nav_Item_False';
+		$e = new $class_name();
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e->_register();
+
+		$this->assertFalse( isset( buddypress()->bp_options_nav[ $g_obj->slug ][ $e->slug ] ) );
+
+		// Clean up
+		buddypress()->bp_options_nav = $old_options_nav;
+	}
+
+	/**
+	 * @group visibility
+	 */
+	public function test_visibility_private() {
+		$old_options_nav = buddypress()->bp_options_nav;
+		$old_current_user = get_current_user_id();
+
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$class_name = 'BPTest_Group_Extension_Visibility_Private';
+		$e = new $class_name();
+
+		// Test as non-logged-in user
+		$this->set_current_user( 0 );
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+		$e->_register();
+		$this->assertFalse( isset( buddypress()->bp_options_nav[ $g_obj->slug ][ $e->slug ] ) );
+
+		// Clean up
+		buddypress()->bp_options_nav = $old_options_nav;
+
+		// Test as group member
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+		$this->add_user_to_group( $u, $g );
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+		$e->_register();
+		$this->assertTrue( isset( buddypress()->bp_options_nav[ $g_obj->slug ][ $e->slug ] ) );
+
+		// Clean up
+		buddypress()->bp_options_nav = $old_options_nav;
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group visibility
+	 *
+	 * visibility=public + status=private results in adding the item to
+	 * the nav. However, BP_Groups_Component::setup_globals() bounces the
+	 * user away from this page on a regular pageload (BP 2.0 and under)
+	 *
+	 * @see https://buddypress.trac.wordpress.org/ticket/4785
+	 */
+	public function test_visibility_public() {
+		$old_options_nav = buddypress()->bp_options_nav;
+		$old_current_user = get_current_user_id();
+
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$class_name = 'BPTest_Group_Extension_Visibility_Public';
+		$e = new $class_name();
+
+		// Test as non-logged-in user
+		$this->set_current_user( 0 );
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+		$e->_register();
+		$this->assertTrue( isset( buddypress()->bp_options_nav[ $g_obj->slug ][ $e->slug ] ) );
+
+		// Clean up
+		buddypress()->bp_options_nav = $old_options_nav;
+
+		// Test as group member
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+		$this->add_user_to_group( $u, $g );
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+		$e->_register();
+		$this->assertTrue( isset( buddypress()->bp_options_nav[ $g_obj->slug ][ $e->slug ] ) );
+
+		// Clean up
+		buddypress()->bp_options_nav = $old_options_nav;
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_visit
+	 */
+	public function test_user_can_visit_inferred_from_enable_nav_item() {
+		$old_current_user = get_current_user_id();
+
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$this->set_current_user( 0 );
+
+		$e = new BPTest_Group_Extension_Inferred_Access_Settings_EnableNavItem_True();
+		$e->_register();
+		$this->assertTrue( $e->user_can_visit() );
+
+		$e2 = new BPTest_Group_Extension_Inferred_Access_Settings_EnableNavItem_False();
+		$e2->_register();
+		$this->assertFalse( $e2->user_can_visit() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_visit
+	 */
+	public function test_user_can_visit_explicit_for_logged_out_user() {
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( 0 );
+
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_Access_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_visit() );
+
+		$e2 = new BPTest_Group_Extension_Access_Loggedin();
+		$e2->_register();
+		$this->assertFalse( $e2->user_can_visit() );
+
+		$e3 = new BPTest_Group_Extension_Access_Member();
+		$e3->_register();
+		$this->assertFalse( $e3->user_can_visit() );
+
+		$e4 = new BPTest_Group_Extension_Access_AdminMod();
+		$e4->_register();
+		$this->assertFalse( $e4->user_can_visit() );
+
+		$e5 = new BPTest_Group_Extension_Access_Admin();
+		$e5->_register();
+		$this->assertFalse( $e5->user_can_visit() );
+
+		$e6 = new BPTest_Group_Extension_Access_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_visit() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_visit
+	 */
+	public function test_user_can_visit_explicit_for_logged_in_user() {
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_Access_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_visit() );
+
+		$e2 = new BPTest_Group_Extension_Access_Loggedin();
+		$e2->_register();
+		$this->assertTrue( $e2->user_can_visit() );
+
+		$e3 = new BPTest_Group_Extension_Access_Member();
+		$e3->_register();
+		$this->assertFalse( $e3->user_can_visit() );
+
+		$e4 = new BPTest_Group_Extension_Access_AdminMod();
+		$e4->_register();
+		$this->assertFalse( $e4->user_can_visit() );
+
+		$e5 = new BPTest_Group_Extension_Access_Admin();
+		$e5->_register();
+		$this->assertFalse( $e5->user_can_visit() );
+
+		$e6 = new BPTest_Group_Extension_Access_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_visit() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_visit
+	 */
+	public function test_user_can_visit_explicit_for_group_member() {
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->add_user_to_group( $u, $g );
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_Access_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_visit() );
+
+		$e2 = new BPTest_Group_Extension_Access_Loggedin();
+		$e2->_register();
+		$this->assertTrue( $e2->user_can_visit() );
+
+		$e3 = new BPTest_Group_Extension_Access_Member();
+		$e3->_register();
+		$this->assertTrue( $e3->user_can_visit() );
+
+		$e4 = new BPTest_Group_Extension_Access_AdminMod();
+		$e4->_register();
+		$this->assertFalse( $e4->user_can_visit() );
+
+		$e5 = new BPTest_Group_Extension_Access_Admin();
+		$e5->_register();
+		$this->assertFalse( $e5->user_can_visit() );
+
+		$e6 = new BPTest_Group_Extension_Access_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_visit() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_visit
+	 */
+	public function test_user_can_visit_explicit_for_group_mod() {
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$m = $this->add_user_to_group( $u, $g );
+		$gm = new BP_Groups_Member( $u, $g );
+		$gm->promote( 'mod' );
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_Access_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_visit() );
+
+		$e2 = new BPTest_Group_Extension_Access_Loggedin();
+		$e2->_register();
+		$this->assertTrue( $e2->user_can_visit() );
+
+		$e3 = new BPTest_Group_Extension_Access_Member();
+		$e3->_register();
+		$this->assertTrue( $e3->user_can_visit() );
+
+		$e4 = new BPTest_Group_Extension_Access_AdminMod();
+		$e4->_register();
+		$this->assertTrue( $e4->user_can_visit() );
+
+		$e5 = new BPTest_Group_Extension_Access_Admin();
+		$e5->_register();
+		$this->assertFalse( $e5->user_can_visit() );
+
+		$e6 = new BPTest_Group_Extension_Access_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_visit() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_visit
+	 */
+	public function test_user_can_visit_explicit_for_group_admin() {
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$m = $this->add_user_to_group( $u, $g );
+		$gm = new BP_Groups_Member( $u, $g );
+		$gm->promote( 'admin' );
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_Access_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_visit() );
+
+		$e2 = new BPTest_Group_Extension_Access_Loggedin();
+		$e2->_register();
+		$this->assertTrue( $e2->user_can_visit() );
+
+		$e3 = new BPTest_Group_Extension_Access_Member();
+		$e3->_register();
+		$this->assertTrue( $e3->user_can_visit() );
+
+		$e4 = new BPTest_Group_Extension_Access_AdminMod();
+		$e4->_register();
+		$this->assertTrue( $e4->user_can_visit() );
+
+		$e5 = new BPTest_Group_Extension_Access_Admin();
+		$e5->_register();
+		$this->assertTrue( $e5->user_can_visit() );
+
+		$e6 = new BPTest_Group_Extension_Access_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_visit() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_see_nav_item
+	 */
+	public function test_user_can_see_nav_item_implied() {
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( 0 );
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_Access_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_see_nav_item() );
+
+		$e2 = new BPTest_Group_Extension_Access_Loggedin();
+		$e2->_register();
+		$this->assertFalse( $e2->user_can_see_nav_item() );
+
+		$e3 = new BPTest_Group_Extension_Access_Member();
+		$e3->_register();
+		$this->assertFalse( $e3->user_can_see_nav_item() );
+
+		$e4 = new BPTest_Group_Extension_Access_AdminMod();
+		$e4->_register();
+		$this->assertFalse( $e4->user_can_see_nav_item() );
+
+		$e5 = new BPTest_Group_Extension_Access_Admin();
+		$e5->_register();
+		$this->assertFalse( $e5->user_can_see_nav_item() );
+
+		$e6 = new BPTest_Group_Extension_Access_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_visit() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_see_nav_item
+	 */
+	public function test_user_can_see_nav_item_explicit_for_logged_out_user() {
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( 0 );
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_ShowTab_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_see_nav_item() );
+
+		$e2 = new BPTest_Group_Extension_ShowTab_Loggedin();
+		$e2->_register();
+		$this->assertFalse( $e2->user_can_see_nav_item() );
+
+		$e3 = new BPTest_Group_Extension_ShowTab_Member();
+		$e3->_register();
+		$this->assertFalse( $e3->user_can_see_nav_item() );
+
+		$e4 = new BPTest_Group_Extension_ShowTab_AdminMod();
+		$e4->_register();
+		$this->assertFalse( $e4->user_can_see_nav_item() );
+
+		$e5 = new BPTest_Group_Extension_ShowTab_Admin();
+		$e5->_register();
+		$this->assertFalse( $e5->user_can_see_nav_item() );
+
+		$e6 = new BPTest_Group_Extension_ShowTab_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_see_nav_item() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_see_nav_item
+	 */
+	public function test_user_can_see_nav_item_explicit_for_logged_in_user() {
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_ShowTab_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_see_nav_item() );
+
+		$e2 = new BPTest_Group_Extension_ShowTab_Loggedin();
+		$e2->_register();
+		$this->assertTrue( $e2->user_can_see_nav_item() );
+
+		$e3 = new BPTest_Group_Extension_ShowTab_Member();
+		$e3->_register();
+		$this->assertFalse( $e3->user_can_see_nav_item() );
+
+		$e4 = new BPTest_Group_Extension_ShowTab_AdminMod();
+		$e4->_register();
+		$this->assertFalse( $e4->user_can_see_nav_item() );
+
+		$e5 = new BPTest_Group_Extension_ShowTab_Admin();
+		$e5->_register();
+		$this->assertFalse( $e5->user_can_see_nav_item() );
+
+		$e6 = new BPTest_Group_Extension_ShowTab_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_see_nav_item() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_see_nav_item
+	 */
+	public function test_user_can_see_nav_item_explicit_for_group_member() {
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->add_user_to_group( $u, $g );
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_ShowTab_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_see_nav_item() );
+
+		$e2 = new BPTest_Group_Extension_ShowTab_Loggedin();
+		$e2->_register();
+		$this->assertTrue( $e2->user_can_see_nav_item() );
+
+		$e3 = new BPTest_Group_Extension_ShowTab_Member();
+		$e3->_register();
+		$this->assertTrue( $e3->user_can_see_nav_item() );
+
+		$e4 = new BPTest_Group_Extension_ShowTab_AdminMod();
+		$e4->_register();
+		$this->assertFalse( $e4->user_can_see_nav_item() );
+
+		$e5 = new BPTest_Group_Extension_ShowTab_Admin();
+		$e5->_register();
+		$this->assertFalse( $e5->user_can_see_nav_item() );
+
+		$e6 = new BPTest_Group_Extension_ShowTab_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_see_nav_item() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_see_nav_item
+	 */
+	public function test_user_can_see_nav_item_explicit_for_group_mod() {
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->add_user_to_group( $u, $g );
+		$gm = new BP_Groups_Member( $u, $g );
+		$gm->promote( 'mod' );
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_ShowTab_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_see_nav_item() );
+
+		$e2 = new BPTest_Group_Extension_ShowTab_Loggedin();
+		$e2->_register();
+		$this->assertTrue( $e2->user_can_see_nav_item() );
+
+		$e3 = new BPTest_Group_Extension_ShowTab_Member();
+		$e3->_register();
+		$this->assertTrue( $e3->user_can_see_nav_item() );
+
+		$e4 = new BPTest_Group_Extension_ShowTab_AdminMod();
+		$e4->_register();
+		$this->assertTrue( $e4->user_can_see_nav_item() );
+
+		$e5 = new BPTest_Group_Extension_ShowTab_Admin();
+		$e5->_register();
+		$this->assertFalse( $e5->user_can_see_nav_item() );
+
+		$e6 = new BPTest_Group_Extension_ShowTab_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_see_nav_item() );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group user_can_see_nav_item
+	 */
+	public function test_user_can_see_nav_item_explicit_for_group_admin() {
+		$g = $this->factory->group->create( array(
+			'status' => 'public',
+		) );
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+
+		$u = $this->factory->user->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->add_user_to_group( $u, $g );
+		$gm = new BP_Groups_Member( $u, $g );
+		$gm->promote( 'admin' );
+
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+
+		$e1 = new BPTest_Group_Extension_ShowTab_Anyone();
+		$e1->_register();
+		$this->assertTrue( $e1->user_can_see_nav_item() );
+
+		$e2 = new BPTest_Group_Extension_ShowTab_Loggedin();
+		$e2->_register();
+		$this->assertTrue( $e2->user_can_see_nav_item() );
+
+		$e3 = new BPTest_Group_Extension_ShowTab_Member();
+		$e3->_register();
+		$this->assertTrue( $e3->user_can_see_nav_item() );
+
+		$e4 = new BPTest_Group_Extension_ShowTab_AdminMod();
+		$e4->_register();
+		$this->assertTrue( $e4->user_can_see_nav_item() );
+
+		$e5 = new BPTest_Group_Extension_ShowTab_Admin();
+		$e5->_register();
+		$this->assertTrue( $e5->user_can_see_nav_item() );
+
+		$e6 = new BPTest_Group_Extension_ShowTab_Noone();
+		$e6->_register();
+		$this->assertFalse( $e6->user_can_see_nav_item() );
+
+		$this->set_current_user( $old_current_user );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-group-member-query.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-group-member-query.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-group-member-query.php	(revision 10410)
@@ -0,0 +1,762 @@
+<?php
+/**
+ * @group groups
+ * @group BP_Group_Member_Query
+ */
+class BP_Tests_BP_Group_Member_Query_TestCases extends BP_UnitTestCase {
+
+	/**
+	 * Make sure that a manual 'include' param is parsed correctly with
+	 * BP_Group_Member_Query's limiting of the query to group members
+	 */
+	public function test_with_include() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+
+		$query = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'include' => array( $u2 ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query->results ) );
+		$this->assertEquals( array( $u2, ), $ids );
+	}
+
+	// Make sure we're falling back on 'member'
+	public function test_with_group_role_null() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+		$this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+
+		$query = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+		) );
+
+		$expected = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'member' ),
+		) );
+
+		$this->assertEquals( $expected->results, $query->results );
+	}
+
+	public function test_with_group_role_member() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+		$this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'member' ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u3, ), $ids );
+	}
+
+	public function test_with_group_role_mod() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+		$this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'mod' ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u2, ), $ids );
+	}
+
+	public function test_with_group_role_admin() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$g  = $this->factory->group->create( array(
+			'creator_id' => $u1
+		) );
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+		$this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'admin' ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1, ), $ids );
+	}
+
+	public function test_with_group_role_member_mod() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+		$this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'member', 'mod' ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u2, $u3, ), $ids );
+	}
+
+	public function test_with_group_role_member_admin() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$g  = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+		$this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'member', 'admin' ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1, $u3, ), $ids );
+	}
+
+	public function test_with_group_role_mod_admin() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$g  = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+		$this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'mod', 'admin' ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1, $u2, ), $ids );
+	}
+
+	public function test_with_group_role_member_mod_admin() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$g  = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+		$this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'member', 'mod', 'admin' ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1, $u2, $u3, ), $ids );
+	}
+
+	public function test_with_group_role_member_mod_admin_banned() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$u4 = $this->factory->user->create();
+		$g  = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+		$this->add_user_to_group( $u3, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+		$this->add_user_to_group( $u4, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ) ) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+		$m3 = new BP_Groups_Member( $u3, $g );
+		$m3->ban();
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'member', 'mod', 'admin', 'banned' ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1, $u2, $u3, $u4, ), $ids );
+	}
+
+	/**
+	 * @group role
+	 */
+	public function test_with_group_role_banned() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+		$this->add_user_to_group( $u2, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ) ) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->ban();
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'banned' ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1, ), $ids );
+	}
+
+	public function test_group_has_no_members_of_role_mod() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ) ) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'group_role' => array( 'mod' ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array(), $ids );
+	}
+
+	public function test_confirmed_members() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+			'is_confirmed' => 0,
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+			'is_confirmed' => 1,
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u2 ), $ids );
+	}
+
+	/**
+	 * @group type
+	 */
+	public function test_get_with_type_last_joined() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 500 ),
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'type' => 'last_joined',
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u2, $u1 ), $ids );
+	}
+
+	/**
+	 * @group type
+	 */
+	public function test_get_with_type_first_joined() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 500 ),
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'type' => 'first_joined',
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1, $u2 ), $ids );
+	}
+
+	/**
+	 * @group type
+	 * @group group_activity
+	 */
+	public function test_get_with_type_group_activity_with_activity_component_disabled() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$c = buddypress()->groups->id;
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 500 ),
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 400 ),
+		) );
+
+		$this->add_user_to_group( $u3, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ),
+		) );
+
+		$this->factory->activity->create( array(
+			'component' => $c,
+			'type' => 'activity_update',
+			'user_id' => $u3,
+			'item_id' => $g,
+			'recorded_time' => gmdate( 'Y-m-d H:i:s', $time - 250 ),
+		) );
+
+		$this->factory->activity->create( array(
+			'component' => $c,
+			'type' => 'activity_update',
+			'user_id' => $u1,
+			'item_id' => $g,
+			'recorded_time' => gmdate( 'Y-m-d H:i:s', $time - 200 ),
+		) );
+
+		$this->factory->activity->create( array(
+			'component' => $c,
+			'type' => 'activity_update',
+			'user_id' => $u2,
+			'item_id' => $g,
+			'recorded_time' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+		) );
+
+		// Deactivate activity component
+		$activity_active = isset( buddypress()->active_components['activity'] );
+		if ( $activity_active ) {
+			unset( buddypress()->active_components['activity'] );
+		}
+
+		$query_members1 = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'type' => 'group_activity',
+		) );
+
+		$query_members2 = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'type' => 'last_joined',
+		) );
+
+		if ( $activity_active ) {
+			buddypress()->active_components['activity'] = '1';
+		}
+
+		$this->assertSame( wp_list_pluck( $query_members2->results, 'ID' ), wp_list_pluck( $query_members1->results, 'ID' ) );
+	}
+
+	/**
+	 * @group type
+	 * @group group_activity
+	 */
+	public function test_get_with_type_group_activity() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$c = buddypress()->groups->id;
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 500 ),
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 400 ),
+		) );
+
+		$this->add_user_to_group( $u3, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ),
+		) );
+
+		$this->factory->activity->create( array(
+			'component' => $c,
+			'type' => 'activity_update',
+			'user_id' => $u3,
+			'item_id' => $g,
+			'recorded_time' => gmdate( 'Y-m-d H:i:s', $time - 250 ),
+		) );
+
+		$this->factory->activity->create( array(
+			'component' => $c,
+			'type' => 'activity_update',
+			'user_id' => $u1,
+			'item_id' => $g,
+			'recorded_time' => gmdate( 'Y-m-d H:i:s', $time - 200 ),
+		) );
+
+		$this->factory->activity->create( array(
+			'component' => $c,
+			'type' => 'activity_update',
+			'user_id' => $u2,
+			'item_id' => $g,
+			'recorded_time' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'type' => 'group_activity',
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u2, $u1, $u3 ), $ids );
+	}
+
+	/**
+	 * @group type
+	 * @group group_activity
+	 */
+	public function test_get_with_type_group_activity_no_dupes() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$c = buddypress()->groups->id;
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 500 ),
+		) );
+
+		$this->factory->activity->create( array(
+			'component' => $c,
+			'type' => 'activity_update',
+			'user_id' => $u1,
+			'item_id' => $g,
+			'recorded_time' => gmdate( 'Y-m-d H:i:s', $time - 250 ),
+		) );
+
+		$this->factory->activity->create( array(
+			'component' => $c,
+			'type' => 'activity_update',
+			'user_id' => $u1,
+			'item_id' => $g,
+			'recorded_time' => gmdate( 'Y-m-d H:i:s', $time - 200 ),
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'type' => 'group_activity',
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1, ), $ids );
+	}
+	/**
+	 * @group type
+	 */
+	public function test_get_with_type_alphabetical() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create( array(
+			'display_name' => 'AAA',
+		) );
+		$u2 = $this->factory->user->create( array(
+			'display_name' => 'CCC',
+		) );
+		$u3 = $this->factory->user->create( array(
+			'display_name' => 'BBB',
+		) );
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ),
+		) );
+
+		$this->add_user_to_group( $u3, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ),
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'type' => 'alphabetical',
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1, $u3, $u2 ), $ids );
+	}
+
+	/**
+	 * @group invite_sent
+	 */
+	public function test_with_invite_sent_true() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+			'is_confirmed' => 0,
+			'invite_sent' => 0,
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+			'is_confirmed' => 0,
+			'invite_sent' => 1,
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'is_confirmed' => false,
+			'invite_sent' => true,
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u2 ), $ids );
+	}
+
+	/**
+	 * @group invite_sent
+	 */
+	public function test_with_invite_sent_false() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+			'is_confirmed' => 0,
+			'invite_sent' => 0,
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+			'is_confirmed' => 0,
+			'invite_sent' => 1,
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'is_confirmed' => false,
+			'invite_sent' => false,
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1 ), $ids );
+	}
+
+	/**
+	 * @group inviter_id
+	 */
+	public function test_with_inviter_id_false() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+			'inviter_id' => 0,
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+			'inviter_id' => 1,
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'inviter_id' => false,
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u1 ), $ids );
+	}
+
+	/**
+	 * @group inviter_id
+	 */
+	public function test_with_inviter_id_specific() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$u4 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+			'inviter_id' => 0,
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ),
+			'inviter_id' => 1,
+		) );
+
+		$this->add_user_to_group( $u3, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ),
+			'inviter_id' => 6,
+		) );
+
+		$this->add_user_to_group( $u4, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 400 ),
+			'inviter_id' => 2,
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'inviter_id' => array( 2, 6 ),
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u3, $u4 ), $ids );
+	}
+
+	/**
+	 * @group inviter_id
+	 */
+	public function test_with_inviter_id_any() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$u4 = $this->factory->user->create();
+		$time = time();
+
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+			'inviter_id' => 0,
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 200 ),
+			'inviter_id' => 1,
+		) );
+
+		$this->add_user_to_group( $u3, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 300 ),
+			'inviter_id' => 6,
+		) );
+
+		$this->add_user_to_group( $u4, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time - 400 ),
+			'inviter_id' => 2,
+		) );
+
+		$query_members = new BP_Group_Member_Query( array(
+			'group_id' => $g,
+			'inviter_id' => 'any',
+		) );
+
+		$ids = wp_parse_id_list( array_keys( $query_members->results ) );
+		$this->assertEquals( array( $u2, $u3, $u4 ), $ids );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-groups-group.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-groups-group.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-groups-group.php	(revision 10410)
@@ -0,0 +1,1045 @@
+<?php
+/**
+ * @group groups
+ * @group BP_Groups_Group
+ */
+class BP_Tests_BP_Groups_Group_TestCases extends BP_UnitTestCase {
+
+	/** __construct()  ***************************************************/
+
+	/**
+	 * @group __construct
+	 */
+	public function test_non_existent_group() {
+		$group = new BP_Groups_Group( 123456789 );
+		$this->assertSame( 0, $group->id );
+	}
+
+	/** get() ************************************************************/
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_exclude() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'exclude' => array(
+				$g1,
+				'foobar',
+			),
+		) );
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g2 ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_include() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'include' => array(
+				$g1,
+				'foobar',
+			),
+		) );
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g1 ) );
+	}
+
+	/**
+	 * @group get
+	 * @group group_meta_query
+	 */
+	public function test_get_with_meta_query() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'meta_query' => array(
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+				),
+			),
+		) );
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g1 ) );
+	}
+
+	/**
+	 * @group get
+	 * @group group_meta_query
+	 */
+	public function test_get_empty_meta_query() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'meta_query' => array(),
+		) );
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g1, $g2, ) );
+	}
+
+	/**
+	 * @group get
+	 * @group group_meta_query
+	 */
+	public function test_get_with_meta_query_multiple_clauses() {
+		$now = time();
+		$g1 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60 ),
+		) );
+		$g2 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60*2 ),
+		) );
+		$g3 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60*3 ),
+		) );
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+		groups_update_groupmeta( $g2, 'foo', 'bar' );
+		groups_update_groupmeta( $g1, 'bar', 'barry' );
+
+		$groups = BP_Groups_Group::get( array(
+			'meta_query' => array(
+				'relation' => 'AND',
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+				),
+				array(
+					'key' => 'bar',
+					'value' => 'barry',
+				),
+			),
+		) );
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g1 ) );
+		$this->assertEquals( 1, $groups['total'] );
+	}
+
+	/**
+	 * @group get
+	 * @group group_meta_query
+	 */
+	public function test_get_with_meta_query_multiple_clauses_relation_or() {
+		$now = time();
+		$g1 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60 ),
+		) );
+		$g2 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60*2 ),
+		) );
+		$g3 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60*3 ),
+		) );
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+		groups_update_groupmeta( $g2, 'foo', 'baz' );
+		groups_update_groupmeta( $g3, 'bar', 'barry' );
+
+		$groups = BP_Groups_Group::get( array(
+			'meta_query' => array(
+				'relation' => 'OR',
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+				),
+				array(
+					'key' => 'bar',
+					'value' => 'barry',
+				),
+			),
+		) );
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEqualSets( array( $g1, $g3 ), $ids );
+		$this->assertEquals( 2, $groups['total'] );
+	}
+
+	/**
+	 * @group get
+	 * @group group_meta_query
+	 * @ticket BP5874
+	 */
+	public function test_get_with_meta_query_multiple_clauses_relation_or_shared_meta_key() {
+		$now = time();
+		$g1 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60 ),
+		) );
+		$g2 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60*2 ),
+		) );
+		$g3 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60*3 ),
+		) );
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+		groups_update_groupmeta( $g2, 'foo', 'baz' );
+		groups_update_groupmeta( $g3, 'foo', 'barry' );
+
+		$groups = BP_Groups_Group::get( array(
+			'meta_query' => array(
+				'relation' => 'OR',
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+				),
+				array(
+					'key' => 'foo',
+					'value' => 'baz',
+				),
+			),
+		) );
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEqualSets( array( $g1, $g2 ), $ids );
+		$this->assertEquals( 2, $groups['total'] );
+	}
+
+	/**
+	 * @group get
+	 * @group group_meta_query
+	 * @ticket BP5824
+	 */
+	public function test_get_with_meta_query_multiple_keys_with_same_value() {
+		$now = time();
+		$g1 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60 ),
+		) );
+		$g2 = $this->factory->group->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 60*60*2 ),
+		) );
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+		groups_update_groupmeta( $g2, 'foo2', 'bar' );
+
+		$groups = BP_Groups_Group::get( array(
+			'meta_query' => array(
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+					'compare' => '=',
+				),
+			),
+		) );
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g1 ) );
+		$this->assertEquals( 1, $groups['total'] );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_normal_search() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => 'This is one cool group',
+		) );
+		$g2 = $this->factory->group->create();
+
+		$groups = BP_Groups_Group::get( array(
+			'search_terms' => 'Cool',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_search_with_underscores() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => '_cool_ dude',
+		) );
+		$g2 = $this->factory->group->create();
+
+		$groups = BP_Groups_Group::get( array(
+			'search_terms' => '_cool_',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_search_with_percent_sign() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => '100% awesome',
+		) );
+		$g2 = $this->factory->group->create();
+
+		$groups = BP_Groups_Group::get( array(
+			'search_terms' => '100%',
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_search_with_quotes() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => "'tis sweet",
+		) );
+		$g2 = $this->factory->group->create();
+
+		$groups = BP_Groups_Group::get( array(
+			'search_terms' => "'tis ",
+		) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	/**
+	 * BP 1.8 will change the default 'type' param in favor of default
+	 * 'order' and 'orderby'. This is to make sure that existing plugins
+	 * will work appropriately
+	 *
+	 * @group get
+	 */
+	public function test_get_with_default_type_value_should_be_newest() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'A Group',
+			'date_created' => bp_core_current_time(),
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'D Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', time() - 100 ),
+		) );
+		$g3 = $this->factory->group->create( array(
+			'name' => 'B Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', time() - 100000 ),
+		) );
+		$g4 = $this->factory->group->create( array(
+			'name' => 'C Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', time() - 1000 ),
+		) );
+
+		$found = BP_Groups_Group::get();
+
+		$this->assertEquals( BP_Groups_Group::get( array( 'type' => 'newest' ) ), $found );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_type_newest() {
+		$time = time();
+		$g1 = $this->factory->group->create( array(
+			'name' => 'A Group',
+			'date_created' => bp_core_current_time(),
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'D Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+		) );
+		$g3 = $this->factory->group->create( array(
+			'name' => 'B Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $time - 100000 ),
+		) );
+		$g4 = $this->factory->group->create( array(
+			'name' => 'C Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $time - 1000 ),
+		) );
+
+		$groups = BP_Groups_Group::get( array( 'type' => 'newest' ) );
+		$found = wp_parse_id_list( wp_list_pluck( $groups['groups'], 'id' ) );
+		$this->assertEquals( array( $g1, $g2, $g4, $g3 ), $found );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_type_popular() {
+		$time = time();
+		$g1 = $this->factory->group->create( array(
+			'name' => 'A Group',
+			'date_created' => bp_core_current_time(),
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'D Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+		) );
+		$g3 = $this->factory->group->create( array(
+			'name' => 'B Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $time - 100000 ),
+		) );
+		$g4 = $this->factory->group->create( array(
+			'name' => 'C Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $time - 1000 ),
+		) );
+
+		groups_update_groupmeta( $g1, 'total_member_count', 1 );
+		groups_update_groupmeta( $g2, 'total_member_count', 4 );
+		groups_update_groupmeta( $g3, 'total_member_count', 2 );
+		groups_update_groupmeta( $g4, 'total_member_count', 3 );
+
+		$groups = BP_Groups_Group::get( array( 'type' => 'popular' ) );
+		$found = wp_parse_id_list( wp_list_pluck( $groups['groups'], 'id' ) );
+		$this->assertEquals( array( $g2, $g4, $g3, $g1 ), $found );
+	}
+
+	/**
+	 * @group get
+	 * @group group_meta_query
+	 * @ticket BP5099
+	 */
+	public function test_meta_query_and_total_groups() {
+		$time = time();
+
+		$g1 = $this->factory->group->create( array(
+			'name' => 'A Group',
+			'date_created' => bp_core_current_time(),
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'D Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $time - 100 ),
+		) );
+		$g3 = $this->factory->group->create( array(
+			'name' => 'B Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $time - 100000 ),
+		) );
+		$g4 = $this->factory->group->create( array(
+			'name' => 'C Group',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $time - 1000 ),
+		) );
+
+		// mark one group with the metakey 'supergroup'
+		groups_update_groupmeta( $g1, 'supergroup', 1 );
+
+		// fetch groups with our 'supergroup' metakey
+		$groups = BP_Groups_Group::get( array(
+			'meta_query' => array(
+				array(
+					'key'     => 'supergroup',
+					'compare' => 'EXISTS',
+				)
+			)
+		) );
+
+		// group total should match 1
+		$this->assertEquals( '1', $groups['total'] );
+	}
+
+	/**
+	 * @group get
+	 * @ticket BP5477
+	 */
+	public function test_get_groups_page_perpage_params() {
+		// Create more than 20 groups (20 is the default per_page number)
+		$group_ids = array();
+
+		for ( $i = 1; $i <= 25; $i++ ) {
+			$group_ids[] = $this->factory->group->create();
+		}
+
+		// Tests
+		// Passing false to 'per_page' and 'page' should result in pagination not being applied
+		$groups = BP_Groups_Group::get( array(
+			'per_page' => false,
+			'page'     => false
+		) );
+
+		// Should return all groups; "paged" group total should be 25
+		$this->assertEquals( count( $group_ids ), count( $groups['groups'] ) );
+
+		unset( $groups );
+
+		// Passing 'per_page' => -1 should result in pagination not being applied.
+		$groups = BP_Groups_Group::get( array(
+			'per_page' => -1
+		) );
+
+		// Should return all groups; "paged" group total should match 25
+		$this->assertEquals( count( $group_ids ), count( $groups['groups'] ) );
+
+		unset( $groups );
+
+		// If "per_page" and "page" are both set, should result in pagination being applied.
+		$groups = BP_Groups_Group::get( array(
+			'per_page' => 12,
+			'page'     => 1
+		) );
+
+		// Should return top 12 groups only
+		$this->assertEquals( '12', count( $groups['groups'] ) );
+	}
+
+	/** convert_type_to_order_orderby() **********************************/
+
+	/**
+	 * @group convert_type_to_order_orderby
+	 */
+	public function test_convert_type_to_order_orderby_newest() {
+		$expected = array(
+			'order' => 'DESC',
+			'orderby' => 'date_created',
+		);
+		$this->assertEquals( $expected, _BP_Groups_Group::_convert_type_to_order_orderby( 'newest' ) );
+	}
+
+	/**
+	 * @group convert_type_to_order_orderby
+	 */
+	public function test_convert_type_to_order_orderby_active() {
+		$expected = array(
+			'order' => 'DESC',
+			'orderby' => 'last_activity',
+		);
+		$this->assertEquals( $expected, _BP_Groups_Group::_convert_type_to_order_orderby( 'active' ) );
+	}
+
+	/**
+	 * @group convert_type_to_order_orderby
+	 */
+	public function test_convert_type_to_order_orderby_popular() {
+		$expected = array(
+			'order' => 'DESC',
+			'orderby' => 'total_member_count',
+		);
+		$this->assertEquals( $expected, _BP_Groups_Group::_convert_type_to_order_orderby( 'popular' ) );
+	}
+
+	/**
+	 * @group convert_type_to_order_orderby
+	 */
+	public function test_convert_type_to_order_orderby_alphabetical() {
+		$expected = array(
+			'order' => 'ASC',
+			'orderby' => 'name',
+		);
+		$this->assertEquals( $expected, _BP_Groups_Group::_convert_type_to_order_orderby( 'alphabetical' ) );
+	}
+
+	/**
+	 * @group convert_type_to_order_orderby
+	 */
+	public function test_convert_type_to_order_orderby_random() {
+		$expected = array(
+			// order gets thrown out
+			'order' => '',
+			'orderby' => 'random',
+		);
+		$this->assertEquals( $expected, _BP_Groups_Group::_convert_type_to_order_orderby( 'random' ) );
+	}
+
+	/**
+	 * @group convert_type_to_order_orderby
+	 */
+	public function test_convert_type_to_order_orderby_invalid() {
+		$expected = array(
+			'order' => '',
+			'orderby' => '',
+		);
+		$this->assertEquals( $expected, _BP_Groups_Group::_convert_type_to_order_orderby( 'foooooooooooooooobar' ) );
+	}
+
+	/** convert_orderby_to_order_by_term() **********************************/
+
+	/**
+	 * @group convert_orderby_to_order_by_term
+	 */
+	public function test_convert_orderby_to_order_by_term_date_created() {
+		$this->assertEquals( 'g.date_created', _BP_Groups_Group::_convert_orderby_to_order_by_term( 'date_created' ) );
+	}
+
+	/**
+	 * @group convert_orderby_to_order_by_term
+	 */
+	public function test_convert_orderby_to_order_by_term_last_activity() {
+		$c = new _BP_Groups_Group();
+		$this->assertEquals( 'last_activity', _BP_Groups_Group::_convert_orderby_to_order_by_term( 'last_activity' ) );
+	}
+
+	/**
+	 * @group convert_orderby_to_order_by_term
+	 */
+	public function test_convert_orderby_to_order_by_term_total_member_count() {
+		$c = new _BP_Groups_Group();
+		$this->assertEquals( 'CONVERT(gm1.meta_value, SIGNED)', _BP_Groups_Group::_convert_orderby_to_order_by_term( 'total_member_count' ) );
+	}
+
+	/**
+	 * @group convert_orderby_to_order_by_term
+	 */
+	public function test_convert_orderby_to_order_by_term_name() {
+		$c = new _BP_Groups_Group();
+		$this->assertEquals( 'g.name', _BP_Groups_Group::_convert_orderby_to_order_by_term( 'name' ) );
+	}
+
+	/**
+	 * @group convert_orderby_to_order_by_term
+	 */
+	public function test_convert_orderby_to_order_by_term_random() {
+		$c = new _BP_Groups_Group();
+		$this->assertEquals( 'rand()', _BP_Groups_Group::_convert_orderby_to_order_by_term( 'random' ) );
+	}
+
+	/**
+	 * @group convert_orderby_to_order_by_term
+	 */
+	public function test_convert_orderby_to_order_by_term_invalid_fallback_to_date_created() {
+		$c = new _BP_Groups_Group();
+		$this->assertEquals( _BP_Groups_Group::_convert_orderby_to_order_by_term( 'date_created' ), _BP_Groups_Group::_convert_orderby_to_order_by_term( 'I am a bad boy' ) );
+	}
+
+	public function test_filter_user_groups_normal_search() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => 'This is one cool group',
+		) );
+		$g2 = $this->factory->group->create();
+		$u = $this->factory->user->create();
+		self::add_user_to_group( $u, $g1 );
+
+		$groups = BP_Groups_Group::filter_user_groups( 'Cool', $u );
+
+		$found = wp_list_pluck( $groups['groups'], 'group_id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	public function test_filter_user_groups_search_with_underscores() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => '_cool_ dude',
+		) );
+		$g2 = $this->factory->group->create();
+
+		$u = $this->factory->user->create();
+		self::add_user_to_group( $u, $g1 );
+		self::add_user_to_group( $u, $g2 );
+
+		$groups = BP_Groups_Group::filter_user_groups( '_cool_', $u );
+
+		$found = wp_list_pluck( $groups['groups'], 'group_id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	public function test_filter_user_groups_search_with_percent_sign() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => '100% awesome',
+		) );
+		$g2 = $this->factory->group->create();
+
+		$u = $this->factory->user->create();
+		self::add_user_to_group( $u, $g1 );
+		self::add_user_to_group( $u, $g2 );
+
+		$groups = BP_Groups_Group::filter_user_groups( '100%', $u );
+
+		$found = wp_list_pluck( $groups['groups'], 'group_id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	public function test_filter_user_groups_search_with_quotes() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => "'tis sweet",
+		) );
+		$g2 = $this->factory->group->create();
+
+		$u = $this->factory->user->create();
+		self::add_user_to_group( $u, $g1 );
+		self::add_user_to_group( $u, $g2 );
+
+		$groups = BP_Groups_Group::filter_user_groups( "'tis ", $u );
+
+		$found = wp_list_pluck( $groups['groups'], 'group_id' );
+
+		// @todo
+		//$this->assertEquals( array( $g1->id ), $found );
+	}
+
+	public function test_search_groups_normal_search() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => 'This is one cool group',
+		) );
+		$g2 = $this->factory->group->create();
+
+		$groups = BP_Groups_Group::search_groups( 'Cool' );
+
+		$found = wp_list_pluck( $groups['groups'], 'group_id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	public function test_search_groups_search_with_underscores() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => '_cool_ dude',
+		) );
+		$g2 = $this->factory->group->create();
+
+		$groups = BP_Groups_Group::search_groups( '_cool_' );
+
+		$found = wp_list_pluck( $groups['groups'], 'group_id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	public function test_search_groups_search_with_percent_sign() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => '100% awesome',
+		) );
+		$g2 = $this->factory->group->create();
+
+		$groups = BP_Groups_Group::search_groups( '100%' );
+
+		$found = wp_list_pluck( $groups['groups'], 'group_id' );
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	public function test_search_groups_search_with_quotes() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Cool Group',
+			'description' => "'tis sweet",
+		) );
+		$g2 = $this->factory->group->create();
+
+		$groups = BP_Groups_Group::search_groups( "'tis " );
+
+		$found = wp_list_pluck( $groups['groups'], 'group_id' );
+
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	public function test_get_by_letter_with_exclude() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Awesome Cool Group',
+			'description' => 'Neat',
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'Another Cool Group',
+			'description' => 'Awesome',
+		) );
+
+		$groups = BP_Groups_Group::get_by_letter( 'A', null, null, true, array( $g1, 'stringthatshouldberemoved' ) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+
+		$this->assertEquals( array( $g2 ), $found );
+
+	}
+
+	public function test_get_by_letter_starts_with_apostrophe() {
+		$g1 = $this->factory->group->create( array(
+			'name' => "'Tis Sweet",
+			'description' => 'Neat',
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'Another Cool Group',
+			'description' => 'Awesome',
+		) );
+
+		$groups = BP_Groups_Group::get_by_letter( "'" );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+
+		// @todo
+		// The test fails but at least it's sanitized
+		//$this->assertEquals( array( $g1->id ), $found );
+	}
+
+	public function test_get_random_with_exclude() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+
+		// There are only two groups, so excluding one should give us the other
+		$groups = BP_Groups_Group::get_random( null, null, 0, false, true, array( $g1, 'ignore this' ) );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+
+		$this->assertEquals( array( $g2 ), $found );
+	}
+
+	public function test_get_random_with_search_terms() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Bodacious',
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'Crummy group',
+		) );
+
+		// Only one group will match, so the random part doesn't matter
+		$groups = BP_Groups_Group::get_random( null, null, 0, 'daci' );
+
+		$found = wp_list_pluck( $groups['groups'], 'id' );
+
+		$this->assertEquals( array( $g1 ), $found );
+	}
+
+	/**
+	 * @group delete
+	 * @group cache
+	 */
+	public function test_delete_clear_cache() {
+		$g = $this->factory->group->create();
+
+		// Prime cache
+		groups_get_group( array( 'group_id' => $g, ) );
+
+		$this->assertNotEmpty( wp_cache_get( $g, 'bp_groups' ) );
+
+		$group = new BP_Groups_Group( $g );
+		$group->delete();
+
+		$this->assertFalse( wp_cache_get( $g, 'bp_groups' ) );
+	}
+
+	/**
+	 * @group save
+	 * @group cache
+	 */
+	public function test_save_clear_cache() {
+		$g = $this->factory->group->create();
+
+		// Prime cache
+		groups_get_group( array( 'group_id' => $g, ) );
+
+		$this->assertNotEmpty( wp_cache_get( $g, 'bp_groups' ) );
+
+		$group = new BP_Groups_Group( $g );
+		$group->name = 'Foo';
+		$group->save();
+
+		$this->assertFalse( wp_cache_get( $g, 'bp_groups' ) );
+	}
+	/**
+	 * @group get_group_extras
+	 */
+	public function test_get_group_extras_non_logged_in() {
+		$paged_groups = array();
+		$paged_groups[] = new stdClass;
+		$paged_groups[] = new stdClass;
+
+		$paged_groups[0]->id = 5;
+		$paged_groups[1]->id = 10;
+
+		$group_ids = array( 5, 10 );
+
+		$expected = array();
+		foreach ( $paged_groups as $key => $value ) {
+			$expected[ $key ] = new stdClass;
+			$expected[ $key ]->id = $value->id;
+			$expected[ $key ]->is_member = '0';
+			$expected[ $key ]->is_invited = '0';
+			$expected[ $key ]->is_pending = '0';
+			$expected[ $key ]->is_banned = false;
+		}
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( 0 );
+
+		$this->assertEquals( $expected, BP_Groups_Group::get_group_extras( $paged_groups, $group_ids ) );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group get_group_extras
+	 */
+	public function test_get_group_extras_non_member() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create();
+
+		$paged_groups = array();
+		$paged_groups[] = new stdClass;
+		$paged_groups[0]->id = $g;
+
+		$group_ids = array( $g );
+
+		$expected = array();
+		foreach ( $paged_groups as $key => $value ) {
+			$expected[ $key ] = new stdClass;
+			$expected[ $key ]->id = $value->id;
+			$expected[ $key ]->is_member = '0';
+			$expected[ $key ]->is_invited = '0';
+			$expected[ $key ]->is_pending = '0';
+			$expected[ $key ]->is_banned = false;
+		}
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->assertEquals( $expected, BP_Groups_Group::get_group_extras( $paged_groups, $group_ids ) );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group get_group_extras
+	 */
+	public function test_get_group_extras_member() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		$this->add_user_to_group( $u, $g );
+
+		$paged_groups = array();
+		$paged_groups[] = new stdClass;
+		$paged_groups[0]->id = $g;
+
+		$group_ids = array( $g );
+
+		$expected = array();
+		foreach ( $paged_groups as $key => $value ) {
+			$expected[ $key ] = new stdClass;
+			$expected[ $key ]->id = $value->id;
+			$expected[ $key ]->is_member = '1';
+			$expected[ $key ]->is_invited = '0';
+			$expected[ $key ]->is_pending = '0';
+			$expected[ $key ]->is_banned = false;
+		}
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->assertEquals( $expected, BP_Groups_Group::get_group_extras( $paged_groups, $group_ids ) );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group get_group_extras
+	 */
+	public function test_get_group_extras_invited() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create();
+
+		$invite                = new BP_Groups_Member;
+		$invite->group_id      = $g;
+		$invite->user_id       = $u;
+		$invite->date_modified = bp_core_current_time();
+		$invite->invite_sent   = true;
+		$invite->is_confirmed  = false;
+		$invite->save();
+
+		$paged_groups = array();
+		$paged_groups[] = new stdClass;
+		$paged_groups[0]->id = $g;
+
+		$group_ids = array( $g );
+
+		$expected = array();
+		foreach ( $paged_groups as $key => $value ) {
+			$expected[ $key ] = new stdClass;
+			$expected[ $key ]->id = $value->id;
+			$expected[ $key ]->is_member = '0';
+			$expected[ $key ]->is_invited = '1';
+			$expected[ $key ]->is_pending = '0';
+			$expected[ $key ]->is_banned = false;
+		}
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->assertEquals( $expected, BP_Groups_Group::get_group_extras( $paged_groups, $group_ids ) );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group get_group_extras
+	 */
+	public function test_get_group_extras_pending() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create();
+
+		$invite                = new BP_Groups_Member;
+		$invite->group_id      = $g;
+		$invite->user_id       = $u;
+		$invite->date_modified = bp_core_current_time();
+		$invite->invite_sent   = false;
+		$invite->is_confirmed  = false;
+		$invite->save();
+
+		$paged_groups = array();
+		$paged_groups[] = new stdClass;
+		$paged_groups[0]->id = $g;
+
+		$group_ids = array( $g );
+
+		$expected = array();
+		foreach ( $paged_groups as $key => $value ) {
+			$expected[ $key ] = new stdClass;
+			$expected[ $key ]->id = $value->id;
+			$expected[ $key ]->is_member = '0';
+			$expected[ $key ]->is_invited = '0';
+			$expected[ $key ]->is_pending = '1';
+			$expected[ $key ]->is_banned = false;
+		}
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->assertEquals( $expected, BP_Groups_Group::get_group_extras( $paged_groups, $group_ids ) );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group get_group_extras
+	 */
+	public function test_get_group_extras_banned() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create();
+
+		$member                = new BP_Groups_Member;
+		$member->group_id      = $g;
+		$member->user_id       = $u;
+		$member->date_modified = bp_core_current_time();
+		$member->is_banned     = true;
+		$member->save();
+
+		$paged_groups = array();
+		$paged_groups[] = new stdClass;
+		$paged_groups[0]->id = $g;
+
+		$group_ids = array( $g );
+
+		$expected = array();
+		foreach ( $paged_groups as $key => $value ) {
+			$expected[ $key ] = new stdClass;
+			$expected[ $key ]->id = $value->id;
+			$expected[ $key ]->is_member = '0';
+			$expected[ $key ]->is_invited = '0';
+			$expected[ $key ]->is_pending = '0';
+			$expected[ $key ]->is_banned = true;
+		}
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u );
+
+		$this->assertEquals( $expected, BP_Groups_Group::get_group_extras( $paged_groups, $group_ids ) );
+
+		$this->set_current_user( $old_user );
+	}
+}
+
+/**
+ * Stub class for accessing protected methods
+ */
+class _BP_Groups_Group extends BP_Groups_Group {
+	static public function _convert_type_to_order_orderby( $type ) {
+		return self::convert_type_to_order_orderby( $type );
+	}
+
+	static public function _convert_orderby_to_order_by_term( $term ) {
+		return self::convert_orderby_to_order_by_term( $term );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-groups-member.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-groups-member.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/class-bp-groups-member.php	(revision 10410)
@@ -0,0 +1,1199 @@
+<?php
+/**
+ * @group groups
+ * @group BP_Groups_Member
+ */
+class BP_Tests_BP_Groups_Member_TestCases extends BP_UnitTestCase {
+	public static function invite_user_to_group( $user_id, $group_id, $inviter_id ) {
+		$invite                = new BP_Groups_Member;
+		$invite->group_id      = $group_id;
+		$invite->user_id       = $user_id;
+		$invite->date_modified = bp_core_current_time();
+		$invite->inviter_id    = $inviter_id;
+		$invite->is_confirmed  = 0;
+		$invite->invite_sent   = 1;
+
+		$invite->save();
+		return $invite->id;
+	}
+
+	public static function create_group_membership_request( $user_id, $group_id ) {
+		$request                = new BP_Groups_Member;
+		$request->group_id      = $group_id;
+		$request->user_id       = $user_id;
+		$request->date_modified = bp_core_current_time();
+		$request->inviter_id    = 0;
+		$request->is_confirmed  = 0;
+
+		$request->save();
+		return $request->id;
+	}
+
+	public function test_get_recently_joined_with_filter() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Tab',
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'Diet Rite',
+		) );
+
+		$u = $this->factory->user->create();
+		self::add_user_to_group( $u, $g1 );
+		self::add_user_to_group( $u, $g2 );
+
+		$groups = BP_Groups_Member::get_recently_joined( $u, false, false, 'Rite' );
+
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g2 ) );
+	}
+
+	public function test_get_is_admin_of_with_filter() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'RC Cola',
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'Pepsi',
+		) );
+
+		$u = $this->factory->user->create();
+		self::add_user_to_group( $u, $g1 );
+		self::add_user_to_group( $u, $g2 );
+
+		$m1 = new BP_Groups_Member( $u, $g1 );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u, $g2 );
+		$m2->promote( 'admin' );
+
+		$groups = BP_Groups_Member::get_is_admin_of( $u, false, false, 'eps' );
+
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g2 ) );
+	}
+
+	public function test_get_is_mod_of_with_filter() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'RC Cola',
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'Pepsi',
+		) );
+
+		$u = $this->factory->user->create();
+		self::add_user_to_group( $u, $g1 );
+		self::add_user_to_group( $u, $g2 );
+
+		$m1 = new BP_Groups_Member( $u, $g1 );
+		$m1->promote( 'mod' );
+		$m2 = new BP_Groups_Member( $u, $g2 );
+		$m2->promote( 'mod' );
+
+		$groups = BP_Groups_Member::get_is_mod_of( $u, false, false, 'eps' );
+
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g2 ) );
+	}
+
+	public function test_get_is_banned_of_with_filter() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'RC Cola',
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'Pepsi',
+		) );
+
+		$u = $this->factory->user->create();
+		self::add_user_to_group( $u, $g1 );
+		self::add_user_to_group( $u, $g2 );
+
+		$m1 = new BP_Groups_Member( $u, $g1 );
+		$m1->ban();
+		$m2 = new BP_Groups_Member( $u, $g2 );
+		$m2->ban();
+
+		$groups = BP_Groups_Member::get_is_banned_of( $u, false, false, 'eps' );
+
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g2 ) );
+	}
+
+	public function test_get_invites_with_exclude() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'RC Cola',
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'Pepsi',
+		) );
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		self::add_user_to_group( $u1, $g1 );
+		self::add_user_to_group( $u1, $g2 );
+		self::invite_user_to_group( $u2, $g1, $u1 );
+		self::invite_user_to_group( $u2, $g2, $u1 );
+
+		$groups = BP_Groups_Member::get_invites( $u2, false, false, array( 'awesome', $g1 ) );
+
+		$ids = wp_list_pluck( $groups['groups'], 'id' );
+		$this->assertEquals( $ids, array( $g2 ) );
+	}
+
+	/**
+	 * @expectedDeprecated BP_Groups_Member::get_all_for_group
+	 */
+	public function test_get_all_for_group_with_exclude() {
+		$g1 = $this->factory->group->create();
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		self::add_user_to_group( $u1, $g1 );
+		self::add_user_to_group( $u2, $g1 );
+
+		$members = BP_Groups_Member::get_all_for_group( $g1, false, false, true, true, array( $u1 ) );
+
+		$mm = (array) $members['members'];
+		$ids = wp_list_pluck( $mm, 'user_id' );
+		$this->assertEquals( array( $u2 ), $ids );
+	}
+
+	/**
+	 * @group bp_groups_user_can_send_invites
+	 */
+	public function test_bp_groups_user_can_send_invites() {
+		$u_nonmembers = $this->factory->user->create();
+		$u_members    = $this->factory->user->create();
+		$u_mods       = $this->factory->user->create();
+		$u_admins     = $this->factory->user->create();
+		$u_siteadmin  = $this->factory->user->create();
+
+		$user_siteadmin = new WP_User( $u_siteadmin );
+		$user_siteadmin->add_role( 'administrator' );
+
+		$g = $this->factory->group->create();
+
+		$time = time() - 60;
+		$old_current_user = get_current_user_id();
+
+		// Create member-level user
+		$this->add_user_to_group( $u_members, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+
+		// Create mod-level user
+		$this->add_user_to_group( $u_mods, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+		$m_mod = new BP_Groups_Member( $u_mods, $g );
+		$m_mod->promote( 'mod' );
+
+		// Create admin-level user
+		$this->add_user_to_group( $u_admins, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+		$m_admin = new BP_Groups_Member( $u_admins, $g );
+		$m_admin->promote( 'admin' );
+
+		// Test with no status
+		// In bp_group_get_invite_status(), no status falls back to "members"
+		$this->assertTrue( '' == groups_get_groupmeta( $g, 'invite_status' ) );
+		$this->assertFalse( bp_groups_user_can_send_invites( $g, $u_nonmembers ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_members ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_mods ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_admins ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_siteadmin ) );
+
+		// Test with members status
+		groups_update_groupmeta( $g, 'invite_status', 'members' );
+		$this->assertFalse( bp_groups_user_can_send_invites( $g, $u_nonmembers ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_members ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_mods ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_admins ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_siteadmin ) );
+		// Falling back to current user
+		$this->set_current_user( $u_members );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, null ) );
+
+		// Test with mod status
+		groups_update_groupmeta( $g, 'invite_status', 'mods' );
+		$this->assertFalse( bp_groups_user_can_send_invites( $g, $u_nonmembers ) );
+		$this->assertFalse( bp_groups_user_can_send_invites( $g, $u_members ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_mods ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_admins ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_siteadmin ) );
+		// Falling back to current user
+		$this->set_current_user( $u_members );
+		$this->assertFalse( bp_groups_user_can_send_invites( $g, null ) );
+		$this->set_current_user( $u_mods );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, null ) );
+
+		// Test with admin status
+		groups_update_groupmeta( $g, 'invite_status', 'admins' );
+		$this->assertFalse( bp_groups_user_can_send_invites( $g, $u_nonmembers ) );
+		$this->assertFalse( bp_groups_user_can_send_invites( $g, $u_members ) );
+		$this->assertFalse( bp_groups_user_can_send_invites( $g, $u_mods ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_admins ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, $u_siteadmin ) );
+		// Falling back to current user
+		$this->set_current_user( $u_mods );
+		$this->assertFalse( bp_groups_user_can_send_invites( $g, null ) );
+		$this->set_current_user( $u_admins );
+		$this->assertTrue( bp_groups_user_can_send_invites( $g, null ) );
+
+		// Bad or null parameters
+		$this->assertFalse( bp_groups_user_can_send_invites( 59876454257, $u_members ) );
+		$this->assertFalse( bp_groups_user_can_send_invites( $g, 958647515 ) );
+		// Not in group context
+		$this->assertFalse( bp_groups_user_can_send_invites( null, $u_members ) );
+		// In group context
+		$g_obj = groups_get_group( array( 'group_id' => $g ) );
+		$this->go_to( bp_get_group_permalink( $g_obj ) );
+		groups_update_groupmeta( $g, 'invite_status', 'mods' );
+		$this->assertFalse( bp_groups_user_can_send_invites( null, $u_nonmembers ) );
+		$this->assertFalse( bp_groups_user_can_send_invites( null, $u_members ) );
+		$this->assertTrue( bp_groups_user_can_send_invites( null, $u_mods ) );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group groups_reject_membership_request
+ 	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_bp_groups_reject_membership_request_remove_request() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		// Membership requests should be removed.
+		self::create_group_membership_request( $u1, $g );
+		groups_reject_membership_request( null, $u1, $g );
+		$u1_has_request = groups_check_for_membership_request( $u1, $g );
+		$this->assertEquals( 0, $u1_has_request );
+	}
+
+	/**
+	 * @group groups_reject_membership_request
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_bp_groups_reject_membership_request_leave_memberships_intact() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$this->add_user_to_group( $u1, $g );
+
+		// Confirmed memberships should be left intact.
+		groups_reject_membership_request( null, $u1, $g );
+		$u1_is_member = groups_is_user_member( $u1, $g );
+		$this->assertTrue( is_numeric( $u1_is_member ) && $u1_is_member > 0 );
+	}
+
+	/**
+	 * @group groups_reject_membership_request
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_bp_groups_reject_membership_request_leave_invites_intact() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$time = time() - 60;
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+
+		// Outstanding invitations should be left intact.
+		self::invite_user_to_group( $u2, $g, $u1 );
+		groups_reject_membership_request( null, $u2, $g );
+		$u2_has_invite = groups_check_user_has_invite( $u2, $g );
+		$this->assertTrue( is_numeric( $u2_has_invite ) && $u2_has_invite > 0 );
+	}
+
+	/**
+	 * @group groups_delete_membership_request
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_bp_groups_delete_membership_request_remove_request() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		// Membership requests should be removed.
+		self::create_group_membership_request( $u1, $g );
+		groups_delete_membership_request( null, $u1, $g );
+		$u1_has_request = groups_check_for_membership_request( $u1, $g );
+		$this->assertEquals( 0, $u1_has_request );
+	}
+
+	/**
+	 * @group groups_delete_membership_request
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_bp_groups_delete_membership_request_leave_memberships_intact() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$this->add_user_to_group( $u1, $g );
+
+		// Confirmed memberships should be left intact.
+		groups_delete_membership_request( null, $u1, $g );
+		$u1_is_member = groups_is_user_member( $u1, $g );
+		$this->assertTrue( is_numeric( $u1_is_member ) && $u1_is_member > 0 );
+	}
+
+	/**
+	 * @group groups_delete_membership_request
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_bp_groups_delete_membership_request_leave_invites_intact() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$time = time() - 60;
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+
+		// Outstanding invitations should be left intact.
+		self::invite_user_to_group( $u2, $g, $u1 );
+		groups_delete_membership_request( null, $u2, $g );
+		$u2_has_invite = groups_check_user_has_invite( $u2, $g );
+		$this->assertTrue( is_numeric( $u2_has_invite ) && $u2_has_invite > 0 );
+	}
+
+	/**
+	 * @group groups_reject_invite
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_bp_groups_reject_invite_remove_invite() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$time = time() - 60;
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+
+		// The invitation should be removed.
+		self::invite_user_to_group( $u2, $g, $u1 );
+		groups_reject_invite( $u2, $g );
+		$u2_has_invite = groups_check_user_has_invite( $u2, $g, 'all' );
+		$this->assertEquals( 0, $u2_has_invite );
+	}
+
+	/**
+	 * @group groups_reject_invite
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_bp_groups_reject_invite_leave_memberships_intact() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$time = time() - 60;
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+
+		// Confirmed memberships should be left intact.
+		groups_reject_invite( $u1, $g );
+		$u1_is_member = groups_is_user_member( $u1, $g );
+		$this->assertTrue( is_numeric( $u1_is_member ) && $u1_is_member > 0 );
+	}
+
+	/**
+	 * @group groups_reject_invite
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_bp_groups_reject_invite_leave_requests_intact() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		// Membership requests should be left intact.
+		self::create_group_membership_request( $u1, $g );
+		groups_reject_invite( $u1, $g );
+		$u1_has_request = groups_check_for_membership_request( $u1, $g );
+		$this->assertTrue( is_numeric( $u1_has_request ) && $u1_has_request > 0 );
+	}
+
+	/**
+	 * @group groups_delete_invite
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_bp_groups_delete_invite_remove_invite() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$time = time() - 60;
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+
+		// The invitation should be removed.
+		self::invite_user_to_group( $u2, $g, $u1 );
+		groups_delete_invite( $u2, $g );
+		$u2_has_invite = groups_check_user_has_invite( $u2, $g, 'all' );
+		$this->assertEquals( 0, $u2_has_invite );
+	}
+
+	/**
+	 * @group groups_delete_invite
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_bp_groups_delete_invite_remove_draft_invite() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g  = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$time = time() - 60;
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+
+		// Create the draft invitation.
+		groups_invite_user( array(
+			'user_id'    => $u2,
+			'group_id'   => $g,
+			'inviter_id' => $u1
+		) );
+
+		// Check that the invite got created.
+		$u2_has_invite = groups_check_user_has_invite( $u2, $g, 'all' );
+		$this->assertTrue( is_numeric( $u2_has_invite ) && $u2_has_invite > 0 );
+
+		// The invitation should be removed.
+		groups_delete_invite( $u2, $g );
+		$u2_has_invite = groups_check_user_has_invite( $u2, $g, 'all' );
+		$this->assertEquals( 0, $u2_has_invite );
+	}
+
+	/**
+	 * @group groups_delete_invite
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_bp_groups_delete_invite_leave_memberships_intact() {
+		$u1 = $this->factory->user->create();
+		$g  = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$time = time() - 60;
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+
+		groups_delete_invite( $u1, $g );
+		$u1_is_member = groups_is_user_member( $u1, $g );
+		$this->assertTrue( is_numeric( $u1_is_member ) && $u1_is_member > 0 );
+	}
+
+	/**
+	 * @group groups_delete_invite
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_bp_groups_delete_invite_leave_requests_intact() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		// Membership requests should be left intact.
+		self::create_group_membership_request( $u1, $g );
+		groups_delete_invite( $u1, $g );
+		$u1_has_request = groups_check_for_membership_request( $u1, $g );
+		$this->assertTrue( is_numeric( $u1_has_request ) && $u1_has_request > 0 );
+	}
+
+	/**
+	 * @group groups_uninvite_user
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_bp_groups_uninvite_user_remove_invite() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$time = time() - 60;
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+
+		// The invitation should be removed.
+		self::invite_user_to_group( $u2, $g, $u1 );
+		groups_uninvite_user( $u2, $g );
+		$u2_has_invite = groups_check_user_has_invite( $u2, $g, 'all' );
+		$this->assertEquals( 0, $u2_has_invite );
+	}
+
+	/**
+	 * @group groups_uninvite_user
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_bp_groups_uninvite_user_leave_memberships_intact() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		$time = time() - 60;
+		$this->add_user_to_group( $u1, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $time ),
+		) );
+
+		// Confirmed memberships should be left intact.
+		groups_is_user_member( $u1, $g );
+		groups_uninvite_user( $u1, $g );
+		$u1_is_member = groups_is_user_member( $u1, $g );
+		$this->assertTrue( is_numeric( $u1_is_member ) && $u1_is_member > 0 );
+	}
+
+	/**
+	 * @group groups_uninvite_user
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_bp_groups_uninvite_user_leave_requests_intact() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'status' => 'private',
+		) );
+
+		// Membership requests should be left intact.
+		self::create_group_membership_request( $u1, $g );
+		groups_uninvite_user( $u1, $g );
+		$u1_has_request = groups_check_for_membership_request( $u1, $g );
+		$this->assertTrue( is_numeric( $u1_has_request ) && $u1_has_request > 0 );
+	}
+
+	/**
+	 * @group groups_join_group
+	 * @group group_membership
+	 */
+	public function test_groups_join_group_basic_join() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create();
+
+		groups_join_group( $g, $u1 );
+		$membership_id = groups_is_user_member( $u1, $g );
+		$this->assertTrue( is_numeric( $membership_id ) && $membership_id > 0 );
+	}
+
+	/**
+	 * @group groups_join_group
+	 * @group group_membership
+	 */
+	public function test_groups_join_group_basic_join_use_current_user() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( $u1 );
+
+		groups_join_group( $g );
+		$membership_id = groups_is_user_member( $u1, $g );
+		$this->assertTrue( is_numeric( $membership_id ) && $membership_id > 0 );
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group groups_join_group
+	 * @group group_membership
+	 */
+	public function test_groups_join_group_already_member() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		$this->add_user_to_group( $u1, $g );
+
+		$this->assertTrue( groups_join_group( $g, $u1 ) );
+	}
+
+	/**
+	 * @group groups_join_group
+	 * @group group_membership
+	 */
+	public function test_groups_join_group_cleanup_invites() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		$this->add_user_to_group( $u1, $g );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+
+		self::invite_user_to_group( $u2, $g, $u1 );
+
+		groups_join_group( $g, $u2 );
+		// Upon joining the group, outstanding invitations should be cleaned up.
+		$this->assertEquals( null, groups_check_user_has_invite( $u2, $g, 'any' ) );
+	}
+
+	/**
+	 * @group groups_join_group
+	 * @group group_membership
+	 */
+	public function test_groups_join_group_cleanup_requests() {
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		self::create_group_membership_request( $u1, $g );
+
+		groups_join_group( $g, $u1 );
+		// Upon joining the group, outstanding requests should be cleaned up.
+		$this->assertEquals( null, groups_check_for_membership_request( $u1, $g ) );
+	}
+
+	/**
+	 * @group groups_leave_group
+	 * @group group_membership
+	 */
+	public function test_groups_leave_group_basic_leave_self_initiated() {
+		$old_current_user = get_current_user_id();
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$u2 = $this->factory->user->create();
+		$this->add_user_to_group( $u2, $g );
+
+		$before = groups_get_total_member_count( $g );
+		$this->set_current_user( $u2 );
+		groups_leave_group( $g, $u2 );
+		$after = groups_get_total_member_count( $g );
+
+		$this->assertEquals( $before - 1, $after );
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group groups_leave_group
+	 * @group group_membership
+	 */
+	public function test_groups_leave_group_basic_leave_use_current_user() {
+		$old_current_user = get_current_user_id();
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$u2 = $this->factory->user->create();
+		$this->add_user_to_group( $u2, $g );
+
+		$before = groups_get_total_member_count( $g );
+		$this->set_current_user( $u2 );
+		groups_leave_group( $g );
+		$after = groups_get_total_member_count( $g );
+
+		$this->assertEquals( $before - 1, $after );
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group groups_leave_group
+	 * @group group_membership
+	 */
+	public function test_groups_leave_group_basic_leave_group_admin_initiated() {
+		$old_current_user = get_current_user_id();
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$u2 = $this->factory->user->create();
+		$this->add_user_to_group( $u2, $g );
+
+		$before = groups_get_total_member_count( $g );
+		$this->set_current_user( $u1 );
+		groups_leave_group( $g, $u2 );
+		$after = groups_get_total_member_count( $g );
+
+		$this->assertEquals( $before - 1, $after );
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group groups_leave_group
+	 * @group group_membership
+	 */
+	public function test_groups_leave_group_basic_leave_site_admin_initiated() {
+		$old_current_user = get_current_user_id();
+		$u1 = $this->factory->user->create();
+		$u1_siteadmin = new WP_User( $u1 );
+		$u1_siteadmin->add_role( 'administrator' );
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$u2 = $this->factory->user->create();
+		$this->add_user_to_group( $u2, $g );
+
+		$before = groups_get_total_member_count( $g );
+		$this->set_current_user( $u1 );
+		groups_leave_group( $g, $u2 );
+		$after = groups_get_total_member_count( $g );
+
+		$this->assertEquals( $before - 1, $after );
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group groups_leave_group
+	 * @group group_membership
+	 */
+	public function test_groups_leave_group_single_admin_prevent_leave() {
+		$old_current_user = get_current_user_id();
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$u2 = $this->factory->user->create();
+		$this->add_user_to_group( $u2, $g );
+
+		$before = groups_get_total_member_count( $g );
+		$this->set_current_user( $u1 );
+		groups_leave_group( $g, $u1 );
+		$after = groups_get_total_member_count( $g );
+
+		$this->assertEquals( $before, $after );
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group groups_leave_group
+	 * @group group_membership
+	 */
+	public function test_groups_leave_group_multiple_admins_allow_leave() {
+		$old_current_user = get_current_user_id();
+		$u1 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$u2 = $this->factory->user->create();
+		$this->add_user_to_group( $u2, $g );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'admin' );
+
+		$before = groups_get_total_member_count( $g );
+		$this->set_current_user( $u1 );
+		groups_leave_group( $g, $u1 );
+		$after = groups_get_total_member_count( $g );
+
+		$this->assertEquals( $before - 1, $after );
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group groups_get_invites_for_user
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_groups_get_invites_for_user() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g3 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		self::invite_user_to_group( $u2, $g1, $u1 );
+		self::invite_user_to_group( $u2, $g2, $u1 );
+		self::invite_user_to_group( $u2, $g3, $u1 );
+
+		$groups = groups_get_invites_for_user( $u2 );
+
+		$this->assertEqualSets( array( $g1, $g2, $g3 ), wp_list_pluck( $groups['groups'], 'id' ) );
+	}
+
+	/**
+	 * @group groups_get_invites_for_user
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_groups_get_invites_for_user_infer_user() {
+		$old_current_user = get_current_user_id();
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g3 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		self::invite_user_to_group( $u2, $g1, $u1 );
+		self::invite_user_to_group( $u2, $g2, $u1 );
+		self::invite_user_to_group( $u2, $g3, $u1 );
+
+		$this->set_current_user( $u2 );
+		$groups = groups_get_invites_for_user();
+		$this->assertEqualSets( array( $g1, $g2, $g3 ), wp_list_pluck( $groups['groups'], 'id' ) );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group groups_get_invites_for_user
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_groups_get_invites_for_user_with_exclude() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g3 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		self::invite_user_to_group( $u2, $g1, $u1 );
+		self::invite_user_to_group( $u2, $g2, $u1 );
+		self::invite_user_to_group( $u2, $g3, $u1 );
+
+		$groups = groups_get_invites_for_user( $u2, false, false, array( $g2 ) );
+		$this->assertEqualSets( array( $g1, $g3 ), wp_list_pluck( $groups['groups'], 'id' ) );
+	}
+
+	/**
+	 * @group groups_get_invite_count_for_user
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_groups_get_invite_count_for_user() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g3 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		self::invite_user_to_group( $u2, $g1, $u1 );
+		self::invite_user_to_group( $u2, $g2, $u1 );
+		self::invite_user_to_group( $u2, $g3, $u1 );
+
+		$this->assertEquals( 3, groups_get_invite_count_for_user( $u2 ) );
+	}
+
+	/**
+	 * @group groups_get_invite_count_for_user
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_groups_get_invite_count_for_user_ignore_drafts() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		// Create draft invitation.
+		groups_invite_user( array(
+			'user_id'       => $u2,
+			'group_id'      => $g1,
+			'inviter_id'    => $u1,
+			'date_modified' => bp_core_current_time(),
+			'is_confirmed'  => 0
+		) );
+
+		// groups_get_invite_count_for_user should ignore draft invitations.
+		$this->assertEquals( 0, groups_get_invite_count_for_user( $u2 ) );
+	}
+
+	/**
+	 * @group groups_invite_user
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_groups_invite_user() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		// Create draft invitation
+		groups_invite_user( array(
+			'user_id'       => $u2,
+			'group_id'      => $g1,
+			'inviter_id'    => $u1,
+			'date_modified' => bp_core_current_time(),
+			'is_confirmed'  => 0
+		) );
+
+		// Check that the draft invitation has been created.
+		$draft = groups_check_user_has_invite( $u2, $g1, 'all' );
+		$this->assertTrue( is_numeric( $draft ) && $draft > 0 );
+	}
+
+	/**
+	 * @group groups_send_invites
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_groups_send_invites() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		// Create draft invitation
+		groups_invite_user( array(
+			'user_id'       => $u2,
+			'group_id'      => $g1,
+			'inviter_id'    => $u1,
+			'date_modified' => bp_core_current_time(),
+			'is_confirmed'  => 0
+		) );
+
+		// Send the invitation
+		groups_send_invites( $u1, $g1 );
+
+		// Check that the invitation has been sent.
+		$sent = groups_check_user_has_invite( $u2, $g1, $type = 'sent' );
+		$this->assertTrue( is_numeric( $sent ) && $sent > 0 );
+	}
+
+	/**
+	 * @group groups_accept_invite
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_groups_accept_invite() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		// Create draft invitation
+		groups_invite_user( array(
+			'user_id'       => $u2,
+			'group_id'      => $g1,
+			'inviter_id'    => $u1,
+			'date_modified' => bp_core_current_time(),
+			'is_confirmed'  => 0
+		) );
+
+		// Send the invitation
+		groups_send_invites( $u1, $g1 );
+
+		// Accept the invitation
+		groups_accept_invite( $u2, $g1 );
+
+		// Check that the user is a member of the group.
+		$member = groups_is_user_member( $u2, $g1 );
+		$this->assertTrue( is_numeric( $member ) && $member > 0 );
+		// Check that the invite has been removed.
+		$invite = groups_check_user_has_invite( $u2, $g1, 'all' );
+		$this->assertTrue( is_null( $invite ) );
+	}
+
+	/**
+	 * @group groups_accept_invite
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_groups_accept_invite_removes_membership_requests() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		// Create draft invitation
+		groups_invite_user( array(
+			'user_id'       => $u2,
+			'group_id'      => $g1,
+			'inviter_id'    => $u1,
+			'date_modified' => bp_core_current_time(),
+			'is_confirmed'  => 0
+		) );
+
+		// Create membership request
+		groups_send_membership_request( $u2, $g1 );
+		$request = groups_check_for_membership_request( $u2, $g1 );
+		$this->assertTrue( is_numeric( $request ) && $request > 0 );
+
+		// Send the invitation
+		groups_send_invites( $u1, $g1 );
+
+		// Accept the invitation
+		groups_accept_invite( $u2, $g1 );
+
+		// Check that the membership request has been removed.
+		$this->assertTrue( 0 == groups_check_for_membership_request( $u2, $g1 ) );
+	}
+
+	/**
+	 * @group groups_send_invites
+	 * @group group_invitations
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_groups_sent_invite_plus_request_equals_member() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		// Create draft invitation
+		groups_invite_user( array(
+			'user_id'       => $u2,
+			'group_id'      => $g1,
+			'inviter_id'    => $u1,
+			'date_modified' => bp_core_current_time(),
+			'is_confirmed'  => 0
+		) );
+
+		// Send the invitation
+		groups_send_invites( $u1, $g1 );
+
+		// Create membership request
+		groups_send_membership_request( $u2, $g1 );
+
+		// User should now be a group member
+		$member = groups_is_user_member( $u2, $g1 );
+		$this->assertTrue( is_numeric( $member ) && $member > 0 );
+	}
+
+	/**
+	 * @group groups_delete_all_group_invites
+	 * @group group_invitations
+	 * @group group_membership
+	 */
+	public function test_groups_delete_all_group_invites() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		self::invite_user_to_group( $u2, $g1, $u1 );
+		self::invite_user_to_group( $u3, $g1, $u1 );
+
+		groups_delete_all_group_invites( $g1 );
+
+		// Get group invitations of any type, from any user in the group.
+		$invitees = new BP_Group_Member_Query( array(
+			'group_id'     => $g1,
+			'is_confirmed' => 0,
+			'invite_sent'  => null,
+			'inviter_id'   => 'any',
+		) );
+
+		$this->assertTrue( empty( $invitees->results ) );
+	}
+
+	/**
+	 * @group groups_send_membership_request
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_groups_send_membership_request() {
+		$u1 = $this->factory->user->create();
+		$g1 = $this->factory->group->create();
+
+		// Create membership request
+		groups_send_membership_request( $u1, $g1 );
+
+		$request = groups_check_for_membership_request( $u1, $g1 );
+		$this->assertTrue( is_numeric( $request ) && $request > 0 );
+	}
+
+	/**
+	 * @group groups_accept_membership_request
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_groups_accept_membership_request_by_membership_id() {
+		$u1 = $this->factory->user->create();
+		$g1 = $this->factory->group->create();
+
+		// Create membership request
+		groups_send_membership_request( $u1, $g1 );
+
+		// Get group invitations of any type, from any user in the group.
+		$member = new BP_Groups_Member( $u1, $g1 );
+
+		groups_accept_membership_request( $member->id );
+
+		// User should now be a group member.
+		$member = groups_is_user_member( $u1, $g1 );
+
+		$this->assertTrue( is_numeric( $member ) && $member > 0 );
+	}
+
+	/**
+	 * @group groups_accept_membership_request
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_groups_accept_membership_request_by_user_id_group_id() {
+		$u1 = $this->factory->user->create();
+		$g1 = $this->factory->group->create();
+
+		// Create membership request
+		groups_send_membership_request( $u1, $g1 );
+
+		groups_accept_membership_request( null, $u1, $g1 );
+
+		// User should now be a group member
+		$member = groups_is_user_member( $u1, $g1 );
+		$this->assertTrue( is_numeric( $member ) && $member > 0 );
+	}
+
+	/**
+	 * @group groups_send_invites
+	 * @group group_invitations
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_groups_membership_request_plus_invite_equals_member() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		// Create membership request
+		groups_send_membership_request( $u2, $g1 );
+
+		// Create draft invitation
+		groups_invite_user( array(
+			'user_id'       => $u2,
+			'group_id'      => $g1,
+			'inviter_id'    => $u1,
+			'date_modified' => bp_core_current_time(),
+			'is_confirmed'  => 0
+		) );
+
+		// Send the invitation
+		groups_send_invites( $u1, $g1 );
+
+		// User should now be a group member
+		$member = groups_is_user_member( $u2, $g1 );
+		$this->assertTrue( is_numeric( $member ) && $member > 0 );
+	}
+
+	/**
+	 * @group groups_accept_all_pending_membership_requests
+	 * @group group_membership_requests
+	 * @group group_membership
+	 */
+	public function test_groups_accept_all_pending_membership_requests() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$g1 = $this->factory->group->create();
+
+		// Create membership request
+		groups_send_membership_request( $u1, $g1 );
+		groups_send_membership_request( $u2, $g1 );
+		groups_send_membership_request( $u3, $g1 );
+
+		groups_accept_all_pending_membership_requests( $g1 );
+
+		// All users should now be group members.
+		$members = new BP_Group_Member_Query( array( 'group_id' => $g1 ) );
+		$this->assertEqualSets( array( $u1, $u2, $u3 ), $members->user_ids );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/functions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/functions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/functions.php	(revision 10410)
@@ -0,0 +1,667 @@
+<?php
+
+/**
+ * @group groups
+ * @group functions
+ */
+class BP_Tests_Groups_Functions extends BP_UnitTestCase {
+	public function test_creating_new_group_as_authenticated_user() {
+		$u = $this->factory->user->create();
+		wp_set_current_user( $u );
+
+		$this->factory->group->create();
+	}
+
+	/**
+	 * @group total_group_count
+	 * @group groups_join_group
+	 */
+	public function test_total_group_count_groups_join_group() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		groups_join_group( $g, $u2 );
+		$this->assertEquals( 1, bp_get_user_meta( $u2, 'total_group_count', true ) );
+	}
+
+	/**
+	 * @group total_group_count
+	 * @group groups_leave_group
+	 */
+	public function test_total_group_count_groups_leave_group() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		groups_join_group( $g1, $u2 );
+		groups_join_group( $g2, $u2 );
+
+		// Set the current user so the leave group request goes through.
+		$this->set_current_user( $u2 );
+		groups_leave_group( $g1, $u2 );
+		$this->assertEquals( 1, bp_get_user_meta( $u2, 'total_group_count', true ) );
+	}
+
+	/**
+	 * @group total_group_count
+	 * @group groups_ban_member
+	 */
+	public function test_total_group_count_groups_ban_member() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		groups_join_group( $g1, $u2 );
+		groups_join_group( $g2, $u2 );
+
+		// Fool the admin check
+		$this->set_current_user( $u1 );
+		buddypress()->is_item_admin = true;
+
+		groups_ban_member( $u2, $g1 );
+
+		$this->assertEquals( 1, bp_get_user_meta( $u2, 'total_group_count', true ) );
+	}
+
+	/**
+	 * @group total_group_count
+	 * @group groups_unban_member
+	 */
+	public function test_total_group_count_groups_unban_member() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		groups_join_group( $g1, $u2 );
+		groups_join_group( $g2, $u2 );
+
+		// Fool the admin check
+		$this->set_current_user( $u1 );
+		buddypress()->is_item_admin = true;
+
+		groups_ban_member( $u2, $g1 );
+
+		groups_unban_member( $u2, $g1 );
+
+		$this->assertEquals( 2, bp_get_user_meta( $u2, 'total_group_count', true ) );
+	}
+
+	/**
+	 * @group total_group_count
+	 * @group groups_accept_invite
+	 */
+	public function test_total_group_count_groups_accept_invite() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create();
+		groups_invite_user( array(
+			'user_id' => $u1,
+			'group_id' => $g,
+			'inviter_id' => $u2,
+		) );
+
+		groups_accept_invite( $u2, $g );
+
+		$this->assertEquals( 1, bp_get_user_meta( $u2, 'total_group_count', true ) );
+	}
+
+	/**
+	 * @group total_group_count
+	 * @group groups_accept_membership_request
+	 */
+	public function test_total_group_count_groups_accept_membership_request() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$current_user = bp_loggedin_user_id();
+		$this->set_current_user( $u2 );
+
+		$g = $this->factory->group->create();
+		groups_send_membership_request( $u1, $g );
+
+		groups_accept_membership_request( 0, $u1, $g );
+
+		$this->assertEquals( 1, bp_get_user_meta( $u1, 'total_group_count', true ) );
+
+		$this->set_current_user( $current_user );
+	}
+
+	/**
+	 * @group total_group_count
+	 * @group groups_remove_member
+	 */
+	public function test_total_group_count_groups_remove_member() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		groups_join_group( $g1, $u2 );
+		groups_join_group( $g2, $u2 );
+
+		// Fool the admin check
+		$this->set_current_user( $u1 );
+		buddypress()->is_item_admin = true;
+
+		groups_remove_member( $u2, $g1 );
+
+		$this->assertEquals( 1, bp_get_user_meta( $u2, 'total_group_count', true ) );
+	}
+
+	/**
+	 * @group total_member_count
+	 * @group groups_join_group
+	 */
+	public function test_total_member_count_groups_join_group() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		groups_join_group( $g, $u2 );
+		$this->assertEquals( 2, groups_get_groupmeta( $g, 'total_member_count' ) );
+	}
+
+	/**
+	 * @group total_member_count
+	 * @group groups_leave_group
+	 */
+	public function test_total_member_count_groups_leave_group() {
+		$u1 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		groups_join_group( $g1, $u1 );
+
+		groups_leave_group( $g1, $u1 );
+		$this->assertEquals( 1, groups_get_groupmeta( $g1, 'total_member_count' ) );
+	}
+
+	/**
+	 * @group total_member_count
+	 * @group groups_ban_member
+	 */
+	public function test_total_member_count_groups_ban_member() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		groups_join_group( $g1, $u2 );
+
+		// Fool the admin check
+		$this->set_current_user( $u1 );
+		buddypress()->is_item_admin = true;
+
+		groups_ban_member( $u2, $g1 );
+
+		$this->assertEquals( 1, groups_get_groupmeta( $g1, 'total_member_count' ) );
+	}
+
+	/**
+	 * @group total_member_count
+	 * @group groups_unban_member
+	 */
+	public function test_total_member_count_groups_unban_member() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		groups_join_group( $g1, $u2 );
+
+		// Fool the admin check
+		$this->set_current_user( $u1 );
+		buddypress()->is_item_admin = true;
+
+		groups_ban_member( $u2, $g1 );
+
+		groups_unban_member( $u2, $g1 );
+
+		$this->assertEquals( 2, groups_get_groupmeta( $g1, 'total_member_count' ) );
+	}
+
+	/**
+	 * @group total_member_count
+	 * @group groups_accept_invite
+	 */
+	public function test_total_member_count_groups_accept_invite() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		groups_invite_user( array(
+			'user_id' => $u1,
+			'group_id' => $g,
+			'inviter_id' => $u2,
+		) );
+
+		groups_accept_invite( $u2, $g );
+
+		$this->assertEquals( 2, groups_get_groupmeta( $g, 'total_member_count' ) );
+	}
+
+	/**
+	 * @group total_member_count
+	 * @group groups_accept_membership_request
+	 */
+	public function test_total_member_count_groups_accept_membership_request() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		groups_send_membership_request( $u2, $g );
+		groups_accept_membership_request( 0, $u2, $g );
+
+		$this->assertEquals( 2, groups_get_groupmeta( $g, 'total_member_count' ) );
+	}
+
+	/**
+	 * @group total_member_count
+	 * @group groups_remove_member
+	 */
+	public function test_total_member_count_groups_remove_member() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		groups_join_group( $g1, $u2 );
+
+		// Fool the admin check
+		$this->set_current_user( $u1 );
+		buddypress()->is_item_admin = true;
+
+		groups_remove_member( $u2, $g1 );
+
+		$this->assertEquals( 1, groups_get_groupmeta( $g1, 'total_member_count' ) );
+	}
+
+	/**
+	 * @group total_member_count
+	 * @group groups_create_group
+	 */
+	public function test_total_member_count_groups_create_group() {
+		$u1 = $this->factory->user->create();
+		$g = groups_create_group( array(
+			'creator_id' => $u1,
+			'name' => 'Boone Is Handsome',
+			'description' => 'Yes',
+			'slug' => 'boone-is-handsome',
+			'status' => 'public',
+			'enable_forum' => 0,
+			'date_created' => bp_core_current_time(),
+		) );
+
+		$this->assertEquals( 1, groups_get_groupmeta( $g, 'total_member_count' ) );
+	}
+
+	/**
+	 * @group groups_create_group
+	 */
+	public function test_groups_create_group_dont_delete_description_for_existing_group_when_no_description_is_passed() {
+		$g = $this->factory->group->create();
+
+		$group_before = groups_get_group( array(
+			'group_id' => $g,
+		) );
+
+		groups_create_group( array(
+			'group_id' => $g,
+			'enable_forum' => 1,
+		) );
+
+		$group_after = groups_get_group( array(
+			'group_id' => $g,
+		) );
+
+		$this->assertSame( $group_before->description, $group_after->description );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @ticket BP5180
+	 */
+	public function test_groups_update_groupmeta_with_line_breaks() {
+		$g = $this->factory->group->create();
+		$meta_value = 'Foo!
+
+Bar!';
+		groups_update_groupmeta( $g, 'linebreak_test', $meta_value );
+
+		$this->assertEquals( $meta_value, groups_get_groupmeta( $g, 'linebreak_test' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 */
+	public function test_groups_update_groupmeta_non_numeric_id() {
+		$this->assertFalse( groups_update_groupmeta( 'foo', 'bar', 'baz' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 */
+	public function test_groups_update_groupmeta_stripslashes() {
+		$g = $this->factory->group->create();
+		$value = "This string is totally slashin\'!";
+		groups_update_groupmeta( $g, 'foo', $value );
+
+		$this->assertSame( stripslashes( $value ), groups_get_groupmeta( $g, 'foo' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 */
+	public function test_groups_update_groupmeta_new() {
+		$g = $this->factory->group->create();
+		$this->assertSame( '', groups_get_groupmeta( $g, 'foo' ), '"foo" meta should be empty for this group.' );
+		$this->assertNotEmpty( groups_update_groupmeta( $g, 'foo', 'bar' ) );
+		$this->assertSame( 'bar', groups_get_groupmeta( $g, 'foo' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 */
+	public function test_groups_update_groupmeta_existing() {
+		$g = $this->factory->group->create();
+		groups_update_groupmeta( $g, 'foo', 'bar' );
+		$this->assertSame( 'bar', groups_get_groupmeta( $g, 'foo' ), '"foo" meta should be set already for this group.' );
+		$this->assertTrue( groups_update_groupmeta( $g, 'foo', 'baz' ) );
+		$this->assertSame( 'baz', groups_get_groupmeta( $g, 'foo' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 */
+	public function test_groups_update_groupmeta_existing_same_value() {
+		$g = $this->factory->group->create();
+		groups_update_groupmeta( $g, 'foo', 'bar' );
+		$this->assertSame( 'bar', groups_get_groupmeta( $g, 'foo' ), '"foo" meta should be set already for this group.' );
+		$this->assertFalse( groups_update_groupmeta( $g, 'foo', 'bar' ) );
+		$this->assertSame( 'bar', groups_get_groupmeta( $g, 'foo' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_update_groupmeta
+	 */
+	public function test_groups_update_groupmeta_prev_value() {
+		$g = $this->factory->group->create();
+		groups_add_groupmeta( $g, 'foo', 'bar' );
+
+		// In earlier versions of WordPress, bp_activity_update_meta()
+		// returns true even on failure. However, we know that in these
+		// cases the update is failing as expected, so we skip this
+		// assertion just to keep our tests passing
+		// See https://core.trac.wordpress.org/ticket/24933
+		if ( version_compare( $GLOBALS['wp_version'], '3.7', '>=' ) ) {
+			$this->assertFalse( groups_update_groupmeta( $g, 'foo', 'bar2', 'baz' ) );
+		}
+
+		$this->assertTrue( groups_update_groupmeta( $g, 'foo', 'bar2', 'bar' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_get_groupmeta
+	 * @ticket BP5399
+	 */
+	public function test_groups_get_groupmeta_with_illegal_key_characters() {
+		$g = $this->factory->group->create();
+		groups_update_groupmeta( $g, 'foo', 'bar' );
+
+		$krazy_key = ' f!@#$%^o *(){}o?+';
+		$this->assertSame( '', groups_get_groupmeta( $g, $krazy_key ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 */
+	public function test_groups_get_groupmeta_all_metas() {
+		$g = $this->factory->group->create();
+		groups_update_groupmeta( $g, 'foo', 'bar' );
+		groups_update_groupmeta( $g, 'Boone', 'is cool' );
+
+		// There's likely some other keys (total_member_count etc)
+		// Just check to make sure both of ours are there
+		$metas = groups_get_groupmeta( $g );
+		$count = count( $metas );
+		$found = array_slice( $metas, $count - 2 );
+
+		$expected = array(
+			'foo' => array(
+				'bar',
+			),
+			'Boone' => array(
+				'is cool',
+			),
+		);
+
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group groupmeta
+	 */
+	public function test_groups_get_groupmeta_all_metas_empty() {
+		$g = $this->factory->group->create();
+
+		// Get rid of any auto-created values
+		global $wpdb;
+
+		$bp = buddypress();
+		$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->groups->table_name_groupmeta} WHERE group_id = %d", $g ) );
+		wp_cache_delete( $g, 'group_meta' );
+
+		$metas = groups_get_groupmeta( $g );
+		$this->assertSame( array(), $metas );
+	}
+
+	/**
+	 * @group groupmeta
+	 */
+	public function test_groups_get_groupmeta_empty() {
+		$g = $this->factory->group->create();
+		$this->assertSame( '', groups_get_groupmeta( $g, 'foo' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_get_groupmeta
+	 */
+	public function test_bp_activity_get_meta_single_true() {
+		$g = $this->factory->group->create();
+		groups_add_groupmeta( $g, 'foo', 'bar' );
+		groups_add_groupmeta( $g, 'foo', 'baz' );
+		$this->assertSame( 'bar', groups_get_groupmeta( $g, 'foo' ) ); // default is true
+		$this->assertSame( 'bar', groups_get_groupmeta( $g, 'foo', true ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_get_groupmeta
+	 */
+	public function test_bp_activity_get_meta_single_false() {
+		$g = $this->factory->group->create();
+		groups_add_groupmeta( $g, 'foo', 'bar' );
+		groups_add_groupmeta( $g, 'foo', 'baz' );
+		$this->assertSame( array( 'bar', 'baz' ), groups_get_groupmeta( $g, 'foo', false ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_get_groupmeta
+	 * @group cache
+	 */
+	public function test_groups_get_groupmeta_cache_all_on_get() {
+		$g = $this->factory->group->create();
+		groups_add_groupmeta( $g, 'foo', 'bar' );
+		groups_add_groupmeta( $g, 'foo1', 'baz' );
+		$this->assertFalse( wp_cache_get( $g, 'group_meta' ) );
+
+		// A single query should prime the whole meta cache
+		groups_get_groupmeta( $g, 'foo' );
+
+		$c = wp_cache_get( $g, 'group_meta' );
+		$this->assertNotEmpty( $c['foo1'] );
+	}
+
+	/**
+	 * @group groupmeta
+	 */
+	public function test_groups_delete_groupmeta_non_numeric_id() {
+		$this->assertFalse( groups_delete_groupmeta( 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_delete_groupmeta
+	 * @ticket BP5399
+	 */
+	public function test_groups_delete_groupmeta_with_illegal_key_characters() {
+		$g = $this->factory->group->create();
+		$this->assertNotEmpty( groups_update_groupmeta( $g, 'foo', 'bar' ), 'Value of "foo" should be set at this point.' );
+
+		$krazy_key = ' f!@#$%^o *(){}o?+';
+		$this->assertSame( 'bar', groups_get_groupmeta( $g, 'foo' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_delete_groupmeta
+	 * @ticket BP6326
+	 */
+	public function test_groups_delete_groupmeta_with_no_meta_key_when_group_has_metadata() {
+		global $wpdb;
+
+		$g = $this->factory->group->create();
+		$m = groups_get_groupmeta( $g );
+		foreach ( $m as $mk => $mv ) {
+			groups_delete_groupmeta( $g, $mk );
+		}
+
+		$found = groups_delete_groupmeta( $g );
+		$this->assertTrue( $found );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_delete_groupmeta
+	 */
+	public function test_groups_delete_groupmeta_with_delete_all_but_no_meta_key() {
+		// With no meta key, don't delete for all items - just delete
+		// all for a single item
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		groups_add_groupmeta( $g1, 'foo', 'bar' );
+		groups_add_groupmeta( $g1, 'foo1', 'bar1' );
+		groups_add_groupmeta( $g2, 'foo', 'bar' );
+		groups_add_groupmeta( $g2, 'foo1', 'bar1' );
+
+		$this->assertTrue( groups_delete_groupmeta( $g1, '', '', true ) );
+		$this->assertEmpty( groups_get_groupmeta( $g1 ) );
+		$this->assertSame( 'bar', groups_get_groupmeta( $g2, 'foo' ) );
+		$this->assertSame( 'bar1', groups_get_groupmeta( $g2, 'foo1' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_delete_groupmeta
+	 */
+	public function test_groups_delete_groupmeta_with_delete_all() {
+		// With no meta key, don't delete for all items - just delete
+		// all for a single item
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		groups_add_groupmeta( $g1, 'foo', 'bar' );
+		groups_add_groupmeta( $g1, 'foo1', 'bar1' );
+		groups_add_groupmeta( $g2, 'foo', 'bar' );
+		groups_add_groupmeta( $g2, 'foo1', 'bar1' );
+
+		$this->assertTrue( groups_delete_groupmeta( $g1, 'foo', '', true ) );
+		$this->assertSame( '', groups_get_groupmeta( $g1, 'foo' ) );
+		$this->assertSame( '', groups_get_groupmeta( $g2, 'foo' ) );
+		$this->assertSame( 'bar1', groups_get_groupmeta( $g1, 'foo1' ) );
+		$this->assertSame( 'bar1', groups_get_groupmeta( $g2, 'foo1' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_add_groupmeta
+	 */
+	public function test_groups_add_groupmeta_no_meta_key() {
+		$this->assertFalse( groups_add_groupmeta( 1, '', 'bar' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_add_groupmeta
+	 */
+	public function test_groups_add_groupmeta_empty_object_id() {
+		$this->assertFalse( groups_add_groupmeta( 0, 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_add_groupmeta
+	 */
+	public function test_groups_add_groupmeta_existing_unique() {
+		$g = $this->factory->group->create();
+		groups_add_groupmeta( $g, 'foo', 'bar' );
+		$this->assertFalse( groups_add_groupmeta( $g, 'foo', 'baz', true ) );
+	}
+
+	/**
+	 * @group groupmeta
+	 * @group groups_add_groupmeta
+	 */
+	public function test_groups_add_groupmeta_existing_not_unique() {
+		$g = $this->factory->group->create();
+		groups_add_groupmeta( $g, 'foo', 'bar' );
+		$this->assertNotEmpty( groups_add_groupmeta( $g, 'foo', 'baz' ) );
+	}
+
+	/**
+	 * @group groups_get_group
+	 * @group cache
+	 */
+	public function test_groups_get_group_cache_different_users() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$this->add_user_to_group( $u1, $g );
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u1 );
+
+		$group1 = groups_get_group( array( 'group_id' => $g, 'populate_extras' => true ) );
+
+		$this->set_current_user( $u2 );
+
+		$group2 = groups_get_group( array( 'group_id' => $g, 'populate_extras' => true ) );
+
+		$this->assertNotEquals( $group1, $group2 );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group counts
+	 */
+	public function test_get_invite_count_for_user() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+
+		// create invitation
+		groups_invite_user( array(
+			'user_id'    => $u2,
+			'group_id'   => $g,
+			'inviter_id' => $u1,
+		) );
+
+		// send the invite
+		// this function is imperative to set the 'invite_sent' flag in the DB
+		// why is this separated from groups_invite_user()?
+		// @see groups_screen_group_invite()
+		groups_send_invites( $u1, $g );
+
+		// assert invite count
+		$this->assertEquals( 1, groups_get_invite_count_for_user( $u2 ) );
+
+		// accept the invite and reassert
+		groups_accept_invite( $u2, $g );
+		$this->assertEquals( 0, groups_get_invite_count_for_user( $u2 ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/functions/groupsIsUser.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/functions/groupsIsUser.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/functions/groupsIsUser.php	(revision 10410)
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * Tests for the `groups_is_user_*()` functions.
+ *
+ * @group groups
+ */
+class BP_Tests_Groups_Functions_GroupsIsUser extends BP_UnitTestCase {
+	static $user;
+	static $admin_user;
+	static $groups;
+
+	public static function setUpBeforeClass() {
+		$f = new BP_UnitTest_Factory();
+
+		self::$user = $f->user->create( array(
+			'user_login' => 'groups_is_user',
+			'user_email' => 'groups_is_user@example.com',
+		) );
+		self::$admin_user = $f->user->create( array(
+			'user_login' => 'groups_is_user_admin',
+			'user_email' => 'groups_is_user_admin@example.com',
+		) );
+		self::$groups = $f->group->create_many( 3, array(
+			'creator_id' => self::$admin_user,
+		) );
+
+		$now = time();
+
+		self::commit_transaction();
+	}
+
+	public static function tearDownAfterClass() {
+		foreach ( self::$groups as $group ) {
+			groups_delete_group( $group );
+		}
+
+		if ( is_multisite() ) {
+			wpmu_delete_user( self::$user );
+			wpmu_delete_user( self::$admin_user );
+		} else {
+			wp_delete_user( self::$user );
+			wp_delete_user( self::$admin_user );
+		}
+
+		self::commit_transaction();
+	}
+
+	public function test_groups_is_user_admin_expected_true() {
+		$this->add_user_to_group( self::$user, self::$groups[0], array(
+			'is_admin' => false,
+		) );
+		$this->add_user_to_group( self::$user, self::$groups[1], array(
+			'is_admin' => true,
+		) );
+
+		$this->assertEquals( true, groups_is_user_admin( self::$user, self::$groups[1] ) );
+	}
+
+	public function test_groups_is_user_admin_expected_false() {
+		$this->add_user_to_group( self::$user, self::$groups[0], array(
+			'is_admin' => false,
+		) );
+		$this->add_user_to_group( self::$user, self::$groups[1], array(
+			'is_admin' => true,
+		) );
+
+		$this->assertEquals( false, groups_is_user_admin( self::$user, self::$groups[0] ) );
+	}
+
+	public function test_groups_is_user_mod_expected_true() {
+		$this->add_user_to_group( self::$user, self::$groups[0], array(
+			'is_mod' => false,
+		) );
+		$this->add_user_to_group( self::$user, self::$groups[1], array(
+			'is_mod' => true,
+		) );
+
+		$this->assertEquals( true, groups_is_user_mod( self::$user, self::$groups[1] ) );
+	}
+
+	public function test_groups_is_user_mod_expected_false() {
+		$this->add_user_to_group( self::$user, self::$groups[0], array(
+			'is_mod' => false,
+		) );
+		$this->add_user_to_group( self::$user, self::$groups[1], array(
+			'is_mod' => true,
+		) );
+
+		$this->assertEquals( false, groups_is_user_mod( self::$user, self::$groups[0] ) );
+	}
+
+	public function test_groups_is_user_mod_should_return_false_when_user_is_also_banned() {
+		$this->add_user_to_group( self::$user, self::$groups[0], array(
+			'is_mod' => false,
+		) );
+		$this->add_user_to_group( self::$user, self::$groups[1], array(
+			'is_mod' => true,
+		) );
+
+		$m = new BP_Groups_Member( self::$user, self::$groups[1] );
+		$m->ban();
+
+		$this->assertEquals( false, groups_is_user_mod( self::$user, self::$groups[1] ) );
+	}
+
+	public function test_groups_is_user_member_expected_true() {
+		$this->add_user_to_group( self::$user, self::$groups[1] );
+
+		$this->assertEquals( true, groups_is_user_member( self::$user, self::$groups[1] ) );
+	}
+
+	public function test_groups_is_user_member_expected_false() {
+		$this->add_user_to_group( self::$user, self::$groups[1] );
+
+		$this->assertEquals( false, groups_is_user_member( self::$user, self::$groups[0] ) );
+	}
+
+	public function test_groups_is_user_member_should_return_false_when_user_is_also_banned() {
+		$this->add_user_to_group( self::$user, self::$groups[1] );
+
+		$m = new BP_Groups_Member( self::$user, self::$groups[1] );
+		$m->ban();
+
+		$this->assertEquals( false, groups_is_user_member( self::$user, self::$groups[1] ) );
+	}
+
+	public function test_groups_is_user_banned_should_return_false_for_non_member() {
+		$this->assertEquals( false, groups_is_user_banned( self::$user, self::$groups[1] ) );
+	}
+
+	/**
+	 * Return values for these functions are terrible.
+	 */
+	public function test_groups_is_user_banned_should_return_false_for_non_banned_member() {
+		$this->add_user_to_group( self::$user, self::$groups[1] );
+		$this->assertEquals( 0, groups_is_user_banned( self::$user, self::$groups[1] ) );
+	}
+
+	public function test_groups_is_user_banned_should_return_true_for_banned_member() {
+		$this->add_user_to_group( self::$user, self::$groups[1] );
+
+		$m = new BP_Groups_Member( self::$user, self::$groups[1] );
+		$m->ban();
+
+		$this->assertEquals( true, groups_is_user_banned( self::$user, self::$groups[1] ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/notifications.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/notifications.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/notifications.php	(revision 10410)
@@ -0,0 +1,221 @@
+<?php
+
+/**
+ * @group groups
+ * @group notifications
+ */
+class BP_Tests_Groups_Notifications extends BP_UnitTestCase {
+	protected $filter_fired;
+	protected $current_user;
+	protected $requesting_user_id;
+	protected $group;
+
+	public function setUp() {
+		parent::setUp();
+		$this->current_user = get_current_user_id();
+		$this->set_current_user( $this->factory->user->create() );
+
+		$this->requesting_user_id = $this->factory->user->create();
+		$this->group = $this->factory->group->create();
+		$this->filter_fired = '';
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->current_user );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_multiple_new_membership_requests_notification() {
+		add_filter( 'bp_groups_multiple_new_membership_requests_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'new_membership_request', $this->group, $this->requesting_user_id, 5 );
+		remove_filter( 'bp_groups_multiple_new_membership_requests_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_multiple_new_membership_requests_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_single_new_membership_request_notification() {
+		add_filter( 'bp_groups_single_new_membership_request_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'new_membership_request', $this->group, 0, 1 );
+		remove_filter( 'bp_groups_single_new_membership_request_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_single_new_membership_request_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_multiple_membership_request_accepted_notification() {
+		add_filter( 'bp_groups_multiple_membership_request_accepted_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'membership_request_accepted', $this->group, 0, 5 );
+		remove_filter( 'bp_groups_multiple_membership_request_accepted_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_multiple_membership_request_accepted_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_single_membership_request_accepted_notification() {
+		add_filter( 'bp_groups_single_membership_request_accepted_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'membership_request_accepted', $this->group, 0, 1 );
+		remove_filter( 'bp_groups_single_membership_request_accepted_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_single_membership_request_accepted_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_multiple_membership_request_rejected_notification() {
+		add_filter( 'bp_groups_multiple_membership_request_rejected_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'membership_request_rejected', $this->group, 0, 5 );
+		remove_filter( 'bp_groups_multiple_membership_request_rejected_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_multiple_membership_request_rejected_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_single_membership_request_rejected_notification() {
+		add_filter( 'bp_groups_single_membership_request_rejected_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'membership_request_rejected', $this->group, 0, 1 );
+		remove_filter( 'bp_groups_single_membership_request_rejected_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_single_membership_request_rejected_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_multiple_member_promoted_to_admin_notification() {
+		add_filter( 'bp_groups_multiple_member_promoted_to_admin_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'member_promoted_to_admin', $this->group, 0, 5 );
+		remove_filter( 'bp_groups_multiple_member_promoted_to_admin_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_multiple_member_promoted_to_admin_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_single_member_promoted_to_admin_notification() {
+		add_filter( 'bp_groups_single_member_promoted_to_admin_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'member_promoted_to_admin', $this->group, 0, 1 );
+		remove_filter( 'bp_groups_single_member_promoted_to_admin_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_single_member_promoted_to_admin_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_multiple_member_promoted_to_mod_notification() {
+		add_filter( 'bp_groups_multiple_member_promoted_to_mod_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'member_promoted_to_mod', $this->group, 0, 5 );
+		remove_filter( 'bp_groups_multiple_member_promoted_to_mod_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_multiple_member_promoted_to_mod_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_single_member_promoted_to_mod_notification() {
+		add_filter( 'bp_groups_single_member_promoted_to_mod_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'member_promoted_to_mod', $this->group, 0, 1 );
+		remove_filter( 'bp_groups_single_member_promoted_to_mod_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_single_member_promoted_to_mod_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_multiple_group_invite_notification() {
+		add_filter( 'bp_groups_multiple_group_invite_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'group_invite', $this->group, 0, 5 );
+		remove_filter( 'bp_groups_multiple_group_invite_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_multiple_group_invite_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group groups_format_notifications
+	 */
+	public function test_groups_format_notifications_bp_groups_single_group_invite_notification() {
+		add_filter( 'bp_groups_single_group_invite_notification', array( $this, 'notification_filter_callback' ) );
+		$n = groups_format_notifications( 'group_invite', $this->group, 0, 1 );
+		remove_filter( 'bp_groups_single_group_invite_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_groups_single_group_invite_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group bp_groups_delete_promotion_notifications
+	 */
+	public function test_bp_groups_delete_promotion_notifications() {
+		// Dummy group and user IDs
+		$u = 5;
+		$g = 12;
+
+		// Admin
+		$n = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+			'item_id' => $g,
+			'component_action' => 'member_promoted_to_admin',
+		) );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+		) );
+
+		// Double check it's there
+		$this->assertEquals( array( $n ), wp_list_pluck( $notifications, 'id' ) );
+
+		// fire the hook
+		do_action( 'groups_demoted_member', $u, $g );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+		) );
+
+		$this->assertEmpty( $notifications );
+
+		// Mod
+		$n = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+			'item_id' => $g,
+			'component_action' => 'member_promoted_to_mod',
+		) );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+		) );
+
+		// Double check it's there
+		$this->assertEquals( array( $n ), wp_list_pluck( $notifications, 'id' ) );
+
+		// fire the hook
+		do_action( 'groups_demoted_member', $u, $g );
+
+		$notifications = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+		) );
+
+		$this->assertEmpty( $notifications );
+	}
+
+	public function notification_filter_callback( $value ) {
+		$this->filter_fired = current_filter();
+		return $value;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/template.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/template.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/template.php	(revision 10410)
@@ -0,0 +1,1117 @@
+<?php
+/**
+ * @group groups
+ * @group template
+ */
+class BP_Tests_Groups_Template extends BP_UnitTestCase {
+	/**
+	 * Integration test to make sure meta_query is getting passed through
+	 *
+	 * @group bp_has_groups
+	 */
+	public function test_bp_has_groups_with_meta_query() {
+		$g1 = $this->factory->group->create();
+		$g2 = $this->factory->group->create();
+		groups_update_groupmeta( $g1, 'foo', 'bar' );
+
+		global $groups_template;
+		bp_has_groups( array(
+			'meta_query' => array(
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+				),
+			),
+		) );
+
+		$ids = wp_list_pluck( $groups_template->groups, 'id' );
+		$this->assertEquals( $ids, array( $g1, ) );
+	}
+
+	/**
+	 * Integration test to make sure order and orderby are interpreted when
+	 * no 'type' value has been passed
+	 *
+	 * @group bp_has_groups
+	 */
+	public function test_bp_has_groups_with_order_orderby_with_null_type() {
+		$now = time();
+		$g1 = $this->factory->group->create( array(
+			'name' => 'AAAAA',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $now - 100 ),
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 1000000 ),
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'BBBBB',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $now - 1000000 ),
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 10000 ),
+		) );
+		$g3 = $this->factory->group->create( array(
+			'name' => 'CCCCC',
+			'date_created' => gmdate( 'Y-m-d H:i:s', $now - 10000 ),
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 10 ),
+		) );
+
+		global $groups_template;
+		bp_has_groups( array(
+			'order' => 'ASC',
+			'orderby' => 'name',
+		) );
+
+		$ids = wp_parse_id_list( wp_list_pluck( $groups_template->groups, 'id' ) );
+		$this->assertEquals( array( $g1, $g2, $g3, ), $ids );
+	}
+
+	/**
+	 * Integration test to make sure 'order' is set to 'DESC' and 'orderby'
+	 * to 'last_activity' when no type or order/orderby params are passed.
+	 * This ensures backpat with the old system, where 'active' was the
+	 * default type param, and there were no order/orderby params.
+	 *
+	 * @group bp_has_groups
+	 */
+	public function test_bp_has_groups_defaults_to_DESC_last_activity_for_default_type_active_backpat() {
+		$now = time();
+		$g1 = $this->factory->group->create( array(
+			'name' => 'AAAAA',
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$g2 = $this->factory->group->create( array(
+			'name' => 'BBBBB',
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 1000000 ),
+		) );
+		$g3 = $this->factory->group->create( array(
+			'name' => 'CCCCC',
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 10000 ),
+		) );
+
+		global $groups_template;
+		bp_has_groups();
+
+		$ids = wp_parse_id_list( wp_list_pluck( $groups_template->groups, 'id' ) );
+		$this->assertEquals( array( $g1, $g3, $g2, ), $ids );
+	}
+
+	/**
+	 * Test using the 'slug' parameter in bp_has_groups()
+	 *
+	 * Note: The 'slug' parameter currently also requires the 'type' to be set
+	 * to 'single-group'.
+	 *
+	 * @group bp_has_groups
+	 */
+	public function test_bp_has_groups_single_group_with_slug() {
+		$g1 = $this->factory->group->create( array(
+			'name' => 'Test Group',
+			'slug' => 'test-group',
+			'last_activity' => gmdate( 'Y-m-d H:i:s', time() - 100 ),
+		) );
+
+		global $groups_template;
+		bp_has_groups( array(
+			'type' => 'single-group',
+			'slug' => 'test-group',
+		) );
+
+		$ids = wp_parse_id_list( wp_list_pluck( $groups_template->groups, 'id' ) );
+		$this->assertEquals( array( $g1 ), $ids );
+
+		$this->assertEquals( 1, $groups_template->group_count );
+	}
+
+	/**
+	 * @group bp_group_has_members
+	 */
+	public function test_bp_group_has_members_vanilla() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g  = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+
+		$this->add_user_to_group( $u1, $g );
+
+		global $members_template;
+		bp_group_has_members( array(
+			'group_id' => $g,
+			'exclude_admins_mods' => false,
+		) );
+
+		$ids = wp_parse_id_list( wp_list_pluck( $members_template->members, 'user_id' ) );
+		$this->assertEquals( array( $u1, ), $ids );
+	}
+
+	/**
+	 * Switching from BP_Groups_Member to BP_Group_Member_Query meant a
+	 * change in the format of the values returned from the query. For
+	 * backward compatibility, we translate some of the return values
+	 * of BP_Group_Member_Query to the older format. This test makes sure
+	 * that the translation happens properly.
+	 *
+	 * @group bp_group_has_members
+	 */
+	public function test_bp_group_has_members_backpat_retval_format() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u2 ) );
+
+		$date_modified = gmdate( 'Y-m-d H:i:s', time() - 100 );
+
+		$this->add_user_to_group( $u1, $g, array( 'date_modified' => $date_modified ) );
+
+		global $members_template;
+		bp_group_has_members( array(
+			'group_id' => $g,
+		) );
+
+		$u1_object = new WP_User( $u1 );
+
+		$expected = new stdClass;
+		$expected->user_id = $u1;
+		$expected->date_modified = $date_modified;
+		$expected->is_banned = 0;
+		$expected->user_login = $u1_object->user_login;
+		$expected->user_nicename = $u1_object->user_nicename;
+		$expected->user_email = $u1_object->user_email;
+		$expected->display_name = $u1_object->display_name;
+
+		// In order to use assertEquals, we need to discard the
+		// irrelevant properties of the found object. Hack alert
+		$found = new stdClass;
+		foreach ( array( 'user_id', 'date_modified', 'is_banned', 'user_login', 'user_nicename', 'user_email', 'display_name' ) as $key ) {
+			if ( isset( $members_template->members[0]->{$key} ) ) {
+				$found->{$key} = $members_template->members[0]->{$key};
+			}
+		}
+
+		$this->assertEquals( $expected, $found );
+	}
+
+	/**
+	 * @group bp_group_has_members
+	 */
+	public function test_bp_group_has_members_with_per_page() {
+		$g = $this->factory->group->create();
+
+		$users = array();
+		for ( $i = 1; $i <= 5; $i++ ) {
+			$users[ $i ] = $this->factory->user->create();
+		}
+
+		$expected = array();
+		$now = time();
+		for ( $i = 3; $i <= 5; $i++ ) {
+			$this->add_user_to_group( $users[ $i ], $g, array(
+				'date_modified' => date( 'Y-m-d H:i:s', $now - ( 60 * $i ) ),
+			) );
+			$expected[] = $users[ $i ];
+		}
+
+		// hack it down to 2 (per page arg below)
+		$expected = array_slice( $expected, 0, 2 );
+
+		global $members_template;
+		bp_group_has_members( array(
+			'group_id' => $g,
+			'per_page' => 2,
+		) );
+
+		$ids = wp_parse_id_list( wp_list_pluck( $members_template->members, 'user_id' ) );
+		$this->assertEquals( $expected, $ids );
+	}
+
+	/**
+	 * Note: 'max' is a weird parameter. It just changes the member_count
+	 * in the global - not the sql query at all. I'm testing what it
+	 * appears to be designed to do, not what it feels like it ought to do
+	 * if it made any sense. Programming is fun, QED.
+	 *
+	 * @group bp_group_has_members
+	 */
+	public function test_bp_group_has_members_with_max() {
+		$g = $this->factory->group->create();
+
+		$users = array();
+		for ( $i = 1; $i <= 5; $i++ ) {
+			$users[ $i ] = $this->factory->user->create();
+		}
+
+		$expected = array();
+		for ( $i = 3; $i <= 5; $i++ ) {
+			$this->add_user_to_group( $users[ $i ], $g );
+			$expected[] = $users[ $i ];
+		}
+
+		global $members_template;
+		bp_group_has_members( array(
+			'group_id' => $g,
+			'max' => 1,
+		) );
+
+		$this->assertEquals( 1, $members_template->member_count );
+	}
+
+	/**
+	 * @group bp_group_has_members
+	 */
+	public function test_bp_group_has_members_with_exclude() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$this->add_user_to_group( $u1, $g );
+		$this->add_user_to_group( $u2, $g );
+
+		global $members_template;
+		bp_group_has_members( array(
+			'group_id' => $g,
+			'exclude' => $u1,
+		) );
+
+		$ids = wp_parse_id_list( wp_list_pluck( $members_template->members, 'user_id' ) );
+		$this->assertEquals( array( $u2 ), $ids );
+	}
+
+	/**
+	 * @group bp_group_has_members
+	 */
+	public function test_bp_group_has_members_with_exclude_admins_mods_1() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$this->add_user_to_group( $u1, $g );
+		$this->add_user_to_group( $u2, $g );
+		$this->add_user_to_group( $u3, $g );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+
+		global $members_template;
+		bp_group_has_members( array(
+			'group_id' => $g,
+			'exclude_admins_mods' => 1,
+		) );
+
+		$ids = wp_parse_id_list( wp_list_pluck( $members_template->members, 'user_id' ) );
+		$this->assertEquals( array( $u3 ), $ids );
+	}
+
+	/**
+	 * @group bp_group_has_members
+	 */
+	public function test_bp_group_has_members_with_exclude_admins_mods_0() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+		$g = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+
+		$now = time();
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $now - 60 ),
+		) );
+		$this->add_user_to_group( $u3, $g, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $now - 60*60 ),
+		) );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->promote( 'admin' );
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->promote( 'mod' );
+
+		global $members_template;
+		bp_group_has_members( array(
+			'group_id' => $g,
+			'exclude_admins_mods' => 0,
+		) );
+
+		$ids = wp_parse_id_list( wp_list_pluck( $members_template->members, 'user_id' ) );
+		$this->assertEquals( array( $u1, $u2, $u3 ), $ids );
+	}
+
+	/**
+	 * @group bp_group_has_members
+	 */
+	public function test_bp_group_has_members_with_exclude_banned_1() {
+		$g = $this->factory->group->create();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$this->add_user_to_group( $u1, $g );
+		$this->add_user_to_group( $u2, $g );
+
+		$m1 = new BP_Groups_Member( $u1, $g );
+		$m1->ban();
+
+		global $members_template;
+		bp_group_has_members( array(
+			'group_id' => $g,
+			'exclude_banned' => 1,
+		) );
+
+		$ids = wp_parse_id_list( wp_list_pluck( $members_template->members, 'user_id' ) );
+		$this->assertEquals( array( $u2, ), $ids );
+	}
+
+	/**
+	 * @group bp_group_has_members
+	 */
+	public function test_bp_group_has_members_with_exclude_banned_0() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$g = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+
+		$now = time();
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $now - 60*60*24 ),
+		) );
+		$this->add_user_to_group( $u3, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $now - 60*60*12 ),
+		) );
+
+		$m2 = new BP_Groups_Member( $u2, $g );
+		$m2->ban();
+
+		global $members_template;
+		bp_group_has_members( array(
+			'group_id' => $g,
+			'exclude_banned' => 0,
+			'exclude_admins_mods' => false,
+		) );
+
+		$ids = wp_parse_id_list( wp_list_pluck( $members_template->members, 'user_id' ) );
+		$this->assertEquals( array( $u1, $u3, $u2 ), $ids );
+	}
+
+	/**
+	 * Default sort order should be the joined date
+	 *
+	 * @tickett BP5106
+	 * @group bp_group_has_members
+	 */
+	public function test_bp_group_has_members_default_order() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 60 ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 600 ),
+		) );
+		$u3 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 6000 ),
+		) );
+
+		$g = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+
+		$this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $now - 60*60*24 ),
+		) );
+
+		$this->add_user_to_group( $u3, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $now - 60*60*12 ),
+		) );
+
+		global $members_template;
+		bp_group_has_members( array(
+			'group_id' => $g,
+			'exclude_banned' => 0,
+			'exclude_admins_mods' => false,
+		) );
+
+		$ids = wp_parse_id_list( wp_list_pluck( $members_template->members, 'user_id' ) );
+		$this->assertEquals( array( $u1, $u3, $u2, ), $ids );
+	}
+
+	/**
+	 * @group bp_group_has_invites
+	 * @group BP_Groups_Invite_Template
+	 */
+	public function test_bp_group_has_invites_template_structure() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 60 ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 600 ),
+		) );
+		$u3 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 6000 ),
+		) );
+		$u4 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 60000 ),
+		) );
+
+
+		$g = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+
+		$m2 = $this->add_user_to_group( $u2, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $now - 60*60*24 ),
+			'is_confirmed' => 0,
+			'inviter_id' => $u1,
+			'invite_sent' => true,
+		) );
+
+		$m3 = $this->add_user_to_group( $u3, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $now - 60*60*12 ),
+			'is_confirmed' => 0,
+			'inviter_id' => $u1,
+			'invite_sent' => true,
+		) );
+
+		$m4 = $this->add_user_to_group( $u4, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $now - 60*60*36 ),
+			'is_confirmed' => 1,
+			'inviter_id' => $u1,
+			'invite_sent' => true,
+		) );
+
+		// Populate the global
+		bp_group_has_invites( array(
+			'group_id' => $g,
+			'user_id' => $u1,
+		) );
+
+		global $invites_template;
+
+		$found_users = array(
+			0 => $u2,
+			1 => $u3,
+		);
+
+		// Invites array
+		$this->assertEquals( $found_users, $invites_template->invites );
+
+		// Make sure user is set when loop starts
+		$counter = 0;
+		while ( bp_group_invites() ) : bp_group_the_invite();
+			$this->assertEquals( $g, $invites_template->invite->group_id );
+
+			$this_user = new BP_Core_User( $found_users[ $counter ] );
+			foreach ( get_object_vars( $this_user ) as $k => $v ) {
+				// Doesn't matter if the backpat provides *more*
+				// details than the old method, so we skip cases
+				// where the BP_Core_User value is empty
+				if ( empty( $v ) ) {
+					continue;
+				}
+
+				$this->assertEquals( $v, $invites_template->invite->user->{$k} );
+			}
+			$counter++;
+		endwhile;
+	}
+
+	/**
+	 * @group bp_group_has_invites
+	 * @group BP_Groups_Invite_Template
+	 */
+	public function test_bp_group_has_invites_pagination() {
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', time() - 60 ),
+		) );
+
+		$g = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+
+		$users = array();
+		$now = time();
+		for ( $i = 1; $i < 6; $i++ ) {
+			$users[ $i ] = $this->factory->user->create( array(
+				'last_activity' => gmdate( 'Y-m-d H:i:s', $now - $i ),
+			) );
+
+			$this->add_user_to_group( $users[ $i ], $g, array(
+				'date_modified' => gmdate( 'Y-m-d H:i:s', $now - $i ),
+				'is_confirmed' => 0,
+				'inviter_id' => $u1,
+				'invite_sent' => true,
+			) );
+		}
+
+		// Populate the global
+		bp_group_has_invites( array(
+			'group_id' => $g,
+			'user_id' => $u1,
+			'page' => 2,
+			'per_page' => 2,
+		) );
+
+		global $invites_template;
+
+		$this->assertEquals( array( $users[ 3 ], $users[ 2 ] ), $invites_template->invites );
+	}
+
+	/**
+	 * Checks for proper queried items
+	 *
+	 * @group bp_group_has_membership_requests
+	 * @group BP_Group_Membership_Requests_Template
+	 */
+	public function test_bp_group_has_membership_requests_results() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $now - 60 ),
+		) );
+
+		$g = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+
+		$users = array();
+		$memberships = array();
+		for ( $i = 1; $i < 5; $i++ ) {
+			$users[ $i ] = $this->factory->user->create( array(
+				'last_activity' => gmdate( 'Y-m-d H:i:s', $now - ( 100 - $i ) ),
+			) );
+
+			$memberships[ $i ] = $this->add_user_to_group( $users[ $i ], $g, array(
+				// this date_modified ensures that order will match
+				// id order. necessary due to a quirk in the legacy
+				// implementation
+				'date_modified' => gmdate( 'Y-m-d H:i:s', $now - ( 100 - $i ) ),
+				'is_confirmed' => 0,
+				'inviter_id' => 0,
+				'invite_sent' => false,
+			) );
+		}
+
+		// Fake the current group
+		global $groups_template;
+
+		if ( ! isset( $groups_template ) ) {
+			$groups_template = new stdClass;
+		}
+
+		if ( ! isset( $groups_template->group ) ) {
+			$groups_template->group = new stdClass;
+		}
+
+		$groups_template->group->id = $g;
+
+		// Populate the global
+		bp_group_has_membership_requests( array(
+			'group_id' => $g,
+			'per_page' => 3,
+		) );
+
+		global $requests_template;
+
+		$expected_user_ids = array();
+		$expected_mem_ids = array();
+		for ( $j = 1; $j <= 3; $j++ ) {
+			$expected_user_ids[] = (string) $users[ $j ];
+			$expected_mem_ids[] = (string) $memberships[ $j ];
+		}
+
+		$this->assertEquals( $expected_user_ids, wp_list_pluck( $requests_template->requests, 'user_id' ) );
+		$this->assertEquals( $expected_mem_ids, wp_list_pluck( $requests_template->requests, 'id' ) );
+	}
+
+	/**
+	 * Checks that the requests_template object is properly formatted
+	 *
+	 * @group bp_group_has_membership_requests
+	 * @group BP_Group_Membership_Requests_Template
+	 */
+	public function test_bp_group_has_membership_requests_format() {
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', time() - 60 ),
+		) );
+
+		$g = $this->factory->group->create( array(
+			'creator_id' => $u1,
+		) );
+
+		$time = time();
+
+		$user = $this->factory->user->create( array(
+			'last_activity' => gmdate( 'Y-m-d H:i:s', $time ),
+		) );
+
+		$membership = $this->add_user_to_group( $user, $g, array(
+			'date_modified' => gmdate( 'Y-m-d H:i:s', $time ),
+			'is_confirmed' => 0,
+			'inviter_id' => 0,
+			'invite_sent' => false,
+		) );
+
+		// Fake the current group
+		global $groups_template;
+
+		if ( ! isset( $groups_template ) ) {
+			$groups_template = new stdClass;
+		}
+
+		if ( ! isset( $groups_template->group ) ) {
+			$groups_template->group = new stdClass;
+		}
+
+		$groups_template->group->id = $g;
+
+		// Populate the global
+		bp_group_has_membership_requests( array(
+			'group_id' => $g,
+			'per_page' => 1,
+			'max' => 1,
+		) );
+
+		global $requests_template;
+
+		$expected = new stdClass;
+		$expected->id = $membership;
+		$expected->group_id = $g;
+		$expected->user_id = $user;
+		$expected->inviter_id = '0';
+		$expected->is_admin = '0';
+		$expected->is_mod = '0';
+		$expected->user_title = '';
+		$expected->date_modified = gmdate( 'Y-m-d H:i:s', $time );
+		$expected->comments = '';
+		$expected->is_confirmed = '0';
+		$expected->is_banned = '0';
+		$expected->invite_sent = '0';
+
+		// Check each expected value. If there are more in the results,
+		// that's OK
+		foreach ( get_object_vars( $expected ) as $k => $v ) {
+			$this->assertEquals( $v, $requests_template->requests[0]->{$k} );
+		}
+	}
+
+	/**
+	 * @group bp_group_is_user_banned
+	 */
+	public function test_bp_group_is_user_banned_in_groups_loop() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+
+		$g1 = $this->factory->group->create( array(
+			'creator_id' => $u1,
+			'last_activity' => $now - 100,
+		) );
+		$g2 = $this->factory->group->create( array(
+			'creator_id' => $u2,
+			'last_activity' => $now - 200,
+		) );
+
+		$this->add_user_to_group( $u1, $g2, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+		$this->add_user_to_group( $u2, $g2, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $now - 500 ),
+		) );
+		$this->add_user_to_group( $u1, $g2, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+
+		// Ban user 1 from group 2
+		// Fool the admin check
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u2 );
+		buddypress()->is_item_admin = true;
+		groups_ban_member( $u1, $g2 );
+
+		// Start the groups loop
+		$this->set_current_user( $u1 );
+		if ( bp_has_groups() ) : while ( bp_groups() ) : bp_the_group();
+			$found[] = bp_group_is_user_banned();
+		endwhile; endif;
+
+		// Assert
+		$expected = array( false, true );
+		$this->assertEquals( $expected, $found );
+
+		// Clean up
+		$GLOBALS['groups_template'] = null;
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group bp_group_is_user_banned
+	 */
+	public function test_bp_group_is_user_banned_not_in_groups_loop() {
+		$now = time();
+		$u1 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$u2 = $this->factory->user->create( array(
+			'last_activity' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u2 ) );
+
+		$this->add_user_to_group( $u1, $g2, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+		$this->add_user_to_group( $u2, $g2, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $now - 500 ),
+		) );
+		$this->add_user_to_group( $u1, $g2, array(
+			'date_modified' => date( 'Y-m-d H:i:s', $now - 50 ),
+		) );
+
+		// Ban user 1 from group 2
+		// Fool the admin check
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u2 );
+		buddypress()->is_item_admin = true;
+		groups_ban_member( $u1, $g2 );
+
+		// Do group ban checks
+		$group1 = new BP_Groups_Group( $g1 );
+		$group2 = new BP_Groups_Group( $g2 );
+
+		$found = array();
+		$found[] = bp_group_is_user_banned( $group1, $u1 );
+		$found[] = bp_group_is_user_banned( $group2, $u1 );
+
+		// Assert
+		$expected = array( false, true );
+		$this->assertEquals( $expected, $found );
+
+		// Clean up
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group bp_get_group_member_count
+	 */
+	public function test_bp_get_group_member_count_0_members() {
+		global $groups_template;
+		$gt = $groups_template;
+		$groups_template = new stdClass;
+		$groups_template->group = new stdClass;
+		$groups_template->group->total_member_count = 0;
+
+		$found = bp_get_group_member_count();
+
+		$groups_template = $gt;
+
+		$this->assertSame( '0 members', $found );
+	}
+
+	/**
+	 * @group bp_get_group_member_count
+	 */
+	public function test_bp_get_group_member_count_1_member() {
+		global $groups_template;
+		$gt = $groups_template;
+		$groups_template = new stdClass;
+		$groups_template->group = new stdClass;
+		$groups_template->group->total_member_count = 1;
+
+		$found = bp_get_group_member_count();
+
+		$groups_template = $gt;
+
+		$this->assertSame( '1 member', $found );
+	}
+
+	/**
+	 * @group bp_get_group_member_count
+	 */
+	public function test_bp_get_group_member_count_2_members() {
+		global $groups_template;
+		$gt = $groups_template;
+		$groups_template = new stdClass;
+		$groups_template->group = new stdClass;
+		$groups_template->group->total_member_count = 2;
+
+		$found = bp_get_group_member_count();
+
+		$groups_template = $gt;
+
+		$this->assertSame( '2 members', $found );
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Template
+	 */
+	public function test_bp_groups_template_should_give_precedence_to_grpage_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['grpage'] = '5';
+
+		$at = new BP_Groups_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 5, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Template
+	 */
+	public function test_bp_groups_template_should_reset_0_pag_page_URL_param_to_default_pag_page_value() {
+		$request = $_REQUEST;
+		$_REQUEST['grpage'] = '0';
+
+		$at = new BP_Groups_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 8, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Template
+	 */
+	public function test_bp_groups_template_should_give_precedence_to_num_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '14';
+
+		$at = new BP_Groups_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 14, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Template
+	 */
+	public function test_bp_groups_template_should_reset_0_pag_num_URL_param_to_default_pag_num_value() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '0';
+
+		$at = new BP_Groups_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 13, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Group_Members_Template
+	 */
+	public function test_bp_groups_group_members_template_should_give_precedence_to_mlpage_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['mlpage'] = '5';
+
+		$at = new BP_Groups_Group_Members_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 5, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Group_Members_Template
+	 */
+	public function test_bp_groups_group_members_template_should_reset_0_pag_page_URL_param_to_default_pag_page_value() {
+		$request = $_REQUEST;
+		$_REQUEST['mlpage'] = '0';
+
+		$at = new BP_Groups_Group_Members_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 8, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Group_Members_Template
+	 */
+	public function test_bp_groups_group_members_template_should_give_precedence_to_num_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '14';
+
+		$at = new BP_Groups_Group_Members_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 14, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Group_Members_Template
+	 */
+	public function test_bp_groups_group_members_template_should_reset_0_pag_num_URL_param_to_default_pag_num_value() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '0';
+
+		$at = new BP_Groups_Group_Members_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 13, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Membership_Requests_Template
+	 */
+	public function test_bp_groups_membership_requests_template_should_give_precedence_to_mrpage_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['mrpage'] = '5';
+
+		$at = new BP_Groups_Membership_Requests_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 5, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Membership_Requests_Template
+	 */
+	public function test_bp_groups_membership_requests_template_should_reset_0_pag_page_URL_param_to_default_pag_page_value() {
+		$request = $_REQUEST;
+		$_REQUEST['mrpage'] = '0';
+
+		$at = new BP_Groups_Membership_Requests_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 8, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Membership_Requests_Template
+	 */
+	public function test_bp_groups_membership_requests_template_should_give_precedence_to_num_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '14';
+
+		$at = new BP_Groups_Membership_Requests_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 14, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Membership_Requests_Template
+	 */
+	public function test_bp_groups_membership_requests_template_should_reset_0_pag_num_URL_param_to_default_pag_num_value() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '0';
+
+		$at = new BP_Groups_Membership_Requests_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 13, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Invite_Template
+	 */
+	public function test_bp_groups_invite_template_should_give_precedence_to_invitepage_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['invitepage'] = '5';
+
+		$at = new BP_Groups_Invite_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 5, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Invite_Template
+	 */
+	public function test_bp_groups_invite_template_should_reset_0_pag_page_URL_param_to_default_pag_page_value() {
+		$request = $_REQUEST;
+		$_REQUEST['invitepage'] = '0';
+
+		$at = new BP_Groups_Invite_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 8, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Invite_Template
+	 */
+	public function test_bp_groups_invite_template_should_give_precedence_to_num_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '14';
+
+		$at = new BP_Groups_Invite_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 14, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Groups_Invite_Template
+	 */
+	public function test_bp_groups_invite_template_should_reset_0_pag_num_URL_param_to_default_pag_num_value() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '0';
+
+		$at = new BP_Groups_Invite_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 13, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/groups/template/bpGroupStatusMessage.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/groups/template/bpGroupStatusMessage.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/groups/template/bpGroupStatusMessage.php	(revision 10410)
@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * @group groups
+ */
+class BP_Tests_Groups_Template_BpGroupStatusMessage extends BP_UnitTestCase {
+	private $current_user;
+	private $groups_template = null;
+
+	public function setUp() {
+		parent::setUp();
+		$this->current_user = bp_loggedin_user_id();
+		$this->set_current_user( 0 );
+
+		if ( isset( $GLOBALS['groups_template'] ) ) {
+			$this->groups_template = $GLOBALS['groups_template'];
+		}
+	}
+
+	public function tearDown() {
+		$this->set_current_user( $this->current_user );
+		if ( $this->groups_template ) {
+			$GLOBALS['groups_template'] = $this->groups_template;
+		}
+
+		parent::tearDown();
+	}
+
+	/**
+	 * @group BP6319
+	 */
+	public function test_private_group_where_logged_in_user_has_not_requested_membership_but_has_been_invited() {
+		$users = $this->factory->user->create_many( 2 );
+		$g = $this->factory->group->create( array( 'status' => 'private' ) );
+
+		$this->set_current_user( $users[0] );
+
+		groups_invite_user( array(
+			'user_id' => $users[0],
+			'group_id' => $g,
+			'inviter_id' => $users[1],
+		) );
+
+		if ( bp_has_groups( array( 'include' => array( $g ) ) ) ) {
+			while ( bp_groups() ) {
+				bp_the_group();
+				$found = get_echo( 'bp_group_status_message' );
+			}
+		}
+
+		$expected = __( 'This is a private group and you must request group membership in order to join.', 'buddypress' );
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group BP6319
+	 */
+	public function test_private_group_where_logged_in_user_has_not_requested_membership_and_has_not_been_invited() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'status' => 'private' ) );
+
+		$this->set_current_user( $u );
+
+		if ( bp_has_groups( array( 'include' => array( $g ) ) ) ) {
+			while ( bp_groups() ) {
+				bp_the_group();
+				$found = get_echo( 'bp_group_status_message' );
+			}
+		}
+
+		$expected = __( 'This is a private group and you must request group membership in order to join.', 'buddypress' );
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group BP6319
+	 */
+	public function test_private_group_visited_by_a_non_logged_in_user() {
+		$g = $this->factory->group->create( array( 'status' => 'private' ) );
+
+		if ( bp_has_groups( array( 'include' => array( $g ) ) ) ) {
+			while ( bp_groups() ) {
+				bp_the_group();
+				$found = get_echo( 'bp_group_status_message' );
+			}
+		}
+
+		$expected = __( 'This is a private group. To join you must be a registered site member and request group membership.', 'buddypress' );
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group BP6319
+	 */
+	public function test_private_group_where_loggedin_user_has_requested_membership() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'status' => 'private' ) );
+
+		$this->set_current_user( $u );
+
+		groups_send_membership_request( $u, $g );
+
+		if ( bp_has_groups( array( 'include' => array( $g ) ) ) ) {
+			while ( bp_groups() ) {
+				bp_the_group();
+				$found = get_echo( 'bp_group_status_message' );
+			}
+		}
+
+		$expected = __( 'This is a private group. Your membership request is awaiting approval from the group administrator.', 'buddypress' );
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group BP6319
+	 */
+	public function test_hidden_group() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'status' => 'hidden' ) );
+
+		$this->set_current_user( $u );
+
+		$group = groups_get_group( array(
+			'group_id' => $g,
+			'populate_extras' => true,
+		) );
+
+		$found = get_echo( 'bp_group_status_message', array( $group ) );
+
+		$expected = __( 'This is a hidden group and only invited members can join.', 'buddypress' );
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group BP6319
+	 */
+	public function test_group_parameter_should_be_obeyed() {
+		$u = $this->factory->user->create();
+		$groups = $this->factory->group->create_many( 2, array( 'status' => 'private' ) );
+
+		$this->set_current_user( $u );
+
+		// Fake the current group.
+		$GLOBALS['groups_template'] = new stdClass;
+		$GLOBALS['groups_template']->group = groups_get_group( array(
+			'group_id' => $groups[0],
+			'populate_extras' => true,
+		) );
+
+		groups_send_membership_request( $u, $groups[1] );
+
+		$group1 = groups_get_group( array(
+			'group_id' => $groups[1],
+			'populate_extras' => true,
+		) );
+
+		$found = get_echo( 'bp_group_status_message', array( $group1 ) );
+
+		$expected = __( 'This is a private group. Your membership request is awaiting approval from the group administrator.', 'buddypress' );
+		$this->assertSame( $expected, $found );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/members/activity.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/members/activity.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/members/activity.php	(revision 10410)
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @group members
+ * @group activity
+ */
+class BP_Tests_Members_Activity extends BP_UnitTestCase {
+
+	/**
+	 * @group activity_action
+	 * @group bp_core_format_activity_action_new_member
+	 */
+	public function test_bp_members_format_activity_action_new_member() {
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->members->id,
+			'type' => 'new_member',
+			'user_id' => $u,
+		) );
+
+		$expected = sprintf( __( '%s became a registered member', 'buddypress' ), bp_core_get_userlink( $u ) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group bp_migrate_new_member_activity_component
+	 */
+	public function test_bp_migrate_new_member_activity_component() {
+		global $wpdb;
+		$bp = buddypress();
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$au1 = $this->factory->activity->create( array(
+			'component' => 'xprofile',
+			'type' => 'new_member',
+			'user_id' => $u1,
+		) );
+
+		$au2 = $this->factory->activity->create( array(
+			'component' => 'xprofile',
+			'type' => 'new_member',
+			'user_id' => $u2,
+		) );
+
+		$au3 = $this->factory->activity->create( array(
+			'component' => 'xprofile',
+			'type' => 'new_member',
+			'user_id' => $u3,
+		) );
+
+		bp_migrate_new_member_activity_component();
+
+		$expected = array(
+			$u1 => $au1,
+			$u2 => $au2,
+			$u3 => $au3,
+		);
+
+		$in = "'" . implode( "', '", array_keys( $expected ) ) . "'";
+		$found = $wpdb->get_results(
+			$wpdb->prepare(
+				"SELECT user_id, id FROM {$bp->members->table_name_last_activity} WHERE component = %s AND type = %s AND user_id IN ({$in}) ORDER BY user_id ASC",
+				$bp->members->id,
+				'new_member'
+		), OBJECT_K );
+
+		$found = array_map( 'intval', wp_list_pluck( $found, 'id' ) );
+
+		$this->assertSame( $expected, $found );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/members/cache.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/members/cache.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/members/cache.php	(revision 10410)
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @group cache
+ * @group members
+ */
+class BP_Tests_Members_Cache extends BP_UnitTestCase {
+	/**
+	 * @group bp_core_get_total_member_count
+	 * @group cache
+	 */
+	public function test_bp_core_get_total_member_count_should_respect_cached_value_of_0() {
+		global $wpdb;
+
+		// set cached value to zero
+		wp_cache_set( 'bp_total_member_count', 0, 'bp' );
+		$num_queries = $wpdb->num_queries;
+
+		// run function
+		bp_core_get_total_member_count();
+
+		// check if function references cache or hits the DB by comparing query count
+		$this->assertEquals( $num_queries, $wpdb->num_queries );
+	}
+}
+
Index: /tags/2.4.3/tests/phpunit/testcases/members/class-bp-signup.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/members/class-bp-signup.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/members/class-bp-signup.php	(revision 10410)
@@ -0,0 +1,332 @@
+<?php
+
+/**
+ * @group members
+ * @group signups
+ * @group BP_Signup
+ */
+class BP_Tests_BP_Signup extends BP_UnitTestCase {
+	protected $signup_allowed;
+
+	public function setUp() {
+
+		if ( is_multisite() ) {
+			$this->signup_allowed = get_site_option( 'registration' );
+			update_site_option( 'registration', 'all' );
+		} else {
+			bp_get_option( 'users_can_register' );
+			bp_update_option( 'users_can_register', 1 );
+		}
+
+		parent::setUp();
+	}
+
+	public function tearDown() {
+		if ( is_multisite() ) {
+			update_site_option( 'registration', $this->signup_allowed );
+		} else {
+			bp_update_option( 'users_can_register', $this->signup_allowed );
+		}
+
+		parent::tearDown();
+	}
+
+	/**
+	 * @group add
+	 */
+	public function test_add() {
+		$time = bp_core_current_time();
+		$args = array(
+			'domain' => 'foo',
+			'path' => 'bar',
+			'title' => 'Foo bar',
+			'user_login' => 'user1',
+			'user_email' => 'user1@example.com',
+			'registered' => $time,
+			'activation_key' => '12345',
+			'meta' => array(
+				'field_1' => 'Foo Bar',
+				'meta1' => 'meta2',
+			),
+		);
+
+		$signup = BP_Signup::add( $args );
+		$this->assertNotEmpty( $signup );
+
+		$s = new BP_Signup( $signup );
+
+		// spot check
+		$this->assertSame( $signup, $s->id );
+		$this->assertSame( 'user1', $s->user_login );
+		$this->assertSame( '12345', $s->activation_key );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_offset() {
+		$s1 = $this->factory->signup->create();
+		$s2 = $this->factory->signup->create();
+		$s3 = $this->factory->signup->create();
+
+		$ss = BP_Signup::get( array(
+			'offset' => 1,
+		) );
+
+		$this->assertEquals( array( $s2 ), wp_list_pluck( $ss['signups'], 'signup_id' ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_number() {
+		$s1 = $this->factory->signup->create();
+		$s2 = $this->factory->signup->create();
+		$s3 = $this->factory->signup->create();
+
+		$ss = BP_Signup::get( array(
+			'number' => 2,
+		) );
+
+		$this->assertEquals( array( $s3, $s2 ), wp_list_pluck( $ss['signups'], 'signup_id' ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_usersearch() {
+		$s1 = $this->factory->signup->create( array(
+			'user_email' => 'fghij@example.com',
+		) );
+		$s2 = $this->factory->signup->create();
+		$s3 = $this->factory->signup->create();
+
+		$ss = BP_Signup::get( array(
+			'usersearch' => 'ghi',
+		) );
+
+		$this->assertEquals( array( $s1 ), wp_list_pluck( $ss['signups'], 'signup_id' ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_orderby_email() {
+		$s1 = $this->factory->signup->create( array(
+			'user_email' => 'fghij@example.com',
+		) );
+		$s2 = $this->factory->signup->create( array(
+			'user_email' => 'abcde@example.com',
+		) );
+		$s3 = $this->factory->signup->create( array(
+			'user_email' => 'zzzzz@example.com',
+		) );
+
+		$ss = BP_Signup::get( array(
+			'orderby' => 'email',
+			'number' => 3,
+		) );
+
+		// default order is DESC
+		$this->assertEquals( array( $s3, $s1, $s2 ), wp_list_pluck( $ss['signups'], 'signup_id' ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_orderby_email_asc() {
+		$s1 = $this->factory->signup->create( array(
+			'user_email' => 'fghij@example.com',
+		) );
+		$s2 = $this->factory->signup->create( array(
+			'user_email' => 'abcde@example.com',
+		) );
+		$s3 = $this->factory->signup->create( array(
+			'user_email' => 'zzzzz@example.com',
+		) );
+
+		$ss = BP_Signup::get( array(
+			'orderby' => 'email',
+			'number' => 3,
+			'order' => 'ASC',
+		) );
+
+		$this->assertEquals( array( $s2, $s1, $s3 ), wp_list_pluck( $ss['signups'], 'signup_id' ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_include() {
+		$s1 = $this->factory->signup->create();
+		$s2 = $this->factory->signup->create();
+		$s3 = $this->factory->signup->create();
+
+		$ss = BP_Signup::get( array(
+			'include' => array( $s1, $s3 ),
+		) );
+
+		$this->assertEquals( array( $s1, $s3 ), wp_list_pluck( $ss['signups'], 'signup_id' ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_activation_key() {
+		$s1 = $this->factory->signup->create( array(
+			'activation_key' => 'foo',
+		) );
+		$s2 = $this->factory->signup->create( array(
+			'activation_key' => 'bar',
+		) );
+		$s3 = $this->factory->signup->create( array(
+			'activation_key' => 'baz',
+		) );
+
+		$ss = BP_Signup::get( array(
+			'activation_key' => 'bar',
+		) );
+
+		$this->assertEquals( array( $s2 ), wp_list_pluck( $ss['signups'], 'signup_id' ) );
+	}
+
+	/**
+	 * @group get
+	 */
+	public function test_get_with_user_login() {
+		$s1 = $this->factory->signup->create( array(
+			'user_login' => 'aaaafoo',
+		) );
+		$s2 = $this->factory->signup->create( array(
+			'user_login' => 'zzzzfoo',
+		) );
+		$s3 = $this->factory->signup->create( array(
+			'user_login' => 'jjjjfoo',
+		) );
+
+		$ss = BP_Signup::get( array(
+			'user_login' => 'zzzzfoo',
+		) );
+
+		$this->assertEquals( array( $s2 ), wp_list_pluck( $ss['signups'], 'signup_id' ) );
+	}
+
+	/**
+	 * @group activate
+	 */
+	public function test_activate_user_accounts() {
+		$signups = array();
+
+		$signups['accountone'] = $this->factory->signup->create( array(
+			'user_login'     => 'accountone',
+			'user_email'     => 'accountone@example.com',
+			'activation_key' => 'activationkeyone',
+		) );
+
+		$signups['accounttwo'] = $this->factory->signup->create( array(
+			'user_login'     => 'accounttwo',
+			'user_email'     => 'accounttwo@example.com',
+			'activation_key' => 'activationkeytwo',
+		) );
+
+		$signups['accountthree'] = $this->factory->signup->create( array(
+			'user_login'     => 'accountthree',
+			'user_email'     => 'accountthree@example.com',
+			'activation_key' => 'activationkeythree',
+		) );
+
+		$results = BP_Signup::activate( $signups );
+		$this->assertNotEmpty( $results['activated'] );
+
+		$users = array();
+
+		foreach ( $signups as $login => $signup_id  ) {
+			$users[ $login ] = get_user_by( 'login', $login );
+		}
+
+		$this->assertEqualSets( $results['activated'], wp_list_pluck( $users, 'ID' ) );
+	}
+
+	/**
+	 * @group activate
+	 */
+	public function test_activate_user_accounts_with_blogs() {
+		global $wpdb, $current_site, $base;
+
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$signups = array();
+
+		// Can't trust this first signup :(
+		$signups['testpath1'] = $this->factory->signup->create( array(
+			'user_login'     => 'testpath1',
+			'user_email'     => 'blogone@example.com',
+			'domain'         => '',
+			'path'           => '',
+			'title'          => '',
+			'activation_key' => 'activationkeyblogone',
+		) );
+
+		$signups['blogtwo'] = $this->factory->signup->create( array(
+			'user_login'     => 'blogtwo',
+			'user_email'     => 'blogtwo@example.com',
+			'domain'         => $current_site->domain,
+			'path'           => $base . 'blogtwo',
+			'title'          => 'Blog Two',
+			'activation_key' => 'activationkeyblogtwo',
+		) );
+
+		$signups['blogthree'] = $this->factory->signup->create( array(
+			'user_login'     => 'blogthree',
+			'user_email'     => 'blogthree@example.com',
+			'domain'         => '',
+			'path'           => '',
+			'title'          => '',
+			'activation_key' => 'activationkeyblogthree',
+		) );
+
+		$signups['blogfour'] = $this->factory->signup->create( array(
+			'user_login'     => 'blogfour',
+			'user_email'     => 'blogfour@example.com',
+			'domain'         => $current_site->domain,
+			'path'           => $base . 'blogfour',
+			'title'          => 'Blog Four',
+			'activation_key' => 'activationkeyblogfour',
+		) );
+
+		// Neutralize db errors
+		$suppress = $wpdb->suppress_errors();
+
+		$results = BP_Signup::activate( $signups );
+
+		$wpdb->suppress_errors( $suppress );
+
+		$this->assertNotEmpty( $results['activated'] );
+
+		$users  = array();
+
+		foreach ( $signups as $login => $signup_id  ) {
+			$users[ $login ] = get_user_by( 'login', $login );
+		}
+
+		$this->assertEqualSets( $results['activated'], wp_list_pluck( $users, 'ID' ) );
+
+		$blogs = array();
+
+		foreach ( $users as $path => $user  ) {
+			// Can't trust this first signup :(
+			if ( 'testpath1' == $path ) {
+				continue;
+			}
+
+			$blogs[ $path ] = get_active_blog_for_user( $user->ID );
+		}
+
+		$blogs = array_filter( $blogs );
+		$blogs = array_map( 'basename', wp_list_pluck( $blogs, 'path' ) );
+
+		$this->assertEqualSets( $blogs, array_keys( $blogs ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/members/functions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/members/functions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/members/functions.php	(revision 10410)
@@ -0,0 +1,590 @@
+<?php
+/**
+ * @group members
+ */
+class BP_Tests_Members_Functions extends BP_UnitTestCase {
+
+	/**
+	 * @ticket BP4915
+	 * @group bp_core_delete_account
+	 */
+	public function test_bp_core_delete_account() {
+		// Stash
+		$current_user = get_current_user_id();
+		$deletion_disabled = bp_disable_account_deletion();
+
+		// Create an admin for testing
+		$admin_user = $this->factory->user->create( array( 'role' => 'administrator' ) );
+		$this->grant_super_admin( $admin_user );
+
+		// 1. Admin can delete user account
+		$this->set_current_user( $admin_user );
+		$user1 = $this->factory->user->create( array( 'role' => 'subscriber' ) );
+		bp_core_delete_account( $user1 );
+		$maybe_user = new WP_User( $user1 );
+		$this->assertEquals( 0, $maybe_user->ID );
+		unset( $maybe_user );
+		$this->restore_admins();
+
+		// 2. Admin cannot delete superadmin account
+		$user2 = $this->factory->user->create( array( 'role' => 'administrator' ) );
+		$this->grant_super_admin( $user2 );
+		bp_core_delete_account( $user2 );
+		$maybe_user = new WP_User( $user2 );
+		$this->assertNotEquals( 0, $maybe_user->ID );
+		unset( $maybe_user );
+
+		// User cannot delete other's account
+		$user3 = $this->factory->user->create( array( 'role' => 'subscriber' ) );
+		$user4 = $this->factory->user->create( array( 'role' => 'subscriber' ) );
+		$this->set_current_user( $user3 );
+		bp_core_delete_account( $user4 );
+		$maybe_user = new WP_User( $user4 );
+		$this->assertNotEquals( 0, $maybe_user->ID );
+		unset( $maybe_user );
+
+		// Cleanup
+		$this->set_current_user( $current_user );
+		bp_update_option( 'bp-disable-account-deletion', $deletion_disabled );
+	}
+
+	/**
+	 * @group object_cache
+	 * @group bp_core_get_directory_pages
+	 */
+	public function test_bp_core_get_user_domain_after_directory_page_update() {
+		// Generate user
+		$user_id = $this->factory->user->create( array( 'role' => 'subscriber' ) );
+
+		// Set object cache first for user domain
+		$user_domain = bp_core_get_user_domain( $user_id );
+
+		// Now change the members directory slug
+		$pages = bp_core_get_directory_pages();
+		$members_page = get_post( $pages->members->id );
+		$members_page->post_name = 'new-members-slug';
+		wp_update_post( $members_page );
+
+		// Go back to members directory page and recheck user domain
+		$this->go_to( trailingslashit( home_url( 'new-members-slug' ) ) );
+		$user = new WP_User( $user_id );
+
+		$this->assertSame( home_url( 'new-members-slug' ) . '/' . $user->user_nicename . '/', bp_core_get_user_domain( $user_id ) );
+	}
+
+	/**
+	 * @group bp_core_get_user_displayname
+	 */
+	public function test_bp_core_get_user_displayname_empty_username() {
+		$this->assertFalse( bp_core_get_user_displayname( '' ) );
+	}
+
+	/**
+	 * @group bp_core_get_user_displayname
+	 */
+	public function test_bp_core_get_user_displayname_translate_username() {
+		$u = $this->factory->user->create();
+
+		$user = new WP_User( $u );
+
+		$found = bp_core_get_user_displayname( $u );
+		$this->assertNotEmpty( $found );
+		$this->assertSame( $found, bp_core_get_user_displayname( $user->user_login ) );
+	}
+
+	/**
+	 * @group bp_core_get_user_displayname
+	 */
+	public function test_bp_core_get_user_displayname_bad_username() {
+		$this->assertFalse( bp_core_get_user_displayname( 'i_dont_exist' ) );
+	}
+
+	/**
+	 * @group bp_core_get_user_displayname
+	 * @group cache
+	 */
+	public function test_bp_core_get_user_displayname_xprofile_populate_cache() {
+		$xprofile_is_active = bp_is_active( 'xprofile' );
+		buddypress()->active_components['xprofile'] = '1';
+
+		$u = $this->factory->user->create( array(
+			'display_name' => 'Foo',
+		) );
+		bp_core_get_user_displayname( $u );
+
+		$this->assertSame( 'Foo', wp_cache_get( 'bp_user_fullname_' . $u, 'bp' ) );
+
+		if ( ! $xprofile_is_active ) {
+			unset( buddypress()->active_components['xprofile'] );
+		}
+	}
+
+	/**
+	 * @group bp_core_get_user_displayname
+	 * @group cache
+	 */
+	public function test_bp_core_get_user_displayname_xprofile_bust_cache_after_xprofile_update() {
+		$xprofile_is_active = bp_is_active( 'xprofile' );
+		buddypress()->active_components['xprofile'] = '1';
+
+		$u = $this->factory->user->create();
+		xprofile_set_field_data( 1, $u, 'Foo Foo' );
+
+		$this->assertFalse( wp_cache_get( 'bp_user_fullname_' . $u, 'bp' ) );
+
+		if ( ! $xprofile_is_active ) {
+			unset( buddypress()->active_components['xprofile'] );
+		}
+	}
+
+	/**
+	 * @group bp_core_get_user_displayname
+	 */
+	public function test_bp_core_get_user_displayname_xprofile_exists() {
+		$xprofile_is_active = bp_is_active( 'xprofile' );
+		buddypress()->active_components['xprofile'] = '1';
+
+		$u = $this->factory->user->create();
+		xprofile_set_field_data( 1, $u, 'Foo Foo' );
+
+		$this->assertSame( 'Foo Foo', bp_core_get_user_displayname( $u ) );
+
+		if ( ! $xprofile_is_active ) {
+			unset( buddypress()->active_components['xprofile'] );
+		}
+	}
+
+	/**
+	 * @group bp_core_get_user_displayname
+	 */
+	public function test_bp_core_get_user_displayname_xprofile_does_not_exist() {
+		$bp = buddypress();
+		$xprofile_is_active = bp_is_active( 'xprofile' );
+		$bp->active_components['xprofile'] = '1';
+
+		$u = $this->factory->user->create( array(
+			'display_name' => 'Foo Foo',
+		) );
+
+		// Delete directly because BP won't let you delete a required
+		// field through the API
+		global $wpdb;
+		$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_data} WHERE user_id = %d AND field_id = 1", $u ) );
+		wp_cache_delete( 'bp_user_fullname_' . $u, 'bp' );
+		wp_cache_delete( "{$u}:1", 'bp_xprofile_data', 'bp' );
+
+		$this->assertSame( '', xprofile_get_field_data( 1, $u ) );
+		$this->assertSame( 'Foo Foo', bp_core_get_user_displayname( $u ) );
+		$this->assertSame( 'Foo Foo', xprofile_get_field_data( 1, $u ) );
+
+		if ( ! $xprofile_is_active ) {
+			unset( $bp->active_components['xprofile'] );
+		}
+	}
+
+	/**
+	 * @group bp_core_get_user_displaynames
+	 */
+	public function test_bp_core_get_user_displayname_arrays_all_bad_entries() {
+		$this->assertSame( array(), bp_core_get_user_displaynames( array( 0, 'foo', ) ) );
+	}
+
+	/**
+	 * @group bp_core_get_user_displaynames
+	 */
+	public function test_bp_core_get_user_displaynames_all_uncached() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		xprofile_set_field_data( 1, $u1, 'Foo' );
+		xprofile_set_field_data( 1, $u2, 'Bar' );
+
+		$expected = array(
+			$u1 => 'Foo',
+			$u2 => 'Bar',
+		);
+
+		$this->assertSame( $expected, bp_core_get_user_displaynames( array( $u1, $u2, ) ) );
+	}
+
+	/**
+	 * @group bp_core_get_user_displaynames
+	 */
+	public function test_bp_core_get_user_displaynames_one_not_in_xprofile() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create( array(
+			'display_name' => 'Bar',
+		) );
+
+		xprofile_set_field_data( 1, $u1, 'Foo' );
+
+		// Delete directly because BP won't let you delete a required
+		// field through the API
+		global $wpdb;
+		$bp = buddypress();
+		$wpdb->query( $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_data} WHERE user_id = %d AND field_id = 1", $u2 ) );
+		wp_cache_delete( 'bp_user_fullname_' . $u2, 'bp' );
+		wp_cache_delete( 1, 'bp_xprofile_data_' . $u2, 'bp' );
+
+		$expected = array(
+			$u1 => 'Foo',
+			$u2 => 'Bar',
+		);
+
+		$this->assertSame( $expected, bp_core_get_user_displaynames( array( $u1, $u2, ) ) );
+	}
+
+	/**
+	 * @group bp_core_get_user_displaynames
+	 */
+	public function test_bp_core_get_user_displaynames_one_in_cache() {
+		$u1 = $this->factory->user->create();
+		xprofile_set_field_data( 1, $u1, 'Foo' );
+
+		// Fake the cache for $u2
+		$u2 = 123;
+		wp_cache_set( 'bp_user_fullname_' . $u2, 'Bar', 'bp' );
+
+		$expected = array(
+			$u1 => 'Foo',
+			$u2 => 'Bar',
+		);
+
+		$this->assertSame( $expected, bp_core_get_user_displaynames( array( $u1, $u2, ) ) );
+	}
+
+	/**
+	 * @group bp_members_migrate_signups
+	 */
+	public function test_bp_members_migrate_signups_standard() {
+		$u = $this->factory->user->create();
+		$u_obj = new WP_User( $u );
+
+		// Fake an old-style registration
+		$key = wp_hash( $u_obj->ID );
+		update_user_meta( $u, 'activation_key', $key );
+
+		global $wpdb;
+		$wpdb->update(
+			$wpdb->users,
+			array( 'user_status' => '2', ),
+			array( 'ID' => $u, ),
+			array( '%d', ),
+			array( '%d', )
+		);
+		clean_user_cache( $u );
+
+		bp_members_migrate_signups();
+
+		$found = BP_Signup::get();
+
+		// Use email address as a sanity check
+		$found_email = isset( $found['signups'][0]->user_email ) ? $found['signups'][0]->user_email : '';
+		$this->assertSame( $u_obj->user_email, $found_email );
+
+		// Check that activation keys match
+		$found_key = isset( $found['signups'][0]->activation_key ) ? $found['signups'][0]->activation_key : '';
+		$this->assertSame( $key, $found_key );
+	}
+
+	/**
+	 * @group bp_members_migrate_signups
+	 */
+	public function test_bp_members_migrate_signups_activation_key_but_user_status_0() {
+		$u = $this->factory->user->create();
+		$u_obj = new WP_User( $u );
+
+		// Fake an old-style registration
+		$key = wp_hash( $u_obj->ID );
+		update_user_meta( $u, 'activation_key', $key );
+
+		// ...but ensure that user_status is 0. This mimics the
+		// behavior of certain plugins that disrupt the BP registration
+		// flow
+		global $wpdb;
+		$wpdb->update(
+			$wpdb->users,
+			array( 'user_status' => '0', ),
+			array( 'ID' => $u, ),
+			array( '%d', ),
+			array( '%d', )
+		);
+		clean_user_cache( $u );
+
+		bp_members_migrate_signups();
+
+		// No migrations should have taken place
+		$found = BP_Signup::get();
+		$this->assertEmpty( $found['total'] );
+	}
+
+	/**
+	 * @group bp_members_migrate_signups
+	 */
+	public function test_bp_members_migrate_signups_no_activation_key_but_user_status_2() {
+		$u = $this->factory->user->create();
+		$u_obj = new WP_User( $u );
+
+		// Fake an old-style registration but without an activation key
+		global $wpdb;
+		$wpdb->update(
+			$wpdb->users,
+			array( 'user_status' => '2', ),
+			array( 'ID' => $u, ),
+			array( '%d', ),
+			array( '%d', )
+		);
+		clean_user_cache( $u );
+
+		bp_members_migrate_signups();
+
+		// Use email address as a sanity check
+		$found = BP_Signup::get();
+		$found_email = isset( $found['signups'][0]->user_email ) ? $found['signups'][0]->user_email : '';
+		$this->assertSame( $u_obj->user_email, $found_email );
+	}
+
+	/**
+	 * @group bp_last_activity_migrate
+	 * @expectedIncorrectUsage update_user_meta( $user_id, 'last_activity' )
+	 * @expectedIncorrectUsage get_user_meta( $user_id, 'last_activity' )
+	 */
+	public function test_bp_last_activity_migrate() {
+		// We explicitly do not want last_activity created, so use the
+		// WP factory methods
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$u3 = $this->factory->user->create();
+
+		$time = time();
+		$t1 = date( 'Y-m-d H:i:s', $time - 50 );
+		$t2 = date( 'Y-m-d H:i:s', $time - 500 );
+		$t3 = date( 'Y-m-d H:i:s', $time - 5000 );
+
+		update_user_meta( $u1, 'last_activity', $t1 );
+		update_user_meta( $u2, 'last_activity', $t2 );
+		update_user_meta( $u3, 'last_activity', $t3 );
+
+		// Create an existing entry in last_activity to test no dupes
+		global $wpdb;
+		$bp = buddypress();
+		$wpdb->query( $wpdb->prepare(
+			"INSERT INTO {$bp->members->table_name_last_activity}
+				(`user_id`, `component`, `type`, `action`, `content`, `primary_link`, `item_id`, `date_recorded` ) VALUES
+				( %d, %s, %s, %s, %s, %s, %d, %s )",
+			$u2, $bp->members->id, 'last_activity', '', '', '', 0, $t1
+		) );
+
+		bp_last_activity_migrate();
+
+		$expected = array(
+			$u1 => $t1,
+			$u2 => $t2,
+			$u3 => $t3,
+		);
+
+		$found = array(
+			$u1 => '',
+			$u2 => '',
+			$u3 => '',
+		);
+
+		foreach ( $found as $uid => $v ) {
+			$found[ $uid ] = bp_get_user_last_activity( $uid );
+		}
+
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group bp_core_get_userid_from_nicename
+	 */
+	public function test_bp_core_get_userid_from_nicename_failure() {
+		$this->assertSame( NULL, bp_core_get_userid_from_nicename( 'non_existent_user' ) );
+	}
+
+	/**
+	 * @group bp_update_user_last_activity
+	 */
+	public function test_bp_last_activity_multi_network() {
+
+		// Filter the usermeta key
+		add_filter( 'bp_get_user_meta_key', array( $this, 'filter_usermeta_key' ) );
+
+		// We explicitly do not want last_activity created, so use the
+		// WP factory methods
+		$user = $this->factory->user->create();
+		$time = date( 'Y-m-d H:i:s', time() - 50 );
+
+		// Update last user activity
+		bp_update_user_last_activity( $user, $time );
+
+		// Setup parameters to assert to be the same
+		$expected = $time;
+		$found    = bp_get_user_meta( $user, 'last_activity', true );
+
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group bp_update_user_last_activity
+	 * @global object $wpdb
+	 * @param  string $key
+	 * @return string
+	 */
+	public function filter_usermeta_key( $key ) {
+		global $wpdb;
+		return $wpdb->prefix . $key;
+	}
+
+	/**
+	 * @group bp_core_process_spammer_status
+	 */
+	public function test_bp_core_process_spammer_status() {
+		if ( is_multisite() ) {
+			return;
+		}
+
+		$bp = buddypress();
+		$displayed_user = $bp->displayed_user;
+
+		$u1 = $this->factory->user->create();
+		$bp->displayed_user->id = $u1;
+
+		// Spam the user
+		bp_core_process_spammer_status( $u1, 'spam' );
+
+		$this->assertTrue( bp_is_user_spammer( $u1 ) );
+
+		// Unspam the user
+		bp_core_process_spammer_status( $u1, 'ham' );
+
+		$this->assertFalse( bp_is_user_spammer( $u1 ) );
+
+		// Reset displayed user
+		$bp->displayed_user = $displayed_user;
+	}
+
+	/**
+	 * @group bp_core_process_spammer_status
+	 */
+	public function test_bp_core_process_spammer_status_ms_bulk_spam() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$bp = buddypress();
+		$displayed_user = $bp->displayed_user;
+
+		$u1 = $this->factory->user->create();
+		$bp->displayed_user->id = $u1;
+
+		// Bulk spam in network admin uses update_user_status
+		update_user_status( $u1, 'spam', '1' );
+
+		$this->assertTrue( bp_is_user_spammer( $u1 ) );
+
+		// Unspam the user
+		bp_core_process_spammer_status( $u1, 'ham' );
+
+		$this->assertFalse( bp_is_user_spammer( $u1 ) );
+
+		// Reset displayed user
+		$bp->displayed_user = $displayed_user;
+	}
+
+	/**
+	 * @group bp_core_process_spammer_status
+	 */
+	public function test_bp_core_process_spammer_status_ms_bulk_ham() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		$bp = buddypress();
+		$displayed_user = $bp->displayed_user;
+
+		$u1 = $this->factory->user->create();
+		$bp->displayed_user->id = $u1;
+
+		// Spam the user
+		bp_core_process_spammer_status( $u1, 'spam' );
+
+		$this->assertTrue( bp_is_user_spammer( $u1 ) );
+
+		// Bulk unspam in network admin uses update_user_status
+		update_user_status( $u1, 'spam', '0' );
+
+		$this->assertFalse( bp_is_user_spammer( $u1 ) );
+
+		// Reset displayed user
+		$bp->displayed_user = $displayed_user;
+	}
+
+	/**
+	 * @group bp_core_process_spammer_status
+	 */
+	public function test_bp_core_process_spammer_status_make_spam_user_filter() {
+		add_filter( 'make_spam_user', array( $this, 'notification_filter_callback' ) );
+
+		$u1 = $this->factory->user->create();
+		$n = bp_core_process_spammer_status( $u1, 'spam' );
+
+		remove_filter( 'make_spam_user', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'make_spam_user', $this->filter_fired );
+
+	}
+
+	public function test_bp_core_process_spammer_status_make_ham_user_filter() {
+		add_filter( 'make_ham_user', array( $this, 'notification_filter_callback' ) );
+
+		$u1 = $this->factory->user->create();
+		$n = bp_core_process_spammer_status( $u1, 'ham' );
+
+		remove_filter( 'make_ham_user', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'make_ham_user', $this->filter_fired );
+
+	}
+
+	public function test_bp_core_process_spammer_status_bp_make_spam_user_filter() {
+		add_filter( 'bp_make_spam_user', array( $this, 'notification_filter_callback' ) );
+
+		$u1 = $this->factory->user->create();
+		$n = bp_core_process_spammer_status( $u1, 'spam' );
+
+		remove_filter( 'bp_make_spam_user', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_make_spam_user', $this->filter_fired );
+
+	}
+
+	public function test_bp_core_process_spammer_status_bp_make_ham_user_filter() {
+		add_filter( 'bp_make_ham_user', array( $this, 'notification_filter_callback' ) );
+
+		$u1 = $this->factory->user->create();
+		$n = bp_core_process_spammer_status( $u1, 'ham' );
+
+		remove_filter( 'bp_make_ham_user', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_make_ham_user', $this->filter_fired );
+
+	}
+
+	public function notification_filter_callback( $value ) {
+		$this->filter_fired = current_filter();
+		return $value;
+	}
+
+	/**
+	 * @ticket BP6208
+	 *
+	 * Note - it's not possible to test this when registration is not configured properly,
+	 * because `bp_has_custom_signup_page()` stores its value in a static variable that cannot
+	 * be toggled.
+	 */
+	public function test_wp_registration_url_should_return_bp_register_page_when_register_page_is_configured_properly() {
+		$this->assertSame( bp_get_signup_page(), wp_registration_url() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/members/template.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/members/template.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/members/template.php	(revision 10410)
@@ -0,0 +1,416 @@
+<?php
+/**
+ * @group members
+ */
+class BP_Tests_Members_Template extends BP_UnitTestCase {
+	public function test_bp_has_members_include_on_user_page() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$this->go_to( bp_core_get_user_domain( $u1 ) );
+
+		global $members_template;
+		bp_has_members( array(
+			'include' => array( $u1, $u2 ),
+		) );
+
+		$users = is_array( $members_template->members ) ? array_values( $members_template->members ) : array();
+		$user_ids = wp_list_pluck( $users, 'ID' );
+		sort( $user_ids );
+
+		$shouldbe = array( $u1, $u2 );
+		sort( $shouldbe );
+
+		$this->assertEquals( $user_ids, $shouldbe );
+	}
+
+	/**
+	 * @ticket BP5898
+	 * @group bp_has_members
+	 */
+	public function test_bp_has_members_search_pagination_with_spaces() {
+		$u1 = $this->factory->user->create( array( 'display_name' => '~ tilde u1' ) );
+		$u2 = $this->factory->user->create( array( 'display_name' => '~ tilde u2' ) );
+
+		$template_args = array(
+			'search_terms' => '~ tilde',
+			'per_page'     => 1,
+		);
+
+		global $members_template;
+		$reset_members_template = $members_template;
+
+		bp_has_members( $template_args );
+
+		preg_match( '/&#038;members_search=(.*)\'/', $members_template->pag_links, $matches );
+
+		$this->assertEquals( urldecode( $matches[1] ), urldecode( $template_args['search_terms'] ) );
+
+		// reset the members template global
+		$members_template = $reset_members_template;
+	}
+
+	public function test_bp_has_members_friendship_requests() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		friends_add_friend( $u1, $u2 );
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u2 );
+
+		$this->go_to( bp_core_get_user_domain( $u2 ) . bp_get_friends_slug() . '/requests/' );
+		$this->restore_admins();
+
+		global $members_template;
+		bp_has_members( array(
+			'include' => bp_get_friendship_requests( $u2 ),
+		) );
+
+		$requests = is_array( $members_template->members ) ? array_values( $members_template->members ) : array();
+		$request_ids = wp_list_pluck( $requests, 'ID' );
+		$this->assertEquals( $request_ids, array( $u1 ) );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group bp_has_members
+	 * @group friends
+	 * @ticket BP5071
+	 */
+	public function test_bp_has_members_friendship_requests_with_no_requests() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$old_user = get_current_user_id();
+		$this->set_current_user( $u2 );
+
+		// For some reason, in all the user switching, the cache gets
+		// confused. Never comes up when BP runs normally, because the
+		// loggedin_user doesn't change on a pageload. @todo Fix for
+		// real in BP
+		wp_cache_delete( 'bp_user_domain_' . $u2, 'bp' );
+
+		$this->go_to( bp_core_get_user_domain( $u2 ) . bp_get_friends_slug() . '/requests/' );
+		$this->restore_admins();
+
+		global $members_template;
+		bp_has_members( array(
+			'include' => bp_get_friendship_requests( $u2 ),
+		) );
+
+		$requests = is_array( $members_template->members ) ? array_values( $members_template->members ) : array();
+		$request_ids = wp_list_pluck( $requests, 'ID' );
+		$this->assertEquals( array(), $request_ids );
+
+		$this->set_current_user( $old_user );
+	}
+
+	/**
+	 * @group bp_has_members
+	 */
+	public function test_bp_has_members_should_pass_member_type_param_to_query() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		global $members_template;
+		$old_members_template = $members_template;
+
+		bp_has_members( array(
+			'member_type' => 'bar',
+		) );
+
+		$members = is_array( $members_template->members ) ? array_values( $members_template->members ) : array();
+		$member_ids = wp_list_pluck( $members, 'ID' );
+		$this->assertEquals( array( $users[1]), $member_ids );
+
+		$GLOBALS['members_template'] = $old_members_template;
+	}
+
+	/**
+	 * @group bp_has_members
+	 * @ticket BP6286
+	 */
+	public function test_bp_has_members_should_infer_member_type_from_get_param() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		global $members_template;
+		$old_members_template = $members_template;
+
+		$old_get = $_GET;
+		$_GET['member_type'] = 'bar';
+
+		bp_has_members();
+
+		$members = is_array( $members_template->members ) ? array_values( $members_template->members ) : array();
+		$member_ids = wp_list_pluck( $members, 'ID' );
+		$this->assertEquals( array( $users[1] ), $member_ids );
+
+		$GLOBALS['members_template'] = $old_members_template;
+	}
+
+	/**
+	 * @group bp_has_members
+	 * @ticket BP6286
+	 */
+	public function test_bp_has_members_should_infer_member_type_from_get_param_comma_sep() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_register_member_type( 'baz' );
+		$users = $this->factory->user->create_many( 4 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+		bp_set_member_type( $users[2], 'baz' );
+
+		global $members_template;
+		$old_members_template = $members_template;
+
+		$old_get = $_GET;
+		$_GET['member_type'] = 'foo,bar';
+
+		bp_has_members();
+
+		$members = is_array( $members_template->members ) ? array_values( $members_template->members ) : array();
+		$member_ids = wp_list_pluck( $members, 'ID' );
+		$this->assertEqualSets( array( $users[0], $users[1] ), $member_ids );
+
+		$GLOBALS['members_template'] = $old_members_template;
+	}
+
+	/**
+	 * @group bp_get_member_last_active
+	 */
+	public function test_bp_get_member_last_active_default_params() {
+		// Fake the global
+		global $members_template;
+
+		$time = date( 'Y-m-d H:i:s', time() - 24*60*60 );
+		$members_template = new stdClass;
+		$members_template->member = new stdClass;
+		$members_template->member->last_activity = $time;
+
+		$this->assertEquals( bp_core_get_last_activity( $time, __( 'active %s', 'buddypress' ) ), bp_get_member_last_active() );
+	}
+
+	/**
+	 * @group bp_get_member_last_active
+	 */
+	public function test_bp_get_member_last_active_active_format_true() {
+		// Fake the global
+		global $members_template;
+
+		$time = date( 'Y-m-d H:i:s', time() - 24*60*60 );
+		$members_template = new stdClass;
+		$members_template->member = new stdClass;
+		$members_template->member->last_activity = $time;
+
+		$this->assertEquals( bp_core_get_last_activity( $time, __( 'active %s', 'buddypress' ) ), bp_get_member_last_active( array( 'active_format' => true, ) ) );
+	}
+
+	/**
+	 * @group bp_get_member_last_active
+	 */
+	public function test_bp_get_member_last_active_active_format_false() {
+		// Fake the global
+		global $members_template;
+
+		$time = date( 'Y-m-d H:i:s', time() - 24*60*60 );
+		$members_template = new stdClass;
+		$members_template->member = new stdClass;
+		$members_template->member->last_activity = $time;
+
+		$this->assertEquals( bp_core_time_since( $time ), bp_get_member_last_active( array( 'active_format' => false, ) ) );
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Core_Members_Template
+	 */
+	public function test_bp_groups_template_should_give_precedence_to_upage_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['upage'] = '5';
+
+		$r = array(
+			'type'            => 'active',
+			'page'            => 8,
+			'per_page'        => 20,
+			'max'             => false,
+			'page_arg'        => 'upage',
+			'include'         => false,
+			'exclude'         => false,
+			'user_id'         => 0,
+			'member_type'     => '',
+			'search_terms'    => null,
+			'meta_key'        => false,
+			'meta_value'	  => false,
+			'populate_extras' => true,
+			'search_terms'    => ''
+		);
+
+		$at = new BP_Core_Members_Template(
+			$r['type'],
+			$r['page'],
+			$r['per_page'],
+			$r['max'],
+			$r['user_id'],
+			$r['search_terms'],
+			$r['include'],
+			$r['populate_extras'],
+			$r['exclude'],
+			$r['meta_key'],
+			$r['meta_value'],
+			$r['page_arg'],
+			$r['member_type']
+		);
+
+		$this->assertEquals( 5, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Core_Members_Template
+	 */
+	public function test_bp_members_template_should_reset_0_pag_page_URL_param_to_default_pag_page_value() {
+		$request = $_REQUEST;
+		$_REQUEST['upage'] = '0';
+
+		$r = array(
+			'type'            => 'active',
+			'page'            => 8,
+			'per_page'        => 20,
+			'max'             => false,
+			'page_arg'        => 'upage',
+			'include'         => false,
+			'exclude'         => false,
+			'user_id'         => 0,
+			'member_type'     => '',
+			'search_terms'    => null,
+			'meta_key'        => false,
+			'meta_value'	  => false,
+			'populate_extras' => true,
+			'search_terms'    => ''
+		);
+
+		$at = new BP_Core_Members_Template(
+			$r['type'],
+			$r['page'],
+			$r['per_page'],
+			$r['max'],
+			$r['user_id'],
+			$r['search_terms'],
+			$r['include'],
+			$r['populate_extras'],
+			$r['exclude'],
+			$r['meta_key'],
+			$r['meta_value'],
+			$r['page_arg'],
+			$r['member_type']
+		);
+
+		$this->assertEquals( 8, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Core_Members_Template
+	 */
+	public function test_bp_members_template_should_give_precedence_to_num_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '14';
+
+		$r = array(
+			'type'            => 'active',
+			'page'            => 1,
+			'per_page'        => 13,
+			'max'             => false,
+			'page_arg'        => 'upage',
+			'include'         => false,
+			'exclude'         => false,
+			'user_id'         => 0,
+			'member_type'     => '',
+			'search_terms'    => null,
+			'meta_key'        => false,
+			'meta_value'	  => false,
+			'populate_extras' => true,
+			'search_terms'    => ''
+		);
+
+		$at = new BP_Core_Members_Template(
+			$r['type'],
+			$r['page'],
+			$r['per_page'],
+			$r['max'],
+			$r['user_id'],
+			$r['search_terms'],
+			$r['include'],
+			$r['populate_extras'],
+			$r['exclude'],
+			$r['meta_key'],
+			$r['meta_value'],
+			$r['page_arg'],
+			$r['member_type']
+		);
+
+		$this->assertEquals( 14, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Core_Members_Template
+	 */
+	public function test_bp_members_template_should_reset_0_pag_num_URL_param_to_default_pag_num_value() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '0';
+
+		$r = array(
+			'type'            => 'active',
+			'page'            => 1,
+			'per_page'        => 13,
+			'max'             => false,
+			'page_arg'        => 'upage',
+			'include'         => false,
+			'exclude'         => false,
+			'user_id'         => 0,
+			'member_type'     => '',
+			'search_terms'    => null,
+			'meta_key'        => false,
+			'meta_value'	  => false,
+			'populate_extras' => true,
+			'search_terms'    => ''
+		);
+
+		$at = new BP_Core_Members_Template(
+			$r['type'],
+			$r['page'],
+			$r['per_page'],
+			$r['max'],
+			$r['user_id'],
+			$r['search_terms'],
+			$r['include'],
+			$r['populate_extras'],
+			$r['exclude'],
+			$r['meta_key'],
+			$r['meta_value'],
+			$r['page_arg'],
+			$r['member_type']
+		);
+
+		$this->assertEquals( 13, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/members/types.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/members/types.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/members/types.php	(revision 10410)
@@ -0,0 +1,395 @@
+<?php
+
+/**
+ * @group members
+ * @group member_types
+ */
+class BP_Tests_Members_Types extends BP_UnitTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		buddypress()->members->types = array();
+	}
+
+	public function test_bp_register_member_type_should_fail_for_existing_member_type() {
+		bp_register_member_type( 'foo' );
+		$this->assertWPError( bp_register_member_type( 'foo' ) );
+	}
+
+	public function test_bp_register_member_type_should_return_type_object() {
+		$this->assertInternalType( 'object', bp_register_member_type( 'foo' ) );
+	}
+
+	/**
+	 * @dataProvider illegal_names
+	 * @ticket BP5192
+	 */
+	public function test_illegal_names( $name ) {
+		$this->assertWPError( bp_register_member_type( $name ) );
+	}
+
+	public function illegal_names() {
+		return array(
+			array( 'any' ),
+			array( 'null' ),
+			array( '_none' ),
+		);
+	}
+
+	/**
+	 * @ticket BP6139
+	 */
+	public function test_bp_register_member_type_should_sanitize_member_type_key() {
+		$key = 'F//oo% -Bar';
+		$sanitized_key = 'foo-bar';
+
+		$object = bp_register_member_type( $key );
+		$this->assertSame( $sanitized_key, $object->name );
+	}
+
+	public function test_bp_register_member_type_should_store_member_type_string_as_name_property() {
+		$object = bp_register_member_type( 'foo' );
+		$this->assertSame( 'foo', $object->name );
+	}
+
+	public function test_bp_register_member_type_should_fill_in_missing_labels_with_ucfirst_member_type() {
+		$object = bp_register_member_type( 'foo' );
+		foreach ( $object->labels as $label ) {
+			$this->assertSame( 'Foo', $label );
+		}
+	}
+
+	/**
+	 * @ticket BP6125
+	 */
+	public function test_bp_register_member_type_should_respect_custom_name_label() {
+		$object = bp_register_member_type( 'foo', array(
+			'labels' => array(
+				'name' => 'Bar',
+			),
+		) );
+
+		// 'singular_name' falls back on 'name'.
+		$this->assertSame( 'Bar', $object->labels['name'] );
+		$this->assertSame( 'Bar', $object->labels['singular_name'] );
+	}
+
+	/**
+	 * @ticket BP6125
+	 */
+	public function test_bp_register_member_type_should_respect_custom_singular_name_label() {
+		$object = bp_register_member_type( 'foo', array(
+			'labels' => array(
+				'singular_name' => 'Bar',
+			),
+		) );
+
+		// 'name' is set to upper-case version of member type name.
+		$this->assertSame( 'Foo', $object->labels['name'] );
+		$this->assertSame( 'Bar', $object->labels['singular_name'] );
+	}
+
+	/**
+	 * @ticket BP6286
+	 */
+	public function test_bp_register_member_type_has_directory_should_default_to_true() {
+		$object = bp_register_member_type( 'foo', array(
+			'has_directory' => true,
+		) );
+
+		$this->assertTrue( $object->has_directory );
+		$this->assertSame( 'foo', $object->directory_slug );
+	}
+
+	/**
+	 * @ticket BP6286
+	 */
+	public function test_bp_register_member_type_has_directory_true() {
+		$object = bp_register_member_type( 'foo', array(
+			'has_directory' => true,
+		) );
+
+		$this->assertTrue( $object->has_directory );
+		$this->assertSame( 'foo', $object->directory_slug );
+	}
+
+	/**
+	 * @ticket BP6286
+	 */
+	public function test_bp_register_member_type_should_store_has_directory_false() {
+		$object = bp_register_member_type( 'foo', array(
+			'has_directory' => false,
+		) );
+
+		$this->assertFalse( $object->has_directory );
+		$this->assertSame( '', $object->directory_slug );
+	}
+
+	/**
+	 * @ticket BP6286
+	 */
+	public function test_bp_register_member_type_should_store_has_directory_string() {
+		$object = bp_register_member_type( 'foo', array(
+			'has_directory' => 'foos',
+		) );
+
+		$this->assertTrue( $object->has_directory );
+		$this->assertSame( 'foos', $object->directory_slug );
+	}
+
+	public function test_bp_get_member_type_object_should_return_null_for_non_existent_member_type() {
+		$this->assertSame( null, bp_get_member_type_object( 'foo' ) );
+	}
+
+	public function test_bp_get_member_type_object_should_return_type_object() {
+		bp_register_member_type( 'foo' );
+		$this->assertInternalType( 'object', bp_get_member_type_object( 'foo' ) );
+	}
+
+	public function test_bp_set_member_type_should_return_false_for_invalid_member_type() {
+		$this->assertFalse( bp_set_member_type( 'foo', 1 ) );
+	}
+
+	public function test_bp_set_member_type_should_remove_member_type_when_passing_an_empty_value() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_set_member_type( $u, 'foo' );
+
+		// Make sure it's set up.
+		$this->assertSame( 'foo', bp_get_member_type( $u ) );
+
+		$this->assertSame( array(), bp_set_member_type( $u, '' ) );
+		$this->assertFalse( bp_get_member_type( $u ) );
+	}
+
+	public function test_bp_set_member_type_success() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+
+		$this->assertNotEmpty( bp_set_member_type( $u, 'foo' ) );
+	}
+
+	public function test_bp_get_member_type_with_default_value_for_single() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u, 'foo' );
+		bp_set_member_type( $u, 'bar', true );
+
+		$this->assertSame( 'foo', bp_get_member_type( $u ) );
+	}
+
+	public function test_bp_get_member_type_with_single_true() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u, 'foo' );
+		bp_set_member_type( $u, 'bar', true );
+
+		$this->assertSame( 'foo', bp_get_member_type( $u, true ) );
+	}
+
+	public function test_bp_get_member_type_with_single_false() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u, 'foo' );
+		bp_set_member_type( $u, 'bar', true );
+
+		$this->assertEqualSets( array( 'foo', 'bar' ), bp_get_member_type( $u, false ) );
+	}
+
+	public function test_bp_get_member_type_should_return_false_when_no_value_is_found() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+
+		$this->assertFalse( bp_get_member_type( $u ) );
+	}
+
+	/**
+	 * @group cache
+	 */
+	public function test_bp_get_member_type_should_hit_cache() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_set_member_type( $u, 'foo' );
+
+		global $wpdb;
+
+		// Initial query should hit the database.
+		bp_get_member_type( $u );
+		$num_queries = $wpdb->num_queries;
+
+		// Next query should not hit the database.
+		bp_get_member_type( $u );
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+	}
+
+	/**
+	 * @group BP6193
+	 */
+	public function test_bp_members_prefetch_member_type_array_cache_set() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u1, 'foo' );
+		bp_set_member_type( $u1, 'bar', true );
+
+		// Get users so that the 'bp_user_query_populate_extras' is fired
+		// and members type prefetched
+		$users = bp_core_get_users( array( 'include' => array( $u1, $u2 ) ) );
+
+		// Get single member type
+		$this->assertSame( 'foo', bp_get_member_type( $u1, true ) );
+		$this->assertEmpty( bp_get_member_type( $u2, true ) );
+
+		// Get all member types for the user
+		$this->assertEqualSets( array( 'foo', 'bar' ), bp_get_member_type( $u1, false ) );
+		$this->assertEmpty( bp_get_member_type( $u2, false ) );
+	}
+
+	/**
+	 * @group cache
+	 */
+	public function test_bp_get_member_type_should_return_false_for_deleted_user() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_set_member_type( $u, 'foo' );
+
+		// Prime the cache.
+		bp_get_member_type( $u );
+
+		if ( is_multisite() ) {
+			wpmu_delete_user( $u );
+		} else {
+			wp_delete_user( $u );
+		}
+
+		$this->assertFalse( wp_cache_get( $u, 'bp_member_type' ) );
+		$this->assertFalse( bp_get_member_type( $u ) );
+	}
+
+	/**
+	 * @group BP6242
+	 * @group cache
+	 */
+	public function test_bp_get_member_type_should_not_conflict_with_term_cache() {
+		global $wpdb;
+
+		// Offset IDs.
+		$dummy_terms = $this->factory->tag->create_many( 7 );
+
+		$u1 = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_set_member_type( $u1, 'foo' );
+
+		// Fetch a term ID.
+		$terms = get_terms( 'bp_member_type', array( 'hide_empty' => false, 'fields' => 'all' ) );
+
+		// Make sure the user's ID matches a term ID, to force a cache confusion.
+		$u2 = $this->factory->user->create();
+		$new_user_id = $terms[0]->term_id;
+		$wpdb->update( $wpdb->users, array( 'ID' => $new_user_id ), array( 'ID' => $u2 ) );
+
+		bp_set_member_type( $new_user_id, 'foo' );
+
+		// Reprime the taxonomy cache.
+		$terms = get_terms( 'bp_member_type', array( 'hide_empty' => false, 'fields' => 'all' ) );
+
+		$this->assertSame( 'foo', bp_get_member_type( $new_user_id, true ) );
+	}
+
+	/**
+	 * @group BP6188
+	 */
+	public function test_bp_remove_member_type_should_return_false_when_member_type_is_empty() {
+		$this->assertFalse( bp_remove_member_type( 5, '' ) );
+	}
+
+	/**
+	 * @group BP6188
+	 */
+	public function test_bp_remove_member_type_should_return_false_when_member_type_is_invalid() {
+		$this->assertFalse( bp_remove_member_type( 5, 'foo' ) );
+	}
+
+	/**
+	 * @group BP6188
+	 */
+	public function test_bp_remove_member_type_should_return_false_when_member_is_not_of_provided_type() {
+		$u1 = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u1, 'bar' );
+
+		$this->assertFalse( bp_remove_member_type( $u1, 'foo' ) );
+		$types = bp_get_member_type( $u1, false );
+		$this->assertEquals( array( 'bar' ), $types );
+	}
+
+	/**
+	 * @group BP6188
+	 */
+	public function test_bp_remove_member_type_should_return_true_for_successful_deletion() {
+		$u1 = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u1, 'foo' );
+		bp_set_member_type( $u1, 'bar', true );
+
+		$this->assertTrue( bp_remove_member_type( $u1, 'foo' ) );
+		$types = bp_get_member_type( $u1, false );
+		$this->assertEquals( array( 'bar' ), $types );
+	}
+
+	/**
+	 * @group BP6138
+	 */
+	function test_bp_has_member_type_should_return_false_when_member_type_is_empty() {
+		$this->assertFalse( bp_has_member_type( 5, '' ) );
+	}
+
+	/**
+	 * @group BP6138
+	 */
+	function test_bp_has_member_type_should_return_false_when_member_type_is_invalid() {
+		$this->assertFalse( bp_has_member_type( 5, 'foo' ) );
+	}
+
+	/**
+	 * @group BP6138
+	 */
+	public function test_bp_has_member_type_should_return_false_when_member_id_is_empty() {
+		bp_register_member_type( 'foo' );
+
+		$this->assertFalse( bp_has_member_type( '', 'foo' ) );
+	}
+
+	/**
+	 * @group BP6138
+	 */
+	public function test_bp_has_member_type_should_return_false_when_member_is_not_of_provided_type() {
+		$u1 = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u1, 'bar' );
+
+		$this->assertFalse( bp_has_member_type( $u1, 'foo' ) );
+	}
+
+	/**
+	 * @group BP6138
+	 */
+	public function test_bp_has_member_type_should_return_true_on_success() {
+		$u1 = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u1, 'foo' );
+		bp_set_member_type( $u1, 'bar', true );
+
+		$this->assertTrue( bp_has_member_type( $u1, 'foo' ) );
+		$types = bp_get_member_type( $u1, false );
+		$this->assertEqualSets( array( 'bar', 'foo' ), $types );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/messages/cache.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/messages/cache.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/messages/cache.php	(revision 10410)
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @group messages
+ * @group cache
+ */
+class BP_Tests_Message_Cache extends BP_UnitTestCase {
+	/**
+	 * @group bp_messages_update_meta_cache
+	 */
+	public function test_bp_messages_update_meta_cache() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create the thread
+		$t1 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'This is a knive',
+		) );
+
+		// create a reply
+		$this->factory->message->create( array(
+			'thread_id'  => $t1,
+			'sender_id'  => $u2,
+			'recipients' => array( $u1 ),
+			'content'    => "That's a spoon",
+		) );
+
+		// grab the message ids as individual variables
+		list( $m1, $m2 ) = $this->get_message_ids( $t1 );
+
+		// add cache for each message
+		bp_messages_update_meta( $m1, 'utensil',  'knive' );
+		bp_messages_update_meta( $m1, 'is_knive', 'yes' );
+
+		bp_messages_update_meta( $m2, 'utensil',  'spoon' );
+		bp_messages_update_meta( $m2, 'is_knive', 'no' );
+		bp_messages_update_meta( $m2, 'is_spoon', 'yes' );
+
+		// prime cache
+		bp_messages_get_meta( $m1, 'utensil' );
+
+		// Ensure an empty cache for second message
+		wp_cache_delete( $m2, 'message_meta' );
+
+		// update message meta cache
+		bp_messages_update_meta_cache( array( $m1, $m2 ) );
+
+		$expected = array(
+			$m1 => array(
+				'utensil' => array(
+					'knive',
+				),
+				'is_knive' => array(
+					'yes',
+				),
+			),
+			$m2 => array(
+				'utensil' => array(
+					'spoon',
+				),
+				'is_knive' => array(
+					'no',
+				),
+				'is_spoon' => array(
+					'yes',
+				),
+			),
+		);
+
+		$found = array(
+			$m1 => wp_cache_get( $m1, 'message_meta' ),
+			$m2 => wp_cache_get( $m2, 'message_meta' ),
+		);
+
+		$this->assertEquals( $expected, $found );
+	}
+
+	/**
+	 * @group bp_messages_update_meta_cache
+	 * @group bp_thread_has_messages
+	 */
+	public function test_bp_messages_metadata_cache_in_message_loop() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create the thread
+		$t1 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'Oy',
+		) );
+
+		// add message cache
+		list( $m1 ) = $this->get_message_ids( $t1 );
+		bp_messages_update_meta( $m1, 'yolo', 'gah' );
+
+		// prime meta cache in message loop
+		bp_thread_has_messages( array(
+			'thread_id' => $t1,
+			'update_meta_cache' => true
+		) );
+
+		$this->assertNotEmpty( wp_cache_get( $m1, 'message_meta' ) );
+	}
+
+	/**
+	 * @group bp_messages_delete_meta
+	 * @group messages_delete_thread
+	 */
+	public function test_bp_messages_delete_metadata_cache_on_thread_delete() {
+		$this->old_current_user = get_current_user_id();
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create the thread
+		$t1 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'Oy',
+		) );
+
+		// create a reply
+		$this->factory->message->create( array(
+			'thread_id'  => $t1,
+			'sender_id'  => $u2,
+			'recipients' => array( $u1 ),
+			'content'    => 'Yo',
+		) );
+
+		// add message meta
+		list( $m1, $m2 ) = $this->get_message_ids( $t1 );
+		bp_messages_update_meta( $m1, 'yolo', 'gah' );
+		bp_messages_update_meta( $m2, 'yolo', 'bah' );
+
+		// prime message meta cache
+		bp_messages_get_meta( $m1, 'yolo' );
+		bp_messages_get_meta( $m2, 'yolo' );
+
+		// delete thread
+		// to outright delete a thread, both recipients must delete it
+		$this->set_current_user( $u1 );
+		messages_delete_thread( $t1 );
+		$this->set_current_user( $u2 );
+		messages_delete_thread( $t1 );
+
+		// assert empty meta cache
+		$this->assertEmpty( wp_cache_get( $m1, 'message_meta' ) );
+		$this->assertEmpty( wp_cache_get( $m2, 'message_meta' ) );
+
+		// cleanup
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	/**
+	 * Helper method to grab the message IDs from a message thread.
+	 *
+	 * @param int $thread_id The message thread ID
+	 * @return array
+	 */
+	protected function get_message_ids( $thread_id = 0 ) {
+		$thread = new BP_Messages_Thread( $thread_id );
+		return wp_list_pluck( $thread->messages, 'id' );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/messages/class.bp-messages-notice.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/messages/class.bp-messages-notice.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/messages/class.bp-messages-notice.php	(revision 10410)
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @group notices
+ */
+class BP_Tests_BP_Messages_Notice_TestCases extends BP_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' ) ) );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	/**
+	 * @group cache
+	 */
+	public function test_get_active_notices() {
+		// send notice
+		$subject = 'Test notice';
+		$message = 'This is a notice';
+		messages_send_notice( $subject, $message );
+
+		// now get the active notice and assert
+		$notice = BP_Messages_Notice::get_active();
+		$this->assertEquals( $subject, $notice->subject );
+		$this->assertEquals( $message, $notice->message );
+
+		// deactivate notice and make sure cache is invalidated
+		$notice->deactivate();
+		$this->assertFalse( wp_cache_get( 'active_notice', 'bp_messages' ) );
+
+		// create a new notice
+		$subject2 = 'Another notice';
+		$message2 = 'Say what?';
+		messages_send_notice( $subject2, $message2 );
+
+		// now get the new active notice
+		BP_Messages_Notice::get_active();
+
+		// grab the cache and make sure it equals our new notice
+		$cache = wp_cache_get( 'active_notice', 'bp_messages' );
+		$this->assertEquals( $subject2, $cache->subject );
+		$this->assertEquals( $message2, $cache->message );
+	}
+
+}
Index: /tags/2.4.3/tests/phpunit/testcases/messages/class.bp-messages-thread.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/messages/class.bp-messages-thread.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/messages/class.bp-messages-thread.php	(revision 10410)
@@ -0,0 +1,431 @@
+<?php
+
+/**
+ * @group BP_Messages_Thread
+ */
+class BP_Tests_BP_Messages_Thread extends BP_UnitTestCase {
+
+	/**
+	 * @group cache
+	 */
+	public function test_construct_cache() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		// prime cache
+		new BP_Messages_Thread( $t1 );
+
+		// Cache should exist
+		$this->assertThat(
+			wp_cache_get( $t1, 'bp_messages_threads' ),
+			$this->logicalNot( $this->equalTo( false ) ),
+			'Message thread cache should exist.'
+		);
+	}
+
+	/**
+	 * @group order
+	 */
+	public function test_construct_order_desc() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create thread
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+		// save message ID
+		$thread = new BP_Messages_Thread( $t1 );
+		$m1 = wp_list_pluck( $thread->messages, 'id' );
+		$m1 = array_pop( $m1 );
+
+		// create reply
+		$t2 = $this->factory->message->create( array(
+			'thread_id' => $t1,
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'content' => 'Bar'
+		) );
+		// save message ID
+		$thread = new BP_Messages_Thread( $t1 );
+		$m2 = wp_list_pluck( $thread->messages, 'id' );
+		$m2 = array_pop( $m2 );
+
+		// now get thread by DESC
+		$thread = new BP_Messages_Thread( $t1, 'DESC' );
+
+		// assert!
+		$this->assertEquals(
+			array( $m2, $m1 ),
+			wp_list_pluck( $thread->messages, 'id' )
+		);
+	}
+
+	/**
+	 * @group get_current_threads_for_user
+	 */
+	public function test_get_current_threads_for_user_with_search_terms_inbox() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$t2 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Bar',
+		) );
+
+		$threads = BP_Messages_Thread::get_current_threads_for_user( array(
+			'user_id' => $u2,
+			'search_terms' => 'ar',
+		) );
+
+		$expected = array( $t2 );
+		$found = wp_parse_id_list( wp_list_pluck( $threads['threads'], 'thread_id' ) );
+
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group get_current_threads_for_user
+	 */
+	public function test_get_current_threads_for_user_with_search_terms_sentbox() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$t2 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Bar',
+		) );
+
+		$threads = BP_Messages_Thread::get_current_threads_for_user( array(
+			'user_id' => $u1,
+			'box' => 'sentbox',
+			'search_terms' => 'ar',
+		) );
+
+		$expected = array( $t2 );
+		$found = wp_parse_id_list( wp_list_pluck( $threads['threads'], 'thread_id' ) );
+
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group get_current_threads_for_user
+	 * @expectedDeprecated BP_Messages_Thread::get_current_threads_for_user
+	 */
+	public function test_get_current_threads_for_user_with_old_args() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$t2 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Bar',
+		) );
+
+		$threads = BP_Messages_Thread::get_current_threads_for_user( $u1, 'sentbox', 'all', null, null, 'ar' );
+
+		$expected = array( $t2 );
+		$found = wp_parse_id_list( wp_list_pluck( $threads['threads'], 'thread_id' ) );
+
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group get_recipients
+	 * @group cache
+	 */
+	public function test_get_recipients_should_cache_its_values() {
+		global $wpdb;
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$thread = new BP_Messages_Thread( $t1 );
+		$recipients = $thread->get_recipients();
+
+		$num_queries = $wpdb->num_queries;
+		$recipients_cached = $thread->get_recipients();
+
+		$this->assertEquals( $recipients, $recipients_cached );
+		$this->assertEquals( $num_queries, $wpdb->num_queries );
+	}
+
+	/**
+	 * @group get_recipients
+	 * @group cache
+	 */
+	public function test_get_recipients_cache_should_be_busted_when_thread_message_is_sent() {
+		global $wpdb;
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$thread = new BP_Messages_Thread( $t1 );
+		$recipients = $thread->get_recipients();
+
+		// Verify that the cache is populated.
+		$num_queries = $wpdb->num_queries;
+		$recipients_cached = $thread->get_recipients();
+		$this->assertEquals( $num_queries, $wpdb->num_queries );
+
+		messages_new_message( array(
+			'sender_id' => $u2,
+			'thread_id' => $t1,
+			'recipients' => array( $u1 ),
+			'subject' => 'Bar',
+			'content' => 'Baz',
+		) );
+
+		// Cache should be empty.
+		$num_queries = $wpdb->num_queries;
+		$recipients_uncached = $thread->get_recipients();
+		$this->assertEquals( $num_queries + 1, $wpdb->num_queries );
+	}
+
+	/**
+	 * @group get_recipients
+	 * @group cache
+	 */
+	public function test_get_recipients_cache_should_be_busted_when_single_thread_is_deleted() {
+		global $wpdb;
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$thread = new BP_Messages_Thread( $t1 );
+		$recipients = $thread->get_recipients();
+
+		// Verify that the cache is populated.
+		$num_queries = $wpdb->num_queries;
+		$recipients_cached = $thread->get_recipients();
+		$this->assertEquals( $num_queries, $wpdb->num_queries );
+
+		messages_delete_thread( $t1 );
+
+		// Cache should be empty.
+		$this->assertFalse( wp_cache_get( 'thread_recipients_' . $t1, 'bp_messages' ) );
+	}
+
+	/**
+	 * @group get_recipients
+	 * @group cache
+	 */
+	public function test_get_recipients_cache_should_be_busted_when_array_of_threads_is_deleted() {
+		global $wpdb;
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$thread = new BP_Messages_Thread( $t1 );
+		$recipients = $thread->get_recipients();
+
+		// Verify that the cache is populated.
+		$num_queries = $wpdb->num_queries;
+		$recipients_cached = $thread->get_recipients();
+		$this->assertEquals( $num_queries, $wpdb->num_queries );
+
+		messages_delete_thread( array( $t1 ) );
+
+		// Cache should be empty.
+		$this->assertFalse( wp_cache_get( 'thread_recipients_' . $t1, 'bp_messages' ) );
+	}
+
+	/**
+	 * @group get_recipients
+	 * @group cache
+	 */
+	public function test_get_recipients_cache_should_be_busted_when_thread_is_read() {
+		global $wpdb;
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$thread = new BP_Messages_Thread( $t1 );
+		$recipients = $thread->get_recipients();
+
+		// Verify that the cache is populated.
+		$num_queries = $wpdb->num_queries;
+		$recipients_cached = $thread->get_recipients();
+		$this->assertEquals( $num_queries, $wpdb->num_queries );
+
+		// Mark thread as read
+		$current_user = get_current_user_id();
+		$this->set_current_user( $u2 );
+		messages_mark_thread_read( $t1 );
+
+		// Cache should be empty.
+		$this->assertFalse( wp_cache_get( 'thread_recipients_' . $t1, 'bp_messages' ) );
+
+		$this->set_current_user( $current_user );
+	}
+
+	/**
+	 * @group get_recipients
+	 * @group cache
+	 */
+	public function test_get_recipients_cache_should_be_busted_when_thread_is_unread() {
+		global $wpdb;
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$thread = new BP_Messages_Thread( $t1 );
+		$recipients = $thread->get_recipients();
+
+		// Verify that the cache is populated.
+		$num_queries = $wpdb->num_queries;
+		$recipients_cached = $thread->get_recipients();
+		$this->assertEquals( $num_queries, $wpdb->num_queries );
+
+		// Mark thread as unread
+		$current_user = get_current_user_id();
+		$this->set_current_user( $u2 );
+		messages_mark_thread_unread( $t1 );
+
+		// Cache should be empty.
+		$this->assertFalse( wp_cache_get( 'thread_recipients_' . $t1, 'bp_messages' ) );
+
+		$this->set_current_user( $current_user );
+	}
+
+	/**
+	 * @group check_access
+	 */
+	public function test_check_access_valid_thread() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		// save recipient ID
+		$thread = new BP_Messages_Thread( $t1 );
+		$r1 = wp_list_pluck( $thread->recipients, 'id' );
+		$r1 = array_pop( $r1 );
+
+		$this->assertEquals( $r1, BP_Messages_Thread::check_access( $t1, $u1 ) );
+	}
+
+	/**
+	 * @group check_access
+	 */
+	public function test_check_access_invalid_thread() {
+		$this->assertEquals( null, BP_Messages_Thread::check_access( 999, 1 ) );
+	}
+
+	/**
+	 * @group is_valid
+	 */
+	public function test_is_valid_valid_thread() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+		) );
+
+		$this->assertEquals( $t1, BP_Messages_Thread::is_valid( $t1 ) );
+	}
+
+	/**
+	 * @group is_valid
+	 */
+	public function test_is_valid_invalid_thread() {
+		$this->assertEquals( null, BP_Messages_Thread::is_valid( 999 ) );
+	}
+
+	/**
+	 * @group last_message
+	 */
+	public function test_last_message_populated() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$date = bp_core_current_time();
+
+		$t1 = $this->factory->message->create( array(
+			'sender_id' => $u1,
+			'recipients' => array( $u2 ),
+			'subject' => 'Foo',
+			'date_sent' => $date,
+			'content' => 'Bar and baz.',
+		) );
+
+		$thread = new BP_Messages_Thread( $t1 );
+
+		$this->assertNotNull( $thread->last_message_id );
+		$this->assertEquals( 'Foo', $thread->last_message_subject );
+		$this->assertEquals( $u1, $thread->last_sender_id );
+		$this->assertEquals( $date, $thread->last_message_date );
+		$this->assertEquals( 'Bar and baz.', $thread->last_message_content );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/messages/functions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/messages/functions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/messages/functions.php	(revision 10410)
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @group messages
+ * @group functions
+ */
+class BP_Tests_Messages_Functions extends BP_UnitTestCase {
+
+	/**
+	 * @group counts
+	 */
+	public function test_get_unread_count() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// send a private message
+		$t1 = messages_new_message( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'A new message',
+			'content'    => 'Hey there!',
+		) );
+
+		// get unread count for $u2
+		$this->set_current_user( $u2 );
+		$this->assertEquals( 1, messages_get_unread_count( $u2 ) );
+
+		// send another message and get recheck unread count
+		$t2 = messages_new_message( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'A new message',
+			'content'    => 'Hey there!',
+		) );
+		$this->assertEquals( 2, messages_get_unread_count( $u2 ) );
+
+		// mark one message as read
+		messages_mark_thread_read( $t1 );
+
+		// recheck unread count
+		$this->assertEquals( 1, messages_get_unread_count( $u2 ) );
+	}
+
+	/**
+	 * @group messages_new_message
+	 */
+	public function test_messages_new_message_invalid_recipient_error_message() {
+		$u1 = $this->factory->user->create();
+
+		// attempt to send a private message to an invalid username
+		$t1 = messages_new_message( array(
+			'sender_id'  => $u1,
+			'recipients' => array( 'homerglumpkin' ),
+			'subject'    => 'A new message',
+			'content'    => 'Hey there!',
+			'error_type' => 'wp_error'
+		) );
+
+		$this->assertSame( 'Message could not be sent because you have entered an invalid username. Please try again.', $t1->get_error_message() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/messages/notifications.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/messages/notifications.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/messages/notifications.php	(revision 10410)
@@ -0,0 +1,151 @@
+<?php
+
+/**
+ * @group notifications
+ * @group messages
+ */
+class BP_Tests_Messages_Notifications extends BP_UnitTestCase {
+
+	protected $filter_fired;
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->filter_fired = '';
+	}
+
+	/**
+	 * @group messages_format_notifications
+	 */
+	public function test_friends_format_notifications_bp_messages_multiple_new_message_notification_nonstring_filter() {
+		// Dummy thread ID
+		$t = 12;
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		// Admin
+		$n = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+			'item_id' => $t,
+			'component_action' => 'new_message',
+		) );
+
+		add_filter( 'bp_messages_multiple_new_message_notification', array( $this, 'notification_filter_callback' ) );
+		$n = messages_format_notifications( 'new_message', $n, '', 2, 'array' );
+		remove_filter( 'bp_messages_multiple_new_message_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_messages_multiple_new_message_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group messages_format_notifications
+	 */
+	public function test_friends_format_notifications_bp_messages_single_new_message_notification_nonstring_filter() {
+		// Dummy thread ID
+		$t = 12;
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		// Admin
+		$n = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+			'item_id' => $t,
+			'component_action' => 'new_message',
+		) );
+
+		add_filter( 'bp_messages_single_new_message_notification', array( $this, 'notification_filter_callback' ) );
+		$n = messages_format_notifications( 'new_message', $n, '', 1, 'array' );
+		remove_filter( 'bp_messages_single_new_message_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_messages_single_new_message_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group messages_format_notifications
+	 */
+	public function test_friends_format_notifications_bp_messages_multiple_new_message_notification_string_filter() {
+		// Dummy thread ID
+		$t = 12;
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		// Admin
+		$n = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+			'item_id' => $t,
+			'component_action' => 'new_message',
+		) );
+
+		add_filter( 'bp_messages_multiple_new_message_notification', array( $this, 'notification_filter_callback' ) );
+		$n = messages_format_notifications( 'new_message', $n, '', 2 );
+		remove_filter( 'bp_messages_multiple_new_message_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_messages_multiple_new_message_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @group messages_format_notifications
+	 */
+	public function test_friends_format_notifications_bp_messages_single_new_message_notification_string_filter() {
+		// Dummy thread ID
+		$t = 12;
+		$u = $this->factory->user->create();
+		$this->set_current_user( $u );
+
+		// Admin
+		$n = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+			'item_id' => $t,
+			'component_action' => 'new_message',
+		) );
+
+		add_filter( 'bp_messages_single_new_message_notification', array( $this, 'notification_filter_callback' ) );
+		$n = messages_format_notifications( 'new_message', $n, '', 1 );
+		remove_filter( 'bp_messages_single_new_message_notification', array( $this, 'notification_filter_callback' ) );
+
+		$this->assertSame( 'bp_messages_single_new_message_notification', $this->filter_fired );
+	}
+
+	/**
+	 * @ticket BP6329
+	 */
+	public function test_messages_notifications_should_be_deleted_when_corresponding_message_is_deleted() {
+		if ( ! bp_is_active( 'messages' ) ) {
+			$this->markTestSkipped( __METHOD__ . ' requires the Messages component.' );
+		}
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$t1 = messages_new_message( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'A new message',
+			'content'    => 'Hey there!',
+		) );
+
+		// Verify that a notification has been created for the message.
+		$n1 = BP_Notifications_Notification::get( array(
+			'component' => 'messages',
+			'user_id' => $u2,
+		) );
+		$this->assertNotEmpty( $n1 );
+
+		$this->assertTrue( messages_delete_thread( $t1 ) );
+
+		$n2 = BP_Notifications_Notification::get( array(
+			'component' => 'messages',
+			'user_id' => $u2,
+		) );
+		$this->assertSame( array(), $n2 );
+	}
+
+	public function notification_filter_callback( $value ) {
+		$this->filter_fired = current_filter();
+		return $value;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/messages/star.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/messages/star.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/messages/star.php	(revision 10410)
@@ -0,0 +1,181 @@
+<?php
+/**
+ * @group messages
+ * @group star
+ */
+class BP_Tests_Messages_Star_ extends BP_UnitTestCase {
+
+	/**
+	 * @group bp_messages_is_message_starred
+	 * @group bp_messages_star_set_action
+	 */
+	public function test_is_message_starred() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create the thread
+		$t1 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'This is a knive',
+		) );
+
+		// create a reply
+		$this->factory->message->create( array(
+			'thread_id'  => $t1,
+			'sender_id'  => $u2,
+			'recipients' => array( $u1 ),
+			'content'    => "That's a spoon",
+		) );
+
+		// grab the message ids as individual variables
+		list( $m1, $m2 ) = $this->get_message_ids( $t1 );
+
+		// star the second message
+		$star = bp_messages_star_set_action( array(
+			'user_id'    => $u1,
+			'message_id' => $m2,
+		) );
+
+		// assert that star is set
+		$this->assertTrue( $star );
+		$this->assertTrue( bp_messages_is_message_starred( $m2, $u1 ) );
+
+		// unstar the second message
+		$unstar = bp_messages_star_set_action( array(
+			'user_id'    => $u1,
+			'message_id' => $m2,
+			'action'     => 'unstar'
+		) );
+
+		// assert that star is removed
+		$this->assertTrue( $unstar );
+		$this->assertFalse( bp_messages_is_message_starred( $m2, $u1 ) );
+	}
+
+	/**
+	 * @group bp_messages_star_set_action
+	 * @group bulk
+	 */
+	public function test_star_set_action_bulk_unstar() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create the thread
+		$t1 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'This is a knive',
+		) );
+
+		// create a reply
+		$this->factory->message->create( array(
+			'thread_id'  => $t1,
+			'sender_id'  => $u2,
+			'recipients' => array( $u1 ),
+			'content'    => "That's a spoon",
+		) );
+
+		// grab the message ids as individual variables
+		list( $m1, $m2 ) = $this->get_message_ids( $t1 );
+
+		// star all messages
+		bp_messages_star_set_action( array(
+			'user_id'    => $u1,
+			'message_id' => $m1,
+		) );
+		bp_messages_star_set_action( array(
+			'user_id'    => $u1,
+			'message_id' => $m2,
+		) );
+
+		// assert that stars are set
+		$this->assertTrue( bp_messages_is_message_starred( $m1, $u1 ) );
+		$this->assertTrue( bp_messages_is_message_starred( $m2, $u1 ) );
+
+		// unstar all messages
+		bp_messages_star_set_action( array(
+			'user_id'    => $u1,
+			'thread_id'  => $t1,
+			'action'     => 'unstar',
+			'bulk'       => true
+		) );
+
+		// assert that star is removed
+		$this->assertFalse( bp_messages_is_message_starred( $m1, $u1 ) );
+		$this->assertFalse( bp_messages_is_message_starred( $m2, $u1 ) );
+	}
+
+	/**
+	 * @group bp_messages_filter_starred_message_threads
+	 */
+	public function test_get_starred_threads_should_not_include_deleted_thread() {
+		$old_current_user = get_current_user_id();
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create three threads
+		$t1 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'A',
+		) );
+		$t2 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'B',
+		) );
+		$t3 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'C',
+		) );
+
+		// grab the message ids as individual variables
+		list( $m1 ) = $this->get_message_ids( $t1 );
+		list( $m2 ) = $this->get_message_ids( $t2 );
+		list( $m3 ) = $this->get_message_ids( $t3 );
+
+		// star all threads
+		bp_messages_star_set_action( array(
+			'user_id'    => $u2,
+			'message_id' => $m1,
+		) );
+		bp_messages_star_set_action( array(
+			'user_id'    => $u2,
+			'message_id' => $m2,
+		) );
+		bp_messages_star_set_action( array(
+			'user_id'    => $u2,
+			'message_id' => $m3,
+		) );
+
+		// delete the second thread
+		$this->set_current_user( $u2 );
+		messages_delete_thread( $t2 );
+
+		// load the starred threads loop
+		global $messages_template;
+		add_filter( 'bp_after_has_message_threads_parse_args', 'bp_messages_filter_starred_message_threads' );
+		bp_has_message_threads();
+		remove_filter( 'bp_after_has_message_threads_parse_args', 'bp_messages_filter_starred_message_threads' );
+
+		// assert that second thread isn't in starred thread loop
+		$thread_ids = wp_list_pluck( $messages_template->threads, 'thread_id' );
+		$this->assertFalse( in_array( $t2, $thread_ids ) );
+
+		// reset
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * Helper method to grab the message IDs from a message thread.
+	 *
+	 * @param int $thread_id The message thread ID
+	 * @return array
+	 */
+	protected function get_message_ids( $thread_id = 0 ) {
+		$thread = new BP_Messages_Thread( $thread_id );
+		return wp_list_pluck( $thread->messages, 'id' );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/messages/template.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/messages/template.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/messages/template.php	(revision 10410)
@@ -0,0 +1,356 @@
+<?php
+
+/**
+ * @group messages
+ * @group template
+ */
+class BP_Tests_Messages_Template extends BP_UnitTestCase {
+	/**
+	 * @group bp_has_message_threads
+	 */
+	public function test_has_message_threads() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create initial thread
+		$t1 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+		) );
+
+		// create some replies to thread
+		$this->factory->message->create( array(
+			'thread_id' => $t1,
+			'sender_id' => $u2,
+			'recipients' => array( $u1 ),
+		) );
+		$this->factory->message->create( array(
+			'thread_id' => $t1,
+			'sender_id' => $u2,
+			'recipients' => array( $u1 ),
+		) );
+
+		$messages_template = new BP_Messages_Box_Template( array( 'user_id' => $u1 ) );
+
+		$this->assertEquals( 1, $messages_template->thread_count );
+		$this->assertSame( array( $t1 ), wp_list_pluck( $messages_template->threads, 'thread_id' ) );
+	}
+
+	/**
+	 * @group bp_has_message_threads
+	 *
+	 * @expectedDeprecated BP_Messages_Box_Template::__construct
+	 */
+	public function test_has_message_threads_old_args() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create initial thread
+		$t1 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+		) );
+
+		// create some replies to thread
+		$this->factory->message->create( array(
+			'thread_id' => $t1,
+			'sender_id' => $u2,
+			'recipients' => array( $u1 ),
+		) );
+		$this->factory->message->create( array(
+			'thread_id' => $t1,
+			'sender_id' => $u2,
+			'recipients' => array( $u1 ),
+		) );
+
+		$messages_template = new BP_Messages_Box_Template( $u1 );
+
+		$this->assertEquals( 1, $messages_template->thread_count );
+		$this->assertSame( array( $t1 ), wp_list_pluck( $messages_template->threads, 'thread_id' ) );
+	}
+
+	/**
+	 * @group bp_has_message_threads
+	 * @group meta_query
+	 */
+	public function test_thread_has_messages_meta_query() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create some threads
+		$t1 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'This is a knive',
+		) );
+		$t2 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'Oy',
+		) );
+
+		// misc threads
+		$this->factory->message->create_many( 3, array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+		) );
+
+		// create some replies for thread 1
+		$this->factory->message->create( array(
+			'thread_id'  => $t1,
+			'sender_id'  => $u2,
+			'recipients' => array( $u1 ),
+			'content'    => "That's a spoon",
+		) );
+		$this->factory->message->create( array(
+			'thread_id'  => $t1,
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'content'    => "I see you've played knivey-spooney before.",
+		) );
+		$this->factory->message->create( array(
+			'thread_id'  => $t1,
+			'sender_id'  => $u2,
+			'recipients' => array( $u1 ),
+			'content'    => '*crickets*',
+		) );
+
+		// create some replies for thread 2
+		$this->factory->message->create( array(
+			'thread_id'  => $t2,
+			'sender_id'  => $u2,
+			'recipients' => array( $u1 ),
+			'content'    => "Oy yourself.",
+		) );
+
+		// grab the message ids as individual variables for thread 1
+		$thread = new BP_Messages_Thread( $t1 );
+		$mids = wp_list_pluck( $thread->messages, 'id' );
+		list( $m1, $m2, $m3, $m4 ) = $mids;
+
+		// grab the message ids as individual variables for thread 2
+		$thread = new BP_Messages_Thread( $t2 );
+		$mids = wp_list_pluck( $thread->messages, 'id' );
+		list( $m5, $m6 ) = $mids;
+
+		// add meta for some of the messages
+		bp_messages_update_meta( $m1, 'utensil',  'knive' );
+		bp_messages_update_meta( $m1, 'is_knive', 'yes' );
+		bp_messages_update_meta( $m1, "starred_by_user_{$u2}", true );
+
+		bp_messages_update_meta( $m2, 'utensil',  'spoon' );
+		bp_messages_update_meta( $m2, 'is_knive', 'no' );
+		bp_messages_update_meta( $m2, 'is_spoon', 'yes' );
+
+		bp_messages_update_meta( $m3, "starred_by_user_{$u2}", true );
+
+		bp_messages_update_meta( $m5, "starred_by_user_{$u2}", true );
+
+		// now, do the message thread loop query
+		global $messages_template;
+		bp_has_message_threads( array(
+			'user_id' => $u2,
+			'meta_query' => array(
+				array(
+					'key' => "starred_by_user_{$u2}"
+				),
+			)
+		) );
+
+		$this->assertEquals( 2, $messages_template->thread_count );
+		$this->assertEqualSets( array( $t1, $t2 ), wp_list_pluck( $messages_template->threads, 'thread_id' ) );
+	}
+
+	/**
+	 * @group bp_has_message_threads
+	 * @group meta_query
+	 */
+	public function test_thread_has_messages_meta_query_multiple_clauses_relation_and() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create some threads
+		$t1 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'This is a knive',
+		) );
+		$t2 = $this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'subject'    => 'Oy',
+		) );
+
+		// misc threads
+		$this->factory->message->create_many( 3, array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+		) );
+
+		// create some replies for thread 1
+		$this->factory->message->create( array(
+			'thread_id'  => $t1,
+			'sender_id'  => $u2,
+			'recipients' => array( $u1 ),
+			'content'    => "That's a spoon",
+		) );
+		$this->factory->message->create( array(
+			'thread_id'  => $t1,
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+			'content'    => "I see you've played knivey-spooney before.",
+		) );
+		$this->factory->message->create( array(
+			'thread_id'  => $t1,
+			'sender_id'  => $u2,
+			'recipients' => array( $u1 ),
+			'content'    => '*crickets*',
+		) );
+
+		// create some replies for thread 2
+		$this->factory->message->create( array(
+			'thread_id'  => $t2,
+			'sender_id'  => $u2,
+			'recipients' => array( $u1 ),
+			'content'    => "Oy yourself.",
+		) );
+
+		// grab the message ids as individual variables for thread 1
+		$thread = new BP_Messages_Thread( $t1 );
+		$mids = wp_list_pluck( $thread->messages, 'id' );
+		list( $m1, $m2, $m3, $m4 ) = $mids;
+
+		// grab the message ids as individual variables for thread 2
+		$thread = new BP_Messages_Thread( $t2 );
+		$mids = wp_list_pluck( $thread->messages, 'id' );
+		list( $m5, $m6 ) = $mids;
+
+		// add meta for some of the messages
+		bp_messages_update_meta( $m1, 'utensil',  'knive' );
+		bp_messages_update_meta( $m1, 'is_knive', 'yes' );
+		bp_messages_update_meta( $m1, "starred_by_user_{$u2}", true );
+
+		bp_messages_update_meta( $m2, 'utensil',  'spoon' );
+		bp_messages_update_meta( $m2, 'is_knive', 'no' );
+		bp_messages_update_meta( $m2, 'is_spoon', 'yes' );
+
+		bp_messages_update_meta( $m3, "starred_by_user_{$u2}", true );
+
+		bp_messages_update_meta( $m5, "starred_by_user_{$u2}", true );
+
+		// now, do the message thread loop query
+		global $messages_template;
+		bp_has_message_threads( array(
+			'user_id' => $u2,
+			'meta_query' => array(
+				'relation' => 'AND',
+				array(
+					'key' => "starred_by_user_{$u2}"
+				),
+				array(
+					'key' => 'utensil',
+					'value' => 'knive',
+				),
+			)
+		) );
+
+		$this->assertEquals( 1, $messages_template->thread_count );
+		$this->assertEqualSets( array( $t1 ), wp_list_pluck( $messages_template->threads, 'thread_id' ) );
+	}
+
+	/**
+	 * @group bp_has_message_threads
+	 */
+	public function test_has_message_threads_anonymous_user_should_not_see_threads() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create initial thread
+		$this->factory->message->create( array(
+			'sender_id'  => $u1,
+			'recipients' => array( $u2 ),
+		) );
+
+		// set user to anonymous
+		$old_current_user = get_current_user_id();
+		$this->set_current_user( 0 );
+
+		// now, do the message thread query
+		global $messages_template;
+		bp_has_message_threads();
+
+		// assert!
+		$this->assertEquals( 0, $messages_template->thread_count );
+		$this->assertEmpty( $messages_template->threads );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Messages_Box_Template
+	 */
+	public function test_bp_messages_template_should_give_precedence_to_mpage_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['mpage'] = '5';
+
+		$at = new BP_Messages_Box_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 5, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Messages_Box_Template
+	 */
+	public function test_bp_messages_template_should_reset_0_pag_page_URL_param_to_default_pag_page_value() {
+		$request = $_REQUEST;
+		$_REQUEST['mpage'] = '0';
+
+		$at = new BP_Messages_Box_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 8, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Messages_Box_Template
+	 */
+	public function test_bp_messages_template_should_give_precedence_to_num_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '14';
+
+		$at = new BP_Messages_Box_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 14, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Messages_Box_Template
+	 */
+	public function test_bp_messages_template_should_reset_0_pag_num_URL_param_to_default_pag_num_value() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '0';
+
+		$at = new BP_Messages_Box_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 13, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/notifications/class-bp-notifications-notification.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/notifications/class-bp-notifications-notification.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/notifications/class-bp-notifications-notification.php	(revision 10410)
@@ -0,0 +1,312 @@
+<?php
+
+/**
+ * @group notifications
+ */
+class BP_Tests_BP_Notifications_Notification_TestCases extends BP_UnitTestCase {
+	/**
+	 * @group get
+	 */
+	public function test_get_null_component_name() {
+		$u = $this->factory->user->create();
+		$n1 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+		) );
+		$n2 = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+		) );
+
+		// temporarily turn on groups, shut off messages
+		$groups_toggle = isset( buddypress()->active_components['groups'] );
+		$messages_toggle = isset( buddypress()->active_components['messages'] );
+		buddypress()->active_components['groups'] = 1;
+		unset( buddypress()->active_components['messages'] );
+
+		$n = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+		) );
+
+		// Check that the correct items are pulled up
+		$expected = array( $n1 );
+		$actual = wp_list_pluck( $n, 'id' );
+		$this->assertEquals( $expected, $actual );
+
+		// reset copmonent toggles
+		if ( $groups_toggle ) {
+			buddypress()->active_components['groups'] = 1;
+		} else {
+			unset( buddypress()->active_components['groups'] );
+		}
+
+		if ( $messages_toggle ) {
+			buddypress()->active_components['messages'] = 1;
+		} else {
+			unset( buddypress()->active_components['messages'] );
+		}
+	}
+
+	/**
+	 * @group get_total_count
+	 * @ticket BP5300
+	 */
+	public function test_get_total_count_null_component_name() {
+		$u = $this->factory->user->create();
+		$n1 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+		) );
+		$n2 = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+		) );
+
+		// temporarily turn on groups, shut off messages
+		$groups_toggle = isset( buddypress()->active_components['groups'] );
+		$messages_toggle = isset( buddypress()->active_components['messages'] );
+		buddypress()->active_components['groups'] = 1;
+		unset( buddypress()->active_components['messages'] );
+
+		$n = BP_Notifications_Notification::get_total_count( array(
+			'user_id' => $u,
+		) );
+
+		// Check that the correct items are pulled up
+		$this->assertEquals( 1, $n );
+
+		// reset copmonent toggles
+		if ( $groups_toggle ) {
+			buddypress()->active_components['groups'] = 1;
+		} else {
+			unset( buddypress()->active_components['groups'] );
+		}
+
+		if ( $messages_toggle ) {
+			buddypress()->active_components['messages'] = 1;
+		} else {
+			unset( buddypress()->active_components['messages'] );
+		}
+	}
+
+	/**
+	 * @group get_total_count
+	 * @ticket BP5300
+	 */
+	public function test_get_total_count_with_component_name() {
+		$u = $this->factory->user->create();
+		$n1 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+		) );
+		$n2 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+		) );
+		$n3 = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+		) );
+
+		$n = BP_Notifications_Notification::get_total_count( array(
+			'user_id' => $u,
+			'component_name' => array( 'messages' ),
+		) );
+
+		$this->assertEquals( 1, $n );
+	}
+
+	/**
+	 * @group order_by
+	 * @group sort_order
+	 */
+	public function test_order_by_date() {
+		$now = time();
+		$u = $this->factory->user->create();
+		$n1 = $this->factory->notification->create( array(
+			'component_name' => 'friends',
+			'user_id' => $u,
+			'date_notified' => date( 'Y-m-d H:i:s', $now - 500 ),
+		) );
+		$n2 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+			'date_notified' => date( 'Y-m-d H:i:s', $now - 100 ),
+		) );
+		$n3 = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+			'date_notified' => date( 'Y-m-d H:i:s', $now - 1000 ),
+		) );
+
+		$n = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+			'order_by' => 'date_notified',
+			'sort_order' => 'DESC',
+		) );
+
+		// Check that the correct items are pulled up
+		$expected = array( $n2, $n1, $n3 );
+		$actual = wp_list_pluck( $n, 'id' );
+		$this->assertEquals( $expected, $actual );
+	}
+
+	/**
+	 * @group is_new
+	 */
+	public function test_is_new_true() {
+		$u = $this->factory->user->create();
+		$n1 = $this->factory->notification->create( array(
+			'component_name' => 'friends',
+			'user_id' => $u,
+			'is_new' => false,
+		) );
+		$n2 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+			'is_new' => true,
+		) );
+		$n3 = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+			'is_new' => true,
+		) );
+
+		$n = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+			'is_new' => true,
+		) );
+
+		// Check that the correct items are pulled up
+		$expected = array( $n2, $n3 );
+		$actual = wp_list_pluck( $n, 'id' );
+		$this->assertEquals( $expected, $actual );
+	}
+
+	/**
+	 * @group is_new
+	 */
+	public function test_is_new_false() {
+		$u = $this->factory->user->create();
+		$n1 = $this->factory->notification->create( array(
+			'component_name' => 'friends',
+			'user_id' => $u,
+			'is_new' => false,
+		) );
+		$n2 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+			'is_new' => true,
+		) );
+		$n3 = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+			'is_new' => true,
+		) );
+
+		$n = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+			'is_new' => false,
+		) );
+
+		// Check that the correct items are pulled up
+		$expected = array( $n1 );
+		$actual = wp_list_pluck( $n, 'id' );
+		$this->assertEquals( $expected, $actual );
+	}
+
+	/**
+	 * @group is_new
+	 */
+	public function test_is_new_both() {
+		$u = $this->factory->user->create();
+		$n1 = $this->factory->notification->create( array(
+			'component_name' => 'friends',
+			'user_id' => $u,
+			'is_new' => false,
+		) );
+		$n2 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+			'is_new' => true,
+		) );
+		$n3 = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+			'is_new' => true,
+		) );
+
+		$n = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+			'is_new' => 'both',
+		) );
+
+		// Check that the correct items are pulled up
+		$expected = array( $n1, $n2, $n3 );
+		$actual = wp_list_pluck( $n, 'id' );
+		$this->assertEquals( $expected, $actual );
+	}
+
+	/**
+	 * @group get
+	 * @group search_terms
+	 */
+	public function test_get_with_search_terms() {
+		$u = $this->factory->user->create();
+		$n1 = $this->factory->notification->create( array(
+			'component_name' => 'friends',
+			'user_id' => $u,
+			'is_new' => false,
+		) );
+		$n2 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id' => $u,
+			'is_new' => true,
+		) );
+		$n3 = $this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id' => $u,
+			'is_new' => true,
+		) );
+
+		$n = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+			'search_terms' => 'roup',
+		) );
+
+		// Check that the correct items are pulled up
+		$expected = array( $n2 );
+		$actual = wp_list_pluck( $n, 'id' );
+		$this->assertEquals( $expected, $actual );
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP6229
+	 */
+	public function test_get_paged_sql() {
+		$u = $this->factory->user->create();
+
+		$notifications = array();
+		for ( $i = 1; $i <= 6; $i++ ) {
+			$notifications[] = $this->factory->notification->create( array(
+				'component_name' => 'activity',
+				'secondary_item_id' => $i,
+				'user_id' => $u,
+				'is_new' => true,
+			) );
+		}
+
+		$found = BP_Notifications_Notification::get( array(
+			'user_id' => $u,
+			'is_new' => true,
+			'page' => 2,
+			'per_page' => 2,
+			'order_by' => 'id',
+		) );
+
+		// Check that the correct number of items are pulled up
+		$expected = array( $notifications[2], $notifications[3] );
+		$this->assertEquals( $expected, wp_list_pluck( $found, 'id' ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/notifications/class-bp-notifications-template.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/notifications/class-bp-notifications-template.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/notifications/class-bp-notifications-template.php	(revision 10410)
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @group notifications
+ * @group template
+ */
+
+class BP_Tests_Notifications_BPNotificationsTemplate extends BP_UnitTestCase {
+	/**
+ 	 * @group pagination
+ 	 * @group BP6229
+ 	 */
+ 	public function test_pagination_params_in_url_should_be_passed_to_query() {
+		$u = $this->factory->user->create();
+
+		$notifications = array();
+		for ( $i = 1; $i <= 6; $i++ ) {
+			$notifications[] = $this->factory->notification->create( array(
+				'component_name' => 'activity',
+				'secondary_item_id' => $i,
+				'user_id' => $u,
+				'is_new' => true,
+			) );
+		}
+
+		$_REQUEST['npage'] = 2;
+
+		$template = new BP_Notifications_Template( array(
+			'user_id' => $u,
+			'is_new' => true,
+			'per_page' => 2,
+			'order_by' => 'id',
+		) );
+
+		unset( $_REQUEST['npage'] );
+
+		// Check that the correct number of items are pulled up
+		$expected = array( $notifications[3], $notifications[2] );
+		$this->assertEquals( $expected, wp_list_pluck( $template->notifications, 'id' ) );
+ 	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/notifications/functions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/notifications/functions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/notifications/functions.php	(revision 10410)
@@ -0,0 +1,236 @@
+<?php
+
+/**
+ * @group notifications
+ */
+class BP_Tests_Notifications_Functions extends BP_UnitTestCase {
+
+	/**
+	 * @group cache
+	 */
+	public function test_cache_invalidation_all_for_user_on_save() {
+		$u = $this->factory->user->create();
+
+		$this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id'        => $u
+		) );
+		$this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id'        => $u,
+			'item_id'        => 1
+		) );
+
+		// prime cache
+		$count = bp_notifications_get_unread_notification_count( $u );
+
+		// just to be sure...
+		$this->assertEquals( 2, $count, 'Cache count should be 2 before invalidation.' );
+
+		// Trigger invalidation via save
+		$this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id'        => $u,
+			'item_id'        => 2
+		) );
+
+		$this->assertFalse( wp_cache_get( 'all_for_user_' . $u, 'bp_notifications' ) );
+	}
+
+	/**
+	 * @group cache
+	 */
+	public function test_cache_invalidation_all_for_user_on_delete() {
+		$u  = $this->factory->user->create();
+		$n1 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id'        => $u
+		) );
+		$this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id'        => $u
+		) );
+
+		// prime cache
+		$count = bp_notifications_get_unread_notification_count( $u );
+
+		// just to be sure...
+		$this->assertEquals( 2, $count, 'Cache count should be 2 before invalidation.' );
+
+		// delete
+		BP_Notifications_Notification::delete( array( 'id' => $n1, ) );
+
+		$this->assertFalse( wp_cache_get( 'all_for_user_' . $u, 'bp_notifications' ) );
+	}
+
+	/**
+	 * @group cache
+	 */
+	public function test_cache_invalidation_all_for_user_on_update_user_id() {
+		$u = $this->factory->user->create();
+
+		$this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id'        => $u
+		) );
+		$this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id'        => $u
+		) );
+
+		// prime cache
+		$count = bp_notifications_get_unread_notification_count( $u );
+
+		// just to be sure...
+		$this->assertEquals( 2, $count, 'Cache count should be 2 before invalidation.' );
+
+		// mark all notifications by user as read
+		BP_Notifications_Notification::update(
+			array( 'is_new'  => false ),
+			array( 'user_id' => $u    )
+		);
+
+		$this->assertFalse( wp_cache_get( 'all_for_user_' . $u, 'bp_notifications' ) );
+	}
+
+	/**
+	 * @group cache
+	 */
+	public function test_cache_invalidation_all_for_user_on_update_id() {
+		$u  = $this->factory->user->create();
+		$n1 = $this->factory->notification->create( array(
+			'component_name' => 'groups',
+			'user_id'        => $u
+		) );
+
+		$this->factory->notification->create( array(
+			'component_name' => 'messages',
+			'user_id'        => $u
+		) );
+
+		// prime cache
+		$count = bp_notifications_get_unread_notification_count( $u );
+
+		// just to be sure...
+		$this->assertEquals( 2, $count, 'Cache count should be 2 before invalidation.' );
+
+		// mark one notification as read
+		BP_Notifications_Notification::update(
+			array( 'is_new' => false ),
+			array( 'id'     => $n1   )
+		);
+
+		$this->assertFalse( wp_cache_get( 'all_for_user_' . $u, 'bp_notifications' ) );
+	}
+
+	/**
+	 * @group bp_notifications_add_notification
+	 */
+	public function test_bp_notifications_add_notification_no_dupes() {
+		$args = array(
+			'user_id'           => 5,
+			'item_id'           => 10,
+			'secondary_item_id' => 25,
+			'component_name'    => 'messages',
+			'component_action'  => 'new_message'
+		);
+
+		$this->factory->notification->create( $args );
+
+		$this->assertFalse( bp_notifications_add_notification( $args ) );
+	}
+
+	/**
+	 * @group bp_notifications_add_notification
+	 */
+	public function test_bp_notifications_add_notification_allow_duplicate() {
+		$args = array(
+			'user_id'           => 5,
+			'item_id'           => 10,
+			'secondary_item_id' => 25,
+			'component_name'    => 'messages',
+			'component_action'  => 'new_message'
+		);
+
+		$this->factory->notification->create( $args );
+
+		$args['allow_duplicate'] = true;
+
+		$this->assertNotEmpty( bp_notifications_add_notification( $args ) );
+	}
+
+	/**
+	 * @group bp_notifications_get_unread_notification_count
+	 * @group cache
+	 */
+	public function test_bp_notifications_get_unread_notification_count_cache() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		$this->factory->notification->create( array(
+			'component_name'    => 'messages',
+			'component_action'  => 'new_message',
+			'item_id'           => 99,
+			'user_id'           => $u2,
+			'secondary_item_id' => $u1,
+			'is_new'            => true
+		) );
+
+		// prime cache
+		bp_notifications_get_unread_notification_count( $u2 );
+
+		// mark the created notification as read
+		bp_notifications_mark_notifications_by_item_id( $u2, 99, 'messages', 'new_message', $u1 );
+
+		// now grab the updated notification count
+		$n = bp_notifications_get_unread_notification_count( $u2 );
+
+		// assert
+		$this->assertEquals( 0, $n );
+	}
+
+	/**
+	 * @group bp_has_notifications
+	 */
+	public function test_bp_has_notifications_filtering() {
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+
+		// create a mixture of different notifications
+		$this->factory->notification->create( array(
+			'component_name'    => 'messages',
+			'component_action'  => 'new_message',
+			'item_id'           => 99,
+			'user_id'           => $u2,
+			'secondary_item_id' => $u1,
+			'is_new'            => true
+		) );
+
+		$this->factory->notification->create( array(
+			'component_name'    => 'activity',
+			'component_action'  => 'new_at_mention',
+			'item_id'           => 99,
+			'user_id'           => $u2,
+			'secondary_item_id' => $u1,
+			'is_new'            => true
+		) );
+
+		$this->factory->notification->create( array(
+			'component_name'    => 'activity',
+			'component_action'  => 'new_at_mention',
+			'item_id'           => 100,
+			'user_id'           => $u2,
+			'secondary_item_id' => $u1,
+			'is_new'            => true
+		) );
+
+		// now fetch only activity notifications
+		bp_has_notifications( array(
+			'component_name' => 'activity',
+			'user_id'        => $u2
+		) );
+
+		// assert
+		$this->assertEquals( 2, buddypress()->notifications->query_loop->total_notification_count );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/notifications/template.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/notifications/template.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/notifications/template.php	(revision 10410)
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @group notifications
+ * @group template
+ */
+class BP_Tests_Notifications_Template extends BP_UnitTestCase {
+	/**
+	 * @group pagination
+	 * @group BP_Notifications_Template
+	 */
+	public function test_bp_notifications_template_should_give_precedence_to_npage_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['npage'] = '5';
+
+		$at = new BP_Notifications_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 5, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Notifications_Template
+	 */
+	public function test_bp_notifications_template_should_reset_0_pag_page_URL_param_to_default_pag_page_value() {
+		$request = $_REQUEST;
+		$_REQUEST['npage'] = '0';
+
+		$at = new BP_Notifications_Template( array(
+			'page' => 8,
+		) );
+
+		$this->assertEquals( 8, $at->pag_page );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Notifications_Template
+	 */
+	public function test_bp_notifications_template_should_give_precedence_to_num_URL_param() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '14';
+
+		$at = new BP_Notifications_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 14, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+
+	/**
+	 * @group pagination
+	 * @group BP_Notifications_Template
+	 */
+	public function test_bp_notifications_template_should_reset_0_pag_num_URL_param_to_default_pag_num_value() {
+		$request = $_REQUEST;
+		$_REQUEST['num'] = '0';
+
+		$at = new BP_Notifications_Template( array(
+			'per_page' => 13,
+		) );
+
+		$this->assertEquals( 13, $at->pag_num );
+
+		$_REQUEST = $request;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/activity.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/activity.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/activity.php	(revision 10410)
@@ -0,0 +1,68 @@
+<?php
+/**
+ * @group activity
+ * @group routing
+ */
+class BP_Tests_Routing_Activity extends BP_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' => 'subscriber' ) ) );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	function test_activity_directory() {
+		$this->go_to( bp_get_activity_directory_permalink() );
+		$this->assertEquals( bp_get_activity_root_slug(), bp_current_component() );
+	}
+
+	/**
+	 * Can't test using bp_activity_get_permalink(); see bp_activity_action_permalink_router().
+	 */
+	function test_activity_permalink() {
+		$a = $this->factory->activity->create();
+		$activity = $this->factory->activity->get_object_by_id( $a );
+
+		$url = bp_core_get_user_domain( $activity->user_id ) . bp_get_activity_slug() . '/' . $activity->id . '/';
+		$this->go_to( $url );
+		$this->assertTrue( bp_is_single_activity() );
+	}
+
+	function test_member_activity() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_activity_slug() );
+		$this->assertTrue( bp_is_user_activity() );
+	}
+
+	function test_member_activity_mentions() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_activity_slug() . '/mentions'  );
+		$this->assertTrue( bp_is_user_activity() );
+	}
+
+	function test_member_activity_favourites() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_activity_slug() . '/favorites'  );
+		$this->assertTrue( bp_is_user_activity() );
+	}
+
+	/**
+	 * @group friends
+	 */
+	function test_member_activity_friends() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_activity_slug() . '/' . bp_get_friends_slug() );
+		$this->assertTrue( bp_is_user_friends_activity() );
+	}
+
+	/**
+	 * @group groups
+	 */
+	function test_member_activity_groups() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_activity_slug() . '/' . bp_get_groups_slug() );
+		$this->assertTrue( bp_is_user_groups_activity() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/anonymous.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/anonymous.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/anonymous.php	(revision 10410)
@@ -0,0 +1,15 @@
+<?php
+/**
+ * @group routing
+ */
+class BP_Tests_Routing_Anonymous extends BP_UnitTestCase {
+	function test_wordpress_page() {
+		$this->go_to( '/' );
+		$this->assertEmpty( bp_current_component() );
+	}
+
+	function test_nav_menu() {
+		$this->go_to( '/' );
+		$this->assertEmpty( buddypress()->bp_nav );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/core.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/core.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/core.php	(revision 10410)
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @group core
+ * @group routing
+ */
+class BP_Tests_Routing_Core extends BP_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' => 'subscriber' ) ) );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+	function test_wordpress_page() {
+		$this->go_to( '/' );
+		$this->assertEmpty( bp_current_component() );
+	}
+
+	function test_nav_menu() {
+		$this->go_to( '/' );
+		$this->assertArrayHasKey( 'activity', buddypress()->bp_nav );
+		$this->assertArrayHasKey( 'profile',  buddypress()->bp_nav );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/friends.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/friends.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/friends.php	(revision 10410)
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @group friends
+ * @group routing
+ */
+class BP_Tests_Routing_Friends extends BP_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' => 'subscriber' ) ) );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	function test_member_friends() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_friends_slug() );
+		$this->assertTrue( bp_is_user_friends() );
+	}
+
+	function test_member_friends_requests() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_friends_slug()  . '/requests' );
+		$this->assertTrue( bp_is_user_friend_requests() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/groups.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/groups.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/groups.php	(revision 10410)
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @group groups
+ * @group routing
+ */
+class BP_Tests_Routing_Groups extends BP_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' => 'subscriber' ) ) );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	function test_member_groups() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_groups_slug() );
+		$this->assertTrue( bp_is_user_groups() );
+	}
+
+	function test_member_groups_invitations() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_groups_slug() . '/invites' );
+		$this->assertTrue( bp_is_user_groups() && bp_is_current_action( 'invites' ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/members.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/members.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/members.php	(revision 10410)
@@ -0,0 +1,111 @@
+<?php
+/**
+ * @group members
+ * @group routing
+ */
+class BP_Tests_Routing_Members extends BP_UnitTestCase {
+	protected $old_current_user = 0;
+
+	public function setUp() {
+		parent::setUp();
+
+		buddypress()->members->types = array();
+		$this->old_current_user = get_current_user_id();
+		$this->set_current_user( $this->factory->user->create( array( 'user_login' => 'paulgibbs', 'role' => 'subscriber' ) ) );
+	}
+
+	public function tearDown() {
+		$this->set_current_user( $this->old_current_user );
+		parent::tearDown();
+	}
+
+	function test_members_directory() {
+		$this->go_to( bp_get_members_directory_permalink() );
+		$this->assertEquals( bp_get_members_root_slug(), bp_current_component() );
+	}
+
+	function test_member_permalink() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) );
+		$this->assertTrue( bp_is_my_profile() );
+	}
+
+	/**
+	 * @ticket BP6475
+	 */
+	public function test_member_directory_when_nested_under_wp_page() {
+		$p = $this->factory->post->create( array(
+			'post_type' => 'page',
+			'post_name' => 'foo',
+		) );
+
+		$members_page = get_page_by_path( 'members' );
+
+		wp_update_post( array(
+			'ID' => $members_page->ID,
+			'post_parent' => $p,
+		) );
+
+		$members_page_permalink = bp_get_root_domain() . '/foo/members/';
+		$this->go_to( $members_page_permalink );
+
+		$this->assertTrue( bp_is_members_component() );
+		$this->assertEquals( '', bp_current_action() );
+	}
+
+	/**
+	 * @ticket BP6286
+	 * @group member_types
+	 */
+	public function test_member_directory_with_member_type() {
+		bp_register_member_type( 'foo' );
+		$this->go_to( bp_get_members_directory_permalink() . 'type/foo/' );
+		$this->assertTrue( bp_is_members_component() );
+	}
+
+	/**
+	 * @ticket BP6286
+	 * @group member_types
+	 */
+	public function test_member_directory_with_member_type_should_obey_filtered_type_slug() {
+		bp_register_member_type( 'foo' );
+
+		add_filter( 'bp_members_member_type_base', array( $this, 'filter_member_type_base' ) );
+		$this->go_to( bp_get_members_directory_permalink() . 'buddypress-member-type/foo/' );
+		remove_filter( 'bp_members_member_type_base', array( $this, 'filter_member_type_base' ) );
+		$this->assertTrue( bp_is_members_component() );
+	}
+
+	public function filter_member_type_base( $base ) {
+		return 'buddypress-member-type';
+	}
+
+	/**
+	 * @ticket BP6286
+	 * @group member_types
+	 */
+	public function test_member_directory_with_member_type_that_has_custom_directory_slug() {
+		bp_register_member_type( 'foo', array( 'has_directory' => 'foos' ) );
+		$this->go_to( bp_get_members_directory_permalink() . 'type/foos/' );
+		$this->assertTrue( bp_is_members_component() );
+	}
+
+	/**
+	 * @ticket BP6286
+	 * @group member_types
+	 */
+	public function test_member_directory_with_member_type_should_be_overridden_by_member_with_same_nicename() {
+		$u = $this->factory->user->create( array( 'user_nicename' => 'foo' ) );
+		bp_register_member_type( 'foo' );
+		$this->go_to( bp_get_members_directory_permalink() . 'type/foo/' );
+	}
+
+	/**
+	 * @ticket BP6286
+	 * @group member_types
+	 */
+	public function test_member_directory_should_404_for_member_types_that_have_no_directory() {
+		bp_register_member_type( 'foo', array( 'has_directory' => false ) );
+		$this->go_to( bp_get_members_directory_permalink() . 'type/foo/' );
+		$this->assertTrue( is_404() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/messages.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/messages.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/messages.php	(revision 10410)
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @group messages
+ * @group routing
+ */
+class BP_Tests_Routing_Messages extends BP_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' => 'subscriber' ) ) );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	function test_member_messages() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_messages_slug() );
+		$this->assertTrue( bp_is_messages_inbox() );
+	}
+
+	function test_member_messages_sentbox() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_messages_slug() . '/sentbox' );
+		$this->assertTrue( bp_is_messages_sentbox() );
+	}
+
+	function test_member_messages_compose() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_messages_slug() . '/compose' );
+		$this->assertTrue( bp_is_messages_compose_screen() );
+	}
+
+	function test_member_messages_notices() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_messages_slug() . '/notices' );
+		$this->assertTrue( bp_is_notices() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/root-profiles.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/root-profiles.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/root-profiles.php	(revision 10410)
@@ -0,0 +1,78 @@
+<?php
+/**
+ * @group members
+ * @group routing
+ * @group root_profiles
+ */
+class BP_Tests_Routing_Members_Root_Profiles extends BP_UnitTestCase {
+	protected $old_current_user = 0;
+	protected $u;
+
+	public function setUp() {
+		parent::setUp();
+
+		add_filter( 'bp_core_enable_root_profiles', '__return_true' );
+
+		$this->old_current_user = get_current_user_id();
+		$uid = $this->factory->user->create( array(
+			'user_login' => 'boone',
+			'user_nicename' => 'boone',
+		) );
+		$this->u = new WP_User( $uid );
+		$this->set_current_user( $uid );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+		remove_filter( 'bp_core_enable_root_profiles', '__return_true' );
+	}
+
+	public function test_members_directory() {
+		$this->go_to( home_url( bp_get_members_root_slug() ) );
+		$this->assertEquals( bp_get_members_root_slug(), bp_current_component() );
+	}
+
+	public function test_member_permalink() {
+		$domain = home_url( $this->u->user_nicename );
+		$this->go_to( $domain );
+
+		$this->assertTrue( bp_is_user() );
+		$this->assertTrue( bp_is_my_profile() );
+		$this->assertEquals( $this->u->ID, bp_displayed_user_id() );
+	}
+
+	/**
+	 * @ticket BP6475
+	 */
+	public function test_member_permalink_when_members_page_is_nested_under_wp_page() {
+		$p = $this->factory->post->create( array(
+			'post_type' => 'page',
+			'post_name' => 'foo',
+		) );
+
+		$members_page = get_page_by_path( 'members' );
+		wp_update_post( array(
+			'ID' => $members_page->ID,
+			'post_parent' => $p,
+		) );
+
+		$domain = home_url( $this->u->user_nicename );
+		$this->go_to( $domain );
+
+		$this->assertTrue( bp_is_user() );
+		$this->assertTrue( bp_is_my_profile() );
+		$this->assertEquals( $this->u->ID, bp_displayed_user_id() );
+	}
+
+	public function test_member_activity_page() {
+		$url = home_url( $this->u->user_nicename ) . '/' . bp_get_activity_slug();
+		$this->go_to( $url );
+
+		$this->assertTrue( bp_is_user() );
+		$this->assertTrue( bp_is_my_profile() );
+		$this->assertEquals( $this->u->ID, bp_displayed_user_id() );
+
+		$this->assertTrue( bp_is_activity_component() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/settings.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/settings.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/settings.php	(revision 10410)
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @group settings
+ * @group routing
+ */
+class BP_Tests_Routing_Settings extends BP_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' => 'subscriber' ) ) );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	function test_member_settings() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_settings_slug() );
+		$this->assertTrue( bp_is_user_settings_general() );
+	}
+
+	function test_member_settings_notifications() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_settings_slug() . '/notifications' );
+		$this->assertTrue( bp_is_user_settings_notifications() );
+	}
+
+	// @todo How best to test this?
+	/*function bp_is_user_settings_account_capbilities() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_settings_slug() . '/capabilities' );
+	}*/
+
+	function bp_is_user_settings_account_delete() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_settings_slug() . '/delete-account' );
+		$this->assertTrue( bp_is_user_settings_account_delete() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/url.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/url.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/url.php	(revision 10410)
@@ -0,0 +1,52 @@
+<?php
+/**
+ * @group url
+ */
+class BP_Tests_URL extends BP_UnitTestCase {
+	function test_bp_core_ajax_url() {
+		$forced = force_ssl_admin();
+
+		// (1) HTTPS off
+		force_ssl_admin( false );
+		$_SERVER['HTTPS'] = 'off';
+
+		// (1a) Front-end
+		$this->go_to( '/' );
+		$this->assertEquals( bp_core_ajax_url(), get_site_url( bp_get_root_blog_id(), '/wp-admin/admin-ajax.php', 'http' ) );
+
+		// (1b) Dashboard
+		$this->go_to( '/wp-admin' );
+		$this->assertEquals( bp_core_ajax_url(), get_site_url( bp_get_root_blog_id(), '/wp-admin/admin-ajax.php', 'http' ) );
+
+		// (2) FORCE_SSL_ADMIN
+		force_ssl_admin( true );
+
+		// (2a) Front-end
+		$this->go_to( '/' );
+		$this->assertEquals( bp_core_ajax_url(), get_site_url( bp_get_root_blog_id(), '/wp-admin/admin-ajax.php', 'http' ) );
+
+		// (2b) Dashboard
+		$this->go_to( '/wp-admin' );
+		$this->assertEquals( bp_core_ajax_url(), get_site_url( bp_get_root_blog_id(), '/wp-admin/admin-ajax.php', 'https' ) );
+
+		force_ssl_admin( $forced );
+
+		// (3) Multisite, root blog other than 1
+		if ( is_multisite() ) {
+			$original_root_blog = bp_get_root_blog_id();
+			$blog_id = $this->factory->blog->create( array(
+				'path' => '/path' . rand() . time() . '/',
+			) );
+			buddypress()->root_blog_id = $blog_id;
+
+			$blog_url = get_blog_option( $blog_id, 'siteurl' );
+
+			$this->go_to( trailingslashit( $blog_url ) );
+			buddypress()->root_blog_id = $original_root_blog;
+			$ajax_url = bp_core_ajax_url();
+			$this->go_to( '/' );
+
+			$this->assertEquals( $blog_url . '/wp-admin/admin-ajax.php', $ajax_url );
+		}
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/routing/xprofile.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/routing/xprofile.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/routing/xprofile.php	(revision 10410)
@@ -0,0 +1,35 @@
+<?php
+/**
+ * @group xprofile
+ * @group routing
+ */
+class BP_Tests_Routing_XProfile extends BP_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' => 'subscriber' ) ) );
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->set_current_user( $this->old_current_user );
+	}
+
+	function test_member_profile() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_profile_slug() );
+		$this->assertTrue( bp_is_user_profile() );
+	}
+
+	function test_member_profile_edit() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_profile_slug() . '/edit' );
+		$this->assertTrue( bp_is_user_profile_edit() );
+	}
+
+	function test_member_profile_change_avatar() {
+		$this->go_to( bp_core_get_user_domain( bp_loggedin_user_id() ) . bp_get_profile_slug() . '/change-avatar' );
+		$this->assertTrue( bp_is_user_change_avatar() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/settings/bpSettingsSanitizeNotificationSettings.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/settings/bpSettingsSanitizeNotificationSettings.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/settings/bpSettingsSanitizeNotificationSettings.php	(revision 10410)
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @group settings
+ */
+class BP_Tests_Settings_BpSettingsSanitizeNotificationSettings extends BP_UnitTestCase {
+	protected $core_notification_settings = array(
+		'notification_messages_new_message',
+		'notification_activity_new_mention',
+		'notification_activity_new_reply',
+		'notification_groups_invite',
+		'notification_groups_group_updated',
+		'notification_groups_admin_promotion',
+		'notification_groups_membership_request',
+		'notification_friends_friendship_request',
+		'notification_friends_friendship_accepted',
+	);
+
+	public function test_should_ignore_setting_not_added_via_bp_notification_settings_callback() {
+		$settings = array(
+			'foo' => 'yes',
+		);
+
+		$sanitized = bp_settings_sanitize_notification_settings( $settings );
+
+		$this->assertArrayNotHasKey( 'foo', $sanitized );
+	}
+
+	public function test_should_accept_setting_added_via_bp_notification_settings_callback() {
+		$settings = array(
+			'foo' => 'yes',
+		);
+
+		add_action( 'bp_notification_settings', array( $this, 'add_custom_notification_setting' ) );
+		$sanitized = bp_settings_sanitize_notification_settings( $settings );
+		remove_action( 'bp_notification_settings', array( $this, 'add_custom_notification_setting' ) );
+
+		$this->assertArrayHasKey( 'foo', $sanitized );
+		$this->assertSame( 'yes', $sanitized['foo'] );
+	}
+
+	public function test_should_sanitize_invalid_values_to_no_for_core_settings() {
+
+		$settings = array();
+		foreach ( $this->core_notification_settings as $key ) {
+			$settings[ $key ] = 'foo';
+		}
+
+		add_action( 'bp_notification_settings', array( $this, 'add_core_notification_settings' ) );
+		$sanitized = bp_settings_sanitize_notification_settings( $settings );
+		remove_action( 'bp_notification_settings', array( $this, 'add_core_notification_settings' ) );
+
+		$expected = array();
+		foreach ( $this->core_notification_settings as $key ) {
+			$expected[ $key ] = 'no';
+		}
+
+		$this->assertEqualSets( $expected, $sanitized );
+	}
+
+	public function test_should_not_sanitize_values_for_custom_setting() {
+		$settings = array(
+			'foo' => 'bar',
+		);
+
+		add_action( 'bp_notification_settings', array( $this, 'add_custom_notification_setting' ) );
+		$sanitized = bp_settings_sanitize_notification_settings( $settings );
+		remove_action( 'bp_notification_settings', array( $this, 'add_custom_notification_setting' ) );
+
+		$this->assertArrayHasKey( 'foo', $sanitized );
+		$this->assertSame( 'bar', $sanitized['foo'] );
+	}
+
+	public function add_custom_notification_setting() {
+		echo '<input name="notifications[foo]" value="yes" />';
+	}
+
+	public function add_core_notification_settings() {
+		foreach ( $this->core_notification_settings as $key ) {
+			echo '<input name="notifications[' . $key . ']" value="yes" />';
+		}
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/xprofile/BP_XProfile_Field/member_types.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/xprofile/BP_XProfile_Field/member_types.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/xprofile/BP_XProfile_Field/member_types.php	(revision 10410)
@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * @group member_types
+ * @group xprofile
+ * @ticket BP5192
+ */
+class BP_Tests_XProfile_BpXprofileField_MemberTypes extends BP_UnitTestCase {
+	protected $field_group_id;
+	protected $field_id;
+	protected $field;
+
+	public function setUp() {
+		parent::setUp();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+
+		$this->field_group_id = $this->factory->xprofile_group->create();
+		$this->field_id = $this->factory->xprofile_field->create( array( 'field_group_id' => $this->field_group_id ) );
+		$this->field = new BP_XProfile_Field( $this->field_id );
+	}
+
+	public function tearDown() {
+		buddypress()->members->types = array();
+		parent::tearDown();
+	}
+
+	public function test_get_single_member_type() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		$this->assertEqualSets( array( 'foo' ), $this->field->get_member_types() );
+	}
+
+	public function test_get_multiple_member_types() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'bar' );
+		$this->assertEqualSets( array( 'foo', 'bar' ), $this->field->get_member_types() );
+	}
+
+	public function test_invalid_member_types_should_not_be_returned() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'phony' );
+		$this->assertEquals( array( 'foo' ), $this->field->get_member_types() );
+	}
+
+	public function test_when_no_stored_types_are_found_all_registered_member_types_as_well_as_null_type_should_be_returned() {
+		$this->assertEqualSets( array( 'null', 'foo', 'bar' ), $this->field->get_member_types() );
+	}
+
+	public function test__none_meta_should_result_in_empty_array() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', '_none' );
+		$this->assertEquals( array(), $this->field->get_member_types() );
+	}
+
+	public function test__none_meta_should_override_other_values() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', '_none' );
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		$this->assertEquals( array(), $this->field->get_member_types() );
+	}
+
+	public function test_set_should_not_append_by_default() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		$this->assertEquals( array( 'bar' ), $this->field->set_member_types( array( 'bar' ) ) );
+	}
+
+	public function test_set_should_not_append_when_append_is_set_to_false() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		$this->assertEquals( array( 'bar' ), $this->field->set_member_types( array( 'bar', false ) ) );
+	}
+
+	public function test_set_should_append_when_append_is_set_to_true() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		$this->assertEqualSets( array( 'foo', 'bar' ), $this->field->set_member_types( array( 'bar' ), true ) );
+	}
+
+	public function test_set_empty_array_with_append_true_should_have_no_effect_on_saved_types() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		$this->assertEqualSets( array( 'foo' ), $this->field->set_member_types( array(), true ) );
+	}
+
+	public function test_set_empty_array_with_append_false_should_result_in_field_being_associated_with_no_member_types() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		$this->assertEqualSets( array(), $this->field->set_member_types( array() ) );
+	}
+
+	public function test_set_should_interpret_null_flag_properly() {
+		$this->assertEqualSets( array( 'null' ), $this->field->set_member_types( array( 'null' ) ) );
+	}
+
+	public function test_set_all_types_plus_null_should_result_in_nothing_stored_in_db() {
+		$types = array( 'null', 'foo', 'bar' );
+		$this->assertEqualSets( $types, $this->field->set_member_types( $types ) );
+
+		$types_db = bp_xprofile_get_meta( $this->field_id, 'field', 'member_type', false );
+		$this->assertEqualSets( array(), $types_db );
+	}
+
+	public function test_get_fields_for_member_type_should_get_field_with_explicit_member_type() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		$found = BP_XProfile_Field::get_fields_for_member_type( 'foo' );
+		$this->assertEqualSets( array( 1, $this->field_id ), array_keys( $found ) );
+	}
+
+	public function test_get_fields_for_member_type_should_ignore_field_that_applies_to_no_member_types() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', '_none' );
+		$found = BP_XProfile_Field::get_fields_for_member_type( 'foo' );
+		$this->assertEqualSets( array( 1 ), array_keys( $found ) );
+	}
+
+	public function test_get_fields_for_member_type_should_ignore_field_that_applies_to_different_member_types() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'bar' );
+		$found = BP_XProfile_Field::get_fields_for_member_type( 'foo' );
+		$this->assertEqualSets( array( 1 ), array_keys( $found ) );
+	}
+
+	public function test_get_fields_for_member_type_should_include_fields_with_no_member_type_restrictions() {
+		// A field with no member_type metadata applies to all member types.
+		$found = BP_XProfile_Field::get_fields_for_member_type( 'foo' );
+		$this->assertEqualSets( array( 1, $this->field_id ), array_keys( $found ) );
+	}
+
+	public function test_passing_member_type_any_to_get_fields_for_member_type_should_return_unrestricted_fields() {
+		$f2 = $this->factory->xprofile_field->create( array( 'field_group_id' => $this->field_group_id ) );
+
+		$field_2 = new BP_XProfile_Field( $f2 );
+		$field_2->set_member_types( array( 'foo' ) );
+
+		$found = BP_XProfile_Field::get_fields_for_member_type( 'any' );
+		$this->assertEqualSets( array( 1, $this->field_id ), array_keys( $found ) );
+	}
+
+	public function test_passing_empty_member_type_to_get_fields_for_member_type_should_return_unrestricted_fields() {
+		$f2 = $this->factory->xprofile_field->create( array( 'field_group_id' => $this->field_group_id ) );
+
+		$field_2 = new BP_XProfile_Field( $f2 );
+		$field_2->set_member_types( array( 'foo' ) );
+
+		$found = BP_XProfile_Field::get_fields_for_member_type( '' );
+		$this->assertEqualSets( array( 1, $this->field_id ), array_keys( $found ) );
+	}
+
+	public function test_passing_member_type_none_to_get_fields_for_member_type_should_return_fields_unavailable_to_all_member_types() {
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', '_none' );
+		$found = BP_XProfile_Field::get_fields_for_member_type( '_none' );
+		$this->assertEqualSets( array( $this->field_id ), array_keys( $found ) );
+	}
+
+	public function test_get_fields_for_member_type_should_accept_an_array_of_member_types() {
+		$f2 = $this->factory->xprofile_field->create( array( 'field_group_id' => $this->field_group_id ) );
+		bp_xprofile_add_meta( $this->field_id, 'field', 'member_type', 'foo' );
+		bp_xprofile_add_meta( $f2, 'field', 'member_type', 'bar' );
+
+		$found = BP_XProfile_Field::get_fields_for_member_type( array( 'foo', 'bar' ) );
+		$this->assertEqualSets( array( 1, $this->field_id, $f2 ), array_keys( $found ) );
+	}
+
+	/**
+	 * @group cache
+	 */
+	public function test_get_fields_for_member_type_should_fetch_restricted_fields_from_cache() {
+		global $wpdb;
+
+		$this->field->set_member_types( array( 'foo' ) );
+
+		// Prime cache.
+		BP_XProfile_Field::get_fields_for_member_type( 'foo' );
+
+		$num_queries = $wpdb->num_queries;
+
+		BP_XProfile_Field::get_fields_for_member_type( 'foo' );
+
+		// get_fields_for_member_type() always does at least one query.
+		$num_queries++;
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+	}
+
+	/**
+	 * @group cache
+	 */
+	public function test_get_fields_for_member_type_should_skip_cache_after_a_fields_member_type_is_modified() {
+		global $wpdb;
+
+		$this->field->set_member_types( array( 'foo' ) );
+
+		// Prime cache.
+		BP_XProfile_Field::get_fields_for_member_type( 'foo' );
+
+		$num_queries = $wpdb->num_queries;
+
+		$this->field->set_member_types( array( 'none' ) );
+
+		$found = BP_XProfile_Field::get_fields_for_member_type( 'foo' );
+
+		$this->assertTrue( $num_queries + 2 <= $wpdb->num_queries );
+		$this->assertEqualSets( array( 1 ), array_keys( $found ) );
+	}
+
+	public function test_get_member_type_label_order() {
+		// Should be alphabetical, with the null option last.
+		bp_register_member_type( 'aaa', array(
+			'labels' => array(
+				'name' => 'AAA',
+			),
+		) );
+		bp_register_member_type( 'zzz', array(
+			'labels' => array(
+				'name' => 'ZZZ',
+			),
+		) );
+
+		$this->field->set_member_types( array( 'aaa', 'zzz', 'null' ) );
+
+		$expected = '(Member types: AAA, ZZZ, Users with no member type)';
+		$this->assertSame( $expected, $this->field->get_member_type_label() );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/xprofile/activity.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/xprofile/activity.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/xprofile/activity.php	(revision 10410)
@@ -0,0 +1,321 @@
+<?php
+
+/**
+ * @group xprofile
+ * @group activity
+ */
+class BP_Tests_XProfile_Activity extends BP_UnitTestCase {
+	protected $updated_profile_data = array();
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_with_errors() {
+		$d = $this->setup_updated_profile_data();
+
+		// Fake new/old values to ensure a change
+		$old_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo2',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertFalse( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f'] ), true ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_throttled() {
+		$d = $this->setup_updated_profile_data();
+
+		$time = time();
+		$prev_time = date( 'Y-m-d H:i:s', $time - ( 119 * 60 ) );
+		$now_time = date( 'Y-m-d H:i:s', $time );
+
+		$this->factory->activity->create( array(
+			'user_id' => $d['u'],
+			'component' => buddypress()->profile->id,
+			'type' => 'updated_profile',
+			'date_recorded' => $prev_time,
+		) );
+
+		// Fake new/old values to ensure a change
+		$old_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo2',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertFalse( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f'] ), false ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_outside_of_throttle() {
+		$d = $this->setup_updated_profile_data();
+
+		$time = strtotime( bp_core_current_time() );
+		$prev_time = date( 'Y-m-d H:i:s', $time - ( 121 * 60 ) );
+		$now_time = date( 'Y-m-d H:i:s', $time );
+
+		$this->factory->activity->create( array(
+			'user_id' => $d['u'],
+			'component' => buddypress()->profile->id,
+			'type' => 'updated_profile',
+			'recorded_time' => $prev_time,
+		) );
+
+		// Fake new/old values to ensure a change
+		$old_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo2',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertTrue( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f'] ), false, $old_values, $new_values ) );
+
+		$existing = bp_activity_get( array(
+			'max' => 1,
+			'filter' => array(
+				'user_id' => $d['u'],
+				'object' => buddypress()->profile->id,
+				'action' => 'updated_profile',
+			),
+			'count_total' => 'count_query',
+		) );
+
+		$this->assertEquals( 1, $existing['total'] );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_no_existing_activity() {
+		$d = $this->setup_updated_profile_data();
+
+		// Fake new/old values to ensure a change
+		$old_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo2',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertTrue( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f'] ), false, $old_values, $new_values ) );
+
+		$existing = bp_activity_get( array(
+			'max' => 1,
+			'filter' => array(
+				'user_id' => $d['u'],
+				'object' => buddypress()->profile->id,
+				'action' => 'updated_profile',
+			),
+			'count_total' => 'count_query',
+		) );
+
+		$this->assertEquals( 1, $existing['total'] );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_no_changes() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertFalse( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f'] ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_no_public_changes() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'loggedin',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'bar',
+				'visibility' => 'loggedin',
+			),
+		);
+
+		$this->assertFalse( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f'] ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_public_changed_to_private() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'bar',
+				'visibility' => 'loggedin',
+			),
+		);
+
+		$this->assertFalse( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f'] ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_private_changed_to_public() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'loggedin',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertTrue( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f'] ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_field_didnt_previously_exist() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array();
+		$new_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'bar',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertTrue( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f'] ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group bp_xprofile_updated_profile_activity
+	 */
+	public function test_bp_xprofile_updated_profile_activity_public_changes() {
+		$d = $this->setup_updated_profile_data();
+
+		$old_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'foo',
+				'visibility' => 'public',
+			),
+		);
+		$new_values = array(
+			$this->updated_profile_data['f'] => array(
+				'value'      => 'bar',
+				'visibility' => 'public',
+			),
+		);
+
+		$this->assertTrue( bp_xprofile_updated_profile_activity( $d['u'], array( $d['f'] ), false, $old_values, $new_values ) );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_xprofile_format_activity_action_new_avatar
+	 */
+	public function test_bp_xprofile_format_activity_action_new_avatar() {
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create( array(
+			'component' => 'profile',
+			'type' => 'new_avatar',
+			'user_id' => $u,
+		) );
+
+		$expected = sprintf( __( '%s changed their profile picture', 'buddypress' ), bp_core_get_userlink( $u ) );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	/**
+	 * @group activity_action
+	 * @group bp_xprofile_format_activity_action_updated_profile
+	 */
+	public function test_bp_xprofile_format_activity_action_updated_profile() {
+		$u = $this->factory->user->create();
+		$a = $this->factory->activity->create( array(
+			'component' => buddypress()->profile->id,
+			'type' => 'updated_profile',
+			'user_id' => $u,
+		) );
+
+		$expected = sprintf( __( "%s's profile was updated", 'buddypress' ), '<a href="' . bp_core_get_user_domain( $u ) . bp_get_profile_slug() . '/">' . bp_core_get_user_displayname( $u ) . '</a>' );
+
+		$a_obj = new BP_Activity_Activity( $a );
+
+		$this->assertSame( $expected, $a_obj->action );
+	}
+
+	protected function setup_updated_profile_data() {
+		$this->updated_profile_data['u'] = $this->factory->user->create();
+		$this->updated_profile_data['g'] = $this->factory->xprofile_group->create();
+		$this->updated_profile_data['f'] = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $this->updated_profile_data['g'],
+		) );
+
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/xprofile/cache.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/xprofile/cache.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/xprofile/cache.php	(revision 10410)
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @group xprofile
+ * @group cache
+ */
+class BP_Tests_XProfile_Cache extends BP_UnitTestCase {
+	/**
+	 * @group bp_xprofile_update_meta_cache
+	 */
+	public function test_bp_xprofile_update_meta_cache() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		$d = new BP_XProfile_ProfileData( $f, $u );
+		$d->user_id = $u;
+		$d->field_id = $f;
+		$d->value = 'foo';
+		$d->last_updated = bp_core_current_time();
+		$d->save();
+
+		bp_xprofile_add_meta( $g, 'group', 'group_foo', 'group_bar' );
+		bp_xprofile_add_meta( $f, 'field', 'field_foo', 'field_bar' );
+		bp_xprofile_add_meta( $d->id, 'data', 'data_foo', 'data_bar' );
+
+		// prime cache
+		bp_xprofile_update_meta_cache( array(
+			'group' => array( $g ),
+			'field' => array( $f ),
+			'data' => array( $d->id ),
+		) );
+
+		$g_expected = array(
+			'group_foo' => array(
+				'group_bar',
+			),
+		);
+
+		$this->assertSame( $g_expected, wp_cache_get( $g, 'xprofile_group_meta' ) );
+
+		$f_expected = array(
+			'field_foo' => array(
+				'field_bar',
+			),
+		);
+
+		$this->assertSame( $f_expected, wp_cache_get( $f, 'xprofile_field_meta' ) );
+
+		$d_expected = array(
+			'data_foo' => array(
+				'data_bar',
+			),
+		);
+
+		$this->assertSame( $d_expected, wp_cache_get( $d->id, 'xprofile_data_meta' ) );
+	}
+
+	/**
+	 * @group bp_xprofile_update_meta_cache
+	 * @group bp_has_profile
+	 */
+	public function test_bp_has_profile_meta_cache() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		$d = new BP_XProfile_ProfileData( $f, $u );
+		$d->user_id = $u;
+		$d->field_id = $f;
+		$d->value = 'foo';
+		$d->last_updated = bp_core_current_time();
+		$d->save();
+
+		bp_xprofile_add_meta( $g, 'group', 'group_foo', 'group_bar' );
+		bp_xprofile_add_meta( $f, 'field', 'field_foo', 'field_bar' );
+		bp_xprofile_add_meta( $d->id, 'data', 'data_foo', 'data_bar' );
+
+		// prime cache
+		bp_has_profile( array(
+			'user_id' => $u,
+			'profile_group_id' => $g,
+		) );
+
+		$g_expected = array(
+			'group_foo' => array(
+				'group_bar',
+			),
+		);
+
+		$this->assertSame( $g_expected, wp_cache_get( $g, 'xprofile_group_meta' ) );
+
+		$f_expected = array(
+			'field_foo' => array(
+				'field_bar',
+			),
+		);
+
+		$this->assertSame( $f_expected, wp_cache_get( $f, 'xprofile_field_meta' ) );
+
+		$d_expected = array(
+			'data_foo' => array(
+				'data_bar',
+			),
+		);
+
+		$this->assertSame( $d_expected, wp_cache_get( $d->id, 'xprofile_data_meta' ) );
+	}
+
+	/**
+	 * @group bp_xprofile_update_meta_cache
+	 * @group bp_has_profile
+	 */
+	public function test_bp_has_profile_meta_cache_update_meta_cache_false() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		$d = new BP_XProfile_ProfileData( $f, $u );
+		$d->user_id = $u;
+		$d->field_id = $f;
+		$d->value = 'foo';
+		$d->last_updated = bp_core_current_time();
+		$d->save();
+
+		bp_xprofile_add_meta( $g, 'group', 'group_foo', 'group_bar' );
+		bp_xprofile_add_meta( $f, 'field', 'field_foo', 'field_bar' );
+		bp_xprofile_add_meta( $d->id, 'data', 'data_foo', 'data_bar' );
+
+		// prime cache
+		bp_has_profile( array(
+			'user_id' => $u,
+			'profile_group_id' => $g,
+			'update_meta_cache' => false,
+		) );
+
+		$this->assertFalse( wp_cache_get( $g, 'xprofile_group_meta' ) );
+		$this->assertFalse( wp_cache_get( $f, 'xprofile_field_meta' ) );
+		$this->assertFalse( wp_cache_get( $d->id, 'xprofile_data_meta' ) );
+	}
+
+	/**
+	 * @ticket BP6638
+	 */
+	public function test_field_cache_should_be_invalidated_on_save() {
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'name' => 'Foo',
+		) );
+
+		$field = xprofile_get_field( $f );
+		$this->assertSame( 'Foo', $field->name );
+
+		$field->name = 'Bar';
+		$this->assertNotEmpty( $field->save() );
+
+		$field_2 = xprofile_get_field( $f );
+		$this->assertSame( 'Bar', $field_2->name );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-field-type.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-field-type.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-field-type.php	(revision 10410)
@@ -0,0 +1,179 @@
+<?php
+/**
+ * @group xprofile
+ * @group BP_XProfile_Field_Type
+ */
+class BP_Tests_XProfile_Field_Type extends BP_UnitTestCase {
+	public function test_unregistered_field_type_returns_textbox() {
+		$field = bp_xprofile_create_field_type( 'fakeyfield' );
+		$this->assertEquals( get_class( $field ), 'BP_XProfile_Field_Type_Placeholder' );
+	}
+
+	public function test_textbox_validate_empty_string() {
+		$field = bp_xprofile_create_field_type( 'textbox' );
+		$this->assertTrue( $field->is_valid( '' ) );
+	}
+
+	public function test_textbox_validate_string() {
+		$field = bp_xprofile_create_field_type( 'textbox' );
+		$this->assertTrue( $field->is_valid( 'my 117th input string!' ) );
+	}
+
+	public function test_textbox_validate_integer() {
+		$field = bp_xprofile_create_field_type( 'textbox' );
+		$this->assertTrue( $field->is_valid( 123 ) );
+	}
+
+	public function test_textbox_validate_whitelisted_string() {
+		$field = bp_xprofile_create_field_type( 'textbox' );
+
+		$this->assertTrue( $field->is_valid( 'a string' ) );
+		$this->assertFalse( $field->set_whitelist_values( 'pizza' )->is_valid( 'pasta' ) );
+		$this->assertTrue( $field->is_valid( 'pizza' ) );
+	}
+
+	public function test_multiselectbox_validate_whitelisted_array() {
+		$field = bp_xprofile_create_field_type( 'multiselectbox' );
+		$field->set_whitelist_values( array( 'cheese', 'pepporoni' ) );
+
+		$this->assertTrue( $field->is_valid( array( 'cheese', 'pepporoni' ) ) );
+		$this->assertTrue( $field->is_valid( array( 'cheese' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'cheese', 'pepporoni', 'pinapple' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'pinapple' ) ) );
+	}
+
+	public function test_multiselectbox_validate_null_value() {
+		$field = bp_xprofile_create_field_type( 'multiselectbox' );
+		$field->set_whitelist_values( array( 'cheese', 'pepporoni' ) );
+
+		$this->assertFalse( $field->is_valid( array( '' ) ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+		$this->assertTrue( $field->is_valid( array() ) );
+	}
+
+	public function test_datebox_do_not_validate_string() {
+		$field = bp_xprofile_create_field_type( 'datebox' );
+		$this->assertFalse( $field->is_valid( 'datebox fields only accept strings in: Y-m-d 00:00:00' ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+	}
+
+	public function test_datebox_do_not_validate_integer() {
+		$field = bp_xprofile_create_field_type( 'datebox' );
+		$this->assertFalse( $field->is_valid( 221213 ) );
+	}
+
+	public function test_datebox_validate_date() {
+		$field = bp_xprofile_create_field_type( 'datebox' );
+		$this->assertTrue( $field->is_valid( '2013-12-22 00:00:00' ) );
+	}
+
+	public function test_datebox_do_not_validate_date_with_timestamp() {
+		$field = bp_xprofile_create_field_type( 'datebox' );
+		$this->assertFalse( $field->is_valid( '2013-12-22 19:11:30' ) );
+		$this->assertFalse( $field->is_valid( '2013-12-22' ) );
+	}
+
+	public function test_number_do_not_validate_string() {
+		$field = bp_xprofile_create_field_type( 'number' );
+		$this->assertFalse( $field->is_valid( 'telephone fields only accept integers' ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+	}
+
+	public function test_number_validate_positive_integer() {
+		$field = bp_xprofile_create_field_type( 'number' );
+		$this->assertTrue( $field->is_valid( 12345678901 ) );
+		$this->assertTrue( $field->is_valid( '12345678901' ) );
+	}
+
+	public function test_number_validate_negative_integer() {
+		$field = bp_xprofile_create_field_type( 'number' );
+		$this->assertTrue( $field->is_valid( -987654321 ) );
+		$this->assertTrue( $field->is_valid( '-987654321' ) );
+	}
+
+	public function test_number_validate_null_value() {
+		$field = bp_xprofile_create_field_type( 'number' );
+		$this->assertTrue( $field->is_valid( '0' ) );
+		$this->assertFalse( $field->is_valid( '-' ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+	}
+
+	public function test_number_validate_whitelisted_array() {
+		$field = bp_xprofile_create_field_type( 'number' );
+		$field->set_whitelist_values( array( 123, 456 ) );
+
+		$this->assertTrue( $field->is_valid( array( 123 ) ) );
+		$this->assertTrue( $field->is_valid( array( 456 ) ) );
+		$this->assertFalse( $field->is_valid( array( 123, 456, 789 ) ) );
+		$this->assertFalse( $field->is_valid( array( 789 ) ) );
+	}
+
+	public function test_radiobutton_validate_whitelisted_array() {
+		$field = bp_xprofile_create_field_type( 'radio' );
+		$field->set_whitelist_values( array( 'cheese', 'pepporoni' ) );
+
+		$this->assertTrue( $field->is_valid( array( 'cheese', 'pepporoni' ) ) );
+		$this->assertTrue( $field->is_valid( array( 'cheese' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'cheese', 'pepporoni', 'pinapple' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'pinapple' ) ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+	}
+
+	public function test_radiobutton_do_not_validate_empty_items_in_whitelist() {
+		$field = bp_xprofile_create_field_type( 'radio' );
+		$field->set_whitelist_values( array( '' ) );
+
+		$this->assertFalse( $field->is_valid( array( '' ) ) );
+	}
+
+	public function test_checkbox_validate_whitelisted_array() {
+		$field = bp_xprofile_create_field_type( 'checkbox' );
+		$field->set_whitelist_values( array( 'cheese', 'pepporoni' ) );
+
+		$this->assertTrue( $field->is_valid( array( 'cheese', 'pepporoni' ) ) );
+		$this->assertTrue( $field->is_valid( array( 'cheese' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'cheese', 'pepporoni', 'pinapple' ) ) );
+		$this->assertFalse( $field->is_valid( array( 'pinapple' ) ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+	}
+
+	public function test_checkbox_validate_null_value() {
+		$field = bp_xprofile_create_field_type( 'checkbox' );
+		$field->set_whitelist_values( array( 'cheese', 'pepporoni' ) );
+
+		$this->assertFalse( $field->is_valid( array( '' ) ) );
+		$this->assertFalse( $field->is_valid( '' ) );
+		$this->assertTrue( $field->is_valid( array() ) );
+	}
+
+	/**
+	 * @group url
+	 */
+	public function test_url_validate_url() {
+		$field = bp_xprofile_create_field_type( 'url' );
+
+		$this->assertTrue( $field->is_valid( 'http://foo.com' ) );
+		$this->assertTrue( $field->is_valid( 'https://foo.com' ) );
+		$this->assertTrue( $field->is_valid( 'http://foo.bar.com' ) );
+		$this->assertTrue( $field->is_valid( 'http://foo.bar.com/' ) );
+		$this->assertTrue( $field->is_valid( 'http://foo.com/bar' ) );
+		$this->assertTrue( $field->is_valid( 'http://foo.com/index.php' ) );
+		$this->assertTrue( $field->is_valid( 'http://foo.com/?bar=baz' ) );
+
+		$this->assertFalse( $field->is_valid( 'htp://foo.com' ) );
+		$this->assertFalse( $field->is_valid( 'http:/foo.com' ) );
+		$this->assertFalse( $field->is_valid( 'http//foo.com' ) );
+		$this->assertFalse( $field->is_valid( 'http://foo' ) );
+		$this->assertFalse( $field->is_valid( 'foo.com' ) );
+	}
+
+	/**
+	 * @group BP_XProfile_Field_Group_Type_Placeholder
+	 */
+	public function test_placeholder_validate_any_value() {
+		$field = bp_xprofile_create_field_type( 'foo' );
+		$this->assertTrue( $field->is_valid( '' ) );
+		$this->assertTrue( $field->is_valid( 'bar' ) );
+		$this->assertTrue( $field->is_valid( array( 'bar' ) ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-field.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-field.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-field.php	(revision 10410)
@@ -0,0 +1,177 @@
+<?php
+/**
+ * @group xprofile
+ * @group BP_XProfile_Field
+ */
+class BP_Tests_BP_XProfile_Field_TestCases extends BP_UnitTestCase {
+
+	/**
+	 * @group xprofile_field_save
+	 */
+	public function test_can_delete_save() {
+		$group = $this->factory->xprofile_group->create();
+		$field = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $group,
+		) );
+
+		$f = new BP_XProfile_Field( $field );
+		$f->can_delete = 0;
+		$f->save();
+
+		$f2 = new BP_XProfile_Field( $field );
+		$this->assertEquals( '0', $f2->can_delete );
+	}
+
+	/**
+	 * @group xprofile_get_field_id_from_name
+	 */
+	public function test_get_id_from_name_field_name_option_value_conflict() {
+		$group = $this->factory->xprofile_group->create();
+
+		// force some checkbox options for our profile field
+		$_POST['checkbox_option'] = array(
+			1 => 'BuddyPress',
+			2 => 'WordPress'
+		);
+
+		// checkbox field
+		$f1 = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $group,
+			'type' => 'checkbox',
+			'name' => 'Interests'
+		) );
+
+		// textbox field with the same name as our checkbox value
+		$f2 = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $group,
+			'type' => 'textbox',
+			'name' => 'BuddyPress'
+		) );
+
+		$this->assertEquals( $f2, xprofile_get_field_id_from_name( 'BuddyPress' ) );
+
+		// cleanup!
+		unset( $_POST['checkbox_option'] );
+	}
+
+	/**
+	 * @group xprofile_field_admin_validate
+	 */
+	public function test_admin_validate_zero_field_name() {
+
+		// Mock POST global values
+		$_POST['title']     = '0';
+		$_POST['required']  = false;
+		$_POST['fieldtype'] = 'textbox';
+
+		// Validate the mocked POST radio button options
+		$result = BP_XProfile_Field::admin_validate();
+
+		// Assert valid
+		$this->assertEquals( $result, true );
+
+		// cleanup
+		unset(
+			$_POST['title'],
+			$_POST['required'],
+			$_POST['fieldtype']
+		);
+	}
+
+	/**
+	 * @group xprofile_field_admin_validate
+	 */
+	public function test_admin_validate_field_options() {
+
+		// Mock POST global values
+		$_POST['title']        = 'Foo';
+		$_POST['required']     = false;
+		$_POST['fieldtype']    = 'radio';
+		$_POST['radio_option'] = array(
+			1 => '0',
+			2 => '1',
+			3 => '4',
+		);
+
+		// Validate the mocked POST radio button options
+		$result = BP_XProfile_Field::admin_validate();
+
+		// Assert valid
+		$this->assertEquals( $result, true );
+
+		// cleanup
+		unset(
+			$_POST['title'],
+			$_POST['required'],
+			$_POST['fieldtype'],
+			$_POST['radio_option' ]
+		);
+	}
+
+	/**
+	 * @ticket BP6545
+	 */
+	public function test_newly_created_field_should_have_field_id_property_set() {
+		$field = new BP_XProfile_Field();
+		$field->group_id = 1;
+		$field->name = 'Foo';
+
+		$new_field_id = $field->save();
+
+		$this->assertSame( $new_field_id, $field->id );
+	}
+
+	/**
+	 * @ticket BP6638
+	 */
+	public function test_default_visibility_should_be_lazy_loaded() {
+		global $wpdb;
+
+		$group = $this->factory->xprofile_group->create();
+		$field = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $group,
+		) );
+
+		bp_xprofile_update_meta( $field, 'field', 'default_visibility', 'loggedin' );
+
+		// Initial setup takes just one query.
+		$num_queries = $wpdb->num_queries;
+		$field_obj = new BP_XProfile_Field( $field );
+		$num_queries++;
+
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+
+		// Fetching the default_visibility should cause another query.
+		$this->assertSame( 'loggedin', $field_obj->default_visibility );
+		$num_queries++;
+
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+	}
+
+	/**
+	 * @ticket BP6638
+	 */
+	public function test_allow_custom_visibility_should_be_lazy_loaded() {
+		global $wpdb;
+
+		$group = $this->factory->xprofile_group->create();
+		$field = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $group,
+		) );
+
+		bp_xprofile_update_meta( $field, 'field', 'allow_custom_visibility', 'disabled' );
+
+		// Initial setup takes just one query.
+		$num_queries = $wpdb->num_queries;
+		$field_obj = new BP_XProfile_Field( $field );
+		$num_queries++;
+
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+
+		// Fetching the allow_custom_visibility should cause another query.
+		$this->assertSame( 'disabled', $field_obj->allow_custom_visibility );
+		$num_queries++;
+
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-group.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-group.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-group.php	(revision 10410)
@@ -0,0 +1,426 @@
+<?php
+
+/**
+ * @group BP_XProfile_Group
+ * @group xprofile
+ */
+class BP_Tests_BP_XProfile_Group extends BP_UnitTestCase {
+
+	/**
+	 * @ticket BP6552
+	 */
+	public function test_save_should_not_return_false_when_no_fields_have_been_altered() {
+		$g = $this->factory->xprofile_group->create();
+		$group = new BP_XProfile_Group( $g );
+
+		$saved = $group->save();
+
+		$this->assertEquals( $g, $saved );
+	}
+
+	/**
+	 * @group fetch_visibility_level
+	 */
+	public function test_fetch_visibility_level() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		$f_obj = new BP_XProfile_Field( $f );
+
+		$fields = array(
+			0 => new stdClass,
+		);
+
+		$fields[0]->id = $f;
+		$fields[0]->name = $f_obj->name;
+		$fields[0]->description = $f_obj->description;
+		$fields[0]->type = $f_obj->type;
+		$fields[0]->group_id = $f_obj->group_id;
+		$fields[0]->is_required = $f_obj->is_required;
+		$fields[0]->data = new stdClass;
+		$fields[0]->data->value = 'foo';
+		$fields[0]->data->id = 123;
+
+		// custom visibility enabled, but no fallback
+		bp_xprofile_update_meta( $f, 'field', 'default_visibility', 'adminsonly' );
+		bp_xprofile_update_meta( $f, 'field', 'allow_custom_visibility', 'enabled' );
+
+		$found = BP_XProfile_Group::fetch_visibility_level( $u, $fields );
+
+		$expected = $fields;
+		$expected[0]->visibility_level = 'adminsonly';
+
+		$this->assertSame( $expected, $found );
+
+		// custom visibility enabled, with user-provided value
+		bp_xprofile_update_meta( $f, 'field', 'default_visibility', 'adminsonly' );
+		bp_xprofile_update_meta( $f, 'field', 'allow_custom_visibility', 'enabled' );
+		xprofile_set_field_visibility_level( $f, $u, 'public' );
+
+		$found = BP_XProfile_Group::fetch_visibility_level( $u, $fields );
+
+		$expected = $fields;
+		$expected[0]->visibility_level = 'public';
+
+		$this->assertSame( $expected, $found );
+
+		// custom visibility disabled
+		bp_xprofile_update_meta( $f, 'field', 'default_visibility', 'adminsonly' );
+		bp_xprofile_update_meta( $f, 'field', 'allow_custom_visibility', 'disabled' );
+		xprofile_set_field_visibility_level( $f, $u, 'public' );
+
+		$found = BP_XProfile_Group::fetch_visibility_level( $u, $fields );
+
+		$expected = $fields;
+		$expected[0]->visibility_level = 'adminsonly';
+
+		$this->assertSame( $expected, $found );
+	}
+
+	/**
+	 * @group get_xprofile_groups
+	 */
+	public function test_get_xprofile_groups() {
+		$g1 = $this->factory->xprofile_group->create();
+		$g2 = $this->factory->xprofile_group->create();
+		$g3 = $this->factory->xprofile_group->create();
+
+		$all = BP_XProfile_Group::get();
+		$all_results = array_map( 'absint', wp_list_pluck( $all, 'id' ) );
+
+		$e1 = array( $g1, $g2 );
+		$groups1 = BP_XProfile_Group::get( array(
+			'exclude_groups' => implode( ',', $e1 ),
+		) );
+
+		$r_groups1 = array_map( 'absint', wp_list_pluck( $groups1, 'id' ) );
+		$found1 = array_diff( $all_results, $r_groups1 );
+
+		$this->assertSame( $e1, array_merge( $found1, array() ) );
+
+		$e2 = array( $g2, $g3 );
+		$groups2 = BP_XProfile_Group::get( array(
+			'exclude_groups' => $e2,
+		) );
+
+		$r_groups2 = array_map( 'absint', wp_list_pluck( $groups2, 'id' ) );
+		$found2 = array_diff( $all_results, $r_groups2 );
+
+		$this->assertSame( $e2, array_merge( $found2, array() ) );
+	}
+
+	/**
+	 * @group member_types
+	 * @ticket BP5192
+	 */
+	public function test_member_type_restrictions_should_be_ignored_when_user_id_is_null_and_member_type_is_not_explicitly_provided() {
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		bp_register_member_type( 'foo' );
+
+		$field = new BP_XProfile_Field( $f );
+		$field->set_member_types( array( 'foo' ) );
+
+		$found_groups = BP_XProfile_Group::get( array(
+			'user_id' => false,
+			'fetch_fields' => true,
+		) );
+
+		// The groups aren't indexed, so we have to go looking for it.
+		foreach ( $found_groups as $fg ) {
+			if ( $g == $fg->id ) {
+				$the_group = $fg;
+			}
+		}
+
+		$this->assertContains( $f, wp_list_pluck( $the_group->fields, 'id' ) );
+	}
+
+	/**
+	 * @group member_types
+	 * @ticket BP5192
+	 */
+	public function test_member_type_restrictions_should_be_ignored_when_user_id_is_0_and_member_type_is_false() {
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		bp_register_member_type( 'foo' );
+
+		$field = new BP_XProfile_Field( $f );
+		$field->set_member_types( array( 'foo' ) );
+
+		$found_groups = BP_XProfile_Group::get( array(
+			'user_id' => 0,
+			'member_type' => false,
+			'fetch_fields' => true,
+		) );
+
+		// The groups aren't indexed, so we have to go looking for it.
+		foreach ( $found_groups as $fg ) {
+			if ( $g == $fg->id ) {
+				$the_group = $fg;
+			}
+		}
+
+		$this->assertContains( $f, wp_list_pluck( $the_group->fields, 'id' ) );
+	}
+
+	/**
+	 * @group member_types
+	 * @ticket BP5192
+	 */
+	public function test_member_type_restrictions_should_be_obeyed_for_nonzero_user_id() {
+		$g = $this->factory->xprofile_group->create();
+		$f1 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		$f2 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		$f3 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		$f4 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+
+		// Field 1 is visible only to 'foo' users.
+		$field1 = new BP_XProfile_Field( $f1 );
+		$field1->set_member_types( array( 'foo' ) );
+
+		// Field 2 is visible only to 'bar' users.
+		$field2 = new BP_XProfile_Field( $f2 );
+		$field2->set_member_types( array( 'bar' ) );
+
+		// Field 3 is visible to all users (no member type set).
+
+		// Field 4 is visible to no one.
+		$field4 = new BP_XProfile_Field( $f4 );
+		$field4->set_member_types( array() );
+
+		// User is in 'foo', so should have f1 and f3 only.
+		$u = $this->factory->user->create();
+		bp_set_member_type( $u, 'foo' );
+
+		$found_groups = BP_XProfile_Group::get( array(
+			'user_id' => $u,
+			'fetch_fields' => true,
+		) );
+
+		// The groups aren't indexed, so we have to go looking for it.
+		foreach ( $found_groups as $fg ) {
+			if ( $g == $fg->id ) {
+				$the_group = $fg;
+			}
+		}
+
+		$this->assertContains( $f1, wp_list_pluck( $the_group->fields, 'id' ) );
+		$this->assertContains( $f3, wp_list_pluck( $the_group->fields, 'id' ) );
+		$this->assertNotContains( $f2, wp_list_pluck( $the_group->fields, 'id' ) );
+		$this->assertNotContains( $f4, wp_list_pluck( $the_group->fields, 'id' ) );
+	}
+
+	/**
+	 * @group member_types
+	 * @ticket BP5192
+	 */
+	public function test_member_type_restrictions_should_be_obeyed_for_nonzero_user_id_with_no_member_types() {
+		$g = $this->factory->xprofile_group->create();
+		$f1 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		$f2 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		$f3 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+
+		// Field 1 is visible only to 'foo' users.
+		$field1 = new BP_XProfile_Field( $f1 );
+		$field1->set_member_types( array( 'foo' ) );
+
+		// Field 2 is visible only to 'null' users.
+		$field2 = new BP_XProfile_Field( $f2 );
+		$field2->set_member_types( array( 'null' ) );
+
+		// Field 3 is visible to all users (no member type set).
+
+		// User has no member types, so should see f2 and f3 .
+		$u = $this->factory->user->create();
+
+		$found_groups = BP_XProfile_Group::get( array(
+			'user_id' => $u,
+			'fetch_fields' => true,
+		) );
+
+		// The groups aren't indexed, so we have to go looking for it.
+		foreach ( $found_groups as $fg ) {
+			if ( $g == $fg->id ) {
+				$the_group = $fg;
+			}
+		}
+
+		$this->assertNotContains( $f1, wp_list_pluck( $the_group->fields, 'id' ) );
+		$this->assertContains( $f2, wp_list_pluck( $the_group->fields, 'id' ) );
+		$this->assertContains( $f3, wp_list_pluck( $the_group->fields, 'id' ) );
+	}
+
+	/**
+	 * @group member_types
+	 * @ticket BP5192
+	 */
+	public function test_member_types_of_provided_user_id_should_take_precedence_over_provided_member_type() {
+		$g = $this->factory->xprofile_group->create();
+		$f1 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		$f2 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+
+		$field1 = new BP_XProfile_Field( $f1 );
+		$field1->set_member_types( array( 'foo' ) );
+		$field2 = new BP_XProfile_Field( $f2 );
+		$field2->set_member_types( array( 'bar' ) );
+
+		$u = $this->factory->user->create();
+		bp_set_member_type( $u, 'foo' );
+
+		$found_groups = BP_XProfile_Group::get( array(
+			'user_id' => $u,
+			'member_type' => 'bar',
+			'fetch_fields' => true,
+		) );
+
+		// The groups aren't indexed, so we have to go looking for it.
+		foreach ( $found_groups as $fg ) {
+			if ( $g == $fg->id ) {
+				$the_group = $fg;
+			}
+		}
+
+		$this->assertContains( $f1, wp_list_pluck( $the_group->fields, 'id' ) );
+	}
+
+	/**
+	 * @group member_types
+	 * @ticket BP5192
+	 */
+	public function test_member_type_single_value_should_be_respected() {
+		$g = $this->factory->xprofile_group->create();
+		$f1 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		$f2 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+
+		$field1 = new BP_XProfile_Field( $f1 );
+		$field1->set_member_types( array( 'foo' ) );
+		$field2 = new BP_XProfile_Field( $f2 );
+		$field2->set_member_types( array( 'bar' ) );
+
+		$found_groups = BP_XProfile_Group::get( array(
+			'member_type' => 'bar',
+			'fetch_fields' => true,
+		) );
+
+		// The groups aren't indexed, so we have to go looking for it.
+		foreach ( $found_groups as $fg ) {
+			if ( $g == $fg->id ) {
+				$the_group = $fg;
+			}
+		}
+
+		$this->assertNotContains( $f1, wp_list_pluck( $the_group->fields, 'id' ) );
+		$this->assertContains( $f2, wp_list_pluck( $the_group->fields, 'id' ) );
+	}
+
+	/**
+	 * @group member_types
+	 * @ticket BP5192
+	 */
+	public function test_member_type_array_value_should_be_respected() {
+		$g = $this->factory->xprofile_group->create();
+		$f1 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		$f2 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+
+		$field1 = new BP_XProfile_Field( $f1 );
+		$field1->set_member_types( array( 'foo' ) );
+		$field2 = new BP_XProfile_Field( $f2 );
+		$field2->set_member_types( array( 'bar' ) );
+
+		$found_groups = BP_XProfile_Group::get( array(
+			'member_type' => array( 'bar' ),
+			'fetch_fields' => true,
+		) );
+
+		// The groups aren't indexed, so we have to go looking for it.
+		foreach ( $found_groups as $fg ) {
+			if ( $g == $fg->id ) {
+				$the_group = $fg;
+			}
+		}
+
+		$this->assertNotContains( $f1, wp_list_pluck( $the_group->fields, 'id' ) );
+		$this->assertContains( $f2, wp_list_pluck( $the_group->fields, 'id' ) );
+	}
+
+	/**
+	 * @group member_types
+	 * @ticket BP5192
+	 */
+	public function test_member_type_null_should_be_respected() {
+		$g = $this->factory->xprofile_group->create();
+		$f1 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		$f2 = $this->factory->xprofile_field->create( array( 'field_group_id' => $g ) );
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+
+		$field1 = new BP_XProfile_Field( $f1 );
+		$field1->set_member_types( array( 'foo' ) );
+
+		$found_groups = BP_XProfile_Group::get( array(
+			'member_type' => array( 'null' ),
+			'fetch_fields' => true,
+		) );
+
+		// The groups aren't indexed, so we have to go looking for it.
+		foreach ( $found_groups as $fg ) {
+			if ( $g == $fg->id ) {
+				$the_group = $fg;
+			}
+		}
+
+		$this->assertNotContains( $f1, wp_list_pluck( $the_group->fields, 'id' ) );
+		$this->assertContains( $f2, wp_list_pluck( $the_group->fields, 'id' ) );
+	}
+
+	/**
+	 * @group save_xprofile_group_name
+	 */
+	public function test_save_xprofile_group_name() {
+		$g1 = $this->factory->xprofile_group->create( array(
+			'name' => "Test ' Name"
+		) );
+
+		$e1 = new BP_XProfile_Group( $g1 );
+		$e1->save();
+
+		wp_cache_delete( $g1, 'bp_xprofile_groups' );
+
+		$e2 = new BP_XProfile_Group( $g1 );
+
+		$this->assertSame( $e1->name, $e2->name );
+	}
+
+	/**
+	 * @group save_xprofile_group_name
+	 */
+	public function test_save_xprofile_group_name_with_single_quote() {
+
+		// Set the original group name with no slashes
+		$pristine_name = "Test \' Name";
+
+		// Create a group
+		$g1 = $this->factory->xprofile_group->create( array(
+			'name' => $pristine_name
+		) );
+
+		// Get the field
+		$e1 = new BP_XProfile_Group( $g1 );
+
+		$this->assertSame( "Test ' Name", $e1->name );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-profiledata.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-profiledata.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-profiledata.php	(revision 10410)
@@ -0,0 +1,425 @@
+<?php
+
+/**
+ * @group xprofile
+ * @group BP_XProfile_ProfileData
+ */
+class BP_Tests_BP_XProfile_ProfileData_TestCases extends BP_UnitTestCase {
+	/**
+	 * @group exists
+	 */
+	public function test_exists_when_doesnt_exist() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		$d = new BP_XProfile_ProfileData( $f, $u );
+
+		$this->assertFalse( $d->exists() );
+	}
+
+	/**
+	 * @group exists
+	 */
+	public function test_exists_when_exists_uncached() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		xprofile_set_field_data( $f, $u, 'foo' );
+
+		$d = new BP_XProfile_ProfileData( $f, $u );
+
+		wp_cache_delete( "{$u}:{$f}", 'bp_xprofile_data' );
+
+		$this->assertTrue( $d->exists() );
+	}
+
+	/**
+	 * @group exists
+	 */
+	public function test_exists_when_exists_in_cache() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+		$d = new BP_XProfile_ProfileData( $f, $u );
+
+		// Fake the cache
+		$c = new stdClass;
+		$c->id = 3;
+		wp_cache_set( "{$u}:{$f}", $c, 'bp_xprofile_data' );
+
+		$this->assertTrue( $d->exists() );
+	}
+
+	/**
+	 * @group get_fielddataid_byid
+	 */
+	public function test_get_fielddataid_byid_when_doesnt_exist() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		// Just to be sure
+		wp_cache_delete( "{$u}:{$f}", 'bp_xprofile_data' );
+
+		$this->assertEquals( 0, BP_XProfile_ProfileData::get_fielddataid_byid( $f, $u ) );
+	}
+
+	/**
+	 * @group get_fielddataid_byid
+	 */
+	public function test_get_fielddataid_byid_when_exists_uncached() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		$d = new BP_XProfile_ProfileData();
+		$d->user_id = $u;
+		$d->field_id = $f;
+		$d->value = 'foo';
+		$d->save();
+
+		// Ensure it's deleted from cache
+		wp_cache_delete( "{$u}:{$f}", 'bp_xprofile_data' );
+
+		$this->assertEquals( $d->id, BP_XProfile_ProfileData::get_fielddataid_byid( $f, $u ) );
+	}
+
+	/**
+	 * @group get_fielddataid_byid
+	 */
+	public function test_get_fielddataid_byid_when_exists_in_cache() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		// Fake the cache
+		$d = new stdClass;
+		$d->id = 5;
+		wp_cache_set( "{$u}:{$f}", $d, 'bp_xprofile_data' );
+
+		$this->assertSame( 5, BP_XProfile_ProfileData::get_fielddataid_byid( $f, $u ) );
+	}
+
+	/**
+	 * @group get_value_byid
+	 */
+	public function test_get_value_byid_singleuser_uncached() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		$d = new BP_XProfile_ProfileData();
+		$d->user_id = $u;
+		$d->field_id = $f;
+		$d->value = 'foo';
+		$d->save();
+
+		// Ensure it's deleted from cache
+		wp_cache_delete( "{$u}:{$f}", 'bp_xprofile_data' );
+
+		$this->assertSame( 'foo', BP_XProfile_ProfileData::get_value_byid( $f, $u ) );
+	}
+
+	/**
+	 * @group get_value_byid
+	 */
+	public function test_get_value_byid_multipleusers_uncached() {
+		$time = date( 'Y-m-d H:i:s', time() - 60*60*24 );
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		// To make the tests reliable, we have to force the last_updated
+		// BP doesn't easily allow this except via filter
+		$this->last_updated = $time;
+		add_filter( 'xprofile_data_last_updated_before_save', array( $this, 'filter_time' ) );
+
+		$d1 = new BP_XProfile_ProfileData();
+		$d1->user_id = $u1;
+		$d1->field_id = $f;
+		$d1->value = 'foo';
+		$d1->last_updated = $time;
+		$d1->save();
+
+		$d2 = new BP_XProfile_ProfileData();
+		$d2->user_id = $u2;
+		$d2->field_id = $f;
+		$d2->value = 'bar';
+		$d2->last_updated = $time;
+		$d2->save();
+
+		remove_filter( 'xprofile_data_last_updated_before_save', array( $this, 'filter_time' ) );
+
+		// Ensure it's deleted from cache
+		wp_cache_delete( "{$u1}:{$f}", 'bp_xprofile_data' );
+		wp_cache_delete( "{$u2}:{$f}", 'bp_xprofile_data' );
+
+		$eu1 = new stdClass;
+		$eu1->user_id = $u1;
+		$eu1->value = 'foo';
+		$eu1->id = $d1->id;
+		$eu1->field_id = $f;
+		$eu1->last_updated = $time;
+
+		$eu2 = new stdClass;
+		$eu2->user_id = $u2;
+		$eu2->value = 'bar';
+		$eu2->id = $d2->id;
+		$eu2->field_id = $f;
+		$eu2->last_updated = $time;
+
+		$expected = array( $eu1, $eu2 );
+
+		$this->assertEquals( $expected, BP_XProfile_ProfileData::get_value_byid( $f, array( $u1, $u2 ) ) );
+	}
+
+	/**
+	 * @group get_value_byid
+	 */
+	public function test_get_value_byid_singleuser_cached() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		$time = bp_core_current_time();
+
+		// Fake the cache
+		$d = new stdClass;
+		$d->value = 'foo';
+		$d->field_id = $f;
+		wp_cache_set( "{$u}:{$f}", $d, 'bp_xprofile_data' );
+
+		$this->assertSame( 'foo', BP_XProfile_ProfileData::get_value_byid( $f, $u ) );
+	}
+
+	/**
+	 * @group get_value_byid
+	 */
+	public function test_get_value_byid_multipleusers_cached() {
+		$time = date( 'Y-m-d H:i:s', time() - 60*60*24 );
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		// Fake the cache
+		$d1 = new stdClass;
+		$d1->id = 10;
+		$d1->user_id = $u1;
+		$d1->field_id = $f;
+		$d1->value = 'foo';
+		$d1->last_updated = $time;
+
+		$d2 = new stdClass;
+		$d1->id = 21;
+		$d2->user_id = $u2;
+		$d2->field_id = $f;
+		$d2->value = 'bar';
+		$d2->last_updated = $time;
+
+		wp_cache_set( "{$u1}:{$f}", $d1, 'bp_xprofile_data' );
+		wp_cache_set( "{$u2}:{$f}", $d2, 'bp_xprofile_data' );
+
+		$eu1 = new stdClass;
+		$eu1->id = 10;
+		$eu1->user_id = $u1;
+		$eu1->field_id = $f;
+		$eu1->value = 'foo';
+		$eu1->last_updated = $time;
+
+		$eu2 = new stdClass;
+		$eu1->id = 21;
+		$eu2->user_id = $u2;
+		$eu2->field_id = $f;
+		$eu2->value = 'bar';
+		$eu2->last_updated = $time;
+
+		$expected = array( $eu1, $eu2 );
+
+		$this->assertEquals( $expected, BP_XProfile_ProfileData::get_value_byid( $f, array( $u1, $u2 ) ) );
+	}
+
+	/**
+	 * @group get_all_for_user
+	 */
+	public function test_get_all_for_user_uncached() {
+		$u = $this->factory->user->create();
+		$g1 = $this->factory->xprofile_group->create();
+		$g2 = $this->factory->xprofile_group->create();
+		$f1 = $this->factory->xprofile_field->create( array(
+			'type' => 'textbox',
+			'field_group_id' => $g1,
+		) );
+		$f2 = $this->factory->xprofile_field->create( array(
+			'type' => 'radio',
+			'field_group_id' => $g2,
+		) );
+
+		$time = bp_core_current_time();
+
+		// Get the fullname field - hackish
+		$f0_id = xprofile_get_field_id_from_name( bp_xprofile_fullname_field_name() );
+		$f0 = new BP_XProfile_Field( $f0_id );
+		$g0 = new BP_XProfile_Group( $f0->group_id );
+		$d0 = new BP_XProfile_ProfileData( $f0->id, $u );
+
+		$d1 = new BP_XProfile_ProfileData();
+		$d1->user_id = $u;
+		$d1->field_id = $f1;
+		$d1->value = 'foo';
+		$d1->last_updated = $time;
+		$d1->save();
+
+		$d2 = new BP_XProfile_ProfileData();
+		$d2->user_id = $u;
+		$d2->field_id = $f2;
+		$d2->value = 'bar';
+		$d2->last_updated = $time;
+		$d2->save();
+
+		// Ensure it's deleted from cache
+		wp_cache_delete( "{$u}:{$f1}", 'bp_xprofile_data' );
+		wp_cache_delete( "{$u}:{$f2}", 'bp_xprofile_data' );
+
+		$u_obj = new WP_User( $u );
+
+		$g1_obj = new BP_XProfile_Group( $g1 );
+		$g2_obj = new BP_XProfile_Group( $g2 );
+		$f1_obj = new BP_XProfile_Field( $f1 );
+		$f2_obj = new BP_XProfile_Field( $f2 );
+
+		$expected = array(
+			'user_login' => $u_obj->user_login,
+			'user_nicename' => $u_obj->user_nicename,
+			'user_email' => $u_obj->user_email,
+			$f0->name => array(
+				'field_group_id' => $g0->id,
+				'field_group_name' => $g0->name,
+				'field_id' => $f0->id,
+				'field_type' => $f0->type,
+				'field_data' => $d0->value,
+			),
+			$f1_obj->name => array(
+				'field_group_id' => $g1,
+				'field_group_name' => $g1_obj->name,
+				'field_id' => $f1,
+				'field_type' => $f1_obj->type,
+				'field_data' => $d1->value,
+			),
+			$f2_obj->name => array(
+				'field_group_id' => $g2,
+				'field_group_name' => $g2_obj->name,
+				'field_id' => $f2,
+				'field_type' => $f2_obj->type,
+				'field_data' => $d2->value,
+			),
+		);
+
+		$this->assertEquals( $expected, BP_XProfile_ProfileData::get_all_for_user( $u ) );
+	}
+
+	/**
+	 * @group get_all_for_user
+	 */
+	public function test_get_all_for_user_cached() {
+		$u = $this->factory->user->create();
+		$g1 = $this->factory->xprofile_group->create();
+		$g2 = $this->factory->xprofile_group->create();
+		$f1 = $this->factory->xprofile_field->create( array(
+			'type' => 'textbox',
+			'field_group_id' => $g1,
+		) );
+		$f2 = $this->factory->xprofile_field->create( array(
+			'type' => 'radio',
+			'field_group_id' => $g2,
+		) );
+
+		$time = bp_core_current_time();
+
+		$g0 = new BP_XProfile_Group( 1 );
+		$f0 = new BP_XProfile_Field( 1 );
+		$d0 = new BP_XProfile_ProfileData( 1, $u );
+
+		$d1 = new stdClass;
+		$d1->user_id = $u;
+		$d1->field_id = $f1;
+		$d1->value = 'foo';
+		$d1->last_updated = $time;
+		$d1->id = 1;
+
+		$d2 = new stdClass;
+		$d2->user_id = $u;
+		$d2->field_id = $f2;
+		$d2->value = 'bar';
+		$d2->last_updated = $time;
+		$d2->id = 2;
+
+		wp_cache_set( "{$u}:{$f1}", $d1, 'bp_xprofile_data' );
+		wp_cache_set( "{$u}:{$f2}", $d2, 'bp_xprofile_data' );
+
+		$u_obj = new WP_User( $u );
+
+		$g1_obj = new BP_XProfile_Group( $g1 );
+		$g2_obj = new BP_XProfile_Group( $g2 );
+		$f1_obj = new BP_XProfile_Field( $f1 );
+		$f2_obj = new BP_XProfile_Field( $f2 );
+
+		$expected = array(
+			'user_login' => $u_obj->user_login,
+			'user_nicename' => $u_obj->user_nicename,
+			'user_email' => $u_obj->user_email,
+			$f0->name => array(
+				'field_group_id' => $g0->id,
+				'field_group_name' => $g0->name,
+				'field_id' => $f0->id,
+				'field_type' => $f0->type,
+				'field_data' => $d0->value,
+			),
+			$f1_obj->name => array(
+				'field_group_id' => $g1,
+				'field_group_name' => $g1_obj->name,
+				'field_id' => $f1,
+				'field_type' => $f1_obj->type,
+				'field_data' => $d1->value,
+			),
+			$f2_obj->name => array(
+				'field_group_id' => $g2,
+				'field_group_name' => $g2_obj->name,
+				'field_id' => $f2,
+				'field_type' => $f2_obj->type,
+				'field_data' => $d2->value,
+			),
+		);
+
+		$this->assertEquals( $expected, BP_XProfile_ProfileData::get_all_for_user( $u ) );
+	}
+
+	public function filter_time() {
+		return $this->last_updated;
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-query.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-query.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/xprofile/class-bp-xprofile-query.php	(revision 10410)
@@ -0,0 +1,587 @@
+<?php
+
+/**
+ * @group xprofile
+ * @group BP_XProfile_Query
+ */
+class BP_Tests_BP_XProfile_Query extends BP_UnitTestCase {
+	protected $group;
+	protected $fields = array();
+	protected $users = array();
+
+	public function tearDown() {
+		parent::tearDown();
+		$this->group = '';
+		$this->fields = array();
+		$this->users = array();
+	}
+
+	public function test_no_field() {
+		$this->create_fields( 2 );
+		$this->create_users( 3 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo');
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'value' => 'foo',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0], $this->users[2] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_no_value() {
+		$this->create_fields( 2 );
+		$this->create_users( 3 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo');
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[1],
+				),
+			),
+		) );
+
+		$expected = array( $this->users[2] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_translate_field_name_to_field_id() {
+		$this->create_fields( 0 );
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $this->group,
+			'type' => 'textbox',
+			'name' => 'Foo Field',
+		) );
+		$this->create_users( 2 );
+
+		xprofile_set_field_data( $f, $this->users[0], 'foo' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => 'Foo Field',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_single_clause_compare_default() {
+		$this->create_fields( 2 );
+		$this->create_users( 3 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo');
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 'foo',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0] );
+		$this->assertEquals( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_single_clause_compare_equals() {
+		$this->create_fields( 2 );
+		$this->create_users( 3 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo');
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 'foo',
+					'compare' => '=',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0] );
+		$this->assertEquals( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_single_clause_compare_not_equals() {
+		$this->create_fields( 1 );
+		$this->create_users( 2 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 'foo',
+					'compare' => '!=',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[1] );
+		$this->assertEquals( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_single_clause_compare_arithmetic_comparisons() {
+		$this->create_fields( 1 );
+		$this->create_users( 3 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], '1' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], '2' );
+		xprofile_set_field_data( $this->fields[0], $this->users[2], '3' );
+
+		// <
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 2,
+					'compare' => '<',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+
+		// <=
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 2,
+					'compare' => '<=',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0], $this->users[1] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+
+		// >=
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 2,
+					'compare' => '>=',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[1], $this->users[2] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+
+		// >
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 2,
+					'compare' => '>',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[2] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_single_clause_compare_like() {
+		$this->create_fields( 1 );
+		$this->create_users( 2 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 'ba',
+					'compare' => 'LIKE',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_single_clause_compare_not_like() {
+		$this->create_fields( 1 );
+		$this->create_users( 3 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'bar' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'rab' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 'ba',
+					'compare' => 'NOT LIKE',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[1] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_single_clause_compare_between_not_between() {
+		$this->create_fields( 1 );
+		$this->create_users( 3 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], '1' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], '10' );
+		xprofile_set_field_data( $this->fields[0], $this->users[2], '100' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => array( 9, 12 ),
+					'compare' => 'BETWEEN',
+					'type' => 'NUMERIC',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[1] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => array( 9, 12 ),
+					'compare' => 'NOT BETWEEN',
+					'type' => 'NUMERIC',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0], $this->users[2] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_single_clause_compare_regexp_rlike() {
+		$this->create_fields( 1 );
+		$this->create_users( 3 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'bar' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'baz' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 'z$',
+					'compare' => 'REGEXP',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[1] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+
+		// RLIKE is a synonym for REGEXP.
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 'z$',
+					'compare' => 'RLIKE',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[1] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_single_clause_compare_not_regexp() {
+		$this->create_fields( 1 );
+		$this->create_users( 3 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'bar' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'baz' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 'z$',
+					'compare' => 'NOT REGEXP',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_single_clause_compare_not_exists() {
+		$this->create_fields( 2 );
+		$this->create_users( 2 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'compare' => 'NOT EXISTS',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[1] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_relation_default_to_and() {
+		$this->create_fields( 2 );
+		$this->create_users( 4 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
+		xprofile_set_field_data( $this->fields[1], $this->users[3], 'bar' );
+		xprofile_set_field_data( $this->fields[0], $this->users[3], 'foo' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				array(
+					'field' => $this->fields[0],
+					'value' => 'foo',
+				),
+				array(
+					'field' => $this->fields[1],
+					'value' => 'bar',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[3] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_relation_and() {
+		$this->create_fields( 2 );
+		$this->create_users( 4 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
+		xprofile_set_field_data( $this->fields[1], $this->users[3], 'bar' );
+		xprofile_set_field_data( $this->fields[0], $this->users[3], 'foo' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				'relation' => 'AND',
+				array(
+					'field' => $this->fields[0],
+					'value' => 'foo',
+				),
+				array(
+					'field' => $this->fields[1],
+					'value' => 'bar',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[3] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_relation_or() {
+		$this->create_fields( 2 );
+		$this->create_users( 4 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
+		xprofile_set_field_data( $this->fields[1], $this->users[3], 'bar' );
+		xprofile_set_field_data( $this->fields[0], $this->users[3], 'foo' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				'relation' => 'OR',
+				array(
+					'field' => $this->fields[0],
+					'value' => 'foo',
+				),
+				array(
+					'field' => $this->fields[1],
+					'value' => 'bar',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0], $this->users[3] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_relation_and_with_compare_not_exists() {
+		$this->create_fields( 2 );
+		$this->create_users( 4 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
+		xprofile_set_field_data( $this->fields[1], $this->users[3], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				'relation' => 'AND',
+				array(
+					'field' => $this->fields[0],
+					'compare' => 'NOT EXISTS',
+				),
+				array(
+					'field' => $this->fields[1],
+					'value' => 'bar',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[3] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_relation_or_with_compare_not_exists() {
+		$this->create_fields( 2 );
+		$this->create_users( 4 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
+		xprofile_set_field_data( $this->fields[1], $this->users[3], 'bar' );
+		xprofile_set_field_data( $this->fields[0], $this->users[3], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				'relation' => 'OR',
+				array(
+					'field' => $this->fields[0],
+					'compare' => 'NOT EXISTS',
+				),
+				array(
+					'field' => $this->fields[1],
+					'value' => 'bar',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[2], $this->users[3] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	/**
+	 * Tests for table join logic.
+	 */
+	public function test_relation_or_compare_equals_and_like() {
+		$this->create_fields( 2 );
+		$this->create_users( 4 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
+		xprofile_set_field_data( $this->fields[1], $this->users[3], 'barry' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				'relation' => 'OR',
+				array(
+					'field' => $this->fields[0],
+					'compare' => '=',
+					'value' => 'foo',
+				),
+				array(
+					'field' => $this->fields[1],
+					'value' => 'bar',
+					'compare' => 'LIKE',
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0], $this->users[3] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	public function test_nested() {
+		$this->create_fields( 2 );
+		$this->create_users( 3 );
+
+		xprofile_set_field_data( $this->fields[0], $this->users[0], 'foo' );
+		xprofile_set_field_data( $this->fields[0], $this->users[1], 'bar' );
+		xprofile_set_field_data( $this->fields[1], $this->users[2], 'foo' );
+		xprofile_set_field_data( $this->fields[1], $this->users[1], 'foo' );
+
+		$q = new BP_User_Query( array(
+			'xprofile_query' => array(
+				'relation' => 'OR',
+				array(
+					'field' => $this->fields[0],
+					'compare' => '=',
+					'value' => 'foo',
+				),
+				array(
+					'relation' => 'AND',
+					array(
+						'field' => $this->fields[0],
+						'value' => 'bar',
+					),
+					array(
+						'field' => $this->fields[1],
+						'value' => 'foo',
+					),
+				),
+			),
+		) );
+
+		$expected = array( $this->users[0], $this->users[1] );
+		$this->assertEqualSets( $expected, array_keys( $q->results ) );
+	}
+
+	/** Helpers **********************************************************/
+
+	protected function create_fields( $count ) {
+		$this->group = $this->factory->xprofile_group->create();
+		for ( $i = 0; $i < $count; $i++ ) {
+			$this->fields[] = $this->factory->xprofile_field->create( array(
+				'field_group_id' => $this->group,
+				'type' => 'textbox',
+			) );
+		}
+	}
+
+	protected function create_users( $count ) {
+		for ( $i = 0; $i < $count; $i++ ) {
+			$this->users[] = $this->factory->user->create();
+		}
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/xprofile/filters.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/xprofile/filters.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/xprofile/filters.php	(revision 10410)
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @group xprofile
+ */
+class BP_Tests_XProfile_Filters extends BP_UnitTestCase {
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_filter_meta_query
+	 */
+	public function test_bp_xprofile_filter_meta_query_select() {
+		global $wpdb;
+
+		// update_meta_cache
+		$q = "SELECT xprofile_group_id, meta_key, meta_value FROM {$wpdb->xprofile_groupmeta} WHERE xprofile_group_id IN (1,2,3) ORDER BY xprofile_group_id ASC";
+		$this->assertSame( "SELECT object_id AS xprofile_group_id, meta_key, meta_value FROM {$wpdb->xprofile_groupmeta} WHERE object_type = 'group' AND object_id IN (1,2,3) ORDER BY object_id ASC", bp_xprofile_filter_meta_query( $q ) );
+
+		// add_metadata
+		$q = "SELECT COUNT(*) FROM {$wpdb->xprofile_groupmeta} WHERE meta_key = 'foo' AND xprofile_group_id = 5";
+		$this->assertSame( "SELECT COUNT(*) FROM {$wpdb->xprofile_groupmeta} WHERE object_type = 'group' AND meta_key = 'foo' AND object_id = 5", bp_xprofile_filter_meta_query( $q ) );
+
+		// delete_metadata
+		$q = "SELECT meta_id FROM {$wpdb->xprofile_groupmeta} WHERE meta_key = 'foo' AND xprofile_group_id = 5 AND meta_value = 'bar'";
+		$this->assertSame( "SELECT meta_id FROM {$wpdb->xprofile_groupmeta} WHERE object_type = 'group' AND meta_key = 'foo' AND object_id = 5 AND meta_value = 'bar'", bp_xprofile_filter_meta_query( $q ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_filter_meta_query
+	 */
+	public function test_bp_xprofile_filter_meta_query_insert() {
+		global $wpdb;
+		$q = "INSERT INTO `{$wpdb->xprofile_groupmeta}` (`xprofile_group_id`,`meta_key`,`meta_value`) VALUES (3,'foo','bar')";
+		$this->assertSame( "INSERT INTO `{$wpdb->xprofile_groupmeta}` (`object_type`,`object_id`,`meta_key`,`meta_value`) VALUES ('group',3,'foo','bar')", bp_xprofile_filter_meta_query( $q ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_filter_meta_query
+	 */
+	public function test_bp_xprofile_filter_meta_query_update() {
+		global $wpdb;
+
+		$q = "UPDATE `{$wpdb->xprofile_groupmeta}` SET meta_value = 'bar' WHERE xprofile_group_id = 3 AND meta_key = 'foo'";
+		$this->assertSame( "UPDATE `{$wpdb->xprofile_groupmeta}` SET meta_value = 'bar' WHERE object_type = 'group' AND object_id = 3 AND meta_key = 'foo'", bp_xprofile_filter_meta_query( $q ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_filter_meta_query
+	 */
+	public function test_bp_xprofile_filter_meta_query_delete() {
+		global $wpdb;
+		$q = "DELETE FROM {$wpdb->xprofile_groupmeta} WHERE xprofile_group_id IN(1,2,3)";
+		$this->assertSame( "DELETE FROM {$wpdb->xprofile_groupmeta} WHERE object_type = 'group' AND object_id IN(1,2,3)", bp_xprofile_filter_meta_query( $q ) );
+	}
+}
Index: /tags/2.4.3/tests/phpunit/testcases/xprofile/functions.php
===================================================================
--- /tags/2.4.3/tests/phpunit/testcases/xprofile/functions.php	(revision 10410)
+++ /tags/2.4.3/tests/phpunit/testcases/xprofile/functions.php	(revision 10410)
@@ -0,0 +1,942 @@
+<?php
+
+/**
+ * @group xprofile
+ * @group functions
+ */
+class BP_Tests_XProfile_Functions extends BP_UnitTestCase {
+	public function test_get_hidden_field_types_for_user_loggedout() {
+		$duser = $this->factory->user->create();
+
+		$old_current_user = bp_loggedin_user_id();
+		$this->set_current_user( 0 );
+
+		$this->assertEquals( array( 'friends', 'loggedin', 'adminsonly' ), bp_xprofile_get_hidden_field_types_for_user( $duser, bp_loggedin_user_id() ) );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_get_hidden_field_types_for_user_loggedin() {
+		$duser = $this->factory->user->create();
+		$cuser = $this->factory->user->create();
+
+		$old_current_user = bp_loggedin_user_id();
+		$this->set_current_user( $cuser );
+
+		$this->assertEquals( array( 'friends', 'adminsonly' ), bp_xprofile_get_hidden_field_types_for_user( $duser, bp_loggedin_user_id() ) );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_get_hidden_field_types_for_user_friends() {
+		$duser = $this->factory->user->create();
+		$cuser = $this->factory->user->create();
+		friends_add_friend( $duser, $cuser, true );
+
+		$old_current_user = bp_loggedin_user_id();
+		$this->set_current_user( $cuser );
+
+		$this->assertEquals( array( 'adminsonly' ), bp_xprofile_get_hidden_field_types_for_user( $duser, bp_loggedin_user_id() ) );
+
+		$this->set_current_user( $old_current_user );
+	}
+
+	public function test_get_hidden_field_types_for_user_admin() {
+		$duser = $this->factory->user->create();
+		$cuser = $this->factory->user->create();
+		$this->grant_bp_moderate( $cuser );
+
+		$old_current_user = bp_loggedin_user_id();
+		$this->set_current_user( $cuser );
+
+		$this->assertEquals( array(), bp_xprofile_get_hidden_field_types_for_user( $duser, bp_loggedin_user_id() ) );
+
+		$this->revoke_bp_moderate( $cuser );
+		$this->set_current_user( $old_current_user );
+	}
+
+	/**
+	 * @group bp_xprofile_update_meta
+	 * @ticket BP5180
+	 */
+	public function test_bp_xprofile_update_meta_with_line_breaks() {
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		$meta_value = 'Foo!
+
+Bar!';
+		bp_xprofile_update_meta( $f, 'field', 'linebreak_field', $meta_value );
+		$this->assertEquals( $meta_value, bp_xprofile_get_meta( $f, 'field', 'linebreak_field' ) );
+	}
+
+	/**
+	 * @group bp_xprofile_fullname_field_id
+	 * @group cache
+	 */
+	public function test_bp_xprofile_fullname_field_id_invalidation() {
+		// Prime the cache
+		$id = bp_xprofile_fullname_field_id();
+
+		bp_update_option( 'bp-xprofile-fullname-field-name', 'foo' );
+
+		$this->assertFalse( wp_cache_get( 'fullname_field_id', 'bp_xprofile' ) );
+	}
+
+	/**
+	 * @group xprofile_get_field_visibility_level
+	 */
+	public function test_bp_xprofile_get_field_visibility_level_missing_params() {
+		$this->assertSame( '', xprofile_get_field_visibility_level( 0, 1 ) );
+		$this->assertSame( '', xprofile_get_field_visibility_level( 1, 0 ) );
+	}
+
+	/**
+	 * @group xprofile_get_field_visibility_level
+	 */
+	public function test_bp_xprofile_get_field_visibility_level_user_set() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		bp_xprofile_update_meta( $f, 'field', 'default_visibility', 'adminsonly' );
+		bp_xprofile_update_meta( $f, 'field', 'allow_custom_visibility', 'allowed' );
+
+		xprofile_set_field_visibility_level( $f, $u, 'loggedin' );
+
+		$this->assertSame( 'loggedin', xprofile_get_field_visibility_level( $f, $u ) );
+	}
+
+	/**
+	 * @group xprofile_get_field_visibility_level
+	 */
+	public function test_bp_xprofile_get_field_visibility_level_user_unset() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		bp_xprofile_update_meta( $f, 'field', 'default_visibility', 'adminsonly' );
+		bp_xprofile_update_meta( $f, 'field', 'allow_custom_visibility', 'allowed' );
+
+		$this->assertSame( 'adminsonly', xprofile_get_field_visibility_level( $f, $u ) );
+
+	}
+
+	/**
+	 * @group xprofile_get_field_visibility_level
+	 */
+	public function test_bp_xprofile_get_field_visibility_level_admin_override() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+		) );
+
+		bp_xprofile_update_meta( $f, 'field', 'default_visibility', 'adminsonly' );
+		bp_xprofile_update_meta( $f, 'field', 'allow_custom_visibility', 'disabled' );
+
+		xprofile_set_field_visibility_level( $f, $u, 'loggedin' );
+
+		$this->assertSame( 'adminsonly', xprofile_get_field_visibility_level( $f, $u ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_delete_meta
+	 */
+	public function test_bp_xprofile_delete_meta_empty_object_id() {
+		$this->assertFalse( bp_xprofile_delete_meta( '', 'group' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_delete_meta
+	 */
+	public function test_bp_xprofile_delete_meta_empty_object_type() {
+		$this->assertFalse( bp_xprofile_delete_meta( 1, '' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_delete_meta
+	 */
+	public function test_bp_xprofile_delete_meta_illegal_object_type() {
+		$this->assertFalse( bp_xprofile_delete_meta( 1, 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_delete_meta
+	 * @ticket BP5399
+	 */
+	public function test_bp_xprofile_delete_meta_illegal_characters() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_update_meta( $g, 'group', 'foo', 'bar' );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+
+		$krazy_key = ' f!@#$%^o *(){}o?+';
+		bp_xprofile_delete_meta( $g, 'group', $krazy_key );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_delete_meta
+	 * @ticket BP5399
+	 */
+	public function test_bp_xprofile_delete_meta_trim_meta_value() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_update_meta( $g, 'group', 'foo', 'bar' );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+
+		bp_xprofile_delete_meta( $g, 'group', 'foo', ' bar  ' );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_delete_meta
+	 */
+	public function test_bp_xprofile_delete_meta_meta_value_match() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_update_meta( $g, 'group', 'foo', 'bar' );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+		$this->assertTrue( bp_xprofile_delete_meta( $g, 'group', 'foo', 'bar' ) );
+		$this->assertSame( '', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_delete_meta
+	 */
+	public function test_bp_xprofile_delete_meta_delete_all() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_update_meta( $g, 'group', 'foo', 'bar' );
+		bp_xprofile_update_meta( $g, 'group', 'foo2', 'bar' );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo2' ) );
+
+		$this->assertTrue( bp_xprofile_delete_meta( $g, 'group' ) );
+
+		$this->assertSame( '', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+		$this->assertSame( '', bp_xprofile_get_meta( $g, 'group', 'foo2' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_delete_meta
+	 */
+	public function test_bp_xprofile_delete_meta_with_delete_all_but_no_meta_key() {
+		// With no meta key, don't delete for all items - just delete
+		// all for a single item
+		$g1 = $this->factory->xprofile_group->create();
+		$g2 = $this->factory->xprofile_group->create();
+		bp_xprofile_add_meta( $g1, 'group', 'foo', 'bar' );
+		bp_xprofile_add_meta( $g1, 'group', 'foo1', 'bar1' );
+		bp_xprofile_add_meta( $g2, 'group', 'foo', 'bar' );
+		bp_xprofile_add_meta( $g2, 'group', 'foo1', 'bar1' );
+
+		$this->assertTrue( bp_xprofile_delete_meta( $g1, 'group', '', '', true ) );
+		$this->assertEmpty( bp_xprofile_get_meta( $g1, 'group' ) );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g2, 'group', 'foo' ) );
+		$this->assertSame( 'bar1', bp_xprofile_get_meta( $g2, 'group', 'foo1' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_delete_meta
+	 */
+	public function test_bp_xprofile_delete_meta_with_delete_all() {
+		// With no meta key, don't delete for all items - just delete
+		// all for a single item
+		$g1 = $this->factory->xprofile_group->create();
+		$g2 = $this->factory->xprofile_group->create();
+		bp_xprofile_add_meta( $g1, 'group', 'foo', 'bar' );
+		bp_xprofile_add_meta( $g1, 'group', 'foo1', 'bar1' );
+		bp_xprofile_add_meta( $g2, 'group', 'foo', 'bar' );
+		bp_xprofile_add_meta( $g2, 'group', 'foo1', 'bar1' );
+
+		$this->assertTrue( bp_xprofile_delete_meta( $g1, 'group', 'foo', '', true ) );
+		$this->assertSame( '', bp_xprofile_get_meta( $g1, 'group', 'foo' ) );
+		$this->assertSame( '', bp_xprofile_get_meta( $g2, 'group', 'foo' ) );
+		$this->assertSame( 'bar1', bp_xprofile_get_meta( $g1, 'group', 'foo1' ) );
+		$this->assertSame( 'bar1', bp_xprofile_get_meta( $g2, 'group', 'foo1' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_get_meta
+	 */
+	public function test_bp_xprofile_get_meta_empty_object_id() {
+		$this->assertFalse( bp_xprofile_get_meta( 0, 'group' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_get_meta
+	 */
+	public function test_bp_xprofile_get_meta_empty_object_type() {
+		$this->assertFalse( bp_xprofile_get_meta( 1, '' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_get_meta
+	 */
+	public function test_bp_xprofile_get_meta_illegal_object_type() {
+		$this->assertFalse( bp_xprofile_get_meta( 1, 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_get_meta
+	 */
+	public function test_bp_xprofile_get_meta_no_meta_key() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_update_meta( $g, 'group', 'foo', 'bar' );
+		bp_xprofile_update_meta( $g, 'group', 'foo2', 'bar' );
+
+		$expected = array(
+			'foo' => array(
+				'bar',
+			),
+			'foo2' => array(
+				'bar',
+			),
+		);
+		$this->assertSame( $expected, bp_xprofile_get_meta( $g, 'group' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_get_meta
+	 */
+	public function test_bp_xprofile_get_meta_single_true() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_add_meta( $g, 'group', 'foo', 'bar' );
+		bp_xprofile_add_meta( $g, 'group', 'foo', 'baz' );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo' ) ); // default is true
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo', true ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_get_meta
+	 */
+	public function test_bp_xprofile_get_meta_single_false() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_add_meta( $g, 'group', 'foo', 'bar' );
+		bp_xprofile_add_meta( $g, 'group', 'foo', 'baz' );
+		$this->assertSame( array( 'bar', 'baz' ), bp_xprofile_get_meta( $g, 'group', 'foo', false ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_get_meta
+	 */
+	public function test_bp_xprofile_get_meta_no_meta_key_no_results() {
+		$g = $this->factory->xprofile_group->create();
+
+		$expected = array();
+		$this->assertSame( $expected, bp_xprofile_get_meta( $g, 'group' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 */
+	public function test_bp_xprofile_update_meta_no_object_id() {
+		$this->assertFalse( bp_xprofile_update_meta( 0, 'group', 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 */
+	public function test_bp_xprofile_update_meta_no_object_type() {
+		$this->assertFalse( bp_xprofile_update_meta( 1, '', 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 */
+	public function test_bp_xprofile_update_meta_illegal_object_type() {
+		$this->assertFalse( bp_xprofile_update_meta( 1, 'foo', 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 * @ticket BP5399
+	 */
+	public function test_bp_xprofile_update_meta_illegal_characters() {
+		$g = $this->factory->xprofile_group->create();
+		$krazy_key = ' f!@#$%^o *(){}o?+';
+		bp_xprofile_update_meta( $g, 'group', $krazy_key, 'bar' );
+		$this->assertSame( '', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 */
+	public function test_bp_xprofile_update_meta_stripslashes() {
+		$g = $this->factory->xprofile_group->create();
+		$v = "Totally \'tubular\'";
+		bp_xprofile_update_meta( $g, 'group', 'foo', $v );
+		$this->assertSame( stripslashes( $v ), bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 */
+	public function test_bp_xprofile_update_meta_empty_value_delete() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_update_meta( $g, 'group', 'foo', 'bar' );
+		bp_xprofile_update_meta( $g, 'group', 'foo', '' );
+		$this->assertSame( '', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 */
+	public function test_bp_xprofile_update_meta_new() {
+		$g = $this->factory->xprofile_group->create();
+		$this->assertSame( '', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+		$this->assertNotEmpty( bp_xprofile_update_meta( $g, 'group', 'foo', 'bar' ) );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 */
+	public function test_bp_xprofile_update_meta_existing() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_update_meta( $g, 'group', 'foo', 'bar' );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+		$this->assertTrue( bp_xprofile_update_meta( $g, 'group', 'foo', 'baz' ) );
+		$this->assertSame( 'baz', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 */
+	public function test_bp_xprofile_update_meta_same_value() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_update_meta( $g, 'group', 'foo', 'bar' );
+		$this->assertSame( 'bar', bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+		$this->assertFalse( bp_xprofile_update_meta( $g, 'group', 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 */
+	public function test_bp_xprofile_update_meta_prev_value() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_add_meta( $g, 'group', 'foo', 'bar' );
+
+		// In earlier versions of WordPress, bp_activity_update_meta()
+		// returns true even on failure. However, we know that in these
+		// cases the update is failing as expected, so we skip this
+		// assertion just to keep our tests passing
+		// See https://core.trac.wordpress.org/ticket/24933
+		if ( version_compare( $GLOBALS['wp_version'], '3.7', '>=' ) ) {
+			$this->assertFalse( bp_xprofile_update_meta( $g, 'group', 'foo', 'bar2', 'baz' ) );
+		}
+
+		$this->assertTrue( bp_xprofile_update_meta( $g, 'group', 'foo', 'bar2', 'bar' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 * @ticket BP5919
+	 */
+	public function test_bp_xprofile_update_meta_where_sql_filter_keywords_are_in_quoted_value() {
+		$g = $this->factory->xprofile_group->create();
+		$value = "SELECT object_id FROM wp_bp_xprofile_groups WHERE \"foo\" VALUES (foo = 'bar'";
+		bp_xprofile_add_meta( $g, 'group', 'foo', 'bar' );
+		bp_xprofile_update_meta( $g, 'group', 'foo', $value );
+		$this->assertSame( $value, bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_update_meta
+	 * @ticket BP5919
+	 */
+	public function test_bp_xprofile_update_meta_where_meta_id_is_in_quoted_value() {
+		$g = $this->factory->xprofile_group->create();
+		$value = "foo meta_id bar";
+		bp_xprofile_add_meta( $g, 'group', 'foo', 'bar' );
+		bp_xprofile_update_meta( $g, 'group', 'foo', $value );
+		$this->assertSame( $value, bp_xprofile_get_meta( $g, 'group', 'foo' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_add_meta
+	 */
+	public function test_bp_xprofile_add_meta_no_meta_key() {
+		$this->assertFalse( bp_xprofile_add_meta( 1, 'group', '', 'bar' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_add_meta
+	 */
+	public function test_bp_xprofile_add_meta_empty_object_id() {
+		$this->assertFalse( bp_xprofile_add_meta( 0, 'group', 'foo', 'bar' ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_add_meta
+	 */
+	public function test_bp_xprofile_add_meta_existing_unique() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_add_meta( $g, 'group', 'foo', 'bar' );
+		$this->assertFalse( bp_xprofile_add_meta( $g, 'group', 'foo', 'baz', true ) );
+	}
+
+	/**
+	 * @group xprofilemeta
+	 * @group bp_xprofile_add_meta
+	 */
+	public function test_bp_xprofile_add_meta_existing_not_unique() {
+		$g = $this->factory->xprofile_group->create();
+		bp_xprofile_add_meta( $g, 'group', 'foo', 'bar' );
+		$this->assertNotEmpty( bp_xprofile_add_meta( $g, 'group', 'foo', 'baz' ) );
+	}
+
+	/**
+	 * @group bp_get_member_profile_data
+	 */
+	public function test_bp_get_member_profile_data_inside_loop() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'name' => 'Neato',
+		) );
+		xprofile_set_field_data( $f, $u, 'foo' );
+
+		if ( bp_has_members() ) : while ( bp_members() ) : bp_the_member();
+		$found = bp_get_member_profile_data( array(
+			'user_id' => $u,
+			'field' => 'Neato',
+		) );
+		endwhile; endif;
+
+		// Cleanup
+		unset( $GLOBALS['members_template'] );
+
+		$this->assertSame( 'foo', $found );
+	}
+	/**
+	 * @group bp_get_member_profile_data
+	 */
+	public function test_bp_get_member_profile_data_outside_of_loop() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'name' => 'Kewl',
+		) );
+		xprofile_set_field_data( $f, $u, 'foo' );
+
+		$found = bp_get_member_profile_data( array(
+			'user_id' => $u,
+			'field' => 'Kewl',
+		) );
+
+		$this->assertSame( 'foo', $found );
+	}
+
+	/**
+	 * @group xprofile_set_field_data
+	 */
+	public function test_get_field_data_integer_zero() {
+		$u = $this->factory->user->create();
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'number',
+			'name' => 'Pens',
+		) );
+		xprofile_set_field_data( $f, $u, 0 );
+
+		$this->assertEquals( 0, xprofile_get_field_data( 'Pens', $u ) );
+	}
+
+	/**
+	 * @group xprofile_set_field_data
+	 * @ticket BP5836
+	 */
+	public function test_xprofile_sync_bp_profile_new_user() {
+		$post_vars = $_POST;
+
+		$_POST = array(
+			'user_login' => 'foobar',
+			'pass1'      => 'password',
+			'pass2'      => 'password',
+			'role'       => 'subscriber',
+			'email'      => 'foo@bar.com',
+			'first_name' => 'Foo',
+			'last_name'  => 'Bar',
+		);
+
+		$id = add_user();
+
+		$display_name = 'Bar Foo';
+
+		$_POST = array(
+			'display_name' => $display_name,
+			'email' => 'foo@bar.com',
+			'nickname' => 'foobar',
+		);
+
+		$id = edit_user( $id );
+
+		// clean up post vars
+		$_POST = $post_vars;
+
+		$this->assertEquals( $display_name, xprofile_get_field_data( bp_xprofile_fullname_field_id(), $id ) );
+	}
+
+	/**
+	 * @group xprofile_insert_field
+	 */
+	public function test_xprofile_insert_field_type_option() {
+		$g = $this->factory->xprofile_group->create();
+		$parent = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'selectbox',
+			'name' => 'Parent',
+		) );
+
+		$f = xprofile_insert_field( array(
+			'field_group_id' => $g,
+			'parent_id' => $parent,
+			'type' => 'option',
+			'name' => 'Option 1',
+			'field_order' => 5,
+		) );
+
+		$this->assertNotEmpty( $f );
+	}
+
+	/**
+	 * @group xprofile_insert_field
+	 * @ticket BP6354
+	 */
+	public function test_xprofile_insert_field_should_process_falsey_values_for_boolean_params_on_existing_fields() {
+		$g = $this->factory->xprofile_group->create();
+		$f = xprofile_insert_field( array(
+			'field_group_id' => $g,
+			'type' => 'textbox',
+			'name' => 'Foo',
+			'is_required' => true,
+			'can_delete' => true,
+			'is_default_option' => true,
+			'parent_id' => 13,
+			'field_order' => 5,
+			'option_order' => 8,
+			'description' => 'foo',
+			'order_by' => 'custom',
+		) );
+
+		$this->assertNotEmpty( $f );
+
+		$field = new BP_XProfile_Field( $f );
+		$this->assertEquals( 1, $field->is_required );
+		$this->assertEquals( 1, $field->can_delete );
+		$this->assertEquals( 1, $field->is_default_option );
+		$this->assertEquals( 13, $field->parent_id );
+		$this->assertEquals( 5, $field->field_order );
+		$this->assertEquals( 8, $field->option_order );
+		$this->assertEquals( 'foo', $field->description );
+		$this->assertEquals( 'custom', $field->order_by );
+
+		$f = xprofile_insert_field( array(
+			'field_group_id' => $g,
+			'type' => 'textbox',
+			'name' => 'Foo',
+			'is_required' => false,
+			'can_delete' => false,
+			'is_default_option' => false,
+			'parent_id' => 0,
+			'field_order' => 0,
+			'option_order' => 0,
+			'description' => '',
+			'order_by' => '',
+		) );
+
+		$this->assertNotEmpty( $f );
+
+		$field = new BP_XProfile_Field( $f );
+		$this->assertEquals( 0, $field->is_required );
+		$this->assertEquals( 0, $field->can_delete );
+		$this->assertEquals( 0, $field->is_default_option );
+		$this->assertEquals( 0, $field->parent_id );
+		$this->assertEquals( 0, $field->field_order );
+		$this->assertEquals( 0, $field->option_order );
+		$this->assertEquals( '', $field->description );
+		$this->assertEquals( '', $field->order_by );
+	}
+
+	/**
+	 * @group xprofile_insert_field
+	 */
+	public function test_xprofile_insert_field_type_option_option_order() {
+		$g = $this->factory->xprofile_group->create();
+		$parent = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'selectbox',
+			'name' => 'Parent',
+		) );
+
+		$f = xprofile_insert_field( array(
+			'field_group_id' => $g,
+			'parent_id' => $parent,
+			'type' => 'option',
+			'name' => 'Option 1',
+			'option_order' => 5,
+		) );
+
+		$field = new BP_XProfile_Field( $f );
+
+		$this->assertEquals( 5, $field->option_order );
+	}
+
+	/**
+	 * @group xprofile_insert_field
+	 * @ticket BP6137
+	 */
+	public function test_xprofile_insert_field_should_set_is_default_option_to_false_for_new_option() {
+		$g = $this->factory->xprofile_group->create();
+		$parent = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'selectbox',
+			'name' => 'Parent',
+		) );
+
+		$f = xprofile_insert_field( array(
+			'field_group_id' => $g,
+			'parent_id' => $parent,
+			'type' => 'option',
+			'name' => 'Option 1',
+			'field_order' => 5,
+			'is_default_option' => false,
+		) );
+
+		$this->assertNotEmpty( $f );
+		$field = new BP_XProfile_Field( $f );
+		$this->assertEquals( 0, $field->is_default_option );
+	}
+
+	/**
+	 * @group xprofile_insert_field
+	 * @ticket BP6137
+	 */
+	public function test_xprofile_insert_field_should_set_is_default_option_to_true_for_new_option() {
+		$g = $this->factory->xprofile_group->create();
+		$parent = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'selectbox',
+			'name' => 'Parent',
+		) );
+
+		$f = xprofile_insert_field( array(
+			'field_group_id' => $g,
+			'parent_id' => $parent,
+			'type' => 'option',
+			'name' => 'Option 1',
+			'field_order' => 5,
+			'is_default_option' => true,
+		) );
+
+		$this->assertNotEmpty( $f );
+		$field = new BP_XProfile_Field( $f );
+		$this->assertEquals( 1, $field->is_default_option );
+	}
+
+	/**
+	 * @group xprofile_insert_field
+	 * @ticket BP6137
+	 */
+	public function test_xprofile_insert_field_should_set_is_default_option_to_false_for_existing_option() {
+		$g = $this->factory->xprofile_group->create();
+		$parent = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'selectbox',
+			'name' => 'Parent',
+		) );
+
+		$f = xprofile_insert_field( array(
+			'field_group_id' => $g,
+			'parent_id' => $parent,
+			'type' => 'option',
+			'name' => 'Option 1',
+			'field_order' => 5,
+			'is_default_option' => true,
+		) );
+
+		$this->assertNotEmpty( $f );
+		$field = new BP_XProfile_Field( $f );
+		$this->assertEquals( 1, $field->is_default_option );
+
+		$f = xprofile_insert_field( array(
+			'field_id' => $f,
+			'field_group_id' => $g,
+			'type' => 'textbox',
+			'is_default_option' => false,
+		) );
+
+		$field2 = new BP_XProfile_Field( $f );
+		$this->assertEquals( 0, $field2->is_default_option );
+	}
+
+	/**
+	 * @group xprofile_insert_field
+	 * @ticket BP6137
+	 */
+	public function test_xprofile_insert_field_should_set_is_default_option_to_true_for_existing_option() {
+		$g = $this->factory->xprofile_group->create();
+		$parent = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'selectbox',
+			'name' => 'Parent',
+		) );
+
+		$f = xprofile_insert_field( array(
+			'field_group_id' => $g,
+			'parent_id' => $parent,
+			'type' => 'option',
+			'name' => 'Option 1',
+			'field_order' => 5,
+			'is_default_option' => false,
+		) );
+
+		$this->assertNotEmpty( $f );
+		$field = new BP_XProfile_Field( $f );
+		$this->assertEquals( 0, $field->is_default_option );
+
+		$f = xprofile_insert_field( array(
+			'field_id' => $f,
+			'field_group_id' => $g,
+			'type' => 'textbox',
+			'is_default_option' => true,
+		) );
+
+		$field2 = new BP_XProfile_Field( $f );
+
+		$this->assertEquals( 1, $field2->is_default_option );
+	}
+
+	/**
+	 * @group xprofile_update_field_group_position
+	 * @group bp_profile_get_field_groups
+	 */
+	public function test_bp_profile_get_field_groups_update_position() {
+		$g1 = $this->factory->xprofile_group->create();
+		$g2 = $this->factory->xprofile_group->create();
+		$g3 = $this->factory->xprofile_group->create();
+
+		// prime the cache
+		bp_profile_get_field_groups();
+
+		// switch the field group positions for the last two groups
+		xprofile_update_field_group_position( $g2, 3 );
+		xprofile_update_field_group_position( $g3, 2 );
+
+		// now refetch field groups
+		$field_groups = bp_profile_get_field_groups();
+
+		// assert!
+		$this->assertEquals( array( 1, $g1, $g3, $g2 ), wp_list_pluck( $field_groups, 'id' ) );
+	}
+
+	/**
+	 * @ticket BP6638
+	 */
+	public function test_xprofile_get_field_should_return_bp_xprofile_field_object() {
+		global $wpdb;
+
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'selectbox',
+			'name' => 'Foo',
+		) );
+
+		$field = xprofile_get_field( $f );
+
+		$this->assertTrue( $field instanceof BP_XProfile_Field );
+	}
+
+	/**
+	 * @ticket BP6638
+	 * @group cache
+	 */
+	public function test_xprofile_get_field_should_prime_field_cache() {
+		global $wpdb;
+
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'selectbox',
+			'name' => 'Foo',
+		) );
+
+		$num_queries = $wpdb->num_queries;
+
+		// Prime the cache.
+		$field_1 = xprofile_get_field( $f );
+		$num_queries++;
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+
+		// No more queries.
+		$field_2 = xprofile_get_field( $f );
+		$this->assertEquals( $field_1, $field_2 );
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+	}
+
+	/**
+	 * @ticket BP5625
+	 */
+	public function test_bp_xprofie_is_richtext_enabled_for_field_should_default_to_true_for_textareas() {
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'textarea',
+		) );
+
+		$this->assertTrue( bp_xprofile_is_richtext_enabled_for_field( $f ) );
+	}
+
+	/**
+	 * @ticket BP5625
+	 */
+	public function test_bp_xprofie_is_richtext_enabled_for_field_should_default_to_false_for_non_textareas() {
+		$g = $this->factory->xprofile_group->create();
+		$f = $this->factory->xprofile_field->create( array(
+			'field_group_id' => $g,
+			'type' => 'radio',
+		) );
+
+		$this->assertFalse( bp_xprofile_is_richtext_enabled_for_field( $f ) );
+	}
+}
