diff --git src/bp-attachments/bp-attachments-actions.php src/bp-attachments/bp-attachments-actions.php
index e69de29..df07840 100644
--- src/bp-attachments/bp-attachments-actions.php
+++ src/bp-attachments/bp-attachments-actions.php
@@ -0,0 +1,347 @@
+<?php
+/**
+ * Attachments Actions.
+ *
+ * @package BuddyPress
+ * @subpackage Attachments Actions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) or 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.
+ *
+ * @since  BuddyPress (2.2.0)
+ *
+ * @package BuddyPress
+ * @subpackage Attachments Actions
+ *
+ * @uses   bp_is_user_change_avatar()
+ * @uses   bp_is_action_variable()
+ * @uses   bp_is_my_profile()
+ * @uses   bp_current_user_can()
+ * @uses   bp_attachments_delete_existing_avatar()
+ * @uses   bp_displayed_user_id()
+ * @uses   bp_core_add_message()
+ * @uses   bp_core_redirect()
+ */
+function bp_attachments_action_xprofile_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_attachments_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', 'bp_attachments_action_xprofile_delete_avatar' );
+
+/**
+ * Handle the display of a new group's Avatar create step.
+ *
+ * @since  BuddyPress (2.2.0)
+ *
+ * @package BuddyPress
+ * @subpackage Attachments Actions
+ *
+ * @param  int $group_id the new group id
+ * @uses   bp_is_active()
+ * @uses   bp_get_groups_current_create_step()
+ * @uses   buddypress()
+ * @uses   bp_get_new_group_id()
+ * @uses   bp_attachments_avatar_handle_upload()
+ * @uses   is_wp_error()
+ * @uses   bp_core_add_message()
+ * @uses   bp_attachments_avatar_handle_crop()
+ */
+function bp_attachments_action_group_avatar_create_step( $group_id = 0 ) {
+	if ( ! bp_is_active( 'groups' ) && 'group-avatar' != bp_get_groups_current_create_step() && ! isset( $_POST['upload'] ) ) {
+		return;
+	}
+
+	$bp = buddypress();
+
+	if ( ! isset( $bp->avatar_admin ) ) {
+		$bp->avatar_admin = new stdClass();
+	}
+
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_new_group_id();
+	}
+
+	if ( ! empty( $_FILES ) && isset( $_POST['upload'] ) ) {
+
+		// Pass the file to the avatar upload handler
+		$uploaded_avatar = bp_attachments_avatar_handle_upload( $_FILES, 'groups', $group_id );
+
+		if ( is_wp_error( $uploaded_avatar ) ) {
+			bp_core_add_message( $uploaded_avatar->get_error_message(), 'error' );
+
+		// Go to crop step
+		} else {
+			// Avoid some error messages to be output if
+			// everything is ok.
+			$bp->template_message   = false;
+			$bp->avatar_admin->step = 'crop-image';
+
+			// Make sure we include the jQuery jCrop file for image cropping
+			add_action( 'wp_print_scripts', 'bp_attachments_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'] ) ) {
+
+		$args = array(
+			'object'        => 'group',
+			'avatar_dir'    => 'avatar_group_dir',
+			'item_id'       => $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_attachments_avatar_handle_crop( $args ) ) {
+			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' ) );
+		}
+	}
+}
+add_action( 'bp_groups_avatar_create_step', 'bp_attachments_action_group_avatar_create_step', 10, 1 );
+
+/**
+ * Add the specific templates needed by BP Attachments Editor
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachment_load_backbone_tmpl() {
+	?>
+	<script type="text/html" id="tmpl-bp-attachment-details">
+		<h3>
+			<?php _e('Attachment Details'); ?>
+
+			<span class="settings-save-status">
+				<span class="spinner"></span>
+				<span class="saved"><?php esc_html_e('Saved.'); ?></span>
+			</span>
+		</h3>
+		<div class="attachment-info">
+			<div class="thumbnail">
+				<# if ( data.uploading ) { #>
+					<div class="media-progress-bar"><div></div></div>
+				<# } else if ( 'image' === data.type ) { #>
+					<img src="{{ data.size.url }}" draggable="false" />
+				<# } else { #>
+					<img src="{{ data.icon }}" class="icon" draggable="false" />
+				<# } #>
+			</div>
+			<div class="details">
+				<div class="filename">{{ data.filename }}</div>
+				<div class="uploaded">{{ data.dateFormatted }}</div>
+
+				<# if ( 'image' === data.type && ! data.uploading ) { #>
+					<# if ( data.width && data.height ) { #>
+						<div class="dimensions">{{ data.width }} &times; {{ data.height }}</div>
+					<# } #>
+
+					<# if ( data.can.save ) { #>
+						<a class="edit-bp-attachment" href="{{ data.editLink }}"><?php _e( 'Edit Options', 'buddypress' ); ?></a>
+					<# } #>
+				<# } #>
+
+				<# if ( data.fileLength ) { #>
+					<div class="file-length"><?php _e( 'Length:' ); ?> {{ data.fileLength }}</div>
+				<# } #>
+
+				<# if ( ! data.uploading && data.can.remove ) { #>
+					<?php if ( MEDIA_TRASH ): ?>
+						<a class="trash-attachment" href="#"><?php _e( 'Trash' ); ?></a>
+					<?php else: ?>
+						<a class="delete-attachment" href="#"><?php _e( 'Delete Permanently' ); ?></a>
+					<?php endif; ?>
+				<# } #>
+
+				<div class="compat-meta">
+					<# if ( data.compat && data.compat.meta ) { #>
+						{{{ data.compat.meta }}}
+					<# } #>
+				</div>
+			</div>
+		</div>
+
+		<# var maybeReadOnly = data.can.save || data.allowLocalEdits ? '' : 'readonly'; #>
+			<label class="setting" data-setting="title">
+				<span><?php _e('Title'); ?></span>
+				<input type="text" value="{{ data.title }}" {{ maybeReadOnly }} />
+			</label>
+			<label class="setting" data-setting="description">
+				<span><?php _e('Description'); ?></span>
+				<textarea {{ maybeReadOnly }}>{{ data.description }}</textarea>
+			</label>
+	</script>
+
+	<script type="text/html" id="tmpl-avatar">
+		<div class="avatar-preview">
+			<# if ( data.uploading ) { #>
+				<div class="media-progress-bar"><div></div></div>
+			<# } else { #>
+			<div class="resized">
+				<div class="centered">
+					<img src="{{ data.size.url }}" draggable="false" id="avatar-to-crop" />
+				</div>
+			</div>
+			<# } #>
+		</div>
+	</script>
+
+	<script type="text/html" id="tmpl-bp-avatar-details">
+		<h3>
+			<?php _e('Avatar preview', 'buddypress'); ?>
+		</h3>
+		<div class="attachment-info">
+			<div class="avatar-thumb" style="width:<?php echo bp_core_avatar_full_width();?>px;height:<?php echo bp_core_avatar_full_height();?>px;overflow:hidden">
+				<img draggable="false" id="avatar-crop-preview" />
+			</div>
+		</div>
+	</script>
+
+	<script type="text/html" id="tmpl-bp-preview">
+		<# if ( data.id ) { #>
+			<div class="centered">
+				<img src="{{ data.img }}" style="max-width:100%"/>
+			</div>
+		<# } #>
+	</script>
+	<?php
+}
+add_action( 'print_media_templates', 'bp_attachment_load_backbone_tmpl' );
+
+
+/**
+ * Handle uploads if no-js
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_catch_upload() {
+
+	if ( ! empty( $_POST['bp_attachment_upload'] ) ) {
+
+		check_admin_referer( 'bp_attachments_upload', 'bp_attachments_upload_nonce' );
+
+		$redirect = $_POST['_wp_http_referer'];
+
+		$file_name = 'bp_attachment_file';
+
+		if ( ! empty( $_POST['file_data'] ) ) {
+			$file_name = $_POST['file_data'];
+		}
+
+		if ( ! empty( $_FILES[ $file_name ] ) ) {
+
+			$args = array();
+
+			if ( ! empty( $_POST['item_id'] ) )
+				$args['item_id'] = absint( $_POST['item_id'] );
+
+			if ( ! empty( $_POST['item_type'] ) ) {
+				$args['item_type'] = $_POST['item_type'];
+			} else {
+				$args['item_type'] = 'attachment';
+			}
+
+			if ( ! empty( $_POST['component'] ) )
+				$args['component'] = $_POST['component'];
+
+			if ( ! empty( $_POST['action'] ) )
+				$args['action'] = $_POST['action'];
+
+			// We don't categorized members as a bp_component term
+			if ( 'members' == $args['component'] ) {
+				unset( $args['component'] );
+			}
+
+			$cap_args = false;
+
+			if ( ! empty( $args['component'] ) ) {
+				$cap_args = array( 'component' => $args['component'], 'item_id' => $args['item_id'] );
+			}
+
+			// capability check
+			if ( ! bp_attachments_current_user_can( 'publish_bp_attachments', $cap_args ) ) {
+				bp_core_add_message( __( 'Error: you are not allowed to create this attachment.', 'buddypress' ), 'error' );
+				bp_core_redirect( $redirect );
+			}
+
+			// Dealing with an Avatar
+			if ( 'avatar' == $args['item_type'] ) {
+				$avatar = bp_attachments_avatar_handle_upload( $_FILES, $args['component'], $args['item_id'] );
+
+				if ( ! is_wp_error( $avatar ) ) {
+					switch ( $component ) {
+						case 'groups' :
+							$object     = 'group';
+							$avatar_dir = 'avatar_group_dir';
+							break;
+
+						case 'blogs' :
+							$object     = 'blog';
+							$avatar_dir = 'avatar_blog_dir';
+							break;
+
+						case 'xprofile' :
+						default         :
+							$object     = 'user';
+							$avatar_dir = 'avatar_user_dir';
+							break;
+					}
+
+					$crop = bp_attachments_avatar_handle_crop( array(
+						'object'        => $object,
+						'avatar_dir'    => $avatar_dir,
+						'item_id'       => $args['item_id'],
+						'original_file' => $avatar,
+					) );
+
+					if ( ! empty( $crop ) ) {
+						$attachment_id = $crop;
+					} else {
+						$attachment_id = new WP_Error( 'crop_error', sprintf( __( 'There was an error saving the %s avatar, please try uploading again.', 'buddypress' ), $object ) );
+					}
+				} else {
+					$attachment_id = $avatar;
+				}
+			// Dealing with an Attachment
+			} else {
+				$attachment_id = bp_attachments_handle_upload( $args );
+			}
+
+			if ( is_wp_error( $attachment_id ) ) {
+				bp_core_add_message( sprintf( __( 'Error: %s', 'buddypress' ), $attachment_id->get_error_message() ), 'error' );
+			} else {
+				bp_core_add_message( sprintf( __( '%s successfully uploaded', 'buddypress' ), ucfirst( $args['item_type'] ) ) );
+			}
+
+			bp_core_redirect( $redirect );
+		}
+	}
+}
+add_action( 'bp_actions', 'bp_attachments_catch_upload' );
diff --git src/bp-attachments/bp-attachments-admin.php src/bp-attachments/bp-attachments-admin.php
index e69de29..7c3f41b 100644
--- src/bp-attachments/bp-attachments-admin.php
+++ src/bp-attachments/bp-attachments-admin.php
@@ -0,0 +1,186 @@
+<?php
+/**
+ * Attachments Admin.
+ *
+ * A media component, for others !
+ *
+ * @package BuddyPress
+ * @subpackage Attachments Administration
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) or exit;
+
+if ( ! class_exists( 'BP_Attachment_Admin' ) ) :
+/**
+ * Load BP Attachments admin area.
+ *
+ * @package BuddyPress
+ * @subpackage Attachments Administration
+ *
+ * @since BuddyPress (2.2.0)
+ */
+class BP_Attachments_Admin {
+	/**
+	 * Setup BP Attachments Admin.
+	 *
+	 * @access public
+	 * @since BuddyPress (2.2.0)
+	 *
+	 * @uses buddypress() to get BuddyPress main instance
+	 */
+	public static function register_attachments_admin() {
+		if( ! is_admin() )
+			return;
+
+		$bp = buddypress();
+
+		if( empty( $bp->attachments->admin ) ) {
+			$bp->attachments->admin = new self;
+		}
+
+		return $bp->attachments->admin;
+	}
+
+	/**
+	 * Constructor method.
+	 *
+	 * @access public
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function __construct() {
+		$this->setup_actions();
+	}
+
+	/**
+	 * Set admin-related actions and filters.
+	 *
+	 * @access private
+	 * @since BuddyPress (2.2.0)
+	 */
+	private function setup_actions() {
+		add_action( 'bp_groups_admin_meta_boxes',        array( $this, 'group_avatar' )        );
+		add_action( 'bp_members_admin_xprofile_metabox', array( $this, 'user_avatar'  ), 10, 2 );
+	}
+
+	/**
+	 * Add a metabox to edit Group's Avatar
+	 *
+	 * @access public
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function group_avatar() {
+		add_meta_box(
+			'bp_groups_avatar',
+			_x( 'Avatar', 'group admin edit screen', 'buddypress' ),
+			array( &$this, 'group_avatar_metabox' ),
+			get_current_screen()->id,
+			'side',
+			'low'
+		);
+	}
+
+	/**
+	 * Displays the metabox to edit Group's Avatar
+	 *
+	 * @access public
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function group_avatar_metabox( $item = null ) {
+		if ( empty( $item ) )
+			return;
+		?>
+		<div id="groups-avatar">
+		<?php
+		// Displays the current avatar and a link to delete it.
+		if ( bp_get_group_has_avatar( $item->id ) ) {
+			echo bp_core_fetch_avatar( array( 'item_id' => $item->id, 'object' => 'group', 'type' => 'full' ) );?>
+			<p><a href="#" id="remove-groups-avatar"><?php esc_html_e( 'Remove Avatar', 'buddypress' ); ?></a></p>
+			<?php
+		}
+		?>
+        </div>
+        <?php
+        // Displays the button to Change the avatar.
+        bp_attachments_browser( 'bp-avatar-upload', array(
+        	'item_id'         => $item->id,
+        	'component'       => 'groups',
+        	'item_type'       => 'avatar',
+        	'btn_caption'     => __( 'Edit Avatar', 'buddypress' ),
+        	'multi_selection' => false,
+        	'action'          => 'bp_avatar_upload',
+        	'file_data_name'  => 'file',
+        	'btn_class'       => 'attachments-new-avatar'
+        ) );
+    }
+
+    /**
+	 * Add a metabox to edit User's Avatar
+	 *
+	 * @access public
+	 * @since BuddyPress (2.2.0)
+	 */
+    public function user_avatar( $user_id = 0, $screen_id = '' ) {
+    	// Set the screen ID if none was passed
+		if ( empty( $screen_id ) ) {
+			$screen_id = buddypress()->members->admin->user_page;
+		}
+
+		if ( bp_attachments_avatar_is_enabled() ) {
+			// Avatar Metabox
+			add_meta_box(
+				'bp_xprofile_avatar',
+				_x( 'Profile Photo', 'attachments user-admin edit screen', 'buddypress' ),
+				array( $this, 'user_avatar_metabox' ),
+				$screen_id,
+				'side',
+				'low'
+			);
+		}
+    }
+
+    /**
+	 * Displays the metabox to edit User's Avatar
+	 *
+	 * @access public
+	 * @since BuddyPress (2.2.0)
+	 */
+    public function user_avatar_metabox( $user = null ) {
+    	if ( empty( $user->ID ) ) {
+			return;
+		} ?>
+
+		<div id="xprofile-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 ) ) : ?>
+
+				<p><a href="#" id="remove-xprofile-avatar"><?php esc_html_e( 'Remove Avatar', 'buddypress' ); ?></a></p>
+
+			<?php endif; ?>
+
+		</div>
+		<?php
+        // Displays the button to Change the avatar.
+        bp_attachments_browser( 'bp-avatar-upload', array(
+        	'item_id'         => $user->ID,
+        	'component'       => 'xprofile',
+        	'item_type'       => 'avatar',
+        	'btn_caption'     => __( 'Edit Avatar', 'buddypress' ),
+        	'multi_selection' => false,
+        	'action'          => 'bp_avatar_upload',
+        	'file_data_name'  => 'file',
+        	'btn_class'       => 'attachments-new-avatar'
+        ) );
+    }
+}
+endif; // class_exists check
+
+// Load the BP Attachments admin
+add_action( 'bp_init', array( 'BP_Attachments_Admin','register_attachments_admin' ) );
diff --git src/bp-attachments/bp-attachments-ajax.php src/bp-attachments/bp-attachments-ajax.php
index e69de29..f1021c3 100644
--- src/bp-attachments/bp-attachments-ajax.php
+++ src/bp-attachments/bp-attachments-ajax.php
@@ -0,0 +1,318 @@
+<?php
+/**
+ * Attachments Ajax.
+ *
+ * @package BuddyPress
+ * @subpackage Attachments Ajax
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) or exit;
+
+/**
+ * Upload an avatar from "BP Attachments Editor".
+ *
+ * Avatars are not handled the same way than attachments
+ * 1- no post type "bp_attachment" is created
+ * 2- it doesn't change anything to the way BuddyPress manage avatars
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_avatar_ajax_upload(){
+	//nonce check
+	check_ajax_referer( 'media-form' );
+
+	$item_id = ! empty( $_REQUEST['item_id'] ) ? intval( $_REQUEST['item_id'] ) : null;
+	$component = ! empty( $_REQUEST['component'] ) ? sanitize_title( $_REQUEST['component'] ) : '';
+	$context = ! empty( $_REQUEST['item_type'] ) ? sanitize_title( $_REQUEST['item_type'] ) : '';
+
+	$cap_args = false;
+	if ( ! empty( $component ) ) {
+		$cap_args = array( 'component' => $component, 'item_id' => $item_id );
+	}
+
+	// We don't categorized members as a bp_component term
+	if ( 'members' == $component ) {
+		$cap_args = false;
+	}
+
+	// capability check
+	if ( ! bp_attachments_current_user_can( 'edit_bp_attachments', $cap_args ) ) {
+		wp_die();
+	}
+
+	$avatar = bp_attachments_avatar_handle_upload( $_FILES, $component, $item_id, true );
+
+	if ( is_wp_error( $avatar ) ) {
+		wp_send_json_error( array(
+			'message'  => $avatar->get_error_message(),
+			'filename' => $_FILES['file']['name'],
+		) );
+	}
+
+	wp_send_json_success( $avatar );
+}
+add_action( 'wp_ajax_bp_avatar_upload', 'bp_attachments_avatar_ajax_upload' );
+
+/**
+ * Crop an avatar from "BP Attachments Editor".
+ *
+ * Avatars are not handled the same way than attachments
+ * 1- no post type "bp_attachment" is created
+ * 2- it doesn't change anything to the way BuddyPress manage avatars
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_set_avatar() {
+	$json = ! empty( $_REQUEST['json'] ); // New-style request
+
+	check_ajax_referer( 'bp_attachments_avatar', 'nonce' );
+
+	if ( empty( $_REQUEST['item_id'] ) || empty( $_REQUEST['object'] ) || empty( $_REQUEST['component'] ) ) {
+		wp_die(0);
+	}
+
+	$component = esc_html( $_REQUEST['component'] );
+	$item_id   = absint( $_REQUEST['item_id'] );
+	$object    = esc_html( $_REQUEST['object'] );
+
+	$cap_args = false;
+	if ( ! empty( $component ) ) {
+		$cap_args = array( 'component' => $component, 'item_id' => $item_id );
+	}
+
+	// We don't categorized members as a bp_component term
+	if ( 'members' == $component ) {
+		$cap_args = false;
+	}
+
+	// capability check
+	if ( ! bp_attachments_current_user_can( 'edit_bp_attachments', $cap_args ) ) {
+		wp_die();
+	}
+
+	// Handle crop
+	if ( bp_attachments_avatar_handle_crop( $_REQUEST ) ) {
+		$return = bp_core_fetch_avatar( array(
+			'object'  => $object,
+			'item_id' => $item_id,
+			'html'    => true,
+			'type'    => 'full',
+		) );
+
+		$return .= '<p><a href="#" id="remove-' . $component . '-avatar">' . esc_html__( 'Remove Avatar', 'buddypress' ) . '</a></p>';
+
+		if ( ! empty( $json ) ) {
+			wp_send_json_success( $return );
+		} else {
+			wp_die( $return );
+		}
+	} else {
+		wp_die( 0 );
+	}
+}
+add_action( 'wp_ajax_bp_attachments_set_avatar', 'bp_attachments_set_avatar' );
+
+/**
+ * Delete an avatar from "BP Attachments Editor".
+ *
+ * Avatars are not handled the same way than attachments
+ * 1- no post type "bp_attachment" is created
+ * 2- it doesn't change anything to the way BuddyPress manage avatars
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_delete_avatar() {
+	$json = ! empty( $_REQUEST['json'] ); // New-style request
+
+	check_ajax_referer( 'bp_attachments_avatar', 'nonce' );
+
+	if ( empty( $_REQUEST['item_id'] ) || empty( $_REQUEST['object'] ) ) {
+		wp_die(0);
+	}
+
+	$item_id = absint( $_REQUEST['item_id'] );
+	$object = esc_html( $_REQUEST['object'] );
+
+	$cap_args = false;
+	if ( ! empty( $object ) ) {
+		$cap_args = array( 'component' => $object, 'item_id' => $item_id );
+	}
+
+	// We don't categorized members as a bp_component term
+	if ( 'members' == $object ) {
+		$cap_args = false;
+	}
+
+	// capability check
+	if ( ! bp_attachments_current_user_can( 'edit_bp_attachments', $cap_args ) ) {
+		wp_die();
+	}
+
+	if ( bp_attachments_delete_existing_avatar( array( 'item_id' => $item_id, 'object' => $object ) ) ) {
+		if ( ! empty( $json ) ) {
+			wp_send_json_success( 1 );
+		} else {
+			wp_die( 1 );
+		}
+	} else {
+		wp_die(0);
+	}
+}
+add_action( 'wp_ajax_bp_attachments_delete_avatar', 'bp_attachments_delete_avatar' );
+
+/**
+ * Query attachments for the "BP Attachments Editor".
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_query() {
+	$query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
+	$query = array_intersect_key( $query, array_flip( array(
+		's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type',
+		'post_parent', 'post__in', 'post__not_in', 'item_type', 'item_id', 'component'
+	) ) );
+
+	$args = array(
+		'show_private'    => false,   // this could be checking bp_is_my_profile() or current user is a group admin
+		'user_id'         => bp_loggedin_user_id(),
+		'per_page'	      => $query['posts_per_page'],
+		'page'		      => $query['paged'],
+		'orderby' 		  => $query['orderby'],
+		'order'           => $query['order'],
+	);
+
+	if ( ! empty( $query['item_id'] ) ) {
+		$args['item_ids'] = array( $query['item_id'] );
+	}
+
+	if ( ! empty( $query['component'] ) && 'members' != $query['component'] ) {
+		$args['component'] = $query['component'];
+	}
+
+	if ( ! empty( $query['s'] ) )
+		$args['search'] = $query['s'];
+
+	$query = bp_attachments_get_attachments( $args );
+
+	$attachments = array_map( 'bp_attachments_prepare_attachment_for_js', $query['attachments'] );
+	$attachments = array_filter( $attachments );
+
+	wp_send_json_success( $attachments );
+}
+add_action( 'wp_ajax_query_bp_attachments', 'bp_attachments_query' );
+
+/**
+ * Process an attachment upload requested in "BP Attachments Editor".
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_upload() {
+	//nonce check
+	check_ajax_referer( 'media-form' );
+
+	$r = bp_parse_args( $_REQUEST, array(
+		'item_id'         => 0,
+		'component'       => '',
+		'item_type'       => 'attachment',
+		'action'          => 'bp_attachments_upload',
+		'file_id'         => 'bp_attachment_file'
+	), 'attachments_ajax_upload' );
+
+	// We don't categorized members as a bp_component term
+	if ( 'members' == $r['component'] ) {
+		unset( $r['component'] );
+	}
+	$cap_args = false;
+
+	if ( ! empty( $r['component'] ) ) {
+		$cap_args = array( 'component' => $r['component'], 'item_id' => $r['item_id'] );
+	}
+
+	// capability check
+	if ( ! bp_attachments_current_user_can( 'publish_bp_attachments', $cap_args ) ) {
+		wp_die();
+	}
+
+	$attachment_id = bp_attachments_handle_upload( $r );
+
+	if ( is_wp_error( $attachment_id ) ) {
+		wp_send_json_error( array(
+			'message'  => $attachment_id->get_error_message(),
+			'filename' => $_FILES['bp_attachment_file']['name'],
+		) );
+	}
+
+	if ( ! $attachment = bp_attachments_prepare_attachment_for_js( $attachment_id ) ) {
+		wp_die();
+	}
+
+	wp_send_json_success( $attachment );
+}
+add_action( 'wp_ajax_bp_attachments_upload', 'bp_attachments_upload' );
+
+/**
+ * Delete an attachment from "BP Attachments Editor".
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_ajax_delete_attachment( $action ) {
+	if ( empty( $action ) ) {
+		$action = 'delete_bp_attachment';
+	}
+
+	$id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
+
+	check_ajax_referer( "{$action}_$id" );
+
+	if ( ! bp_attachments_current_user_can( 'delete_bp_attachment', $id ) ) {
+		wp_die( -1 );
+	}
+
+	if ( bp_attachments_delete_attachment( $id ) ) {
+		wp_die( 1 );
+	} else {
+		wp_die( 0 );
+	}
+}
+add_action( 'wp_ajax_delete_bp_attachment', 'bp_attachments_ajax_delete_attachment' );
+
+/**
+ * Update an attachment from "BP Attachments Editor".
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_ajax_update_attachment() {
+	if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) ) {
+		wp_send_json_error();
+	}
+
+	if ( ! $id = absint( $_REQUEST['id'] ) ) {
+		wp_send_json_error();
+	}
+
+	check_ajax_referer( 'update_bp_attachment_' . $id, 'nonce' );
+
+	if ( ! bp_attachments_current_user_can( 'edit_bp_attachment', $id ) ) {
+		wp_send_json_error();
+	}
+
+	$changes = $_REQUEST['changes'];
+
+	$update = array( 'id' => $id, 'ajax' => true );
+
+	if ( isset( $changes['title'] ) ) {
+		$update['title'] = $changes['title'];
+	}
+
+	if ( isset( $changes['description'] ) ) {
+		$update['description'] = $changes['description'];
+	}
+
+	if ( ! bp_attachments_update_attachment( $update ) ) {
+		wp_send_json_error();
+	}
+
+	wp_send_json_success();
+}
+add_action( 'wp_ajax_update_bp_attachment', 'bp_attachments_ajax_update_attachment' );
diff --git src/bp-attachments/bp-attachments-caps.php src/bp-attachments/bp-attachments-caps.php
index e69de29..973d9ce 100644
--- src/bp-attachments/bp-attachments-caps.php
+++ src/bp-attachments/bp-attachments-caps.php
@@ -0,0 +1,292 @@
+<?php
+/**
+ * Attachments caps
+ *
+ * @package BuddyPress
+ * @subpackage Caps
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) or exit;
+
+/**
+ * bp_attachment post type caps
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_get_attachment_caps() {
+	return apply_filters( 'bp_attachments_get_attachment_caps', array (
+		'edit_posts'          => 'edit_bp_attachments',
+		'edit_others_posts'   => 'edit_others_bp_attachments',
+		'publish_posts'       => 'publish_bp_attachments',
+		'read_private_posts'  => 'read_private_bp_attachments',
+		'delete_posts'        => 'delete_bp_attachments',
+		'delete_others_posts' => 'delete_others_bp_attachments'
+	) );
+}
+
+/**
+ * bp_component taxonomy caps
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_get_component_caps() {
+	return apply_filters( 'bp_attachments_get_component_caps', array (
+		'manage_terms' => 'manage_bp_components',
+		'edit_terms'   => 'edit_bp_components',
+		'delete_terms' => 'delete_bp_components',
+		'assign_terms' => 'assign_bp_components'
+	) );
+}
+
+/**
+ * Map capabilities
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  array   $caps
+ * @param  string  $cap
+ * @param  integer $user_id
+ * @param  array   $args
+ * @return array   $caps
+ */
+function bp_attachments_map_meta_caps( $caps = array(), $cap = '', $user_id = 0, $args = array() ) {
+
+	// What capability is being checked?
+	switch ( $cap ) {
+
+		/** Reading ***********************************************************/
+
+		case 'read_private_bp_attachments' :
+
+			if ( ! empty( $args[0] ) ) {
+				// Get the post
+				$_post = get_post( $args[0] );
+				if ( ! empty( $_post ) ) {
+
+					// Get caps for post type object
+					$post_type = get_post_type_object( $_post->post_type );
+					$caps      = array();
+
+					// Allow author to edit his attachment
+					if ( $user_id == $_post->post_author ) {
+						// In a network, most members has no role
+						$caps[] = 'exist';
+
+					// @todo private attachments will need other rules
+
+					// Admins can always edit
+					} else if ( user_can( $user_id, 'manage_options' ) ) {
+						$caps = array( 'manage_options' );
+					} else {
+						$caps[] = $post_type->cap->edit_others_posts;
+					}
+
+				}
+			}
+
+
+			break;
+
+		/** Publishing ********************************************************/
+
+		case 'publish_bp_attachments' :
+
+			if ( bp_is_my_profile() ) {
+				// In a network, most members has no role
+				$caps = array( 'exist' );
+			}
+
+			if ( ! empty( $args[0] ) && is_a( $args[0], 'BP_Attachments_Can' ) ) {
+
+				if ( ! empty( $args[0]->component ) && ! empty( $args[0]->item_id ) ){
+					switch( $args[0]->component ) {
+						case 'groups':
+							if( groups_is_user_member( $user_id, $args[0]->item_id ) ) {
+								// In a network, most members has no role
+								$caps = array( 'exist' );
+							}
+							break;
+
+						// and so on for other components
+					}
+				}
+
+			}
+
+			// Admins can always publish
+			if ( user_can( $user_id, 'manage_options' ) ) {
+				$caps = array( 'manage_options' );
+			}
+
+			break;
+
+		/** Editing ***********************************************************/
+
+		case 'edit_bp_attachments'        :
+
+			if ( bp_is_my_profile() ) {
+				// In a network, most members has no role
+				$caps = array( 'exist' );
+			}
+
+			if ( ! empty( $args[0] ) && is_a( $args[0], 'BP_Attachments_Can' ) ) {
+
+				if ( ! empty( $args[0]->component ) && ! empty( $args[0]->item_id ) ){
+					switch( $args[0]->component ) {
+						case 'groups':
+						case 'group' :
+							if( groups_is_user_admin( $user_id, $args[0]->item_id ) ) {
+								// In a network, most members has no role
+								$caps = array( 'exist' );
+							}
+							break;
+
+						// and so on for other components
+					}
+				}
+
+			}
+
+			// Admins can always edit
+			if ( user_can( $user_id, 'manage_options' ) ) {
+				$caps = array( 'manage_options' );
+			}
+
+			break;
+
+		// Used primarily in wp-admin
+		case 'edit_others_bp_attachments' :
+
+			// Admins can always edit
+			if ( user_can( $user_id, 'manage_options' ) ) {
+				$caps = array( 'manage_options' );
+			}
+
+			break;
+
+		// Used everywhere
+		case 'edit_bp_attachment' :
+
+			// Get the post
+			$_post = get_post( $args[0] );
+			if ( ! empty( $_post ) ) {
+
+				// Get caps for post type object
+				$post_type = get_post_type_object( $_post->post_type );
+				$caps      = array();
+
+				// Allow author to edit his attachment
+				if ( $user_id == $_post->post_author ) {
+					// In a network, most members has no role
+					$caps[] = 'exist';
+
+				// Admins can always edit
+				} else if ( user_can( $user_id, 'manage_options' ) ) {
+					$caps = array( 'manage_options' );
+				} else {
+					$caps[] = $post_type->cap->edit_others_posts;
+				}
+
+			}
+
+			break;
+
+		/** Deleting **********************************************************/
+
+		case 'delete_bp_attachment' :
+
+			// Get the post
+			$_post = get_post( $args[0] );
+			if ( ! empty( $_post ) ) {
+
+				// Get caps for post type object
+				$post_type = get_post_type_object( $_post->post_type );
+				$caps      = array();
+
+				// Allow author to edit his attachment
+				if ( $user_id == $_post->post_author ) {
+					// In a network, most members has no role
+					$caps[] = 'exist';
+
+				// Admins can always edit
+				} else if ( user_can( $user_id, 'manage_options' ) ) {
+					$caps = array( 'manage_options' );
+				} else {
+					$caps[] = $post_type->cap->delete_others_posts;
+				}
+			}
+
+			break;
+
+		// Moderation override
+		case 'delete_bp_attachments'        :
+		case 'delete_others_bp_attachments' :
+
+			// Moderators can always delete
+			if ( user_can( $user_id, 'manage_options' ) ) {
+				$caps = array( 'manage_options' );
+			}
+
+			break;
+
+		/** Admin *************************************************************/
+
+		case 'bp_attachments_moderate' :
+
+			// Admins can always moderate
+			if ( user_can( $user_id, 'manage_options' ) ) {
+				$caps = array( 'manage_options' );
+			}
+
+			break;
+
+		/** bp component term ************************************************/
+		case 'manage_bp_components'    :
+		case 'edit_bp_components'      :
+		case 'delete_bp_components'    :
+		case 'bp_attachments_components_admin' :
+
+			// Admins can always edit
+			if ( user_can( $user_id, 'manage_options' ) ) {
+				$caps = array( 'manage_options' );
+			}
+
+			break;
+
+		// This should be improved..
+		case 'assign_bp_components'   :
+			if ( is_user_logged_in() ) {
+				// In a network, most members has no role
+				$caps = array( 'exist' );
+			}
+			break;
+
+	}
+
+	return apply_filters( 'bp_attachments_map_meta_caps', $caps, $cap, $user_id, $args );
+}
+add_filter( 'bp_map_meta_caps', 'bp_attachments_map_meta_caps', 10, 4 );
+
+/**
+ * Specific function to check a user's capability
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @uses BP_Attachments_Can to create the argument to check upon
+ * such as :
+ * - component
+ * - single item id
+ * - attachment id
+ */
+function bp_attachments_current_user_can( $capability = '', $args = false ) {
+	if ( ! empty( $args ) && is_array( $args ) )
+		$args = new BP_Attachments_Can( $args );
+
+	$blog_id = bp_get_root_blog_id();
+	$args = array( $blog_id, $capability, $args );
+
+	$retval = call_user_func_array( 'current_user_can_for_blog', $args );
+
+	return (bool) apply_filters( 'bp_attachments_current_user_can', $retval, $args );
+}
diff --git src/bp-attachments/bp-attachments-classes.php src/bp-attachments/bp-attachments-classes.php
index e69de29..588cab8 100644
--- src/bp-attachments/bp-attachments-classes.php
+++ src/bp-attachments/bp-attachments-classes.php
@@ -0,0 +1,926 @@
+<?php
+/**
+ * Attachments Classes.
+ *
+ * @package BuddyPress
+ * @subpackage Attachments Classes
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) or exit;
+
+/**
+ * Attachments Upload Class.
+ *
+ * This class is used to handle upload
+ * for components that are using  the
+ * "bp_attachment"s post type
+ *
+ * @since BuddyPress (2.2.0)
+ */
+class BP_Attachments_Upload {
+
+	protected static $instance = null;
+
+	/**
+	 * Construct the uploader.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function __construct( $args = array() ) {
+		$this->setup_globals( $args );
+		$this->includes();
+		$this->setup_actions();
+		$this->upload();
+		$this->reset_actions();
+	}
+
+	/**
+	 * Starting point.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function start( $args = array() ) {
+		if ( empty( $args['action'] ) || empty( $args['file_id'] ) )
+			return;
+
+		// If the single instance hasn't been set, set it now.
+		if ( null == self::$instance ) {
+			self::$instance = new self( $args );
+		}
+
+		return self::$instance;
+	}
+
+	/**
+	 * Define globals.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function setup_globals( $args = array() ) {
+
+		$this->attachment_id = 0;
+
+		$r = bp_parse_args( $args,  array(
+			'item_id'         => 0,                       // what is the item id ?
+			'component'       => '',                      // what is the component groups/members/messages ?
+			'item_type'       => 'attachment',            // attachment / avatar
+			'action'          => 'bp_attachments_upload', // are specific strings needed ?
+			'file_id'         => 'bp_attachment_file',    // the name of the $_FILE to upload
+		), 'attachments_uploader_args' );
+
+		foreach( $r as $key => $value ) {
+			$this->{$key} = $value;
+		}
+	}
+
+	/**
+	 * Include needed files.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	private function includes() {
+		require_once( ABSPATH . '/wp-admin/includes/file.php' );
+		require_once( ABSPATH . '/wp-admin/includes/media.php' );
+		require_once( ABSPATH . '/wp-admin/includes/image.php' );
+	}
+
+	/**
+	 * Actions and filters to run
+	 * before media_handle_upload() function is fired
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	private function setup_actions() {
+		// Filters
+		add_filter( 'upload_dir',                array( $this, 'upload_dir' ),          10, 1 );
+		add_filter( '_wp_relative_upload_path',  array( $this, 'relative_path' ),       10, 2 );
+		add_filter( 'wp_insert_attachment_data', array( $this, 'attachment_data' ),     10, 2 );
+		// Actions
+		add_action( 'add_attachment',            array( $this, 'attachment_metadata' ), 10, 1 );
+	}
+
+	/**
+	 * Upload!
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	private function upload() {
+		$attachment_data = array(
+			'post_type' => 'bp_attachment',
+			'context'   => 'buddypress',
+		);
+
+		// components are terms, an attachment can be attached to more than one component
+		if ( ! empty( $this->component ) ) {
+			$term = bp_attachments_get_component_term_id( $this->component );
+
+			if ( ! empty( $term ) ) {
+				$attachment_data['tax_input'] = array( 'bp_component' => array( $term ) );
+			}
+		}
+
+		$this->attachment_id = media_handle_upload( $this->file_id, 0, $attachment_data, array( 'action' => $this->action ) );
+	}
+
+	/**
+	 * Actions and filters to remove
+	 * once media_handle_upload() function has done
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	private function reset_actions() {
+		// filters
+		remove_filter( 'upload_dir',                array( $this, 'upload_dir' ),      10, 1 );
+		remove_filter( '_wp_relative_upload_path',  array( $this, 'relative_path' ),   10, 2 );
+		remove_filter( 'wp_insert_attachment_data', array( $this, 'attachment_data' ), 10, 2 );
+		// actions
+		remove_action( 'add_attachment',            array( $this, 'attachment_metadata' ), 10, 1 );
+	}
+
+	/**
+	 * Filter upload dir
+	 *
+	 * @since BuddyPress (2.2.0)
+	 *
+	 * @todo handle private files using the post status
+	 * of the "bp_attachment" post type
+	 */
+	public function upload_dir( $upload_data = array() ) {
+		$bp = buddypress();
+
+		/**
+		 * @todo private files!
+		 *
+		 * Using the post_status
+		 */
+
+		$path = bp_attachments_get_upload_dir( 'attachments_public_dir' ) . '/' . bp_loggedin_user_id();
+
+		if ( ! file_exists( $path ) ) {
+			mkdir( $path );
+		}
+
+		$url = bp_attachments_get_upload_dir( 'attachments_public_url' ) . '/' . bp_loggedin_user_id();
+
+		$r = bp_parse_args( array(
+			'path'    => $path,
+			'url'     => $url,
+			'subdir'  => false,
+			'basedir' => $path,
+			'baseurl' => $url,
+		), $upload_data, 'attachments_upload_dir_args' );
+
+		return $r;
+	}
+
+	/**
+	 * Filter relative path
+	 *
+	 * @since BuddyPress (2.2.0)
+	 *
+	 * @todo handle private files using the post status
+	 * of the "bp_attachment" post type
+	 */
+	public function relative_path( $new_path = '', $path = '' ) {
+		$bp = buddypress();
+
+		/**
+		 * @todo private files!
+		 *
+		 * Using the post_status
+		 */
+
+		if ( ! bp_attachments_get_upload_dir( 'attachments_public_dir' ) ) {
+			return $new_path;
+		}
+
+		if ( false !== strpos( $path, bp_attachments_get_upload_dir( 'attachments_public_dir' ) ) ) {
+			$new_path = str_replace( bp_attachments_get_upload_dir( 'attachments_dir' ), '', $path );
+			$new_path = basename( bp_attachments_get_upload_dir( 'attachments_dir' ) ) . $new_path;
+		}
+
+		return $new_path;
+	}
+
+	/**
+	 * Set the post type to bp_attachment
+	 * instead of attachmnent
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function attachment_data( $data = array(), $object = array() ) {
+		if( 'buddypress' == $object['context'] ) {
+			$post_name = sanitize_title( $object['post_title'] );
+			$post_name = wp_unique_post_slug( $post_name, 0, $data['post_status'], 'bp_attachment', $data['post_parent'] );
+			$data = array_merge( $data, array(
+				'post_type' => 'bp_attachment',
+				'post_name' => $post_name
+			) );
+		}
+
+		return $data;
+	}
+
+	/**
+	 * Finally add post meta to link to a single item id of the component
+	 *
+	 * for instance a group id. It's possible to add an attachment to more
+	 * than one single item of the component
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function attachment_metadata( $attachment_id = 0 ) {
+		if ( empty( $attachment_id ) ) {
+			return;
+		}
+
+		if ( ! empty( $this->component ) && ! empty( $this->item_id ) ) {
+			add_post_meta( $attachment_id, "_bp_{$this->component}_id", $this->item_id );
+		}
+	}
+}
+
+/**
+ * Attachments Browser Class.
+ *
+ * This class is used to create the
+ * "BP Attachments Editor" for attachments
+ * or Avatars
+ *
+ * @since BuddyPress (2.2.0)
+ */
+class BP_Attachments_Browser {
+
+	private static $settings = array();
+
+	private function __construct() {}
+
+	/**
+	 * Set the BP Attachments Editor settings
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function set( $browser_id, $settings ) {
+		$set = bp_parse_args( $settings,  array(
+			'item_id'         => 0,                                     // what is the item id ?
+			'component'       => 'members',                             // what is the component groups/xprofile/members/messages ?
+			'item_type'       => 'avatar',                              // avatar / image custom..
+			'post_id'         => 0,
+			'btn_caption'     => __( 'Edit Avatar', 'buddypress' ), // the button caption
+			'btn_class'       => 'bp_avatar',                           // the button class
+			'file_data_name'  => 'bp_attachment_file',                  // the name of the $_FILE to upload
+			'multi_selection' => false,                                 // allow multiple file upload ?
+			'action'          => 'bp_attachments_upload',               // the ajax action to deal with the file
+			'callback'        => false,
+			'callback_id'     => false,
+		), 'attachments_browser_args' );
+
+		self::$settings = array_merge( $set, array( 'bp_attachments_button_id' => '#' . $browser_id ) );
+		return $set;
+	}
+
+	/**
+	 * Display the button to launch the BP Attachments Editor
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function browser( $browser_id, $settings = array() ) {
+		$set = self::set( $browser_id, $settings );
+
+		$args = false;
+		if ( ! empty( $set['component'] ) && ! empty( $set['item_id'] ) )
+			$args = wp_array_slice_assoc( $set, array( 'component', 'item_id' ) );
+
+		if ( bp_attachments_current_user_can( 'publish_bp_attachments', $args ) ) {
+			// Need some extra attribute to the wrapper
+			add_filter( 'bp_button_attachments_create-' . $set['component'] . '-' . $set['item_type'], array( __CLASS__, 'btn_extra_attribute' ), 10, 4 );
+
+			bp_button( array(
+				'id'                => 'create-' . $set['component'] . '-' . $set['item_type'],
+				'component'         => 'attachments',
+				'must_be_logged_in' => true,
+				'block_self'        => false,
+				'wrapper_id'        => $browser_id,
+				'wrapper_class'     => $set['btn_class'],
+				'link_class'        => 'add-' .  $set['item_type'],
+				'link_href'         => '#',
+				'link_title'        => $set['btn_caption'],
+				'link_text'         => $set['btn_caption']
+			) );
+
+			remove_filter( 'bp_button_attachments_create-' . $set['component'] . '-' . $set['item_type'], array( __CLASS__, 'btn_extra_attribute' ), 10, 4 );
+
+			// do not forget the fallback form if js is disabled or bp-media-editor out of service..
+			if ( ! is_admin() ) {
+				add_action( 'bp_attachments_uploader_fallback', array( __CLASS__, 'fallback_uploader' ), 10 );
+			}
+		}
+
+		self::browser_settings( $browser_id );
+	}
+
+	/**
+	 * This filter temporarly hide the button
+	 *
+	 * If no problem avoided the BP Attachments Editor to load
+	 * then the javascript media-editor.js will show the button
+	 * and remove the fallback uploader
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function btn_extra_attribute( $contents = '', $button = null, $before = '', $after ='' ) {
+		$before = substr( $before, 0, -1 );
+		$contents = str_replace( $before, $before .' style="display:none"', $contents );
+
+		return $contents;
+	}
+
+	/**
+	 * Fallback uploader when no-js
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function fallback_uploader() {
+		$settings = self::$settings;
+		?>
+		<form action="" method="post" id="attachment-upload-form" class="standard-form" enctype="multipart/form-data">
+			<p id="attachment-upload">
+				<input type="file" name="<?php echo esc_attr( $settings['file_data_name'] );?>" id="file" />
+				<input type="submit" name="bp_attachment_upload" id="upload" value="<?php esc_attr_e( 'Upload', 'buddypress' ); ?>" />
+
+				<?php if ( 'avatar' == $settings['item_type'] ) :?>
+					<input type="hidden" name="item_type" id="item-type" value="<?php echo esc_attr( $settings['item_type'] );?>" />
+				<?php endif ;?>
+
+				<?php if ( ! empty( $settings['component'] ) ) :?>
+					<input type="hidden" name="component" id="component" value="<?php echo esc_attr( $settings['component'] );?>" />
+				<?php endif ;?>
+
+				<?php if ( ! empty( $settings['item_id'] ) ) :?>
+					<input type="hidden" name="item_id" id="item-id" value="<?php echo esc_attr( $settings['item_id'] );?>" />
+				<?php endif ;?>
+
+				<?php if ( ! empty( $settings['post_id'] ) ) :?>
+					<input type="hidden" name="post_id" id="post-id" value="<?php echo esc_attr( $settings['post_id'] );?>" />
+				<?php endif ;?>
+				<input type="hidden" name="action" id="action" value="<?php echo esc_attr( $settings['action'] );?>" />
+				<input type="hidden" name="file_data" id="file-data" value="<?php echo esc_attr( $settings['file_data_name'] );?>" />
+				<?php wp_nonce_field( 'bp_attachments_upload', 'bp_attachments_upload_nonce' ); ?>
+			</p>
+		</form>
+		<?php
+	}
+
+	/**
+	 * Filter the Media Editor strings, settings & params
+	 * and enqueue the needed scripts
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function browser_settings( $browser_id ) {
+		$settings = self::$settings;
+
+		$args = array();
+		// Need to attach to a custom post types, let's use WordPress built in attachment features
+		if ( ! empty( self::$settings['post_id'] ) ) {
+			$args = array( 'post' => absint( self::$settings['post_id'] ) );
+		}
+
+		// media view filters
+		add_filter( 'media_view_strings', array( __CLASS__, 'media_view_strings' ), 10, 1 );
+		add_filter( 'media_view_settings', array( __CLASS__, 'media_view_settings' ), 10, 1 );
+
+		// Plupload filters
+		add_filter( 'plupload_default_settings', array( __CLASS__, 'plupload_settings' ), 10, 1 );
+		add_filter( 'plupload_default_params',   array( __CLASS__, 'plupload_params' ), 10, 1 );
+
+		// jcrop in case of avatar
+		if ( 'avatar' == self::$settings['item_type'] ) {
+			wp_enqueue_style( 'jcrop' );
+			wp_enqueue_script( 'jcrop', array( 'jquery' ) );
+		}
+
+		// Decide whether to load the dev version of the CSS and JavaScript
+		$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : 'min.';
+
+		// time to enqueue scripts
+		wp_enqueue_media( $args );
+		wp_enqueue_script( 'bp-attachments-editor', buddypress()->plugin_url . "bp-attachments/js/script.{$min}js", array( 'media-editor' ), bp_get_version(), true );
+	}
+
+	/**
+	 * Custom strings for the BP Attachments Editor
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function media_view_strings( $strings ) {
+		$bp_attachments_strings = array( 'bp_attachments' => array() );
+
+		if ( 'avatar' == self::$settings['item_type'] ) {
+			$title = 'groups' == self::$settings['component'] ? __( 'Group Avatar', 'buddypress' ) : __( 'Avatar', 'buddypress' );
+			$bp_attachments_strings = array( 'bp_attachments' => array(
+				'title'     => $title,
+				'uploadtab' => __( 'Upload Avatar', 'buddypress' ),
+				'croptab'   => __( 'Crop Avatar', 'buddypress' ),
+			) );
+		}
+
+		if ( 'attachment' == self::$settings['item_type'] ) {
+			$title = 'groups' == self::$settings['component'] ? __( 'Group Attachments', 'buddypress' ) : __( 'Attachments', 'buddypress' );
+			$bp_attachments_strings = array( 'bp_attachments' => array(
+				'title'       => $title,
+				'uploadtab'   => __( 'Upload Attachments', 'buddypress' ),
+				'managetab'   => __( 'Manage Attachments', 'buddypress' ),
+			) );
+		}
+
+		if ( ! self::$settings['multi_selection'] ) {
+			$bp_attachments_strings['bp_attachments'] = array_merge(
+				$bp_attachments_strings['bp_attachments'],
+				array( 'files_error' => __( 'One file at a time', 'buddypress' ) )
+			);
+		}
+
+		$bp_attachments_strings['bp_attachments'] = apply_filters( 'bp_attachments_strings', $bp_attachments_strings['bp_attachments'], self::$settings['bp_attachments_button_id'] );
+		$bp_attachments_strings['bp_attachments'] = array_filter( $bp_attachments_strings['bp_attachments'] );
+
+		$strings = array_merge( $strings, $bp_attachments_strings );
+
+		return $strings;
+	}
+
+	/**
+	 * Custom settings for the BP Attachments Editor
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function media_view_settings( $settings ) {
+		$component = self::$settings['component'];
+
+		$settings['bp_attachments'] = array(
+			'item_id'   => self::$settings['item_id'],
+			'item_type' => $component,
+			'button_id' => self::$settings['bp_attachments_button_id'],
+		);
+
+		if ( 'avatar' == self::$settings['item_type'] ) {
+
+			switch ( $component ) {
+				case 'groups' :
+					$object     = 'group';
+					$avatar_dir = 'avatar_group_dir';
+					break;
+
+				case 'blogs' :
+					$object     = 'blog';
+					$avatar_dir = 'avatar_blog_dir';
+					break;
+
+				case 'xprofile' :
+				default         :
+					$object     = 'user';
+					$avatar_dir = 'avatar_user_dir';
+					break;
+			}
+
+			$settings['bp_attachments'] = array_merge(
+				$settings['bp_attachments'],
+				array(
+					'full_h'     => bp_core_avatar_full_height(),
+					'full_w'     => bp_core_avatar_full_width(),
+					'object'     => $object,
+					'avatar_dir' => $avatar_dir,
+				)
+			);
+		}
+
+		return $settings;
+	}
+
+	/**
+	 * Custom settings for plupload
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function plupload_settings( $settings ) {
+		$settings['url'] = admin_url( 'admin-ajax.php' );
+		$settings['file_data_name'] = self::$settings['file_data_name'];
+		$settings['multi_selection'] = self::$settings['multi_selection'];
+
+		return $settings;
+	}
+
+	/**
+	 * Custom params for plupload
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function plupload_params( $params ) {
+		$params = array(
+			'action'      => self::$settings['action'],
+			'item_id'     => self::$settings['item_id'],
+			'component'   => self::$settings['component'],
+			'item_type'   => self::$settings['item_type'],
+			'post_id'     => self::$settings['post_id'],
+			'_bpnonce'    => wp_create_nonce( 'bp_attachments_' . self::$settings['item_type'] ),
+			'callback'    => self::$settings['callback'],
+			'callback_id' => self::$settings['callback_id'],
+		);
+
+		return $params;
+	}
+}
+
+/**
+ * Attachments "CRUD" Class.
+ *
+ * Create is handled by the Upload Class
+ *
+ * @since BuddyPress (2.2.0)
+ */
+class BP_Attachments {
+	public $id;
+	public $user_id;
+	public $title;
+	public $description;
+	public $mime_type;
+	public $guid;
+	public $status;
+	public $item_ids;
+	public $components;
+	public $attachment;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	function __construct( $id = 0 ){
+		if ( ! empty( $id ) ) {
+			$this->id = $id;
+			$this->populate();
+		}
+	}
+
+	/**
+	 * request an item id
+	 *
+	 * @uses get_post()
+	 * @uses wp_get_object_terms()
+	 */
+	public function populate() {
+		$this->attachment  = get_post( $this->id );
+		$this->user_id     = $this->attachment->post_author;
+		$this->title       = $this->attachment->post_title;
+		$this->description = $this->attachment->post_content;
+		$this->mime_type   = $this->attachment->post_mime_type;
+		$this->guid        = $this->attachment->guid;
+		$this->status      = $this->attachment->post_status;
+		$this->components  = wp_get_object_terms( $this->id, 'bp_component', array( 'fields' => 'all' ) );
+
+		if ( ! empty( $this->components ) ) {
+			$this->item_ids = new stdClass();
+			foreach( (array) $this->components as $component ) {
+				/**
+				 * @todo transform ids into components slug
+				 * bp_attachments_get_component_term_id( $component_id )
+				 *
+				 * $component_id = bp_attachments_get_term_component_id( $component->term_id );
+				 * + Change $component->slug in favor of $component_id
+				 */
+				$this->item_ids->{$component->slug} = get_post_meta( $this->id, "_bp_{$component->slug}_id" );
+			}
+		}
+
+	}
+
+	/**
+	 * Update an attachment.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 * @todo changing from inherit (public) to private
+	 * This will need to move files from one dir to another
+	 */
+	public function update() {
+		$this->id          = apply_filters_ref_array( 'bp_attachments_id_before_update',          array( $this->id,          &$this ) );
+		$this->user_id     = apply_filters_ref_array( 'bp_attachments_user_id_before_update',     array( $this->user_id,     &$this ) );
+		$this->title       = apply_filters_ref_array( 'bp_attachments_title_before_update',       array( $this->title,       &$this ) );
+		$this->description = apply_filters_ref_array( 'bp_attachments_description_before_update', array( $this->description, &$this ) );
+		$this->status      = apply_filters_ref_array( 'bp_attachments_status_before_update',      array( $this->status,      &$this ) );
+		$this->item_ids    = apply_filters_ref_array( 'bp_attachments_item_ids_before_update',    array( $this->item_ids,    &$this ) );
+		$this->components  = apply_filters_ref_array( 'bp_attachments_components_before_update',  array( $this->components,  &$this ) );
+
+		// Use this, not the filters above
+		do_action_ref_array( 'bp_attachments_before_update', array( &$this ) );
+
+		if ( ! $this->id || ! $this->user_id || ! $this->title )
+			return false;
+
+		if ( ! $this->status )
+			$this->status = 'inherit';
+
+		/**
+		 * If the status changed, we'll need to move files
+		 */
+
+		$update_args = array(
+			'ID'		     => $this->id,
+			'post_author'	 => $this->user_id,
+			'post_title'	 => $this->title,
+			'post_content'	 => $this->description,
+			'post_type'		 => 'bp_attachment',
+			'post_status'	 => $this->status
+		);
+
+		$updated = wp_update_post( $update_args );
+
+		$result = false;
+
+		if ( $updated ) {
+			$result = $this->title;
+		}
+
+		do_action_ref_array( 'bp_attachments_after_update', array( &$this ) );
+
+		return $result;
+	}
+
+	/**
+	 * The selection query
+	 *
+	 * @since BuddyPress (2.2.0)
+	 * @param array $args arguments to customize the query
+	 * @uses bp_parse_args
+	 */
+	public static function get( $args = array() ) {
+
+		$defaults = array(
+			'item_ids'        => array(), // one or more item ids regarding the component (eg group_ids, message_ids )
+			'component'	      => false,   // groups / messages / blogs / xprofile...
+			'show_private'    => false,   // wether to include private attachment
+			'user_id'	      => false,   // the author id of the attachment
+			'post_id'         => false,
+			'per_page'	      => 20,
+			'page'		      => 1,
+			'search'          => false,
+			'exclude'		  => false,   // comma separated list or array of attachment ids.
+			'orderby' 		  => 'modified',
+			'order'           => 'DESC',
+		);
+
+		$r = bp_parse_args( $args, $defaults, 'attachments_query_args' );
+
+		$attachment_status = 'inherit';
+
+		if ( ! empty( $r['show_private'] ) ) {
+			$attachment_status = array( 'inherit', 'private' );
+		}
+
+		$query_args = array(
+			'post_status'	 => $attachment_status,
+			'post_type'	     => 'bp_attachment',
+			'posts_per_page' => $r['per_page'],
+			'paged'		     => $r['page'],
+			'orderby' 		 => $r['orderby'],
+			'order'          => $r['order'],
+		);
+
+		if ( ! empty( $r['user_id'] ) ) {
+			$query_args['author'] = $r['user_id'];
+		}
+
+		if ( ! empty( $r['exclude'] ) ) {
+			if ( ! is_array( $r['exclude'] ) ) {
+				$r['exclude'] = explode( ',', $r['exclude'] );
+			}
+
+			$query_args['post__not_in'] = $r['exclude'];
+		}
+
+		if ( ! empty( $r['post_id'] ) ) {
+			$query_args['post_parent'] = $r['post_id'];
+		}
+
+		if ( ! empty( $r['component'] ) ) {
+			/**
+			 * @todo transform slugs into ids using
+			 * bp_attachments_get_component_term_id( $component_id )
+			 *
+			 * $component_ids = array_map( 'bp_attachments_get_component_term_id', $r['component'] )
+			 * + Change the tax query in favor of
+			 * array(
+			 *		'taxonomy' => 'bp_component',
+			 *		'field'    => 'term_id',
+			 *		'terms'    => $component_ids
+			 *	)
+			 */
+			$query_args['tax_query'] = array(
+				array(
+					'taxonomy' => 'bp_component',
+					'field' => 'slug',
+					'terms' => $r['component']
+				)
+			);
+
+			$component = $r['component'];
+
+			// component is defined, we can zoom on specific ids
+			if ( ! empty( $r['item_ids'] ) ) {
+				// We really want an array!
+				$item_ids = (array) $r['item_ids'];
+
+				$query_args['meta_query'] = array(
+					array(
+						'key'     => "_bp_{$component}_id",
+						'value'   => $item_ids,
+						'compare' => 'IN',
+					)
+				);
+			}
+		}
+
+		$attachments = new WP_Query( $query_args );
+
+		return array( 'attachments' => $attachments->posts, 'total' => $attachments->found_posts );
+	}
+
+	/**
+	 * Return the number of attachments depending on the context.
+	 *
+	 * Used in BuddyPress nav (group & user)
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public static function count( $args = array() ) {
+		global $wpdb;
+
+		$r = bp_parse_args( $args, array(
+				'status'    => false,
+				'term_id'   => false,
+				'user_id'   => false,
+				'item_id'   => false,
+				'term_slug' => false,
+			)
+			, 'attachments_count_args'
+		);
+
+		$sql = array();
+		$detailed_count = array( 'total' => 0 );
+
+		$sql['select'] = "SELECT COUNT( p.ID ) as count, p.post_status as status, t.term_taxonomy_id as term_id";
+		$sql['from'] = "FROM {$wpdb->posts} p LEFT JOIN {$wpdb->term_relationships} t ON( p.ID = t.object_id )";
+		$sql['where'] = array( $wpdb->prepare( "p.post_type = %s", 'bp_attachment' ) );
+		$sql['groupby'] = array( 'p.post_status', 't.term_taxonomy_id' );
+
+		if ( ! empty( $r['status'] ) ) {
+			$sql['where'][] = $wpdb->prepare( "p.post_status = %s", $r['status'] );
+		}
+
+		if ( ! empty( $r['term_id'] ) ) {
+			$sql['where'][] = $wpdb->prepare( "t.term_taxonomy_id = %d", $r['term_id'] );
+		}
+
+		if ( ! empty( $r['user_id'] ) ) {
+			$sql['where'][] = $wpdb->prepare( "p.post_author = %s", $r['user_id'] );
+		}
+
+		if ( ! empty( $r['item_id'] ) && ! empty( $r['term_slug'] ) ) {
+			$sql['from'] .= " LEFT JOIN {$wpdb->postmeta} m ON ( p.ID = m.post_id )";
+			$sql['where'][] = $wpdb->prepare( "m.meta_key = %s AND m.meta_value = %d", '_bp_' . $r['term_slug'] .'_id', $r['item_id'] );
+		}
+
+		// join
+		$query = $sql['select'] . ' ' . $sql['from'] . ' WHERE ' . join( ' AND ', $sql['where'] ) . ' GROUP BY ' . join( ',', $sql['groupby'] );
+
+		$results = $wpdb->get_results( $query );
+
+		if ( empty( $results ) ) {
+			return $detailed_count;
+		}
+
+		foreach( $results as $result ) {
+			if ( empty( $result->count ) )
+				continue;
+
+			$count = absint( $result->count );
+
+			$detailed_count['total'] += $count;
+
+			if ( ! empty( $result->status ) ) {
+				// status
+				$detailed_count[ $result->status ] = empty( $detailed_count[ $result->status ] ) ? $count : absint( $detailed_count[ $result->status ] ) + $count;
+
+				// terms
+				if ( ! empty( $result->term_id ) ) {
+					$detailed_count[ $result->term_id ]['total'] = empty( $detailed_count[ $result->term_id ]['total'] ) ? $count : absint( $detailed_count[ $result->term_id ]['total'] ) + $count;
+					$detailed_count[ $result->term_id ][ $result->status ] = empty( $detailed_count[ $result->term_id ][ $result->status ] ) ? $count : absint( $detailed_count[ $result->term_id ][ $result->status ] ) + $count;
+				}
+			}
+		}
+
+		return $detailed_count;
+	}
+
+	/**
+	 * Delete an attachment
+	 *
+	 * @since BuddyPress (2.2.0)
+	 * @see wp_delete_attachment() this is an adapted version..
+	 */
+	public static function delete( $attachment_id = 0 ) {
+		global $wpdb;
+
+		if( empty( $attachment_id ) ) {
+			return false;
+		}
+
+		if ( ! $attachment = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$wpdb->posts} WHERE ID = %d", $attachment_id ) ) ) {
+			return $attachment;
+		}
+
+		if ( 'bp_attachment' != $attachment->post_type ) {
+			return false;
+		}
+
+		$meta = wp_get_attachment_metadata( $attachment_id );
+		$file = get_attached_file( $attachment_id );
+
+		$intermediate_sizes = array();
+		foreach ( get_intermediate_image_sizes() as $size ) {
+			if ( $intermediate = image_get_intermediate_size( $attachment_id, $size ) ) {
+				$intermediate_sizes[] = $intermediate;
+			}
+		}
+
+		if ( is_multisite() ) {
+			delete_transient( 'dirsize_cache' );
+		}
+
+		do_action( 'bp_attachments_before_attachment_delete', $attachment_id );
+
+		wp_delete_object_term_relationships( $attachment_id, get_object_taxonomies( $attachment->post_type ) );
+
+		delete_metadata( 'post', null, '_thumbnail_id', $attachment_id, true ); // delete all for any posts.
+
+		$post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM {$wpdb->postmeta} WHERE post_id = %d", $attachment_id ) );
+		foreach ( $post_meta_ids as $mid ) {
+			delete_metadata_by_mid( 'post', $mid );
+		}
+
+		$result = $wpdb->delete( $wpdb->posts, array( 'ID' => $attachment_id ) );
+		if ( ! $result ) {
+			return false;
+		}
+
+		do_action( 'bp_attachments_after_attachment_db_delete', $attachment_id );
+
+		$uploadpath = wp_upload_dir();
+
+		if ( ! empty( $meta['thumb'] ) ) {
+			$thumbfile = str_replace( basename( $file), $meta['thumb'], $file );
+			@ unlink( path_join( $uploadpath['basedir'], $thumbfile ) );
+		}
+
+		foreach ( $intermediate_sizes as $intermediate ) {
+			@ unlink( path_join( $uploadpath['basedir'], $intermediate['path'] ) );
+		}
+
+		if ( ! empty( $file ) ) {
+			@ unlink( $file );
+		}
+
+		clean_post_cache( $attachment );
+
+		do_action( 'bp_attachments_after_attachment_files_delete', $attachment_id );
+
+		return $attachment;
+	}
+}
+
+/**
+ * Attachments Capability Class.
+ *
+ * This class is used to create the
+ * arguments to check in the capability
+ * mapping filter
+ *
+ * @since BuddyPress (2.2.0)
+ */
+class BP_Attachments_Can {
+	public function __construct( $args = array() ) {
+		if ( empty( $args ) ) {
+			return false;
+		}
+
+		$r = bp_parse_args( $args, array(
+			'component'     => '',
+			'item_id'       => '',
+			'attachment_id' => ''
+		), 'attachments_can_args' );
+
+		foreach( $r as $key => $value ) {
+			$this->{$key} = $value;
+		}
+	}
+}
diff --git src/bp-attachments/bp-attachments-cssjs.php src/bp-attachments/bp-attachments-cssjs.php
index e69de29..d182bf5 100644
--- src/bp-attachments/bp-attachments-cssjs.php
+++ src/bp-attachments/bp-attachments-cssjs.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Attachments CssJs.
+ *
+ * @package BuddyPress
+ * @subpackage Attachments CssJs
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) or exit;
+
+/**
+ * Enqueues jCrop library and hooks BP's custom cropper JS.
+ */
+function bp_attachments_add_jquery_cropper() {
+	wp_enqueue_style( 'jcrop' );
+	wp_enqueue_script( 'jcrop', array( 'jquery' ) );
+	add_action( 'wp_head', 'bp_attachments_add_cropper_inline_js' );
+	add_action( 'wp_head', 'bp_attachments_add_cropper_inline_css' );
+}
+
+/**
+ * Output the inline JS needed for the cropper to work on a per-page basis.
+ */
+function bp_attachments_add_cropper_inline_js() {
+
+	// Bail if no image was uploaded
+	$image = apply_filters( 'bp_inline_cropper_image', getimagesize( bp_attachments_get_upload_dir() . 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
+	} else if ( $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
+	} else if ( $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; ?> ]
+			});
+			updateCoords({x: <?php echo (int) $crop_left; ?>, y: <?php echo (int) $crop_top; ?>, w: <?php echo (int) $crop_right; ?>, h: <?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_attachments_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
+}
diff --git src/bp-attachments/bp-attachments-functions.php src/bp-attachments/bp-attachments-functions.php
index e69de29..0879b54 100644
--- src/bp-attachments/bp-attachments-functions.php
+++ src/bp-attachments/bp-attachments-functions.php
@@ -0,0 +1,1550 @@
+<?php
+/**
+ * Attachments functions
+ *
+ * @package BuddyPress
+ * @subpackage Attachments Functions
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) or exit;
+
+/**
+ * Check avatar uploads settings
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_avatar_is_enabled() {
+	$bp = buddypress();
+
+	if ( ! (int) $bp->site_options['bp-disable-avatar-uploads'] && $bp->avatar->show_avatars ) {
+		return true;
+	}
+	return false;
+}
+
+/**
+ * Create the upload directories
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @uses    is_multisite()
+ * @uses    switch_to_blog()
+ * @uses    bp_get_root_blog_id()
+ * @uses    wp_upload_dir()
+ * @uses    restore_current_blog()
+ * @uses    wp_mkdir_p()
+ * @uses    apply_filters() call 'bp_attachments_use_attachments_api' to create the dirs
+ *                          required by the BP Attachments API
+ * @return  array list of upload urls and dirs
+ */
+function bp_attachments_set_upload_dirs() {
+	// We need to switch to the root blog on multisite installs
+	if ( is_multisite() ) {
+		switch_to_blog( bp_get_root_blog_id() );
+	}
+
+	// Get upload directory information from current site
+	$upload_datas = wp_upload_dir();
+
+	// Will bail if not switched
+	restore_current_blog();
+
+	$bp_attachments_sub_dirs = array(
+		'avatar_user'  => array( 'main' => 'avatars' ),
+	);
+
+	if ( bp_is_active( 'groups' ) ) {
+		$bp_attachments_sub_dirs['avatar_group'] = array( 'main' => 'group-avatars' );
+	}
+
+	if ( bp_is_active( 'blogs' ) ) {
+		$bp_attachments_sub_dirs['avatar_blog'] = array( 'main' => 'blog-avatars' );
+	}
+
+	if ( buddypress()->attachments->use_api ) {
+		$bp_attachments_sub_dirs = array_merge( $bp_attachments_sub_dirs, array(
+			'attachments' => array( 'main' => 'bp-attachments', 'subs' => array( 'public', 'private' ) ),
+		) );
+	}
+
+	$bp_attachments_upload_data = array();
+	$avatar_upload_data         = array();
+
+	if ( is_ssl() ) {
+		$upload_datas["baseurl"] = str_replace( 'http://', 'https://', $upload_datas["baseurl"] );
+	}
+
+	foreach ( $bp_attachments_sub_dirs as $key => $dirs ) {
+		if ( false !== strpos( $key, 'avatar' ) ) {
+			$avatar_upload_data = array_merge( $avatar_upload_data, array(
+				$key . '_dir' => $upload_datas["basedir"] .'/' . $dirs['main'],
+				$key . '_url' => $upload_datas["baseurl"] .'/' . $dirs['main'],
+			) );
+
+			if ( defined( 'BP_AVATAR_UPLOAD_PATH' ) ) {
+				$avatar_upload_data[ $key . '_dir' ] = trailingslashit( BP_AVATAR_UPLOAD_PATH ) . $dirs['main'];
+			}
+
+			if ( defined( 'BP_AVATAR_URL' ) ) {
+				$avatar_upload_data[ $key . '_url' ] = trailingslashit( BP_AVATAR_URL ) . $dirs['main'];
+			}
+
+		} else if ( 'attachments' == $key ) {
+			// basedir and baseurl for BuddyPress attachments
+			$bp_attachments_upload_data = array(
+				$key . '_dir' => $upload_datas["basedir"] . '/' . $dirs['main'],
+				$key . '_url' => $upload_datas["baseurl"] . '/' . $dirs['main'],
+			);
+
+			// Loop in subs
+			foreach ( $dirs['subs'] as $sub ) {
+				$bp_attachments_upload_data = array_merge( $bp_attachments_upload_data, array(
+					$key . '_' . $sub . '_dir' => $upload_datas["basedir"] . '/' . $dirs['main'] . '/' . $sub,
+					$key . '_' . $sub . '_url' => $upload_datas["baseurl"] . '/' . $dirs['main'] . '/' . $sub,
+				) );
+			}
+		}
+	}
+
+	// Append Avatar upload data
+	$bp_attachments_upload_data = array_merge( $avatar_upload_data, $bp_attachments_upload_data );
+
+	// Loop in dirs to eventually create them if they do not exist
+	foreach ( $bp_attachments_upload_data as $key_upload_dir => $upload_dir ) {
+
+		if( ! file_exists( $upload_dir ) ) {
+			@wp_mkdir_p( $upload_dir );
+
+			// Do additional actions (for instance create an .htaccess file for private dir)
+			do_action( 'bp_attachments_create_upload_dir', $key_upload_dir, $upload_dir );
+		}
+	}
+
+	// Do backcompat for bp_core_get_upload_dir()
+	$bp_attachments_upload_data = array_merge( array(
+		'upload_path' => $upload_datas["basedir"],
+		'url'         => $upload_datas["baseurl"],
+	), $bp_attachments_upload_data );
+
+	// finally returns upload_data
+	return $bp_attachments_upload_data;
+}
+
+/**
+ * Get the upload dir/url
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  string $type    type dir/url to get
+ * @uses   apply_filters() call 'bp_attachments_use_attachments_api' to get the dirs
+ *                         of the BP Attachments API
+ * @return string          upload url or path
+ */
+function bp_attachments_get_upload_dir( $type = 'upload_path' ) {
+	$possible_matches = array(
+		'upload_path'     => 1,
+		'url'             => 1,
+		'avatar_user_dir' => 1,
+		'avatar_user_url' => 1,
+	);
+
+	if ( bp_is_active( 'groups' ) ) {
+		$possible_matches = array_merge( $possible_matches, array(
+			'avatar_group_dir' => 1,
+			'avatar_group_url' => 1,
+		) );
+	}
+
+	if ( bp_is_active( 'blogs' ) ) {
+		$possible_matches = array_merge( $possible_matches, array(
+			'avatar_blog_dir' => 1,
+			'avatar_blog_url' => 1,
+		) );
+	}
+
+	if ( buddypress()->attachments->use_api ) {
+		$possible_matches = array_merge( $possible_matches, array(
+			'attachments_dir'         => 1,
+			'attachments_url'         => 1,
+			'attachments_public_dir'  => 1,
+			'attachments_public_url'  => 1,
+			'attachments_private_dir' => 1,
+			'attachments_private_url' => 1,
+		) );
+	}
+
+	// Only return upload datas
+	if ( empty( $possible_matches[ $type ] ) ) {
+		return false;
+	}
+
+	$hook_prefix = 'bp_attachments_get_';
+
+	// Backcompat
+	if ( 'upload_path' == $type || 'url' == $type ) {
+		$hook_prefix = 'bp_core_avatar_';
+	}
+
+	return apply_filters( $hook_prefix . $type , buddypress()->attachments->{$type} );
+}
+
+/**
+ * Search for local avatar data and return an avatar object if any
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  object $avatar
+ * @return object the avatar object with local data if found
+ */
+function bp_attachments_avatar_local_data( $avatar = null ) {
+	if ( empty( $avatar->avatar_dir ) || ! bp_attachments_avatar_is_enabled() ) {
+		return $avatar;
+	}
+
+	// Set img URL and DIR based on prepopulated constants
+	$avatar->folder_dir = bp_attachments_get_upload_dir( $avatar->avatar_dir . '_dir' );
+
+	if ( empty( $avatar->folder_dir ) ) {
+		$avatar->loc_path  = trailingslashit( bp_attachments_get_upload_dir() );
+		$avatar->loc_url   = trailingslashit( bp_attachments_get_upload_dir( 'url' ) );
+
+		$avatar->loc_dir    = trailingslashit( $avatar->avatar_dir );
+		$avatar->folder_url = apply_filters( 'bp_core_avatar_folder_url', ( $avatar->loc_url  . $avatar->loc_dir . $avatar->item_id ), $avatar->item_id, $avatar->object, $avatar->avatar_dir );
+		$avatar->folder_dir = apply_filters( 'bp_core_avatar_folder_dir', ( $avatar->loc_path . $avatar->loc_dir . $avatar->item_id ), $avatar->item_id, $avatar->object, $avatar->avatar_dir );
+	} else {
+		$avatar->folder_dir = apply_filters( 'bp_core_avatar_folder_dir', trailingslashit( $avatar->folder_dir ) . $avatar->item_id, $avatar->item_id, $avatar->object, $avatar->avatar_dir );
+		$avatar->folder_url = bp_attachments_get_upload_dir( $avatar->avatar_dir . '_url' );
+		$avatar->folder_url = apply_filters( 'bp_core_avatar_folder_url', trailingslashit( $avatar->folder_url ) . $avatar->item_id, $avatar->item_id, $avatar->object, $avatar->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' == $avatar->type ) ? '-bpfull' : '-bpthumb';
+	$legacy_user_avatar_name  = ( 'full' == $avatar->type ) ? '-avatar2' : '-avatar1';
+	$legacy_group_avatar_name = ( 'full' == $avatar->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 ( empty( $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 ( empty( $avatar->url ) ) {
+						foreach( $avatar_files as $key => $value ) {
+							if ( strpos ( $value, $legacy_group_avatar_name )!== false ) {
+								$avatar->url = $avatar->folder_url . '/' . $avatar_files[$key];
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return $avatar;
+}
+add_filter( 'bp_core_avatar_local_data', 'bp_attachments_avatar_local_data', 10, 1 );
+
+/**
+ * Delete an existing avatar.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param array $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_attachments_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
+	);
+
+	$r = wp_parse_args( $args, $defaults );
+
+	if ( empty( $r['item_id'] ) ) {
+		if ( 'user' == $r['object'] ) {
+			$r['item_id'] = bp_displayed_user_id();
+		} else if ( 'group' == $r['object'] ) {
+			$r['item_id'] = buddypress()->groups->current_group->id;
+		} else if ( 'blog' == $r['object'] ) {
+			$r['item_id'] = get_current_blog_id();
+		}
+
+		$r['item_id'] = apply_filters( 'bp_core_avatar_item_id', $r['item_id'], $r['object'] );
+
+		if ( ! $r['item_id'] ) {
+			return false;
+		}
+	}
+
+	if ( empty( $r['avatar_dir'] ) ) {
+		if ( 'user' == $r['object'] ) {
+			$r['avatar_dir'] = 'avatar_user_dir';
+		} else if ( 'group' == $r['object'] ) {
+			$r['avatar_dir'] = 'avatar_group_dir';
+		} else if ( 'blog' == $r['object'] ) {
+			$r['avatar_dir'] = 'avatar_blog_dir';
+		}
+
+		$r['avatar_dir'] = apply_filters( 'bp_core_avatar_dir', $r['avatar_dir'], $r['object'] );
+
+		if ( ! $r['avatar_dir'] ) {
+			return false;
+		}
+	}
+
+	$avatar_folder_dir = bp_attachments_get_upload_dir( $r['avatar_dir'] );
+
+	// Backcompat
+	if ( empty( $avatar_folder_dir ) ) {
+		$avatar_folder_dir = bp_attachments_get_upload_dir() . '/' . $r['avatar_dir'];
+	}
+
+	$avatar_folder_dir .= '/' . $r['item_id'];
+
+	$avatar_folder_dir = apply_filters( 'bp_core_avatar_folder_dir', $avatar_folder_dir, $r['item_id'], $r['object'], $r['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 );
+
+	do_action( 'bp_core_delete_existing_avatar', $args );
+
+	return true;
+}
+
+/**
+ * Setup the avatar upload directory for a user.
+ *
+ * @since BuddyPress (2.2.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 bp_attachments_xprofile_avatar_upload_dir( $directory = 'avatar_user_dir', $user_id = 0 ) {
+	$bp = buddypress();
+
+	// Use displayed user if no user ID was passed
+	if ( empty( $user_id ) ) {
+		// Default
+		$user_id = bp_displayed_user_id();
+
+		// In Profile administration screen displayed user id is not set
+		if ( ! empty( $bp->avatar_admin->image->item_id ) && 'xprofile' == $bp->avatar_admin->image->component ) {
+			$user_id = $bp->avatar_admin->image->item_id;
+		}
+	}
+
+	// Failsafe against accidentally nooped $directory parameter
+	if ( empty( $directory ) ) {
+		$directory = 'avatar_user_dir';
+	}
+
+	$avatar_folder_dir = bp_attachments_get_upload_dir( $directory );
+
+	// Backcompat
+	if ( empty( $avatar_folder_dir ) ) {
+		$avatar_folder_dir = bp_attachments_get_upload_dir() . '/' . $directory;
+	}
+
+	$path    = $avatar_folder_dir . '/' . $user_id;
+	$newbdir = $path;
+
+	if ( ! file_exists( $path ) ) {
+		@wp_mkdir_p( $path );
+	}
+
+	// Backcompat
+	if ( 'avatar_user_dir' != $directory ) {
+		$newurl = bp_attachments_get_upload_dir( 'url' ) . '/' . $directory. '/' . $user_id;
+	} else {
+		$newurl = bp_attachments_get_upload_dir( 'avatar_user_url' ) . '/' . $user_id;
+	}
+
+	$newburl   = $newurl;
+
+	return apply_filters( 'xprofile_avatar_upload_dir', array(
+		'path'    => $path,
+		'url'     => $newurl,
+		'subdir'  => false,
+		'basedir' => $newbdir,
+		'baseurl' => $newburl,
+		'error'   => false
+	) );
+}
+
+/**
+ * Generate the avatar upload directory path for a given group.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param int $group_id Optional. ID of the group. Default: ID of the
+ *        current group.
+ * @return string
+ */
+function bp_attachments_groups_avatar_upload_dir( $group_id = 0 ) {
+	$bp = buddypress();
+
+	// Bail if groups component is not active
+	if ( ! bp_is_active( 'groups' ) ) {
+		return false;
+	}
+
+	if ( empty( $group_id ) ) {
+		$group_id = bp_get_current_group_id();
+
+		// In Group administration screen current group id is not set
+		if ( ! empty( $bp->avatar_admin->image->item_id ) && 'groups' == $bp->avatar_admin->image->component ) {
+			$group_id = $bp->avatar_admin->image->item_id;
+		}
+	}
+
+	$path    = bp_attachments_get_upload_dir( 'avatar_group_dir' ) . '/' . $group_id;
+	$newbdir = $path;
+
+	if ( ! file_exists( $path ) ) {
+		@wp_mkdir_p( $path );
+	}
+
+	$newurl    = bp_attachments_get_upload_dir( 'avatar_group_url' ) . '/' . $group_id;
+	$newburl   = $newurl;
+
+	return apply_filters( 'groups_avatar_upload_dir', array(
+		'path'    => $path,
+		'url'     => $newurl,
+		'subdir'  => false,
+		'basedir' => $newbdir,
+		'baseurl' => $newburl,
+		'error'   => false
+	) );
+}
+
+/**
+ * Get the avatar storage directory for use during registration.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @return string|bool Directory path on success, false on failure.
+ */
+function bp_attachments_signup_avatar_upload_dir() {
+	$bp = buddypress();
+
+	if ( empty( $bp->signup->avatar_dir ) ) {
+		return false;
+	}
+
+	$path  = bp_attachments_get_upload_dir( 'avatar_user_dir' ) . '/signups/' . $bp->signup->avatar_dir;
+	$newbdir = $path;
+
+	if ( ! file_exists( $path ) ) {
+		@wp_mkdir_p( $path );
+	}
+
+	$newurl = bp_attachments_get_upload_dir( 'avatar_user_url' ) . '/signups/' . $bp->signup->avatar_dir;
+	$newburl = $newurl;
+
+	return apply_filters( 'bp_core_signup_avatar_upload_dir', array(
+		'path'    => $path,
+		'url'     => $newurl,
+		'subdir'  => false,
+		'basedir' => $newbdir,
+		'baseurl' => $newburl,
+		'error' => false
+	) );
+}
+
+/**
+ * Prepare an avatar to be displayed in the BP Attachments Editor
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_prepare_avatar_for_js( $avatar = null ) {
+	if ( empty( $avatar ) ) {
+		return;
+	}
+
+	$response = array(
+		'id'        => $avatar->item_id,
+		'title'     => $avatar->name,
+		'component' => $avatar->component,
+		'filename'  => wp_basename( $avatar->dir ),
+		'url'       => $avatar->url,
+		'path'      => $avatar->dir,
+		'width'     => $avatar->width,
+		'height'    => $avatar->height,
+		'src'       => $avatar->src,
+	);
+
+	return apply_filters( 'bp_attachments_prepare_avatar_for_js', $response, $avatar );
+}
+
+/**
+ * Handle avatar uploading.
+ *
+ * The functions starts off by checking that the file has been uploaded
+ * properly using bp_attachments_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.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @see bp_attachments_check_avatar_upload()
+ * @see bp_attachments_check_avatar_type()
+ *
+ * @param array  $file The appropriate entry the from $_FILES superglobal.
+ * @param string $component the component for the avatar (xprofile, groups,blogs..).
+ * @return string json object or url to the uploaded avatar
+ */
+function bp_attachments_avatar_handle_upload( $file, $component, $item_id = 0, $do_js = false ) {
+	$bp = buddypress();
+
+	/***
+	 * You may want to hook into this filter if you want to override this function.
+	 * Make sure you return false.
+	 */
+	if ( ! apply_filters( 'bp_core_pre_avatar_handle_upload', true, $file, $component . '_avatar_upload_dir', $component, $item_id, $do_js ) ) {
+		return true;
+	}
+
+	/**
+	 * Map Dirs for backcompat
+	 */
+	if ( empty( $bp->active_components[ $component ] ) ) {
+		$dirs = array(
+			'group-avatars'  => $bp->groups->id,
+			'blog-avatars'   => $bp->blogs->id,
+			'avatars/signup' => 'signup',
+			'avatars'        => $bp->profile->id,
+		);
+
+		if ( ! empty( $dirs[ $component ] ) ) {
+			$component = $dirs[ $component ];
+		} else if ( ! empty( $bp->signup->avatar_dir ) ) {
+			$component = 'signup';
+		}
+	}
+
+	require_once( ABSPATH . '/wp-admin/includes/file.php' );
+
+	$uploadErrors = array(
+		0 => __( 'The image was uploaded successfully', 'buddypress' ),
+		1 => __( 'The image exceeds the maximum allowed file size of: ', 'buddypress' ) . size_format( bp_attachments_avatar_original_max_filesize() ),
+		2 => __( 'The image exceeds the maximum allowed file size of: ', 'buddypress' ) . size_format( bp_attachments_avatar_original_max_filesize() ),
+		3 => __( 'The uploaded file was only partially uploaded.', 'buddypress' ),
+		4 => __( 'The image was not uploaded.', 'buddypress' ),
+		6 => __( 'Missing a temporary folder.', 'buddypress' )
+	);
+
+	if ( ! bp_attachments_check_avatar_upload( $file ) ) {
+		return new WP_Error( 'upload_error', sprintf( __( 'Your upload failed, please try again. Error was: %s', 'buddypress' ), $uploadErrors[$file['file']['error']] ), $file );
+	}
+
+	if ( ! bp_attachments_check_avatar_size( $file ) ) {
+		return new WP_Error( 'upload_error', sprintf( __( 'The file you uploaded is too big. Please upload a file under %s', 'buddypress' ), size_format( bp_attachments_avatar_original_max_filesize() ) ), $file );
+	}
+
+	if ( ! bp_attachments_check_avatar_type( $file ) ) {
+		return new WP_Error( 'upload_error', __( 'Please upload only JPG, GIF or PNG photos.', 'buddypress' ), $file );
+	}
+
+	if ( ! isset( $bp->avatar_admin ) ) {
+		$bp->avatar_admin = new stdClass();
+	}
+
+	// Set the item id and the component of the uploaded avatar
+	$bp->avatar_admin->image = (object) array(
+		'item_id'   => $item_id,
+		'component' => $component,
+	);
+
+	if ( ! is_callable( "bp_attachments_{$component}_avatar_upload_dir" ) ) {
+		return new WP_Error( 'upload_error', sprintf( __( 'Avatar cannot be uploaded for the component: %s. Please contact the administrator.', 'buddypress' ), $component ), $file );
+	}
+
+	// Filter the upload location
+	add_filter( 'upload_dir', "bp_attachments_{$component}_avatar_upload_dir", 10, 0 );
+
+	$bp->avatar_admin->original = wp_handle_upload( $file['file'], array( 'action'=> 'bp_avatar_upload' ) );
+
+	// Remove the upload_dir filter, so that other upload URLs on the page
+	// don't break
+	remove_filter( 'upload_dir',"bp_attachments_{$component}_avatar_upload_dir", 10, 0 );
+
+	// Move the file to the correct upload location.
+	if ( ! empty( $bp->avatar_admin->original['error'] ) ) {
+		return new WP_Error( 'upload_error', sprintf( __( 'Upload Failed! Error was: %s', 'buddypress' ), $bp->avatar_admin->original['error'] ), $file );
+	}
+
+	// Get image size
+	$size  = @getimagesize( $bp->avatar_admin->original['file'] );
+	$error = false;
+
+	// Check image size and shrink if too large
+	if ( $size[0] > bp_attachments_avatar_original_max_width() ) {
+		$editor = wp_get_image_editor( $bp->avatar_admin->original['file'] );
+
+		if ( ! is_wp_error( $editor ) ) {
+			$editor->set_quality( 100 );
+
+			$resized = $editor->resize( bp_attachments_avatar_original_max_width(), bp_attachments_avatar_original_max_width(), false );
+			if ( ! is_wp_error( $resized ) ) {
+				$thumb = $editor->save( $editor->generate_filename() );
+			} else {
+				$error = $resized;
+			}
+
+			// Check for thumbnail creation errors
+			if ( false === $error && is_wp_error( $thumb ) ) {
+				$error = $thumb;
+			}
+
+			// Thumbnail is good so proceed
+			if ( false === $error ) {
+				$bp->avatar_admin->resized = $thumb;
+			}
+
+		} else {
+			$error = $editor;
+		}
+
+		if ( false !== $error ) {
+			return $error;
+		}
+	}
+
+	// We only want to handle one image after resize.
+	if ( empty( $bp->avatar_admin->resized ) ) {
+		$bp->avatar_admin->image->dir = str_replace( bp_attachments_get_upload_dir(), '', $bp->avatar_admin->original['file'] );
+	} else {
+		$size = @getimagesize( $bp->avatar_admin->resized['path'] );
+		$bp->avatar_admin->image->dir = str_replace( bp_attachments_get_upload_dir(), '', $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->avatar_admin->image->dir->add_data( sprintf( __( 'Upload failed! Error was: %s', 'buddypress' ), $bp->avatar_admin->image->dir->get_error_message() ) );
+		return $bp->avatar_admin->image->dir;
+	}
+
+	// If the uploaded image is smaller than the "full" dimensions, throw
+	// a warning
+	$uploaded_image = @getimagesize( bp_attachments_get_upload_dir() . $bp->avatar_admin->image->dir );
+	$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 ) {
+		// Remove the image that doesn't match the minimum dimensions.
+		@unlink( $bp->avatar_admin->original['file'] );
+
+		// Return the error
+		return new WP_Error( 'upload_error', sprintf( __( 'You have selected an image that is smaller than recommended. For best results, upload a picture larger than %d x %d pixels.', 'buddypress' ), $full_width, $full_height ), $file );
+	}
+
+	// Set the name of the file
+	$name = $file['file']['name'];
+	$name_parts = pathinfo( $name );
+	$name = trim( substr( $name, 0, - ( 1 + strlen( $name_parts['extension'] ) ) ) );
+	$bp->avatar_admin->image->name = $name;
+
+	if ( ! empty( $size ) ) {
+		$bp->avatar_admin->image->width = $size[0];
+		$bp->avatar_admin->image->height = $size[1];
+	}
+
+	// Set the url value for the image
+	$bp->avatar_admin->image->url = bp_attachments_get_upload_dir( 'url' ) . $bp->avatar_admin->image->dir;
+	$bp->avatar_admin->image->src = apply_filters( 'bp_get_avatar_to_crop_src', str_replace( WP_CONTENT_DIR, '', $bp->avatar_admin->image->dir ) );
+
+	if ( ! empty( $do_js ) ) {
+		return bp_attachments_prepare_avatar_for_js( $bp->avatar_admin->image );
+	} else {
+		return $bp->avatar_admin->image->src;
+	}
+}
+
+/**
+ * Reset the week parameter of the WordPress main query if needed
+ *
+ * When cropping an avatar, a $_POST['w'] var is sent, setting the 'week'
+ * paramater 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  BuddyPress (2.2.0)
+ *
+ * @param  WP_Quert $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_attachments_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
+	} else if ( 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_attachments_avatar_reset_query', 10, 1 );
+
+/**
+ * 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
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param array $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 papth 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_attachments_avatar_handle_crop( $args = '' ) {
+
+	$r = wp_parse_args( $args, array(
+		'object'        => 'user',
+		'avatar_dir'    => 'avatar_user_dir',
+		'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
+	) );
+
+	/***
+	 * You may want to hook into this filter if you want to override this function.
+	 * Make sure you return false.
+	 */
+	if ( ! apply_filters( 'bp_core_pre_avatar_handle_crop', true, $r ) ) {
+		return true;
+	}
+
+	if ( empty( $r['original_file'] ) ) {
+		return false;
+	}
+
+	$original_file = bp_attachments_get_upload_dir() . $r['original_file'];
+
+	if ( ! file_exists( $original_file ) ) {
+		return false;
+	}
+
+	$avatar_folder_dir = bp_attachments_get_upload_dir( $r['avatar_dir'] );
+
+	// Backcompat
+	if ( empty( $avatar_folder_dir ) ) {
+		$avatar_folder_dir = bp_attachments_get_upload_dir() . '/' . $r['avatar_dir'];
+	}
+
+	if ( empty( $r['item_id'] ) ) {
+		$avatar_folder_dir = apply_filters( 'bp_core_avatar_folder_dir', dirname( $original_file ), $r['item_id'], $r['object'], $r['avatar_dir'] );
+	} else {
+		$avatar_folder_dir = apply_filters( 'bp_core_avatar_folder_dir',  trailingslashit( $avatar_folder_dir ) . $r['item_id'], $r['item_id'], $r['object'], $r['avatar_dir'] );
+	}
+
+	if ( ! file_exists( $avatar_folder_dir ) ) {
+		return false;
+	}
+
+	require_once( ABSPATH . '/wp-admin/includes/image.php' );
+	require_once( ABSPATH . '/wp-admin/includes/file.php' );
+
+	// Delete the existing avatar files for the object
+	$existing_avatar = bp_core_fetch_avatar( array(
+		'object'  => $r['object'],
+		'item_id' => $r['item_id'],
+		'html' => false,
+	) );
+
+	if ( ! empty( $existing_avatar ) ) {
+		// Check that the new avatar doesn't have the same name as the
+		// old one before deleting
+		$upload_dir           = wp_upload_dir();
+		$existing_avatar_path = str_replace( $upload_dir['baseurl'], '', $existing_avatar );
+		$new_avatar_path      = str_replace( $upload_dir['basedir'], '', $original_file );
+
+		if ( $existing_avatar_path !== $new_avatar_path ) {
+			bp_attachments_delete_existing_avatar( array( 'object' => $r['object'], 'item_id' => $r['item_id'], 'avatar_path' => $avatar_folder_dir ) );
+		}
+	}
+
+	// Make sure we at least have a width and height for cropping
+	if ( empty( $r['crop_w'] ) ) {
+		$r['crop_w'] = bp_core_avatar_full_width();
+	}
+
+	if ( empty( $r['crop_h'] ) ) {
+		$r['crop_h'] = bp_core_avatar_full_height();
+	}
+
+	// Get the file extension
+	$data = @getimagesize( $original_file );
+	$ext  = $data['mime'] == 'image/png' ? 'png' : 'jpg';
+
+	// Set the full and thumb filenames
+	$full_filename  = wp_hash( $original_file . time() ) . '-bpfull.'  . $ext;
+	$thumb_filename = wp_hash( $original_file . time() ) . '-bpthumb.' . $ext;
+
+	// Crop the image
+	$full_cropped  = wp_crop_image( $original_file, (int) $r['crop_x'], (int) $r['crop_y'], (int) $r['crop_w'], (int) $r['crop_h'], bp_core_avatar_full_width(),  bp_core_avatar_full_height(),  false, $avatar_folder_dir . '/' . $full_filename  );
+	$thumb_cropped = wp_crop_image( $original_file, (int) $r['crop_x'], (int) $r['crop_y'], (int) $r['crop_w'], (int) $r['crop_h'], bp_core_avatar_thumb_width(), bp_core_avatar_thumb_height(), false, $avatar_folder_dir . '/' . $thumb_filename );
+
+	// Check for errors
+	if ( empty( $full_cropped ) || empty( $thumb_cropped ) || is_wp_error( $full_cropped ) || is_wp_error( $thumb_cropped ) ) {
+		return false;
+	}
+
+	// Remove the original
+	@unlink( $original_file );
+
+	return true;
+}
+
+/**
+ * Is the current avatar upload error-free?
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param array $file The $_FILES array.
+ * @return bool True if no errors are found. False if there are errors.
+ */
+function bp_attachments_check_avatar_upload( $file ) {
+	if ( isset( $file['error'] ) && $file['error'] )
+		return false;
+
+	return true;
+}
+
+/**
+ * Is the file size of the current avatar upload permitted?
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param array $file The $_FILES array.
+ * @return bool True if the avatar is under the size limit, otherwise false.
+ */
+function bp_attachments_check_avatar_size( $file ) {
+	if ( $file['file']['size'] > bp_attachments_avatar_original_max_filesize() )
+		return false;
+
+	return true;
+}
+
+/**
+ * Does the current avatar upload have an allowed file type?
+ *
+ * Permitted file types are JPG, GIF and PNG.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param array $file The $_FILES array.
+ * @return bool True if the file extension is permitted, otherwise false.
+ */
+function bp_attachments_check_avatar_type( $file ) {
+	if ( ( !empty( $file['file']['type'] ) && !preg_match('/(jpe?g|gif|png)$/i', $file['file']['type'] ) ) || !preg_match( '/(jpe?g|gif|png)$/i', $file['file']['name'] ) )
+		return false;
+
+	return true;
+}
+
+/**
+ * Get the max width for original avatar uploads.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @return int The max width for original avatar uploads.
+ */
+function bp_attachments_avatar_original_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 BuddyPress (2.2.0)
+ *
+ * @return int The max filesize for original avatar uploads.
+ */
+function bp_attachments_avatar_original_max_filesize() {
+	return apply_filters( 'bp_core_avatar_original_max_filesize', (int) buddypress()->avatar->original_max_filesize );
+}
+
+/** Attachments **************************************************************/
+
+/**
+ * Get the term id for a given component
+ *
+ * Using term slugs, there's a possibility $bp->{component}->id != term slug
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  string $component_id the component id
+ * @return int    the term id
+ */
+function bp_attachments_get_component_term_id( $component_id = '' ) {
+	$bp = buddypress();
+
+	if ( ! empty( $bp->attachments->component_terms[ $component_id ] ) ) {
+		return (int) $bp->attachments->component_terms[ $component_id ];
+	}
+
+	return false;
+}
+
+/**
+ * Get the component id for a given term
+ *
+ * Using term slugs, there's a possibility $bp->{component}->id != term slug
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  int $term_id the term id
+ * @return string       the component id
+ */
+function bp_attachments_get_term_component_id( $term_id = 0 ) {
+	$bp = buddypress();
+
+	$terms_component = array_flip( $bp->attachments->component_terms );
+
+	if ( ! empty( $terms_component[ $term_id ] ) ) {
+		return $terms_component[ $term_id ];
+	}
+
+	return false;
+}
+
+/**
+ * Get a single attachment
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  integer $attachment_id
+ * @return BP_Attachments
+ */
+function bp_attachments_get_attachment( $attachment_id = 0 ) {
+	if ( empty( $attachment_id ) )
+		return false;
+
+	$attachment = new BP_Attachments( $attachment_id );
+
+	return apply_filters( 'bp_attachments_get_attachment', $attachment );
+}
+
+/**
+ * Get a single attachment
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  array $args
+ * @return array of BP_Attachments
+ */
+function bp_attachments_get_attachments( $args = array() ) {
+
+	$defaults = array(
+		'item_ids'        => array(), // one or more item ids regarding the component (eg group_ids, message_ids )
+		'component'	      => false,   // groups / messages / blogs / xprofile...
+		'show_private'    => false,   // wether to include private attachment
+		'user_id'	      => false,   // the author id of the attachment
+		'per_page'	      => 20,
+		'page'		      => 1,
+		'search'          => false,
+		'exclude'		  => false,   // comma separated list or array of attachment ids.
+		'orderby' 		  => 'ID',
+		'order'           => 'DESC',
+	);
+
+	$r = bp_parse_args( $args, $defaults, 'attachments_get_args' );
+
+	$attachments = wp_cache_get( 'bp_attachments_attachments', 'bp' );
+
+	if ( empty( $attachments ) ) {
+		$attachments = BP_Attachments::get( array(
+			'item_ids'        => (array) $r['item_ids'],
+			'component'	      => $r['component'],
+			'show_private'    => (bool) $r['show_private'],
+			'user_id'	      => $r['user_id'],
+			'per_page'	      => $r['per_page'],
+			'page'		      => $r['page'],
+			'search'          => $r['search'],
+			'exclude'		  => $r['exclude'],
+			'orderby' 		  => $r['orderby'],
+			'order'           => $r['order'],
+		) );
+
+		wp_cache_set( 'bp_attachments_attachments', $attachments, 'bp' );
+	}
+
+	return apply_filters_ref_array( 'bp_attachments_get_attachments', array( &$attachments, &$r ) );
+}
+
+/**
+ * Upload attachment
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  array $args
+ * @return int the id of the created attachment
+ */
+function bp_attachments_handle_upload( $args = array() ) {
+
+	$r = bp_parse_args( $args, array(
+		'item_id'         => 0,
+		'component'       => '',
+		'item_type'       => 'attachment',
+		'action'          => 'bp_attachments_upload',
+		'file_id'         => 'bp_attachment_file',
+	), 'attachments_handle_upload_args' );
+
+	$attachment_upload = BP_Attachments_Upload::start( $r );
+
+	return $attachment_upload->attachment_id;
+}
+
+/**
+ * Delete attachment
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  int the id of the attachment
+ * @return mixed false/title of the deleted attachment
+ */
+function bp_attachments_delete_attachment( $attachment_id = 0 ) {
+	if ( empty( $attachment_id ) ) {
+		return false;
+	}
+
+	if ( ! bp_attachments_current_user_can( 'delete_bp_attachment', $attachment_id ) ) {
+		return false;
+	}
+
+	$deleted = BP_Attachments::delete( $attachment_id );
+
+	if ( ! empty( $deleted->post_title ) ) {
+		return $deleted->post_title;
+	} else {
+		return false;
+	}
+}
+
+/**
+ * Delete attachment
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  array $args
+ * @return mixed false/title of the updated attachment
+ */
+function bp_attachments_update_attachment( $args = array() ) {
+	$r = bp_parse_args( $args, array(
+		'id'          => 0,
+		'title'       => '',
+		'description' => '',
+		'privacy'     => 'inherit',
+		'component'   => array(),
+		'terms'       => '',
+		'ajax'        => false
+	), 'attachments_update_attachment_args' );
+
+	if ( empty( $r['id'] ) ) {
+		return false;
+	}
+
+	if ( ! bp_attachments_current_user_can( 'edit_bp_attachment', $r['id'] ) ) {
+		return false;
+	}
+
+	$attachment = new BP_Attachments( $r['id'] );
+
+	if ( empty( $attachment ) ) {
+		return false;
+	}
+
+	if ( empty( $r['title'] ) ) {
+		$r['title'] = $attachment->title;
+	}
+
+	$attachment->title       = $r['title'];
+	$attachment->description = $r['description'];
+	$attachment->status      = $r['privacy'];
+
+	if ( empty( $r['ajax'] ) ) {
+
+		$prev_item_ids = ! empty( $attachment->item_ids ) ? $attachment->item_ids : false;
+
+		if ( ! empty( $r['terms'] ) ) {
+			$terms = explode( ',', $r['terms'] );
+			// Let's handle the item ids here
+			foreach ( $terms as $key => $term ) {
+				if ( empty( $r['component'][ $term ] ) ) {
+					// delete all !
+					delete_post_meta( $id, "_bp_{$term}_id" );
+					unset( $terms[ $key ] );
+				} else {
+					if ( empty( $prev_item_ids->{$term} ) ) {
+						foreach( $r['component'][ $term ] as $item_id )
+							add_post_meta( $id, "_bp_{$term}_id", $item_id );
+
+					} else {
+						$to_delete = array_diff( $prev_item_ids->{$term}, $r['component'][ $term ] );
+						$to_add    = array_diff( $r['component'][ $term ], $prev_item_ids->{$term} );
+
+						if ( ! empty( $to_delete ) ){
+							// Delete item ids
+							foreach ( $to_delete as $item_id ) {
+								delete_post_meta( $id, "_bp_{$term}_id", $item_id );
+							}
+						}
+
+						if ( ! empty( $to_add ) ){
+							// Delete item ids
+							foreach ( $to_add as $item_id ) {
+								add_post_meta( $id, "_bp_{$term}_id", $item_id );
+							}
+						}
+					}
+				}
+			}
+
+			if ( empty( $terms ) ) {
+				$terms = null;
+			}
+
+			/**
+			 * @todo transform slugs into ids using
+			 * bp_attachments_get_component_term_id( $component_id )
+			 */
+			wp_set_object_terms( $r['id'], $terms, 'bp_component' );
+
+		} else {
+			wp_set_object_terms( $r['id'], null, 'bp_component' );
+		}
+
+	}
+
+	return $attachment->update();
+}
+
+/**
+ * Launch the BP Attachments Editor
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_browser( $browser_id, $settings = array() ) {
+	BP_Attachments_Browser::browser( $browser_id, $settings );
+}
+
+/**
+ * Retrieve the URL for an attachment.
+ *
+ * This is an adapted version of wp_get_attachment_url()
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param int $post_id Attachment ID.
+ * @return string
+ */
+function bp_attachments_get_attachment_url( $post_id = 0 ) {
+	$post_id = (int) $post_id;
+	$post = get_post( $post_id );
+
+	if ( empty( $post ) ) {
+		return false;
+	}
+
+	if ( 'bp_attachment' != $post->post_type ) {
+		return false;
+	}
+
+	$url = '';
+	// Get attached file
+	$file = get_post_meta( $post->ID, '_wp_attached_file', true );
+	if ( ! empty( $file ) ) {
+		$uploads = wp_upload_dir();
+
+		// Get upload directory
+		if ( ! empty( $uploads ) && false === $uploads['error'] ) {
+
+			// Check that the upload base exists in the file location
+			if ( 0 === strpos( $file, $uploads['basedir'] ) ) {
+				// replace file location with url location
+				$url = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
+			} else if ( false !== strpos( $file, 'wp-content/uploads' ) ) {
+				$url = $uploads['baseurl'] . substr( $file, strpos($file, 'wp-content/uploads') + 18 );
+
+			// Its a newly uploaded file, therefor $file is relative to the basedir.
+			} else {
+				$url = $uploads['baseurl'] . "/$file";
+			}
+		}
+	}
+
+	//If any of the above options failed, Fallback on the GUID
+	if ( empty( $url ) ) {
+		$url = get_the_guid( $post->ID );
+	}
+
+	$url = apply_filters( 'bp_attachments_get_attachment_url', $url, $post->ID );
+
+	if ( empty( $url ) ) {
+		return false;
+	}
+
+	return $url;
+}
+
+/**
+ * Custom filter for WordPress image_downsize
+ *
+ * Use it before and after wp_get_attachment_image()
+ *
+ * @see bp_attachments_get_attachment_image()
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_image_downsize( $output = '', $id = 0, $size = 'medium' ) {
+	$img_url = bp_attachments_get_attachment_url( $id );
+	$meta = wp_get_attachment_metadata($id);
+	$width = $height = 0;
+	$is_intermediate = false;
+	$img_url_basename = wp_basename($img_url);
+
+	// try for a new style intermediate size
+	if ( $intermediate = image_get_intermediate_size($id, $size) ) {
+		$img_url = str_replace($img_url_basename, $intermediate['file'], $img_url);
+		$width = $intermediate['width'];
+		$height = $intermediate['height'];
+		$is_intermediate = true;
+	}
+	elseif ( $size == 'thumbnail' ) {
+		// fall back to the old thumbnail
+		if ( ($thumb_file = wp_get_attachment_thumb_file($id)) && $info = getimagesize($thumb_file) ) {
+			$img_url = str_replace($img_url_basename, wp_basename($thumb_file), $img_url);
+			$width = $info[0];
+			$height = $info[1];
+			$is_intermediate = true;
+		}
+	}
+	if ( !$width && !$height && isset( $meta['width'], $meta['height'] ) ) {
+		// any other type: use the real image
+		$width = $meta['width'];
+		$height = $meta['height'];
+	}
+
+	if ( $img_url) {
+		// we have the actual image size, but might need to further constrain it if content_width is narrower
+		list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
+
+		return array( $img_url, $width, $height, $is_intermediate );
+	}
+	return false;
+}
+
+/**
+ * Get an HTML img element representing an image attachment
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param  int          $attachment_id Image attachment ID.
+ * @param  string|array $size          Optional. Default 'thumbnail'.
+ * @param  bool         $icon          Optional. Whether it is an icon. Default false.
+ * @param  string|array $attr          Optional. Attributes for the image markup. Default empty string.
+ * @uses   wp_get_attachment_image()
+ * @return string HTML img element or empty string on failure.
+ */
+function bp_attachments_get_attachment_image( $attachment_id, $size = 'thumbnail', $icon = false, $attr = '' ) {
+	// Temporarly filter image_downsize()
+	add_filter( 'image_downsize', 'bp_attachments_image_downsize', 10, 3 );
+
+	$attachment_image = wp_get_attachment_image( $attachment_id, $size, $icon, $attr );
+
+	// Reset image_downsize()
+	remove_filter( 'image_downsize', 'bp_attachments_image_downsize', 10, 3 );
+
+	return apply_filters( 'bp_attachments_get_attachment_image', $attachment_image, $attachment_id, $size, $icon, $attr );
+}
+
+/**
+ * Prepare an attachment to be displayed in the BP Attachments Editor
+ *
+ * this is an adapted copy of wp_prepare_attachment_for_js()
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_prepare_attachment_for_js( $attachment ) {
+	if ( empty( $attachment ) ) {
+		return;
+	}
+
+	if ( ! is_a( $attachment, 'WP_Post' ) && is_numeric( $attachment ) ) {
+		$get_attachment = bp_attachments_get_attachment( $attachment );
+
+		if ( ! empty( $get_attachment->attachment ) ) {
+			$attachment = $get_attachment->attachment;
+		}
+	}
+
+	if ( 'bp_attachment' != $attachment->post_type ) {
+		return;
+	}
+
+	$meta = wp_get_attachment_metadata( $attachment->ID );
+	if ( false !== strpos( $attachment->post_mime_type, '/' ) ) {
+		list( $type, $subtype ) = explode( '/', $attachment->post_mime_type );
+	} else {
+		list( $type, $subtype ) = array( $attachment->post_mime_type, '' );
+	}
+
+	$attachment_url = bp_attachments_get_attachment_url( $attachment->ID );
+
+	$response = array(
+		'id'            => $attachment->ID,
+		'title'         => $attachment->post_title,
+		'filename'      => wp_basename( $attachment->guid ),
+		'url'           => $attachment_url,
+		'link'          => get_attachment_link( $attachment->ID ),
+		'alt'           => get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ),
+		'author'        => $attachment->post_author,
+		'description'   => $attachment->post_content,
+		'caption'       => $attachment->post_excerpt,
+		'name'          => $attachment->post_name,
+		'status'        => $attachment->post_status,
+		'uploadedTo'    => $attachment->post_parent,
+		'date'          => strtotime( $attachment->post_date_gmt ) * 1000,
+		'modified'      => strtotime( $attachment->post_modified_gmt ) * 1000,
+		'menuOrder'     => $attachment->menu_order,
+		'mime'          => $attachment->post_mime_type,
+		'type'          => $type,
+		'subtype'       => $subtype,
+		'icon'          => wp_mime_type_icon( $attachment->ID ),
+		'dateFormatted' => mysql2date( get_option('date_format'), $attachment->post_date ),
+		'nonces'        => array(
+			'update'    => false,
+			'delete'    => false,
+		),
+		'editLink'      => false,
+	);
+
+	if ( current_user_can( 'edit_bp_attachment', $attachment->ID ) ) {
+		$response['nonces']['update'] = wp_create_nonce( 'update_bp_attachment_' . $attachment->ID );
+		$response['editLink'] = bp_attachments_get_edit_link( $attachment->ID, $attachment->post_author );
+	}
+
+	if ( current_user_can( 'delete_bp_attachment', $attachment->ID ) ) {
+		$response['nonces']['delete'] = wp_create_nonce( 'delete_bp_attachment_' . $attachment->ID );
+	}
+
+	if ( $meta && 'image' === $type ) {
+		$sizes = array();
+		/** This filter is documented in wp-admin/includes/media.php */
+		$possible_sizes = apply_filters( 'image_size_names_choose', array(
+			'thumbnail' => __('Thumbnail'),
+			'medium'    => __('Medium'),
+			'large'     => __('Large'),
+			'full'      => __('Full Size'),
+		) );
+		unset( $possible_sizes['full'] );
+
+		// Loop through all potential sizes that may be chosen. Try to do this with some efficiency.
+		// First: run the image_downsize filter. If it returns something, we can use its data.
+		// If the filter does not return something, then image_downsize() is just an expensive
+		// way to check the image metadata, which we do second.
+		foreach ( $possible_sizes as $size => $label ) {
+			if ( $downsize = apply_filters( 'image_downsize', false, $attachment->ID, $size ) ) {
+				if ( ! $downsize[3] ) {
+					continue;
+				}
+
+				$sizes[ $size ] = array(
+					'height'      => $downsize[2],
+					'width'       => $downsize[1],
+					'url'         => $downsize[0],
+					'orientation' => $downsize[2] > $downsize[1] ? 'portrait' : 'landscape',
+				);
+			} elseif ( isset( $meta['sizes'][ $size ] ) ) {
+				if ( ! isset( $base_url ) ) {
+					$base_url = str_replace( wp_basename( $attachment_url ), '', $attachment_url );
+				}
+
+				// Nothing from the filter, so consult image metadata if we have it.
+				$size_meta = $meta['sizes'][ $size ];
+
+				// We have the actual image size, but might need to further constrain it if content_width is narrower.
+				// Thumbnail, medium, and full sizes are also checked against the site's height/width options.
+				list( $width, $height ) = image_constrain_size_for_editor( $size_meta['width'], $size_meta['height'], $size, 'edit' );
+
+				$sizes[ $size ] = array(
+					'height'      => $height,
+					'width'       => $width,
+					'url'         => $base_url . $size_meta['file'],
+					'orientation' => $height > $width ? 'portrait' : 'landscape',
+				);
+			}
+		}
+
+		$sizes['full'] = array( 'url' => $attachment_url );
+
+		if ( isset( $meta['height'], $meta['width'] ) ) {
+			$sizes['full']['height'] = $meta['height'];
+			$sizes['full']['width'] = $meta['width'];
+			$sizes['full']['orientation'] = $meta['height'] > $meta['width'] ? 'portrait' : 'landscape';
+		}
+
+		$response = array_merge( $response, array( 'sizes' => $sizes ), $sizes['full'] );
+	} elseif ( $meta && 'video' === $type ) {
+		if ( isset( $meta['width'] ) ) {
+			$response['width'] = (int) $meta['width'];
+		}
+
+		if ( isset( $meta['height'] ) ) {
+			$response['height'] = (int) $meta['height'];
+		}
+	}
+
+	if ( $meta && ( 'audio' === $type || 'video' === $type ) ) {
+		if ( isset( $meta['length_formatted'] ) ) {
+			$response['fileLength'] = $meta['length_formatted'];
+		}
+	}
+
+	return apply_filters( 'bp_attachments_prepare_attachment_for_js', $response, $attachment, $meta );
+}
+
+/**
+ * Build the edit link of an attachement
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_get_edit_link( $attachment_id = 0, $user_id = 0 ) {
+	if ( empty( $attachment_id ) ) {
+		return false;
+	}
+
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	$edit_link = trailingslashit( bp_core_get_user_domain( $user_id ) . buddypress()->attachments->slug );
+	$edit_link = add_query_arg( array( 'attachment' => $attachment_id, 'action' => 'edit' ), $edit_link );
+
+	return apply_filters( 'bp_attachments_get_edit_link', $edit_link, $attachment_id, $user_id );
+}
+
+/**
+ * Build the delete link of an attachement
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_get_delete_link( $attachment_id = 0, $user_id = 0 ) {
+	if ( empty( $attachment_id ) ) {
+		return false;
+	}
+
+	if ( empty( $user_id ) ) {
+		$user_id = bp_loggedin_user_id();
+	}
+
+	$delete_link = trailingslashit( bp_core_get_user_domain( $user_id ) . buddypress()->attachments->slug );
+	$delete_link = add_query_arg( array( 'attachment' => $attachment_id, 'action' => 'delete' ), $delete_link );
+
+	return apply_filters( 'bp_attachments_get_delete_link', $delete_link, $attachment_id, $user_id );
+}
diff --git src/bp-attachments/bp-attachments-loader.php src/bp-attachments/bp-attachments-loader.php
index e69de29..e37024f 100644
--- src/bp-attachments/bp-attachments-loader.php
+++ src/bp-attachments/bp-attachments-loader.php
@@ -0,0 +1,400 @@
+<?php
+/**
+ * Attachments Component.
+ *
+ * A media component, for others !
+ *
+ * @package BuddyPress
+ * @subpackage Attachments
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) or exit;
+/**
+ * Main Attachments Class.
+ *
+ * @since BuddyPress (2.2.0)
+ */
+class BP_Attachments_Component extends BP_Component {
+
+	/**
+	 * If true enables upload dirs, post type & taxonomy
+	 *
+	 * @var bool
+	 */
+	public $use_api;
+
+	/**
+	 * Start the attachments component setup process.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function __construct() {
+		parent::start(
+			'attachments',
+			__( 'Attachments', 'buddypress' ),
+			buddypress()->plugin_dir
+		);
+
+		// Filter here to enable Attachments API
+		$this->use_api = apply_filters( 'bp_attachments_use_attachments_api', false );
+
+		// Launch specific hooks
+		$this->actions();
+	}
+
+	/**
+	 * Include component files.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function includes( $includes = array() ) {
+		// Files to include
+		$includes = array(
+			'cssjs',
+			'actions',
+			'filters',
+			'screens',
+			'caps',
+			'classes',
+			'ajax',
+			'functions',
+		);
+
+		if ( is_admin() ) {
+			$includes[] = 'admin';
+		}
+
+		parent::includes( $includes );
+	}
+
+	/**
+	 * Set up component global variables.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function setup_globals( $args = array() ) {
+		$bp = buddypress();
+
+		// Define a slug, if necessary
+		if ( ! defined( 'BP_ATTACHMENTS_SLUG' ) ) {
+			define( 'BP_ATTACHMENTS_SLUG', $this->id );
+		}
+
+		// All globals for attachments component.
+		$args = array(
+			'slug'                  => BP_ATTACHMENTS_SLUG,
+			'has_directory'         => false,
+			'notification_callback' => 'bp_attachments_format_notifications',
+		);
+
+		parent::setup_globals( $args );
+	}
+
+	/**
+	 * Run specific actions to create post type/taxonomy...
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function actions() {
+		// register upload datas
+		add_action( 'bp_' . $this->id . '_setup_globals', array( $this, 'register_upload_datas' ), 10 );
+
+		// Edit user's profile photo nav
+		add_action( 'bp_xprofile_setup_nav', array( $this, 'xprofile_setup_nav' )        );
+		add_filter( 'bp_xprofile_admin_nav', array( $this, 'xprofile_setup_nav' ), 10, 1 );
+
+		// Group's avatar creation step
+		add_filter( 'groups_create_group_steps', array( $this, 'groups_setup_avatar_step' ), 10, 1 );
+		add_action( 'bp_groups_setup_nav',       array( $this, 'groups_setup_manage_tab'  )        );
+
+		if ( ! empty( $this->use_api ) && get_current_blog_id() == bp_get_root_blog_id() ) {
+			// register bp_attachments post type
+			add_action( 'bp_init', array( $this, 'register_post_types' ) );
+			// register bp_component taxonomy
+			add_action( 'bp_init', array( $this, 'register_taxonomies' ) );
+
+			// Eventually create/update the component terms mapping
+			add_action( 'bp_init', array( $this, 'component_terms' ), 999 );
+		}
+	}
+
+	/**
+	 * Set upload dirs and avatar globals
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function register_upload_datas() {
+		$bp = buddypress();
+
+		$bp->{$this->id}->use_api = $this->use_api;
+
+		// Set upload dirs
+		$upload_data = bp_attachments_set_upload_dirs();
+
+		if( ! empty( $upload_data ) && is_array( $upload_data ) ) {
+			foreach ( $upload_data as $key => $data ) {
+				// adding the uploads dir and url to Attachments component global.
+				$bp->{$this->id}->{$key} = $data;
+			}
+		}
+
+		if ( empty( $bp->avatar ) ) {
+			return;
+		}
+
+		if ( ! defined( 'BP_AVATAR_ORIGINAL_MAX_WIDTH' ) ) {
+			define( 'BP_AVATAR_ORIGINAL_MAX_WIDTH', 450 );
+		}
+
+		if ( ! defined( 'BP_AVATAR_ORIGINAL_MAX_FILESIZE' ) ) {
+
+			if ( ! isset( $bp->site_options['fileupload_maxk'] ) ) {
+				define( 'BP_AVATAR_ORIGINAL_MAX_FILESIZE', 5120000 ); // 5mb
+			} else {
+				define( 'BP_AVATAR_ORIGINAL_MAX_FILESIZE', $bp->site_options['fileupload_maxk'] * 1024 );
+			}
+		}
+
+		// Upload maximums
+		$bp->avatar->original_max_width    = BP_AVATAR_ORIGINAL_MAX_WIDTH;
+		$bp->avatar->original_max_filesize = BP_AVATAR_ORIGINAL_MAX_FILESIZE;
+
+		// These have to be set on page load in order to avoid infinite filter loops at runtime
+		$bp->avatar->upload_path = bp_attachments_get_upload_dir();
+		$bp->avatar->url         = bp_attachments_get_upload_dir( 'url' );
+
+		// 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 );
+		}
+	}
+
+	/**
+	 * Add the xProfile Change photo navigation
+	 *
+	 * @since BuddyPress (2.2.0)
+	 * @param array $wp_admin_nav
+	 */
+	public function xprofile_setup_nav( $wp_admin_nav = array() ) {
+		$bp = buddypress();
+
+		if ( empty( $bp->profile->slug ) || ! bp_attachments_avatar_is_enabled() ) {
+			return $wp_admin_nav;
+		}
+
+		// 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 $wp_admin_nav;
+		}
+
+		$profile_link = trailingslashit( $user_domain . $bp->profile->slug );
+
+		if ( ! is_array( $wp_admin_nav ) ) {
+			bp_core_new_subnav_item( array(
+				'name'            => _x( 'Change Profile Photo', 'Profile header sub menu', 'buddypress' ),
+				'slug'            => 'change-avatar',
+				'parent_url'      => $profile_link,
+				'parent_slug'     => $bp->profile->slug,
+				'screen_function' => 'bp_attachments_screen_change_avatar',
+				'position'        => 30,
+				'user_has_access' => bp_core_can_edit_settings()
+			) );
+		} else {
+			$profile_link = trailingslashit( bp_loggedin_user_domain() . $bp->profile->slug );
+
+			$wp_admin_nav[] = array(
+				'parent' => 'my-account-' . $bp->profile->id,
+				'id'     => 'my-account-' . $bp->profile->id . '-change-avatar',
+				'title'  => _x( 'Change Profile Photo', 'My Account Profile sub nav', 'buddypress' ),
+				'href'   => trailingslashit( $profile_link . 'change-avatar' )
+			);
+
+			return $wp_admin_nav;
+		}
+	}
+
+	/**
+	 * Add the Group's avatar create step
+	 *
+	 * @since BuddyPress (2.2.0)
+	 * @param array $group_creation_steps
+	 */
+	public function groups_setup_avatar_step( $group_creation_steps = array() ) {
+		$bp = buddypress();
+
+		// If avatar uploads are not disabled, add avatar option
+		if ( bp_attachments_avatar_is_enabled() ) {
+			$group_creation_steps['group-avatar'] = array(
+				'name'     => _x( 'Photo', 'Group screen nav', 'buddypress' ),
+				'position' => 20
+			);
+		}
+
+		return $group_creation_steps;
+	}
+
+	/**
+	 * Add the Group's change avatar nav
+	 *
+	 * @since BuddyPress (2.2.0)
+	 * @param array $group_creation_steps
+	 */
+	public function groups_setup_manage_tab() {
+		if ( ! bp_is_group() || ! bp_attachments_avatar_is_enabled() ) {
+			return;
+		}
+
+		$group = groups_get_current_group();
+		$admin_link = trailingslashit( bp_get_group_permalink( $group ) . 'admin' );
+
+		bp_core_new_subnav_item( array(
+			'name'              => __( 'Photo', 'buddypress' ),
+			'slug'              => 'group-avatar',
+			'parent_url'        => $admin_link,
+			'parent_slug'       => $group->slug . '_manage',
+			'screen_function'   => 'groups_screen_group_admin',
+			'position'          => 20,
+			'user_has_access'   => bp_is_item_admin(),
+			'show_in_admin_bar' => true,
+		) );
+	}
+
+	/**
+	 * Register the Attachment post type
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function register_post_types() {
+
+		$bp_attachments_labels = array(
+			'name'	             => __( 'BuddyPress Attachments',                                             'buddypress' ),
+			'singular'           => _x( 'Attachment',                    'bp-attachments singular',           'buddypress' ),
+			'menu_name'          => _x( 'Attachments',                   'bp-attachments menu name',          'buddypress' ),
+			'all_items'          => _x( 'All Attachments',               'bp-attachments all items',          'buddypress' ),
+			'singular_name'      => _x( 'Attachment',                    'bp-attachments singular name',      'buddypress' ),
+			'add_new'            => _x( 'Add New Attachment',            'bp-attachments add new',            'buddypress' ),
+			'edit_item'          => _x( 'Edit Attachment',               'bp-attachments edit item',          'buddypress' ),
+			'new_item'           => _x( 'New Attachment',                'bp-attachments new item',           'buddypress' ),
+			'view_item'          => _x( 'View Attachment',               'bp-attachments view item',          'buddypress' ),
+			'search_items'       => _x( 'Search Attachments',            'bp-attachments search items',       'buddypress' ),
+			'not_found'          => _x( 'No Attachments Found',          'bp-attachments not found',          'buddypress' ),
+			'not_found_in_trash' => _x( 'No Attachments Found in Trash', 'bp-attachments not found in trash', 'buddypress' )
+		);
+
+		$bp_attachments_args = array(
+			'label'	            => _x( 'Attachments',                    'bp-attachments label',              'buddypress' ),
+			'labels'            => $bp_attachments_labels,
+			'public'            => false,
+			'rewrite'           => false,
+			'show_ui'           => false, // As BP Attachments can be activated on the network, we need to use a specific UI
+			'show_in_admin_bar' => false,
+			'show_in_nav_menus' => false,
+			'capabilities'      => bp_attachments_get_attachment_caps(),
+			'capability_type'   => array( 'bp_attachment', 'bp_attachments' ),
+			'delete_with_user'  => true,
+			'supports'          => array( 'title', 'author' )
+		);
+
+		// Register the post type for attachments.
+		register_post_type( 'bp_attachment', $bp_attachments_args );
+
+		parent::register_post_types();
+	}
+
+	/**
+	 * Register the Component taxonomy
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function register_taxonomies() {
+		$labels = array(
+			'name'              => _x( 'BuddyPress Components', 'bp-attachments taxonomy general name',  'buddypress' ),
+			'singular_name'     => _x( 'Component',             'bp-attachments taxonomy singular name', 'buddypress' ),
+			'search_items'      => _x( 'Search Components',     'bp-attachments search items',           'buddypress' ),
+			'all_items'         => _x( 'All Components',        'bp-attachments all items',              'buddypress' ),
+			'parent_item'       => _x( 'Parent Component',      'bp-attachments parent item',            'buddypress' ),
+			'parent_item_colon' => _x( 'Parent Component:',     'bp-attachments parent item colon',      'buddypress' ),
+			'edit_item'         => _x( 'Edit Component',        'bp-attachments edit item',              'buddypress' ),
+			'update_item'       => _x( 'Update Component',      'bp-attachments update item',            'buddypress' ),
+			'add_new_item'      => _x( 'Add New Component',     'bp-attachments add new item',           'buddypress' ),
+			'new_item_name'     => _x( 'New Component Name',    'bp-attachments new item name',          'buddypress' ),
+			'menu_name'         => _x( 'BP Component',          'bp-attachments menu name',              'buddypress' ),
+		);
+
+		$args = array(
+			'hierarchical'          => true,
+			'labels'                => $labels,
+			'show_ui'               => true,
+			'show_admin_column'     => true,
+			'query_var'             => false,
+			'rewrite'               => false,
+			'capabilities'          => bp_attachments_get_component_caps(),
+			'update_count_callback' => '_update_generic_term_count',
+		);
+
+		// Register the taxonomy for attachments.
+		register_taxonomy( 'bp_component', array( 'bp_attachment' ), $args );
+
+		parent::register_taxonomies();
+	}
+
+	/**
+	 * Map component ids with term ids if needed
+	 *
+	 * @since BuddyPress (2.2.0)
+	 */
+	public function component_terms() {
+		$bp = buddypress();
+
+		// Get terms mapping
+		$terms_mapping = bp_get_option( 'bp_attachments_term_ids', array() );
+		$terms_toadd = array();
+
+		// Loop in loaded components to check if the ones who need attachments are already mapped
+		foreach ( array_keys( $bp->loaded_components ) as $component_id ) {
+			if ( ! empty( $bp->{$component_id}->can_attachments ) && empty( $terms_mapping[ $bp->{$component_id}->id ] ) ) {
+				$terms_toadd[ $bp->{$component_id}->id ] = array(
+					'name' => $bp->{$component_id}->name,
+					'slug' => $bp->{$component_id}->slug
+				);
+			}
+		}
+
+		// No term to add, simply globalize the terms
+		if ( empty( $terms_toadd ) ) {
+			$bp->{$this->id}->component_terms = $terms_mapping;
+			return;
+		}
+
+		// Insert needed terms and build the mapping
+		foreach ( $terms_toadd as $key => $component ) {
+			$component_term = wp_insert_term( $component['name'], 'bp_component', array( 'slug' => $component['slug'] ) );
+
+			if ( ! empty( $component_term['term_id'] ) ) {
+				$terms_mapping[ $key ] = $component_term['term_id'];
+			}
+		}
+
+		// Update terms mapping and globalize it
+		bp_update_option( 'bp_attachments_term_ids', $terms_mapping );
+		$bp->{$this->id}->component_terms = $terms_mapping;
+	}
+}
+
+/**
+ * Bootstrap the Attachments component.
+ */
+function bp_setup_attachments() {
+	buddypress()->attachments = new BP_Attachments_Component();
+}
+add_action( 'bp_setup_components', 'bp_setup_attachments', 6 );
diff --git src/bp-attachments/bp-attachments-screens.php src/bp-attachments/bp-attachments-screens.php
index e69de29..3e98e8a 100644
--- src/bp-attachments/bp-attachments-screens.php
+++ src/bp-attachments/bp-attachments-screens.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * Attachments Screens.
+ *
+ * @package BuddyPress
+ * @subpackage Attachments Screens
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) or exit;
+
+/**
+ * Handles the uploading and cropping of a user avatar. Displays the change avatar page.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @package BuddyPress
+ * @subpackage Attachments Screens
+ *
+ * @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 bp_attachments_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';
+	$user_id                = bp_displayed_user_id();
+
+	if ( ! empty( $_FILES ) ) {
+
+		// Check the nonce
+		check_admin_referer( 'bp_avatar_upload' );
+
+		// Pass the file to the avatar upload handler
+		$uploaded_avatar = bp_attachments_avatar_handle_upload( $_FILES, 'xprofile', $user_id );
+
+		if ( is_wp_error( $uploaded_avatar ) ) {
+			bp_core_add_message( $uploaded_avatar->get_error_message(), 'error' );
+
+		// Go to crop step
+		} else {
+			// Avoid some error messages to be output if
+			// everything is ok.
+			$bp->template_message   = false;
+			$bp->avatar_admin->step = 'crop-image';
+
+			// Make sure we include the jQuery jCrop file for image cropping
+			add_action( 'wp_print_scripts', 'bp_attachments_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'       => $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_attachments_avatar_handle_crop( $args ) ) {
+			bp_core_add_message( __( 'There was a problem cropping your profile photo.', 'buddypress' ), 'error' );
+		} else {
+			do_action( 'xprofile_avatar_uploaded' );
+			bp_core_add_message( __( 'Your new profile photo was uploaded successfully.', 'buddypress' ) );
+			bp_core_redirect( bp_displayed_user_domain() );
+		}
+	}
+
+	do_action( 'xprofile_screen_change_avatar' );
+
+	bp_core_load_template( apply_filters( 'xprofile_template_change_avatar', 'members/single/home' ) );
+}
+
+/**
+ * Handle the display of a group's Change Avatar page.
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_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_attachments_avatar_is_enabled() ) {
+		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_attachments_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
+		$uploaded_avatar = bp_attachments_avatar_handle_upload( $_FILES, 'groups', $bp->groups->current_group->id );
+
+		if ( is_wp_error( $uploaded_avatar ) ) {
+			bp_core_add_message( $uploaded_avatar->get_error_message(), 'error' );
+
+		// Go to crop step
+		} else {
+			// Avoid some error messages to be output if
+			// everything is ok.
+			$bp->template_message   = false;
+			$bp->avatar_admin->step = 'crop-image';
+
+			// Make sure we include the jQuery jCrop file for image cropping
+			add_action( 'wp_print_scripts', 'bp_attachments_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'    => 'avatar_group_dir',
+			'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_attachments_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' ) );
+		}
+	}
+
+	do_action( 'groups_screen_group_admin_avatar', $bp->groups->current_group->id );
+
+	bp_core_load_template( apply_filters( 'groups_template_group_admin_avatar', 'groups/single/home' ) );
+}
+add_action( 'bp_screens', 'bp_attachments_screen_group_admin_avatar' );
+
+/**
+ * Move the avatar in user's folder once the signup activated
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_attachments_avatar_activate_signup_screen( $hashed_key = '', $user = 0 ) {
+	if ( empty( $hashed_key ) || empty( $user ) ) {
+		return;
+	}
+
+	// Check if the avatar folder exists. If it does, move rename it, move
+	// it and delete the signup avatar dir
+	if ( file_exists( bp_attachments_get_upload_dir( 'avatar_user_dir' ) . '/signups/' . $hashed_key ) ) {
+		@rename( bp_attachments_get_upload_dir( 'avatar_user_dir' ) . '/signups/' . $hashed_key, bp_attachments_get_upload_dir( 'avatar_user_dir' ) . '/' . $user );
+	}
+}
+add_action( 'bp_core_activate_account_avatar', 'bp_attachments_avatar_activate_signup_screen', 10, 2 );
diff --git src/bp-attachments/js/script.js src/bp-attachments/js/script.js
index e69de29..35b9b5d 100644
--- src/bp-attachments/js/script.js
+++ src/bp-attachments/js/script.js
@@ -0,0 +1,1244 @@
+var bp = bp || {};
+
+/**
+ * BP Attachments Editor !
+ */
+( function( $ ) {
+	var BPmedia;
+
+	bp.media = BPmedia = {};
+
+	// map bp.media to wp.media
+	bp.media = wp.media;
+
+	bp.media.params = {
+		item_id:        _wpPluploadSettings.defaults.multipart_params.item_id,
+		item_component: _wpPluploadSettings.defaults.multipart_params.component,
+		item_type:      _wpPluploadSettings.defaults.multipart_params.item_type,
+		item_post_id:   _wpPluploadSettings.defaults.multipart_params.post_id,
+		nonce:          _wpPluploadSettings.defaults.multipart_params._bpnonce,
+		callback:       _wpPluploadSettings.defaults.multipart_params.callback,
+		callback_id:    _wpPluploadSettings.defaults.multipart_params.callback_id,
+	};
+
+	_.extend( BPmedia, { model: {}, view: {}, controller: {}, frames: {} } );
+
+	// Models
+	bp.media.attachment = function( id ) {
+		return BPattachment.get( id );
+	};
+
+	/**
+	 * bp.media.model.Attachment
+	 *
+	 * It's an adpated copy of WordPress Attachment model
+	 *
+	 * @constructor
+	 * @augments Backbone.Model
+	 */
+	BPattachment = BPmedia.model.Attachment = bp.media.model.Attachment.extend({
+
+		/**
+		 * Triggered when attachment details change
+		 * Overrides Backbone.Model.sync
+		 *
+		 * @param {string} method
+		 * @param {bp.media.model.Attachment} model
+		 * @param {Object} [options={}]
+		 *
+		 * @returns {Promise}
+		 */
+		sync: function( method, model, options ) {
+			// If the attachment does not yet have an `id`, return an instantly
+			// rejected promise. Otherwise, all of our requests will fail.
+			if ( _.isUndefined( this.id ) ) {
+				return $.Deferred().rejectWith( this ).promise();
+			}
+
+			// Overload the `read` request so Attachment.fetch() functions correctly.
+			if ( 'read' === method ) {
+				options = options || {};
+				options.context = this;
+				options.data = _.extend( options.data || {}, {
+					action: 'get_bp_attachment',
+					id: this.id
+				});
+				return bp.media.ajax( options );
+
+			// Overload the `update` request so properties can be saved.
+			} else if ( 'update' === method ) {
+				// If we do not have the necessary nonce, fail immeditately.
+				if ( ! this.get('nonces') || ! this.get('nonces').update ) {
+					return $.Deferred().rejectWith( this ).promise();
+				}
+
+				options = options || {};
+				options.context = this;
+
+				// Set the action and ID.
+				options.data = _.extend( options.data || {}, {
+					action:  'update_bp_attachment',
+					id:      this.id,
+					nonce:   this.get('nonces').update,
+					post_id: bp.media.model.settings.post.id
+				});
+
+				// Record the values of the changed attributes.
+				if ( model.hasChanged() ) {
+					options.data.changes = {};
+
+					_.each( model.changed, function( value, key ) {
+						options.data.changes[ key ] = this.get( key );
+					}, this );
+				}
+
+				return bp.media.ajax( options );
+
+			// Overload the `delete` request so attachments can be removed.
+			// This will permanently delete an attachment.
+			} else if ( 'delete' === method ) {
+				options = options || {};
+
+				if ( ! options.wait ) {
+					this.destroyed = true;
+				}
+
+				options.context = this;
+				options.data = _.extend( options.data || {}, {
+					action:   'delete_bp_attachment',
+					id:       this.id,
+					_wpnonce: this.get('nonces')['delete']
+				});
+
+				return bp.media.ajax( options ).done( function() {
+					this.destroyed = true;
+				}).fail( function() {
+					this.destroyed = false;
+				});
+
+			// Otherwise, fall back to `Backbone.sync()`.
+			} else {
+				/**
+				 * Call `sync` directly on Backbone.Model
+				 */
+				return bp.media.model.Attachment.prototype.sync.apply( this, arguments );
+			}
+		},
+		/**
+		 * Convert date strings into Date objects.
+		 *
+		 * @param {Object} resp The raw response object, typically returned by fetch()
+		 * @returns {Object} The modified response object, which is the attributes hash
+		 *    to be set on the model.
+		 */
+		parse: function( resp ) {
+			if ( ! resp ) {
+				return resp;
+			}
+
+			resp.date = new Date( resp.date );
+			resp.modified = new Date( resp.modified );
+			return resp;
+		},
+	}, {
+		/**
+		 * Add a model to the end of the static 'all' collection and return it.
+		 *
+		 * @static
+		 * @param {Object} attrs
+		 * @returns {wp.media.model.Attachment}
+		 */
+		create: function( attrs ) {
+			return BPattachments.all.push( attrs );
+		},
+		/**
+		 * Retrieve a model, or add it to the end of the static 'all' collection before returning it.
+		 *
+		 * @static
+		 * @param {string} id A string used to identify a model.
+		 * @param {Backbone.Model|undefined} attachment
+		 * @returns {wp.media.model.Attachment}
+		 */
+		get: _.memoize( function( id, attachment ) {
+			return BPattachments.all.push( attachment || { id: id } );
+		})
+	});
+
+	/**
+	 * Override media.model.Attachment.create function
+	 * so that the uploader is using BPattachment model
+	 */
+	bp.media.model.Attachment.create = function( attrs ) {
+		return BPattachments.all.push( attrs );
+	};
+
+	/**
+	 * Override media.model.Attachment.get function
+	 * so that the uploader is using BPattachment model
+	 */
+	bp.media.model.Attachment.get = _.memoize( function( id, attachment ) {
+		return BPattachments.all.push( attachment || { id: id } );
+	} );
+
+	/**
+	 * bp.media.model.BPattachments
+	 *
+	 * it's an adapted copy of WordPress Attachments Collection
+	 *
+	 * @constructor
+	 * @augments Backbone.Collection
+	 */
+	BPattachments = bp.media.model.Attachments.extend({
+		/**
+		 * @type {wp.media.model.Attachment}
+		 */
+		model: BPattachment,
+		/**
+		 * @param {Array} [models=[]] Array of models used to populate the collection.
+		 * @param {Object} [options={}]
+		 */
+		initialize: function( models, options ) {
+			options = options || {};
+
+			bp.media.model.Attachments.prototype.initialize.apply( this, arguments );
+		},
+
+		/**
+		 * @access private
+		 */
+		_requery: function() {
+			if ( this.props.get('query') ) {
+				this.mirror( bp.media.model.Query.get( this.props.toJSON() ) );
+			}
+		},
+
+		parse: function( resp, xhr ) {
+			if ( ! _.isArray( resp ) ) {
+				resp = [resp];
+			}
+
+			return _.map( resp, function( attrs ) {
+				var id, attachment, newAttributes;
+
+				if ( attrs instanceof Backbone.Model ) {
+					id = attrs.get( 'id' );
+					attrs = attrs.attributes;
+				} else {
+					id = attrs.id;
+				}
+
+				attachment = BPattachment.get( id );
+				newAttributes = attachment.parse( attrs, xhr );
+
+				if ( ! _.isEqual( attachment.attributes, newAttributes ) ) {
+					attachment.set( newAttributes );
+				}
+
+				return attachment;
+			});
+		},
+
+	});
+
+	/**
+	 * @static
+	 * @member {bp.media.model.BPattachments}
+	 */
+	BPattachments.all = new BPattachments();
+
+	/**
+	 * bp.media.model.Avatar
+	 *
+	 * @constructor
+	 * @augments Backbone.Model
+	 */
+	Avatar = bp.media.model.Avatar = Backbone.Model.extend( {
+		defaults : {
+            id: 0,
+			title: '',
+			component: '',
+			filename: '',
+			url:'',
+			path:'',
+        },
+	} );
+
+	/**
+	 * bp.media.model.Query
+	 *
+	 * It's an adpated copy of WordPress Query
+	 *
+	 * @constructor
+	 * @augments Backbone.Model
+	 */
+	BPquery = BPmedia.model.Query = bp.media.model.Query = BPattachments.extend({
+
+		initialize: function( models, options ) {
+			var allowed;
+
+			options = options || {};
+			BPattachments.prototype.initialize.apply( this, arguments );
+
+			this.args     = options.args;
+			this._hasMore = true;
+			this.created  = new Date();
+
+			this.filters.order = function( attachment ) {
+				var orderby = this.props.get('orderby'),
+					order = this.props.get('order');
+
+				if ( ! this.comparator ) {
+					return true;
+				}
+
+				// We want any items that can be placed before the last
+				// item in the set. If we add any items after the last
+				// item, then we can't guarantee the set is complete.
+				if ( this.length ) {
+					return 1 !== this.comparator( attachment, this.last(), { ties: true });
+
+				// Handle the case where there are no items yet and
+				// we're sorting for recent items. In that case, we want
+				// changes that occurred after we created the query.
+				} else if ( 'DESC' === order && ( 'date' === orderby || 'modified' === orderby ) ) {
+					return attachment.get( orderby ) >= this.created;
+
+				// If we're sorting by menu order and we have no items,
+				// accept any items that have the default menu order (0).
+				} else if ( 'ASC' === order && 'menuOrder' === orderby ) {
+					return attachment.get( orderby ) === 0;
+				}
+
+				// Otherwise, we don't want any items yet.
+				return false;
+			};
+
+			// Observe the central `wp.Uploader.queue` collection to watch for
+			// new matches for the query.
+			//
+			// Only observe when a limited number of query args are set. There
+			// are no filters for other properties, so observing will result in
+			// false positives in those queries.
+			allowed = [ 's', 'order', 'orderby', 'posts_per_page', 'post_mime_type', 'post_parent', 'component', 'item_type', 'item_id' ];
+			if ( wp.Uploader && _( this.args ).chain().keys().difference( allowed ).isEmpty().value() ) {
+				this.observe( wp.Uploader.queue );
+			}
+		},
+		/**
+		 * @returns {Boolean}
+		 */
+		hasMore: function() {
+			return this._hasMore;
+		},
+		/**
+		 * @param {Object} [options={}]
+		 * @returns {Promise}
+		 */
+		more: function( options ) {
+			var query = this;
+
+			if ( this._more && 'pending' === this._more.state() ) {
+				return this._more;
+			}
+
+			if ( ! this.hasMore() || 'avatar' == this.args.item_type ) {
+				return $.Deferred().resolveWith( this ).promise();
+			}
+
+			options = options || {};
+			options.remove = false;
+
+			return this._more = this.fetch( options ).done( function( resp ) {
+				if ( _.isEmpty( resp ) || -1 === this.args.posts_per_page || resp.length < this.args.posts_per_page ) {
+					query._hasMore = false;
+				}
+			});
+		},
+
+		sync: function( method, model, options ) {
+			var args, fallback;
+
+			// Overload the read method so BPattachment.fetch() functions correctly.
+			if ( 'read' === method ) {
+				options = options || {};
+				options.context = this;
+
+				if ( 'avatar' == this.args.item_type ) {
+					return false;
+				}
+
+				options.data = _.extend( options.data || {}, {
+					action:  'query_bp_attachments',
+					post_id: bp.media.model.settings.post.id
+				});
+
+				// Clone the args so manipulation is non-destructive.
+				args = _.clone( this.args );
+
+				// Determine which page to query.
+				if ( -1 !== args.posts_per_page ) {
+					args.paged = Math.floor( this.length / args.posts_per_page ) + 1;
+				}
+
+				options.data.query = args;
+				return bp.media.ajax( options );
+
+			// Otherwise, fall back to Backbone.sync()
+			} else {
+				/**
+				 * Call bp.media.model.BPattachments.sync or Backbone.sync
+				 */
+				fallback = BPattachments.prototype.sync ? BPattachments.prototype : Backbone;
+				return fallback.sync.apply( this, arguments );
+			}
+		}
+
+	}, {
+		/**
+		 * @readonly
+		 */
+		defaultProps: {
+			orderby: 'date',
+			order:   'DESC',
+			component: bp.media.params.item_component,
+			item_type: bp.media.params.item_type,
+			item_id: bp.media.params.item_id,
+		},
+		/**
+		 * @readonly
+		 */
+		defaultArgs: {
+			posts_per_page: 40
+		},
+		/**
+		 * @readonly
+		 */
+		orderby: {
+			allowed:  [ 'name', 'author', 'date', 'title', 'modified', 'uploadedTo', 'id', 'post__in', 'menuOrder' ],
+			valuemap: {
+				'id':         'ID',
+				'uploadedTo': 'parent',
+				'attachedTo': 'item_id',
+				'menuOrder':  'menu_order ID'
+			}
+		},
+		/**
+		 * @readonly
+		 */
+		propmap: {
+			'search':    's',
+			'type':      'post_mime_type',
+			'perPage':   'posts_per_page',
+			'menuOrder': 'menu_order',
+			'uploadedTo': 'post_parent',
+			'attachedTo': 'item_id',
+		},
+		/**
+		 * @static
+		 * @method
+		 *
+		 * @returns {wp.media.model.Query} A new query.
+		 */
+		// Caches query objects so queries can be easily reused.
+		get: (function(){
+			/**
+			 * @static
+			 * @type Array
+			 */
+			var queries = [];
+
+			/**
+			 * @param {Object} props
+			 * @param {Object} options
+			 * @returns {Query}
+			 */
+			return function( props, options ) {
+				var args     = {},
+					orderby  = BPquery.orderby,
+					defaults = BPquery.defaultProps,
+					query;
+
+				// Remove the `query` property. This isn't linked to a query,
+				// this *is* the query.
+				delete props.query;
+
+				// Fill default args.
+				_.defaults( props, defaults );
+
+				// Normalize the order.
+				props.order = props.order.toUpperCase();
+				if ( 'DESC' !== props.order && 'ASC' !== props.order ) {
+					props.order = defaults.order.toUpperCase();
+				}
+
+				// Ensure we have a valid orderby value.
+				if ( ! _.contains( orderby.allowed, props.orderby ) ) {
+					props.orderby = defaults.orderby;
+				}
+
+				// Generate the query `args` object.
+				// Correct any differing property names.
+				_.each( props, function( value, prop ) {
+					if ( _.isNull( value ) ) {
+						return;
+					}
+
+					args[ BPquery.propmap[ prop ] || prop ] = value;
+				});
+
+				// Fill any other default query args.
+				_.defaults( args, BPquery.defaultArgs );
+
+				// `props.orderby` does not always map directly to `args.orderby`.
+				// Substitute exceptions specified in orderby.keymap.
+				args.orderby = orderby.valuemap[ props.orderby ] || props.orderby;
+
+				// Search the query cache for matches.
+				query = _.find( queries, function( query ) {
+					return _.isEqual( query.args, args );
+				});
+
+				// Otherwise, create a new query and add it to the cache.
+				if ( ! query ) {
+					query = new BPquery( [], _.extend( options || {}, {
+						props: props,
+						args:  args
+					} ) );
+					queries.push( query );
+				}
+
+				return query;
+			};
+		}())
+
+	});
+
+	bp.media.query = function( props ) {
+		return new BPattachments( null, {
+			props: _.extend( _.defaults( props || {}, { orderby: 'date' } ), { query: true } )
+		});
+	};
+
+	bp.media.controller.bpLibrary = bp.media.controller.Library.extend({
+		defaults: {
+			id:         'bp_library',
+			multiple:   false,
+			describe:   false,
+			toolbar:    'bp_select',
+			sidebar:    'bp_settings',
+			content:    'bp_upload',
+			router:     'bp_browse',
+			menu:       'default',
+			searchable: false,
+			filterable: false,
+			sortable:   false,
+			title:      bp.media.view.l10n.bp_attachments.title,
+
+			// Uses a user setting to override the content mode.
+			contentUserSetting: false,
+
+			// Sync the selection from the last state when 'multiple' matches.
+			syncSelection: true
+		},
+
+		initialize: function() {
+			type = '';
+
+			if ( 'avatar' == bp.media.params.item_type ) {
+				type = { type: 'image' };
+			}
+
+			if ( ! this.get('library') ) {
+				this.set( 'library', bp.media.query( type ) );
+			}
+
+			bp.media.controller.Library.prototype.initialize.apply( this, arguments );
+		},
+
+		uploading: function( attachment ) {
+			var content = this.frame.content,
+				mode = 'bpbrowse';
+
+			if ( 'avatar' == bp.media.params.item_type )
+				mode = 'crop';
+
+			if ( 'bp_upload' === content.mode() ) {
+				this.frame.content.mode( mode );
+			}
+
+			this.get('selection').add( attachment );
+		},
+
+	});
+
+	BPmedia.view.Avatar = bp.media.view.Avatar = bp.media.view.Attachment.extend( {
+		tagName:   'li',
+		className: 'avatar',
+		template:  bp.media.template('avatar'),
+
+		initialize: function() {
+			var selection = this.options.selection;
+
+			bp.media.view.Attachment.prototype.initialize.apply( this, arguments );
+		},
+
+		render: function() {
+			var selection = this.options.selection;
+
+			bp.media.view.Attachment.prototype.render.apply( this, arguments );
+
+			if( ! _.isUndefined( selection.single() ) && ! selection.single()._changing ) {
+				var tocrop = this.$el.find( 'img' );
+				var ratio = 1;
+
+				if ( ! _.isUndefined( bp.media.view.settings.bp_attachments.full_h ) && ! _.isUndefined( bp.media.view.settings.bp_attachments.full_w ) && bp.media.view.settings.bp_attachments.full_h != bp.media.view.settings.bp_attachments.full_w ) {
+					ratio = bp.media.view.settings.bp_attachments.full_h / bp.media.view.settings.bp_attachments.full_w;
+				}
+
+				crop_left   = Math.round( selection.single().get('width' ) / 4 );
+				crop_top    = Math.round( selection.single().get('height' ) / 4 );
+				crop_right  = selection.single().get('width' ) - crop_left;
+				crop_bottom = selection.single().get('height' ) - crop_top;
+
+				tocrop.Jcrop({
+					onChange: this.showPreview,
+					onSelect: this.showPreview,
+					onSelect: this.updateCoords,
+					aspectRatio: ratio,
+	        		setSelect: [ crop_left, crop_top, crop_right, crop_bottom ]
+				});
+
+				this.updateCoords({x: crop_left, y: crop_top, w: crop_right, h: crop_bottom});
+
+			}
+
+			return this;
+		},
+
+		updateCoords: function( args ) {
+			var selection = BPmedia.frame().state().get( 'selection' ).single();
+
+			// Need to find a better way
+			selection.attributes.x = args.x;
+			selection.attributes.y = args.y;
+			selection.attributes.w = args.w;
+			selection.attributes.h = args.h;
+		},
+
+		showPreview: function( coords ) {
+			var selection = BPmedia.frame().state().get( 'selection' ).single();
+
+			// For some reason, the Avatar Detail view is not rendered
+			$( '#avatar-crop-preview' ).prop( 'src', selection.get( 'url') );
+
+			if ( parseInt(coords.w) > 0 ) {
+				var fw = bp.media.view.settings.bp_attachments.full_w;
+				var fh = bp.media.view.settings.bp_attachments.full_h;
+				var rx = fw / coords.w;
+				var ry = fh / coords.h;
+
+				$( '#avatar-crop-preview' ).css( {
+					maxWidth:'none',
+					width: Math.round(rx *  selection.get('width') )+ 'px',
+					height: Math.round(ry * selection.get('height') )+ 'px',
+					marginLeft: '-' + Math.round(rx * coords.x) + 'px',
+					marginTop: '-' + Math.round(ry * coords.y) + 'px'
+				} );
+			}
+		},
+	} );
+
+	bp.media.view.AvatarDetails = bp.media.view.Attachment.Details.extend({
+
+		tagName:   'div',
+		className: 'attachment-details',
+		template:  bp.media.template('bp-avatar-details'),
+
+		render: function() {
+			var options = _.defaults( this.model.toJSON(), {
+					orientation:   'landscape',
+					uploading:     false,
+					type:          '',
+					subtype:       '',
+					icon:          '',
+					filename:      '',
+					caption:       '',
+					title:         '',
+					dateFormatted: '',
+					width:         '',
+					height:        '',
+					compat:        false,
+					alt:           '',
+					description:   ''
+				});
+
+			options.buttons  = this.buttons;
+			options.describe = this.controller.state().get('describe');
+
+			if ( 'image' === options.type ) {
+				options.size = this.imageSize();
+			}
+
+			options.can = {};
+			if ( options.nonces ) {
+				options.can.remove = !! options.nonces['delete'];
+				options.can.save = !! options.nonces.update;
+			}
+
+			if ( this.controller.state().get('allowLocalEdits') ) {
+				options.allowLocalEdits = true;
+			}
+
+			this.views.detach();
+			this.$el.html( this.template( options ) );
+
+			this.$el.toggleClass( 'uploading', options.uploading );
+			if ( options.uploading ) {
+				this.$bar = this.$('.media-progress-bar div');
+			} else {
+				delete this.$bar;
+			}
+
+			// Check if the model is selected.
+			this.updateSelect();
+
+			// Update the save status.
+			this.updateSave();
+
+			/**
+			 *
+			 * ! this is not rendered ?
+			 *
+			 *
+			 */
+			this.views.render();
+
+			return this;
+		},
+	});
+
+	bp.media.view.AttachmentsDetails = bp.media.view.Attachment.Details.extend({
+
+		tagName:   'div',
+		className: 'attachment-details',
+		template:  bp.media.template('bp-attachment-details'),
+
+		render: function() {
+			var options = _.defaults( this.model.toJSON(), {
+					orientation:   'landscape',
+					uploading:     false,
+					type:          '',
+					subtype:       '',
+					icon:          '',
+					filename:      '',
+					caption:       '',
+					title:         '',
+					dateFormatted: '',
+					width:         '',
+					height:        '',
+					compat:        false,
+					alt:           '',
+					description:   ''
+				});
+
+			options.buttons  = this.buttons;
+			options.describe = this.controller.state().get('describe');
+
+			if ( 'image' === options.type ) {
+				options.size = this.imageSize();
+			}
+
+			options.can = {};
+			if ( options.nonces ) {
+				options.can.remove = !! options.nonces['delete'];
+				options.can.save = !! options.nonces.update;
+			}
+
+			if ( this.controller.state().get('allowLocalEdits') ) {
+				options.allowLocalEdits = true;
+			}
+
+			this.views.detach();
+			this.$el.html( this.template( options ) );
+
+			this.$el.toggleClass( 'uploading', options.uploading );
+			if ( options.uploading ) {
+				this.$bar = this.$('.media-progress-bar div');
+			} else {
+				delete this.$bar;
+			}
+
+			// Check if the model is selected.
+			this.updateSelect();
+
+			// Update the save status.
+			this.updateSave();
+
+			this.views.render();
+
+			return this;
+		},
+
+	});
+
+	BPmedia.view.AttachmentsBrowser = bp.media.view.BPattachmentsBrowser = bp.media.view.AttachmentsBrowser.extend({
+
+		createToolbar: function () {
+			bp.media.view.AttachmentsBrowser.prototype.createToolbar.apply( this, arguments );
+
+			// Remove the date field and label
+			this.toolbar.unset( 'dateFilterLabel' );
+			this.toolbar.unset( 'dateFilter' );
+		},
+
+		createSingle: function() {
+			var sidebar = this.sidebar,
+				single = this.options.selection.single();
+
+			sidebar.set( 'details', new bp.media.view.AttachmentsDetails({
+				controller: this.controller,
+				model:      single,
+				priority:   80
+			}) );
+
+			if ( this.options.display ) {
+				sidebar.set( 'display', new bp.media.view.Settings.AttachmentDisplay({
+					controller:   this.controller,
+					model:        this.model.display( single ),
+					attachment:   single,
+					priority:     160,
+					userSettings: this.model.get('displayUserSettings')
+				}) );
+			}
+		},
+	});
+
+	BPmedia.view.AvatarCroper = bp.media.view.AvatarCroper = bp.media.view.AttachmentsBrowser.extend( {
+		tagName:   'div',
+		className: 'attachments-browser',
+
+		initialize: function() {
+			_.defaults( this.options, {
+				filters: false,
+				search:  false,
+				display: false,
+
+				AttachmentView: bp.media.view.Avatar
+			});
+
+			this.createToolbar();
+			this.updateContent();
+			this.createSidebar();
+
+			this.collection.on( 'add remove reset', this.updateContent, this );
+			this.on( 'ready', this.customizeUploader, this );
+		},
+
+		customizeUploader:function(){
+			$( '.upload-ui h3.upload-instructions' ).html( 'Drop your avatar anywhere to upload' );
+			$( '.upload-ui a.button-hero' ).html( 'Select an image' );
+		},
+
+		createToolbar: function () {
+			bp.media.view.AttachmentsBrowser.prototype.createToolbar.apply( this, arguments );
+
+			// Remove the date field and label
+			this.toolbar.unset( 'dateFilterLabel' );
+			this.toolbar.unset( 'dateFilter' );
+		},
+
+		updateContent: function() {
+			var view = this;
+
+			if( ! this.attachments ) {
+				this.createAttachments();
+			}
+
+			if ( ! this.collection.length ) {
+				this.collection.more().done( function() {
+					if ( ! view.collection.length ) {
+						view.createUploader();
+					}
+				});
+			}
+		},
+
+		createAttachments: function() {
+			this.removeContent();
+
+			this.attachments = new bp.media.view.Attachments({
+				controller: this.controller,
+				collection: this.collection,
+				selection:  this.options.selection,
+				model:      this.model,
+				sortable:   this.options.sortable,
+
+				// The single `Attachment` view to be used in the `Attachments` view.
+				AttachmentView: this.options.AttachmentView
+			});
+
+			this.views.add( this.attachments );
+		},
+
+		removeContent: function() {
+			_.each(['attachments','uploader'], function( key ) {
+				if ( this[ key ] ) {
+					this[ key ].remove();
+					delete this[ key ];
+				}
+			}, this );
+		},
+
+		createSidebar: function() {
+			var options = this.options,
+				selection = options.selection,
+				sidebar = this.sidebar = new bp.media.view.Sidebar({
+					controller: this.controller
+				});
+
+			this.views.add( sidebar );
+
+			if ( this.controller.uploader ) {
+				sidebar.set( 'uploads', new bp.media.view.UploaderStatus({
+					controller: this.controller,
+					priority:   40
+				}) );
+			}
+
+			selection.on( 'selection:single', this.createSingle, this );
+			selection.on( 'selection:unsingle', this.disposeSingle, this );
+
+			if ( selection.single() ) {
+				this.createSingle();
+			}
+		},
+
+		createSingle: function() {
+			var sidebar = this.sidebar,
+				single = this.options.selection.single();
+
+			sidebar.set( 'details', new bp.media.view.AvatarDetails({
+				controller: this.controller,
+				model:      single,
+				priority:   80
+			}) );
+		},
+	} );
+
+	bp.media.UploaderInline = bp.media.view.UploaderInline.extend( {
+		initialize:function() {
+			bp.media.view.UploaderInline.prototype.initialize.apply( this, arguments );
+
+			if ( 'avatar' == bp.media.params.item_type ) {
+				this.on( 'ready', this.customizeUploader, this );
+			}
+		},
+
+		customizeUploader:function(){
+			$( '.upload-ui h3.upload-instructions' ).html( 'Drop your avatar anywhere to upload' );
+			$( '.upload-ui a.button-hero' ).html( 'Select an image' );
+		}
+	} );
+
+	bp.media.view.ToolbarSelect = bp.media.view.Toolbar.Select.extend({
+		initialize: function() {
+			var options = this.options;
+			/**
+			 * call 'initialize' directly on the parent class
+			 */
+			bp.media.view.Toolbar.Select.prototype.initialize.apply( this, arguments );
+		},
+
+		refresh: function() {
+			var library = BPmedia.BPattachmentsBrowser._frame.state().get( 'library' ),
+			    selection = BPmedia.BPattachmentsBrowser._frame.state().get('selection');
+
+			if ( -1 == bp.media.params.callback.indexOf( 'http://' ) ) {
+				if ( selection.length > 0 ) {
+					this.get( 'select' ).model.set( 'disabled', false );
+				} else {
+					this.get( 'select' ).model.set( 'disabled', true );
+				}
+			} else {
+				this.get( 'select' ).model.set( 'text', 'Ok' );
+				if ( library.length > 0 ) {
+					this.get( 'select' ).model.set( 'disabled', false );
+				} else {
+					this.get( 'select' ).model.set( 'disabled', true );
+				}
+			}
+		},
+	});
+
+	BPmedia.BPattachmentsBrowser = _.extend( BPmedia, {
+		frame: function() {
+			if ( this._frame )
+				return this._frame;
+
+			var states = [new bp.media.controller.bpLibrary()];
+
+			this._frame = bp.media( {
+				className: 'media-frame no-sidebar',
+				states: states,
+				state: 'bp_library'
+			} );
+
+			this._frame.on( 'open', this.open );
+			this._frame.on( 'close', this.close );
+			this._frame.on( 'router:create:bp_browse', this.createRouter, this  );
+			this._frame.on( 'router:render:bp_browse', this.bpBrowse, this );
+			this._frame.on( 'content:create:bpbrowse', this.bpBrowseContent, this );
+			this._frame.on( 'content:create:crop', this.cropAvatar, this );
+			this._frame.on( 'content:render:bp_upload', this.uploadContent, this );
+			this._frame.on( 'toolbar:create:bp_select', this.createSelectToolbar, this );
+
+			this._frame.state( 'bp_library' ).on( 'select', this.select );
+
+			// Check if one file at a time
+			this._frame.listenToOnce( this._frame.states.get('bp_library').frame.uploader, 'ready', this.oneAtatime, this );
+
+			return this._frame;
+		},
+
+		oneAtatime:function() {
+			// plupload customs 1 at a time if set so !
+			this.uploader.uploader.uploader.bind( 'FilesAdded', function( up, files ) {
+				// one file at a time !
+				if( _wpPluploadSettings.defaults.multi_selection == false && files.length > 1 ) {
+					var default_error = pluploadL10n.default_error;
+					pluploadL10n.default_error = bp.media.view.l10n.bp_attachments.files_error;
+
+					for ( i in files ) {
+						this.trigger( 'Error', {
+	      					code : 'bp_failed',
+						    file : files[i]
+	    				});
+						up.removeFile(files[i]);
+					}
+					pluploadL10n.default_error = default_error;
+				}
+			} );
+		},
+
+		createRouter:function( router ) {
+			router.view = new bp.media.view.Router({
+				controller: this._frame
+			});
+		},
+
+		bpBrowse:function( view ) {
+
+			if ( 'avatar' == bp.media.params.item_type ) {
+				view.set({
+					bp_upload: {
+						text:     bp.media.view.l10n.bp_attachments.uploadtab,
+						priority: 20
+					},
+					crop: {
+						text:     bp.media.view.l10n.bp_attachments.croptab,
+						priority: 40
+					}
+				});
+			} else {
+				view.set({
+					bp_upload: {
+						text:     bp.media.view.l10n.bp_attachments.uploadtab,
+						priority: 20
+					},
+					bpbrowse: {
+						text:     bp.media.view.l10n.bp_attachments.managetab,
+						priority: 40
+					}
+				});
+			}
+
+		},
+
+		bpBrowseContent:function( content ) {
+			var state = this._frame.state();
+
+			this._frame.$el.removeClass('hide-toolbar');
+
+			// Browse our library of attachments.
+			content.view = new BPmedia.view.AttachmentsBrowser({
+				controller: this._frame,
+				collection: state.get('library'),
+				selection:  state.get('selection'),
+				model:      state,
+				sortable:   state.get('sortable'),
+				search:     state.get('searchable'),
+				filters:    state.get('filterable'),
+				display:    state.get('displaySettings'),
+				dragInfo:   state.get('dragInfo'),
+
+				AttachmentView: state.get('AttachmentView')
+			});
+		},
+
+		cropAvatar: function( content ) {
+			var state = this._frame.state();
+
+			this._frame.$el.removeClass('hide-toolbar');
+
+			// Browse our library of attachments.
+			content.view = new BPmedia.view.AvatarCroper({
+				controller: this._frame,
+				collection: state.get('library'),
+				selection:  state.get('selection'),
+				model:      state,
+				display:    state.get('displaySettings'),
+				dragInfo:   state.get('dragInfo')
+			});
+		},
+
+		createSelectToolbar: function( toolbar, options ) {
+			options = options || this._frame.options.button || {};
+			options.controller = this._frame;
+
+			if ( 'avatar' == bp.media.params.item_type ) {
+				toolbar.view = new bp.media.view.Toolbar.Select( options );
+			}
+
+			if ( ! _.isUndefined( bp.media.params.callback ) && bp.media.params.callback ) {
+				toolbar.view = new bp.media.view.ToolbarSelect( options );
+			}
+		},
+
+		uploadContent: function() {
+
+			if ( 'avatar' == bp.media.params.item_type ) {
+				this._frame.reset();
+				this._frame.state().get('library').reset();
+			}
+
+			this._frame.$el.removeClass('hide-toolbar');
+			this._frame.content.set( new bp.media.UploaderInline({
+				controller: this._frame
+			}) );
+		},
+
+		open: function() {
+			$( '.media-modal' ).css({
+				"top":    "10%",
+				"right":  "15%",
+				"bottom": "10%",
+				"left":   "15%"
+		    });
+
+			// Hide screen reader text on front end
+			if ( ! $( 'body' ).hasClass('wp-admin' ) ) {
+				$( 'a.media-modal-close .screen-reader-text' ).hide();
+			}
+		},
+
+		close: function() {
+			$( '.media-modal' ).removeAttr( 'style');
+		},
+
+		select: function() {
+			var settings = bp.media.view.settings.bp_attachments,
+				selection = this.get( 'selection' ).single();
+
+			$( '.added' ).remove();
+			BPmedia.set( selection );
+		},
+
+		set: function( attachment ) {
+			if ( 'avatar' == bp.media.params.item_type ) {
+				bp.media.post( 'bp_attachments_set_avatar', {
+					json:          true,
+					object:        bp.media.view.settings.bp_attachments.object,
+					component:     bp.media.params.item_component,
+					avatar_dir:    bp.media.view.settings.bp_attachments.avatar_dir,
+					item_id:       bp.media.view.settings.bp_attachments.item_id,
+					original_file: attachment.get( 'src' ),
+					crop_w:        attachment.get( 'w' ),
+					crop_h:        attachment.get( 'h' ),
+					crop_x:        attachment.get( 'x' ),
+					crop_y:        attachment.get( 'y' ),
+					nonce:         bp.media.params.nonce
+				}).done( function( html ) {
+					$( '#' + bp.media.params.item_component + '-avatar', '#bp_' + bp.media.params.item_component + '_avatar' ).html( html );
+					$( bp.media.view.settings.bp_attachments.button_id ).hide();
+				});
+				// reseting the frame
+				this._frame.reset();
+				this._frame.state().get('library').reset();
+
+			} else {
+
+				if ( -1 == bp.media.params.callback.indexOf( 'http://' ) ) {
+					bp.media.post( bp.media.params.callback, {
+						json:          true,
+						id:            attachment.get('id'),
+						object:        bp.media.view.settings.bp_attachments.object,
+						component:     bp.media.params.item_component,
+						item_id:       bp.media.view.settings.bp_attachments.item_id,
+						nonce:         bp.media.params.nonce
+					}).done( function( html ) {
+						$( bp.media.params.callback_id ).html( html );
+					});
+				} else {
+					window.location.href = bp.media.params.callback;
+				}
+			}
+		},
+
+		deleteAvatar: function() {
+			bp.media.post( 'bp_attachments_delete_avatar', {
+				json:          true,
+				object:        bp.media.view.settings.bp_attachments.object,
+				avatar_dir:    bp.media.view.settings.bp_attachments.avatar_dir,
+				component:     bp.media.params.item_component,
+				item_id:       bp.media.view.settings.bp_attachments.item_id,
+				nonce:         bp.media.params.nonce
+			}).done( function( result ) {
+
+				if ( 1 == result ) {
+					$( '#' + bp.media.params.item_component + '-avatar img' ).remove();
+					$( '#' + bp.media.params.item_component + '-avatar p' ).remove();
+					$( bp.media.view.settings.bp_attachments.button_id ).show();
+				}
+
+			});
+		},
+
+		init: function() {
+
+			if ( 'avatar' == bp.media.params.item_type ) {
+				// Avatar
+				if ( $( '#attachment-upload-form' ).length ) {
+					$( '#attachment-upload-form' ).remove();
+				}
+
+				if ( $( '#create-group-form p#attachment-upload' ).length ) {
+					$( '#create-group-form p#attachment-upload' ).remove();
+				}
+
+				if ( ! $( '#remove-' + bp.media.params.item_component + '-avatar' ).length ) {
+					$( bp.media.view.settings.bp_attachments.button_id ).show();
+				}
+				$( '#' + bp.media.params.item_component + '-avatar' ).on( 'click', 'a', function(e) {
+					e.preventDefault();
+
+					BPmedia.BPattachmentsBrowser.deleteAvatar();
+				});
+			} else {
+				if ( $( '#attachment-upload-form' ).length ) {
+					$( '#attachment-upload-form' ).remove();
+					$( bp.media.view.settings.bp_attachments.button_id ).show();
+				}
+			}
+
+			$( bp.media.view.settings.bp_attachments.button_id ).on( 'click', 'a', function( e ) {
+				e.preventDefault();
+
+				BPmedia.BPattachmentsBrowser.frame().open();
+			});
+		}
+	} );
+
+	// Attachments & Avatars
+	$( BPmedia.BPattachmentsBrowser.init );
+
+} )( jQuery );
diff --git src/bp-core/admin/bp-core-components.php src/bp-core/admin/bp-core-components.php
index f05d285..6e05930 100644
--- src/bp-core/admin/bp-core-components.php
+++ src/bp-core/admin/bp-core-components.php
@@ -255,6 +255,21 @@ function bp_core_admin_components_settings_handler() {
 		bp_core_install( $bp->active_components );
 		bp_core_add_page_mappings( $bp->active_components );
 		bp_update_option( 'bp-active-components', $bp->active_components );
+
+		/**
+		 * Disable avatar uploads if the Attachments component has been deactivated
+		 *
+		 * As src/bp-templates/bp-legacy/buddypress/members/single/profile/change-avatar.php
+		 * is directly checking this option, we are a bit forced to update this option in case
+		 * the attachments component is not active
+		 */
+		if ( empty( $bp->active_components['attachments'] ) ) {
+			bp_update_option( 'bp-disable-avatar-uploads', 1 );
+
+		// As Only avatars are managed so far, restore the option if Attachments component is activated
+		} else {
+			bp_update_option( 'bp-disable-avatar-uploads', 0 );
+		}
 	}
 
 	// Where are we redirecting to?
@@ -395,7 +410,11 @@ function bp_core_admin_get_components( $type = 'all' ) {
 		'blogs'    => array(
 			'title'       => __( 'Site Tracking', 'buddypress' ),
 			'description' => __( 'Record activity for new posts and comments from your site.', 'buddypress' )
-		)
+		),
+		'attachments' => array(
+			'title'       => __( 'Attachments', 'buddypress' ),
+			'description' => __( 'Utility to manage your users attachments such as avatars', 'buddypress' )
+ 		),
 	);
 
 
diff --git src/bp-core/admin/bp-core-settings.php src/bp-core/admin/bp-core-settings.php
index 71f8f33..11834bc 100644
--- src/bp-core/admin/bp-core-settings.php
+++ src/bp-core/admin/bp-core-settings.php
@@ -218,6 +218,15 @@ function bp_admin_setting_callback_bbpress_configuration() {
 <?php
 }
 
+/** Attachments ***************************************************************/
+
+/**
+ * Attachments settings section description for the settings page
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_admin_setting_callback_attachments_section() { }
+
 /** Settings Page *************************************************************/
 
 /**
diff --git src/bp-core/admin/css/common.css src/bp-core/admin/css/common.css
index 0899ce7..fd85770 100644
--- src/bp-core/admin/css/common.css
+++ src/bp-core/admin/css/common.css
@@ -237,6 +237,10 @@ body.branch-3-7 #adminmenu li.toplevel_page_bp-general-settings .wp-menu-image {
 	content: "\f454";
 }
 
+.settings_page_bp-components tr.attachments td.plugin-title span:before {
+	content: "\f128";
+}
+
 /* Settings - Legacy (< WP 3.8) */
 body.branch-3-6.settings_page_bp-components tr.activity td.plugin-title span:before,
 body.branch-3-6.settings_page_bp-components tr.notifications td.plugin-title span:before,
diff --git src/bp-core/bp-core-admin.php src/bp-core/bp-core-admin.php
index 217eb08..7f65e67 100644
--- src/bp-core/bp-core-admin.php
+++ src/bp-core/bp-core-admin.php
@@ -336,8 +336,6 @@ class BP_Admin {
 			// Add the main section
 			add_settings_section( 'bp_xprofile', _x( 'Profile Settings', 'BuddyPress setting tab', 'buddypress' ), 'bp_admin_setting_callback_xprofile_section', 'buddypress' );
 
-			$avatar_setting = 'bp_xprofile';
-
 			// 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'                                                                                  );
@@ -350,9 +348,6 @@ class BP_Admin {
 			// Add the main section
 			add_settings_section( 'bp_groups',        __( 'Groups Settings',  'buddypress' ), 'bp_admin_setting_callback_groups_section',   'buddypress'              );
 
-			if ( empty( $avatar_setting ) )
-				$avatar_setting = 'bp_groups';
-
 			// 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'                                                                                );
@@ -392,12 +387,15 @@ class BP_Admin {
 			}
 		}
 
-		/** Avatar upload for users or groups ************************************/
+		/** Attachments section ***********************************************/
+
+		if ( bp_is_active( 'attachments' ) ) {
+			// Add the main section
+			add_settings_section( 'bp_attachments',    __( 'Attachments Settings', 'buddypress' ), 'bp_admin_setting_callback_attachments_section',     'buddypress'                   );
 
-		if ( ! empty( $avatar_setting ) ) {
 		    // Allow avatar uploads
-		    add_settings_field( 'bp-disable-avatar-uploads', __( 'Profile Photo Uploads',   'buddypress' ), 'bp_admin_setting_callback_avatar_uploads',   'buddypress', $avatar_setting );
-		    register_setting  ( 'buddypress',         'bp-disable-avatar-uploads',   'intval'                                                                                    );
+		    add_settings_field( 'bp-disable-avatar-uploads', __( 'Profile Photo Uploads',   'buddypress' ), 'bp_admin_setting_callback_avatar_uploads', 'buddypress', 'bp_attachments' );
+		    register_setting  ( 'buddypress',         'bp-disable-avatar-uploads',   'intval'                                                                                          );
 		}
 	}
 
@@ -512,7 +510,7 @@ class BP_Admin {
 				</a>
 			</h2>
 
-			<?php if ( $is_new_install ) : ?> 
+			<?php if ( $is_new_install ) : ?>
 
 				<div id="welcome-panel" class="welcome-panel">
 					<div class="welcome-panel-content">
@@ -521,10 +519,10 @@ class BP_Admin {
 							<div class="welcome-panel-column">
 								<h4><?php _e( 'Configure Buddypress', 'buddypress' ); ?></h4>
 								<ul>
-									<li><?php printf( 
+									<li><?php printf(
 									'<a href="%s" class="welcome-icon welcome-edit-page">' . __( 'Set Up Components', 'buddypress' ) . '</a>', bp_get_admin_url( add_query_arg( array( 'page' => 'bp-components' ), $this->settings_page ) )
 									); ?></li>
-									<li><?php printf( 
+									<li><?php printf(
 									'<a href="%s" class="welcome-icon welcome-edit-page">' . __( 'Assign Components to Pages', 'buddypress' ) . '</a>', bp_get_admin_url( add_query_arg( array( 'page' => 'bp-page-settings' ), $this->settings_page ) )
 									); ?></li>
 									<li><?php printf(
@@ -552,7 +550,7 @@ class BP_Admin {
 							</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="http://codex.buddypress.org/">BuddyPress Codex</a> has you covered.', 'buddypress' ) ?></p> 
+								<p class="welcome-icon welcome-learn-more" style="margin-right:10px"><?php _e( 'Looking for help? The <a href="http://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="http://buddypress.org/support/">our support forums</a>, where active BuddyPress users and developers are waiting to share tips and more.', 'buddypress' ) ?></p>
 							</div>
 						</div>
@@ -649,7 +647,7 @@ class BP_Admin {
 								_e( 'When making searches with <code>BP_User_Query</code>, a new <code>search_wildcard</code> parameter gives you finer control over how the search SQL is constructed.', 'buddypress' );
 								?>
 							</li>
-	
+
 
 							<li><?php printf( __( '<a href="%s">&hellip;and lots more!</a>', 'buddypress' ), 'https://codex.buddypress.org/releases/version-2-1' ); ?></li>
 						</ul>
diff --git src/bp-core/bp-core-avatars.php src/bp-core/bp-core-avatars.php
index 65c516e..44e3ae6 100644
--- src/bp-core/bp-core-avatars.php
+++ src/bp-core/bp-core-avatars.php
@@ -26,18 +26,6 @@ function bp_core_set_avatar_constants() {
 	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' ) ) {
-
-		if ( !isset( $bp->site_options['fileupload_maxk'] ) ) {
-			define( 'BP_AVATAR_ORIGINAL_MAX_FILESIZE', 5120000 ); // 5mb
-		} else {
-			define( 'BP_AVATAR_ORIGINAL_MAX_FILESIZE', $bp->site_options['fileupload_maxk'] * 1024 );
-		}
-	}
-
 	if ( ! defined( 'BP_SHOW_AVATARS' ) ) {
 		define( 'BP_SHOW_AVATARS', bp_get_option( 'show_avatars' ) );
 	}
@@ -62,30 +50,14 @@ function bp_core_set_avatar_globals() {
 	$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 );
-
 	do_action( 'bp_core_set_avatar_globals' );
 }
 add_action( 'bp_setup_globals', 'bp_core_set_avatar_globals' );
@@ -200,242 +172,187 @@ function bp_core_fetch_avatar( $args = '' ) {
 		'title'      => '',
 	) );
 
+	$avatar             = (object) $params;
+	$avatar->folder_url = '';
+	$avatar->folder_dir = '';
+	$avatar->url        = '';
+
 	/** Set item_id ***********************************************************/
 
-	if ( empty( $params['item_id'] ) ) {
+	if ( empty( $avatar->item_id ) ) {
 
-		switch ( $params['object'] ) {
+		switch ( $avatar->object ) {
 
 			case 'blog'  :
-				$params['item_id'] = $current_blog->id;
+				$avatar->item_id = $current_blog->id;
 				break;
 
 			case 'group' :
 				if ( bp_is_active( 'groups' ) ) {
-					$params['item_id'] = $bp->groups->current_group->id;
+					$avatar->item_id = $bp->groups->current_group->id;
 				} else {
-					$params['item_id'] = false;
+					$avatar->item_id = false;
 				}
 
 				break;
 
 			case 'user'  :
 			default      :
-				$params['item_id'] = bp_displayed_user_id();
+				$avatar->item_id = bp_displayed_user_id();
 				break;
 		}
 
-		$params['item_id'] = apply_filters( 'bp_core_avatar_item_id', $params['item_id'], $params['object'], $params );
+		$avatar->item_id = apply_filters( 'bp_core_avatar_item_id', $avatar->item_id, $avatar->object, $avatar );
 
-		if ( empty( $params['item_id'] ) ) {
+		if ( empty( $avatar->item_id ) ) {
 			return false;
 		}
 	}
 
 	/** Set avatar_dir ********************************************************/
 
-	if ( empty( $params['avatar_dir'] ) ) {
+	if ( empty( $avatar->avatar_dir ) ) {
 
-		switch ( $params['object'] ) {
+		switch ( $avatar->object ) {
 
 			case 'blog'  :
-				$params['avatar_dir'] = 'blog-avatars';
+				$avatar->avatar_dir = 'avatar_blog';
 				break;
 
 			case 'group' :
 				if ( bp_is_active( 'groups' ) ) {
-					$params['avatar_dir'] = 'group-avatars';
+					$avatar->avatar_dir = 'avatar_group';
 				} else {
-					$params['avatar_dir'] = false;
+					$avatar->avatar_dir = false;
 				}
 
 				break;
 
 			case 'user'  :
 			default      :
-				$params['avatar_dir'] = 'avatars';
+				$avatar->avatar_dir = 'avatar_user';
 				break;
 		}
 
-		$params['avatar_dir'] = apply_filters( 'bp_core_avatar_dir', $params['avatar_dir'], $params['object'], $params );
+		$avatar->avatar_dir = apply_filters( 'bp_core_avatar_dir', $avatar->avatar_dir, $avatar->object, $avatar );
 
-		if ( empty( $params['avatar_dir'] ) ) {
+		if ( empty( $avatar->avatar_dir ) ) {
 			return false;
 		}
 	}
 
 	/** <img> alt *************************************************************/
 
-	if ( false !== strpos( $params['alt'], '%s' ) || false !== strpos( $params['alt'], '%1$s' ) ) {
+	if ( false !== strpos( $avatar->alt, '%s' ) || false !== strpos( $avatar->alt, '%1$s' ) ) {
 
-		switch ( $params['object'] ) {
+		switch ( $avatar->object ) {
 
 			case 'blog'  :
-				$item_name = get_blog_option( $params['item_id'], 'blogname' );
+				$item_name = get_blog_option( $avatar->item_id, 'blogname' );
 				break;
 
 			case 'group' :
-				$item_name = bp_get_group_name( groups_get_group( array( 'group_id' => $params['item_id'] ) ) );
+				$item_name = bp_get_group_name( groups_get_group( array( 'group_id' => $avatar->item_id ) ) );
 				break;
 
 			case 'user'  :
 			default :
-				$item_name = bp_core_get_user_displayname( $params['item_id'] );
+				$item_name = bp_core_get_user_displayname( $avatar->item_id );
 				break;
 		}
 
-		$item_name = apply_filters( 'bp_core_avatar_alt', $item_name, $params['item_id'], $params['object'], $params );
-		$params['alt'] = sprintf( $params['alt'], $item_name );
+		$item_name = apply_filters( 'bp_core_avatar_alt', $item_name, $avatar->item_id, $avatar->object, $avatar );
+		$avatar->alt = sprintf( $avatar->alt, $item_name );
 	}
 
 	/** Sanity Checks *********************************************************/
 
 	// Get a fallback for the 'alt' parameter, create html output
-	if ( empty( $params['alt'] ) ) {
-		$params['alt'] = __( 'Profile Photo', 'buddypress' );
+	if ( empty( $avatar->alt ) ) {
+		$avatar->alt = __( 'Profile Photo', 'buddypress' );
 	}
-	$html_alt = ' alt="' . esc_attr( $params['alt'] ) . '"';
+	$html_alt = ' alt="' . esc_attr( $avatar->alt ) . '"';
 
 	// Filter image title and create html string
 	$html_title = '';
-	$params['title'] = apply_filters( 'bp_core_avatar_title', $params['title'], $params['item_id'], $params['object'], $params );
+	$avatar->title = apply_filters( 'bp_core_avatar_title', $avatar->title, $avatar->item_id, $avatar->object, $avatar );
 
-	if ( ! empty( $params['title'] ) ) {
-		$html_title = ' title="' . esc_attr( $params['title'] ) . '"';
+	if ( ! empty( $avatar->title ) ) {
+		$html_title = ' title="' . esc_attr( $avatar->title ) . '"';
 	}
 
 	// Set CSS ID and create html string
 	$html_css_id = '';
-	$params['css_id'] = apply_filters( 'bp_core_css_id', $params['css_id'], $params['item_id'], $params['object'], $params );
+	$avatar->css_id = apply_filters( 'bp_core_css_id', $avatar->css_id, $avatar->item_id, $avatar->object, $avatar );
 
-	if ( ! empty( $params['css_id'] ) ) {
-		$html_css_id = ' id="' . esc_attr( $params['css_id'] ) . '"';
+	if ( ! empty( $avatar->css_id ) ) {
+		$html_css_id = ' id="' . esc_attr( $avatar->css_id ) . '"';
 	}
 
 	// Set image width
-	if ( false !== $params['width'] ) {
+	if ( false !== $avatar->width ) {
 		// Width has been specified. No modification necessary.
-	} else if ( 'thumb' == $params['type'] ) {
-		$params['width'] = bp_core_avatar_thumb_width();
+	} else if ( 'thumb' == $avatar->type ) {
+		$avatar->width = bp_core_avatar_thumb_width();
 	} else {
-		$params['width'] = bp_core_avatar_full_width();
+		$avatar->width = bp_core_avatar_full_width();
 	}
-	$html_width = ' width="' . $params['width'] . '"';
+	$html_width = ' width="' . $avatar->width . '"';
 
 	// Set image height
-	if ( false !== $params['height'] ) {
+	if ( false !== $avatar->height ) {
 		// Height has been specified. No modification necessary.
-	} else if ( 'thumb' == $params['type'] ) {
-		$params['height'] = bp_core_avatar_thumb_height();
+	} else if ( 'thumb' == $avatar->type ) {
+		$avatar->height = bp_core_avatar_thumb_height();
 	} else {
-		$params['height'] = bp_core_avatar_full_height();
+		$avatar->height = bp_core_avatar_full_height();
 	}
-	$html_height = ' height="' . $params['height'] . '"';
+	$html_height = ' height="' . $avatar->height . '"';
 
 	// Create CSS class html string
-	$params['class'] = apply_filters( 'bp_core_avatar_class', $params['class'], $params['item_id'], $params['object'], $params );
-	$html_class = ' class="' . sanitize_html_class( $params['class'] ) . ' ' . sanitize_html_class( $params['object'] . '-' . $params['item_id'] . '-avatar' ) . ' ' . sanitize_html_class( 'avatar-' . $params['width'] ) . ' 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'] );
-	$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'] );
-	$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'] );
+	$avatar->class = apply_filters( 'bp_core_avatar_class', $avatar->class, $avatar->item_id, $avatar->object, $avatar );
+	$html_class = ' class="' . sanitize_html_class( $avatar->class ) . ' ' . sanitize_html_class( $avatar->object . '-' . $avatar->item_id . '-avatar' ) . ' ' . sanitize_html_class( 'avatar-' . $avatar->width ) . ' photo"';
 
 	/**
-	 * 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.
+	 * Do no use this filter, for internal use only.
+	 * The Attachments component if active will check
+	 * for a local avatar
 	 */
-	$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 );
+	$avatar = apply_filters( 'bp_core_avatar_local_data', $avatar );
 
-		// If we found a locally uploaded avatar
-		if ( isset( $avatar_url ) ) {
+	// If we found a locally uploaded avatar
+	if ( ! empty( $avatar->url ) ) {
 
-			// Return it wrapped in an <img> element
-			if ( true === $params['html'] ) {
-				return apply_filters( 'bp_core_fetch_avatar', '<img src="' . $avatar_url . '"' . $html_class . $html_css_id  . $html_width . $html_height . $html_alt . $html_title . ' />', $params, $params['item_id'], $params['avatar_dir'], $html_css_id, $html_width, $html_height, $avatar_folder_url, $avatar_folder_dir );
+		// Return it wrapped in an <img> element
+		if ( true === $avatar->html ) {
+			return apply_filters( 'bp_core_fetch_avatar', '<img src="' . $avatar->url . '"' . $html_class . $html_css_id  . $html_width . $html_height . $html_alt . $html_title . ' />', $params, $avatar->item_id, $avatar->avatar_dir, $html_css_id, $html_width, $html_height, $avatar->folder_url, $avatar->folder_dir );
 
-			// ...or only the URL
-			} else {
-				return apply_filters( 'bp_core_fetch_avatar_url', $avatar_url, $params );
-			}
+		// ...or only the URL
+		} else {
+			return apply_filters( 'bp_core_fetch_avatar_url', $avatar->url, $params );
 		}
 	}
 
 	// If no avatars could be found, try to display a gravatar
 
 	// Skips gravatar check if $params['no_grav'] is passed
-	if ( ! apply_filters( 'bp_core_fetch_avatar_no_grav', $params['no_grav'], $params ) ) {
+	if ( ! apply_filters( 'bp_core_fetch_avatar_no_grav', $avatar->no_grav, $avatar ) ) {
 
 		// Set gravatar type
-		if ( empty( $bp->grav_default->{$params['object']} ) ) {
+		if ( empty( $bp->grav_default->{$avatar->object} ) ) {
 			$default_grav = 'wavatar';
-		} else if ( 'mystery' == $bp->grav_default->{$params['object']} ) {
-			$default_grav = apply_filters( 'bp_core_mysteryman_src', 'mm', $params['width'] );
+		} else if ( 'mystery' == $bp->grav_default->{$avatar->object} ) {
+			$default_grav = apply_filters( 'bp_core_mysteryman_src', 'mm', $avatar->width );
 		} else {
-			$default_grav = $bp->grav_default->{$params['object']};
+			$default_grav = $bp->grav_default->{$avatar->object};
 		}
 
 		// Set gravatar object
-		if ( empty( $params['email'] ) ) {
-			if ( 'user' == $params['object'] ) {
-				$params['email'] = bp_core_get_user_email( $params['item_id'] );
-			} else if ( 'group' == $params['object'] || 'blog' == $params['object'] ) {
-				$params['email'] = $params['item_id'] . '-' . $params['object'] . '@' . bp_get_root_domain();
+		if ( empty( $avatar->email ) ) {
+			if ( 'user' == $avatar->object ) {
+				$avatar->email = bp_core_get_user_email( $avatar->item_id );
+			} else if ( 'group' == $avatar->object || 'blog' == $avatar->object ) {
+				$avatar->email = $avatar->item_id . '-' . $avatar->object . '@' . bp_get_root_domain();
 			}
 		}
 
@@ -446,8 +363,8 @@ function bp_core_fetch_avatar( $args = '' ) {
 		}
 
 		// Filter gravatar vars
-		$params['email'] = apply_filters( 'bp_core_gravatar_email', $params['email'], $params['item_id'], $params['object'] );
-		$gravatar = apply_filters( 'bp_gravatar_url', $host ) . md5( strtolower( $params['email'] ) ) . '?d=' . $default_grav . '&amp;s=' . $params['width'];
+		$avatar->email = apply_filters( 'bp_core_gravatar_email', $avatar->email, $avatar->item_id, $avatar->object );
+		$gravatar = apply_filters( 'bp_gravatar_url', $host ) . md5( strtolower( $avatar->email ) ) . '?d=' . $default_grav . '&amp;s=' . $avatar->width;
 
 		// Gravatar rating; http://bit.ly/89QxZA
 		$rating = get_option( 'avatar_rating' );
@@ -457,352 +374,17 @@ function bp_core_fetch_avatar( $args = '' ) {
 
 	// No avatar was found, and we've been told not to use a gravatar.
 	} else {
-		$gravatar = apply_filters( 'bp_core_default_avatar_' . $params['object'], bp_core_avatar_default( 'local' ), $params );
+		$gravatar = apply_filters( 'bp_core_default_avatar_' . $avatar->object, bp_core_avatar_default( 'local' ), $avatar );
 	}
 
-	if ( true === $params['html'] ) {
-		return apply_filters( 'bp_core_fetch_avatar', '<img src="' . $gravatar . '"' . $html_css_id . $html_class . $html_width . $html_height . $html_alt . $html_title . ' />', $params, $params['item_id'], $params['avatar_dir'], $html_css_id, $html_width, $html_height, $avatar_folder_url, $avatar_folder_dir );
+	if ( true === $avatar->html ) {
+		return apply_filters( 'bp_core_fetch_avatar', '<img src="' . $gravatar . '"' . $html_css_id . $html_class . $html_width . $html_height . $html_alt . $html_title . ' />', $params, $avatar->item_id, $avatar->avatar_dir, $html_css_id, $html_width, $html_height, $avatar->folder_url, $avatar->folder_dir );
 	} else {
 		return apply_filters( 'bp_core_fetch_avatar_url', $gravatar, $params );
 	}
 }
 
 /**
- * Delete an existing avatar.
- *
- * @param array $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();
-		else if ( 'group' == $object )
-			$item_id = buddypress()->groups->current_group->id;
-		else if ( 'blog' == $object )
-			$item_id = $current_blog->id;
-
-		$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';
-		else if ( 'group' == $object )
-			$avatar_dir = 'group-avatars';
-		else if ( 'blog' == $object )
-			$avatar_dir = 'blog-avatars';
-
-		$avatar_dir = apply_filters( 'bp_core_avatar_dir', $avatar_dir, $object );
-
-		if ( !$avatar_dir ) return false;
-	}
-
-	$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 );
-
-	do_action( 'bp_core_delete_existing_avatar', $args );
-
-	return true;
-}
-
-/**
- * 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 ) {
-
-	/***
-	 * You may want to hook into this filter if you want to override this function.
-	 * Make sure you return false.
-	 */
-	if ( !apply_filters( 'bp_core_pre_avatar_handle_upload', true, $file, $upload_dir_filter ) )
-		return true;
-
-	require_once( ABSPATH . '/wp-admin/includes/file.php' );
-
-	$uploadErrors = array(
-		0 => __( 'The image was uploaded successfully', 'buddypress' ),
-		1 => __( 'The image exceeds the maximum allowed file size of: ', 'buddypress' ) . size_format( bp_core_avatar_original_max_filesize() ),
-		2 => __( 'The image exceeds the maximum allowed file size of: ', 'buddypress' ) . size_format( bp_core_avatar_original_max_filesize() ),
-		3 => __( 'The uploaded file was only partially uploaded.', 'buddypress' ),
-		4 => __( 'The image was not uploaded.', 'buddypress' ),
-		6 => __( 'Missing a temporary folder.', 'buddypress' )
-	);
-
-	if ( ! bp_core_check_avatar_upload( $file ) ) {
-		bp_core_add_message( sprintf( __( 'Your upload failed, please try again. Error was: %s', 'buddypress' ), $uploadErrors[$file['file']['error']] ), 'error' );
-		return false;
-	}
-
-	if ( ! bp_core_check_avatar_size( $file ) ) {
-		bp_core_add_message( sprintf( __( 'The file you uploaded is too big. Please upload a file under %s', 'buddypress' ), size_format( bp_core_avatar_original_max_filesize() ) ), 'error' );
-		return false;
-	}
-
-	if ( ! bp_core_check_avatar_type( $file ) ) {
-		bp_core_add_message( __( 'Please upload only JPG, GIF or PNG photos.', 'buddypress' ), 'error' );
-		return false;
-	}
-
-	// Filter the upload location
-	add_filter( 'upload_dir', $upload_dir_filter, 10, 0 );
-
-	$bp = buddypress();
-
-	$bp->avatar_admin->original = wp_handle_upload( $file['file'], array( 'action'=> 'bp_avatar_upload' ) );
-
-	// Remove the upload_dir filter, so that other upload URLs on the page
-	// don't break
-	remove_filter( 'upload_dir', $upload_dir_filter, 10, 0 );
-
-	// Move the file to the correct upload location.
-	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;
-	}
-
-	// Get image size
-	$size  = @getimagesize( $bp->avatar_admin->original['file'] );
-	$error = false;
-
-	// Check image size and shrink if too large
-	if ( $size[0] > bp_core_avatar_original_max_width() ) {
-		$editor = wp_get_image_editor( $bp->avatar_admin->original['file'] );
-
-		if ( ! is_wp_error( $editor ) ) {
-			$editor->set_quality( 100 );
-
-			$resized = $editor->resize( bp_core_avatar_original_max_width(), bp_core_avatar_original_max_width(), false );
-			if ( ! is_wp_error( $resized ) ) {
-				$thumb = $editor->save( $editor->generate_filename() );
-			} else {
-				$error = $resized;
-			}
-
-			// Check for thumbnail creation errors
-			if ( false === $error && is_wp_error( $thumb ) ) {
-				$error = $thumb;
-			}
-
-			// Thumbnail is good so proceed
-			if ( false === $error ) {
-				$bp->avatar_admin->resized = $thumb;
-			}
-
-		} else {
-			$error = $editor;
-		}
-
-		if ( false !== $error ) {
-			bp_core_add_message( sprintf( __( 'Upload Failed! Error was: %s', 'buddypress' ), $error->get_error_message() ), 'error' );
-			return false;
-		}
-	}
-
-	if ( ! isset( $bp->avatar_admin->image ) )
-		$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->dir = str_replace( bp_core_avatar_upload_path(), '', $bp->avatar_admin->original['file'] );
-	} else {
-		$bp->avatar_admin->image->dir = str_replace( bp_core_avatar_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
-	$uploaded_image = @getimagesize( bp_core_avatar_upload_path() . buddypress()->avatar_admin->image->dir );
-	$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 ) {
-		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' ), $full_width, $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;
-}
-
-/**
- * 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 $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 papth 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
-	) );
-
-	/***
-	 * You may want to hook into this filter if you want to override this function.
-	 * Make sure you return false.
-	 */
-	if ( !apply_filters( 'bp_core_pre_avatar_handle_crop', true, $r ) )
-		return true;
-
-	extract( $r, EXTR_SKIP );
-
-	if ( empty( $original_file ) )
-		return false;
-
-	$original_file = bp_core_avatar_upload_path() . $original_file;
-
-	if ( !file_exists( $original_file ) )
-		return false;
-
-	if ( empty( $item_id ) ) {
-		$avatar_folder_dir = apply_filters( 'bp_core_avatar_folder_dir', dirname( $original_file ), $item_id, $object, $avatar_dir );
-	} else {
-		$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;
-
-	require_once( ABSPATH . '/wp-admin/includes/image.php' );
-	require_once( ABSPATH . '/wp-admin/includes/file.php' );
-
-	// Delete the existing avatar files for the object
-	$existing_avatar = bp_core_fetch_avatar( array(
-		'object'  => $object,
-		'item_id' => $item_id,
-		'html' => false,
-	) );
-
-	if ( ! empty( $existing_avatar ) ) {
-		// Check that the new avatar doesn't have the same name as the
-		// old one before deleting
-		$upload_dir           = wp_upload_dir();
-		$existing_avatar_path = str_replace( $upload_dir['baseurl'], '', $existing_avatar );
-		$new_avatar_path      = str_replace( $upload_dir['basedir'], '', $original_file );
-
-		if ( $existing_avatar_path !== $new_avatar_path ) {
-			bp_core_delete_existing_avatar( array( 'object' => $object, 'item_id' => $item_id, 'avatar_path' => $avatar_folder_dir ) );
-		}
-	}
-
-
-
-	// Make sure we at least have a width and height for cropping
-	if ( empty( $crop_w ) ) {
-		$crop_w = bp_core_avatar_full_width();
-	}
-
-	if ( empty( $crop_h ) ) {
-		$crop_h = bp_core_avatar_full_height();
-	}
-
-	// Get the file extension
-	$data = @getimagesize( $original_file );
-	$ext  = $data['mime'] == 'image/png' ? 'png' : 'jpg';
-
-	// Set the full and thumb filenames
-	$full_filename  = wp_hash( $original_file . time() ) . '-bpfull.'  . $ext;
-	$thumb_filename = wp_hash( $original_file . time() ) . '-bpthumb.' . $ext;
-
-	// Crop the image
-	$full_cropped  = wp_crop_image( $original_file, (int) $crop_x, (int) $crop_y, (int) $crop_w, (int) $crop_h, bp_core_avatar_full_width(),  bp_core_avatar_full_height(),  false, $avatar_folder_dir . '/' . $full_filename  );
-	$thumb_cropped = wp_crop_image( $original_file, (int) $crop_x, (int) $crop_y, (int) $crop_w, (int) $crop_h, bp_core_avatar_thumb_width(), bp_core_avatar_thumb_height(), false, $avatar_folder_dir . '/' . $thumb_filename );
-
-	// Check for errors
-	if ( empty( $full_cropped ) || empty( $thumb_cropped ) || is_wp_error( $full_cropped ) || is_wp_error( $thumb_cropped ) )
-		return false;
-
-	// Remove the original
-	@unlink( $original_file );
-
-	return true;
-}
-
-/**
  * Replace default WordPress avatars with BP avatars, if available.
  *
  * Filters 'get_avatar'.
@@ -870,161 +452,6 @@ function bp_core_fetch_avatar_filter( $avatar, $user, $size, $default, $alt = ''
 add_filter( 'get_avatar', 'bp_core_fetch_avatar_filter', 10, 5 );
 
 /**
- * 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;
-}
-
-/**
- * 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) {
-	if ( ( !empty( $file['file']['type'] ) && !preg_match('/(jpe?g|gif|png)$/i', $file['file']['type'] ) ) || !preg_match( '/(jpe?g|gif|png)$/i', $file['file']['name'] ) )
-		return false;
-
-	return true;
-}
-
-/**
- * Fetch 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 BuddyPress (1.8.0)
- *
- * @uses wp_upload_dir()
- *
- * @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 {
-				// We need to switch to the root blog on multisite installs
-				if ( is_multisite() ) {
-					switch_to_blog( bp_get_root_blog_id() );
-				}
-
-				// Get upload directory information from current site
-				$upload_dir = wp_upload_dir();
-
-				// Will bail if not switched
-				restore_current_blog();
-
-				// 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 wp_upload_dir To get upload directory info
- *
- * @return string Absolute path to WP upload directory.
- */
-function bp_core_avatar_upload_path() {
-	return apply_filters( 'bp_core_avatar_upload_path', bp_core_get_upload_dir() );
-}
-
-/**
- * Get the raw base URL for root site upload location.
- *
- * @uses wp_upload_dir To get upload directory info.
- *
- * @return string Full URL to current upload location.
- */
-function bp_core_avatar_url() {
-	return apply_filters( 'bp_core_avatar_url', bp_core_get_upload_dir( 'url' ) );
-}
-
-/**
  * Check if a given user ID has an uploaded avatar.
  *
  * @since BuddyPress (1.0.0)
@@ -1107,28 +534,6 @@ function bp_core_avatar_full_height() {
 }
 
 /**
- * Get the max width for original avatar uploads.
- *
- * @since BuddyPress (1.5.0)
- *
- * @return int The max width for original avatar uploads.
- */
-function bp_core_avatar_original_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 BuddyPress (1.5.0)
- *
- * @return int The max filesize for original avatar uploads.
- */
-function bp_core_avatar_original_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 BuddyPress (1.5.0)
diff --git src/bp-core/bp-core-component.php src/bp-core/bp-core-component.php
index 539359f..9bd3b82 100644
--- src/bp-core/bp-core-component.php
+++ src/bp-core/bp-core-component.php
@@ -125,6 +125,8 @@ class BP_Component {
 	 */
 	public $global_tables = array();
 
+	public $can_attachments = false;
+
 	/** Methods ***************************************************************/
 
 	/**
@@ -220,6 +222,7 @@ class BP_Component {
 			'search_string'         => '',
 			'global_tables'         => '',
 			'meta_tables'           => '',
+			'can_attachments'       => false,
 		) );
 
 		// Slug used for permalink URI chunk after root
@@ -250,6 +253,11 @@ class BP_Component {
 			$this->register_meta_tables( $r['meta_tables'] );
 		}
 
+		// Use Attachments
+		if ( ! empty( $r['can_attachments'] ) ) {
+			$this->can_attachments = apply_filters( 'bp_' . $this->id . '_can_attachments', $r['can_attachments'] );
+		}
+
 		/** BuddyPress ********************************************************/
 
 		// Register this component in the loaded components array
diff --git src/bp-core/bp-core-cssjs.php src/bp-core/bp-core-cssjs.php
index ce59ad2..181e60f 100644
--- src/bp-core/bp-core-cssjs.php
+++ src/bp-core/bp-core-cssjs.php
@@ -17,7 +17,7 @@ if ( !defined( 'ABSPATH' ) ) exit;
 function bp_core_register_common_scripts() {
 	$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
 	$url = buddypress()->plugin_url . 'bp-core/js/';
-	
+
 	$scripts = apply_filters( 'bp_core_register_common_scripts', array(
 
 		// Legacy
@@ -86,136 +86,6 @@ add_action( 'bp_enqueue_scripts',    'bp_core_confirmation_js' );
 add_action( 'admin_enqueue_scripts', 'bp_core_confirmation_js' );
 
 /**
- * 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() {
-
-	// Bail if no image was uploaded
-	$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
-	} else if ( $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
-	} else if ( $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; ?> ]
-			});
-			updateCoords({x: <?php echo (int) $crop_left; ?>, y: <?php echo (int) $crop_top; ?>, w: <?php echo (int) $crop_right; ?>, h: <?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 BuddyPress (1.1.0)
diff --git src/bp-core/bp-core-loader.php src/bp-core/bp-core-loader.php
index 6b0d556..ccc7857 100644
--- src/bp-core/bp-core-loader.php
+++ src/bp-core/bp-core-loader.php
@@ -54,7 +54,7 @@ class BP_Core extends BP_Component {
 		/** Components ********************************************************/
 
 		// Set the included and optional components.
-		$bp->optional_components = apply_filters( 'bp_optional_components', array( 'activity', 'blogs', 'forums', 'friends', 'groups', 'messages', 'notifications', 'settings', 'xprofile' ) );
+		$bp->optional_components = apply_filters( 'bp_optional_components', array( 'activity', 'attachments', 'blogs', 'forums', 'friends', 'groups', 'messages', 'notifications', 'settings', 'xprofile' ) );
 
 		// Set the required components
 		$bp->required_components = apply_filters( 'bp_required_components', array( 'members' ) );
diff --git src/bp-core/bp-core-update.php src/bp-core/bp-core-update.php
index 6b27154..da05267 100644
--- src/bp-core/bp-core-update.php
+++ src/bp-core/bp-core-update.php
@@ -193,6 +193,7 @@ function bp_version_updater() {
 		'settings'      => 1,
 		'xprofile'      => 1,
 		'notifications' => 1,
+		'attachments'   => 1,
 	) );
 
 	require_once( buddypress()->plugin_dir . '/bp-core/admin/bp-core-schema.php' );
@@ -241,6 +242,11 @@ function bp_version_updater() {
 		if ( $raw_db_version < 8311 ) {
 			bp_update_to_2_0_1();
 		}
+
+		// 2.2
+		if ( $raw_db_version < 9131 ) {
+			bp_update_to_2_2();
+		}
 	}
 
 	/** All done! *************************************************************/
@@ -299,23 +305,11 @@ function bp_update_to_1_6() {
  * component to the active components option to retain existing functionality.
  *
  * @since BuddyPress (1.9.0)
+ *
+ * @uses  bp_update_active_components() to activate the notifications component
  */
 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 );
+	bp_update_active_components( 'notifications' );
 }
 
 /**
@@ -390,6 +384,59 @@ function bp_update_to_2_0_1() {
 }
 
 /**
+ * 2.2 upgrade routine
+ *
+ * Checks if avatar uploads are not disabled and eventually activate Attachments
+ * component
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @uses  bp_update_active_components() to activate the attachments component
+ * @return void
+ */
+function bp_update_to_2_2() {
+	/**
+	 * Only activate Attachments if avatar uploads
+	 * are not disabled
+	 */
+	if ( bp_disable_avatar_uploads() ) {
+		return;
+	}
+
+	bp_update_active_components( 'attachments' );
+}
+
+/**
+ * Update the active components
+ *
+ * When a component has been created by moving bp-core features, this
+ * function make sure it will be activated if needed.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @return void
+ */
+function bp_update_active_components( $new_component_id = '' ) {
+	if ( empty( $new_component_id ) ) {
+		return;
+	}
+
+	// Setup hardcoded keys
+	$active_components_key      = 'bp-active-components';
+
+	// Get the active components
+	$active_components          = bp_get_option( $active_components_key );
+
+	// Add notifications
+	if ( ! in_array( $new_component_id, $active_components ) ) {
+		$active_components[ $new_component_id ] = 1;
+	}
+
+	// Update the active components option
+	bp_update_option( $active_components_key, $active_components );
+}
+
+/**
  * Redirect user to BP's What's New page on first page load after activation.
  *
  * @since BuddyPress (1.7.0)
diff --git src/bp-core/deprecated/2.2.php src/bp-core/deprecated/2.2.php
index e69de29..e4d24f7 100644
--- src/bp-core/deprecated/2.2.php
+++ src/bp-core/deprecated/2.2.php
@@ -0,0 +1,493 @@
+<?php
+/**
+ * Deprecated functions
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @deprecated 2.2.0
+ */
+
+// Exit if accessed directly
+defined( 'ABSPATH' ) or exit;
+
+/** Core Avatars **************************************************************/
+
+/**
+ * Fetch 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 BuddyPress (1.8.0)
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @uses wp_upload_dir()
+ *
+ * @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' ) {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_get_upload_dir()' );
+		return bp_attachments_get_upload_dir( $type );
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_get_upload_dir() has been replaced by bp_attachments_get_upload_dir() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+
+}
+
+/**
+ * Get the absolute upload path for the WP installation.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @uses wp_upload_dir To get upload directory info
+ *
+ * @return string Absolute path to WP upload directory.
+ */
+function bp_core_avatar_upload_path() {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_get_upload_dir()' );
+		return bp_attachments_get_upload_dir();
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_avatar_upload_path() has been replaced by bp_attachments_get_upload_dir() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Get the raw base URL for root site upload location.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @uses wp_upload_dir To get upload directory info.
+ *
+ * @return string Full URL to current upload location.
+ */
+function bp_core_avatar_url() {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', "bp_attachments_get_upload_dir( 'url' )" );
+		return bp_attachments_get_upload_dir( 'url' );
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( "bp_core_avatar_url() has been replaced by bp_attachments_get_upload_dir( 'url' ) which requires the Attachments component to be active.", 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Delete an existing avatar.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @param array $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 = '' ) {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_delete_existing_avatar()' );
+		return bp_attachments_delete_existing_avatar( $args );
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_delete_existing_avatar() has been replaced by bp_attachments_delete_existing_avatar() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * 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.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @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 ) {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_avatar_handle_upload()' );
+		return bp_attachments_avatar_handle_upload( $file, $upload_dir_filter );
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_avatar_handle_upload() has been replaced by bp_attachments_avatar_handle_upload() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * 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
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @param array $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 papth 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 = '' ) {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_avatar_handle_crop()' );
+		return bp_attachments_avatar_handle_crop( $args );
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_avatar_handle_crop() has been replaced by bp_attachments_avatar_handle_crop() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Is the current avatar upload error-free?
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @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 ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_check_avatar_upload()' );
+		return bp_attachments_check_avatar_upload( $file );
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_check_avatar_upload() has been replaced by bp_attachments_check_avatar_upload() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Is the file size of the current avatar upload permitted?
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @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 ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_check_avatar_size()' );
+		return bp_attachments_check_avatar_size( $file );
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_check_avatar_size() has been replaced by bp_attachments_check_avatar_size() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Does the current avatar upload have an allowed file type?
+ *
+ * Permitted file types are JPG, GIF and PNG.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @param array $file The $_FILES array.
+ * @return bool True if the file extension is permitted, otherwise false.
+ */
+function bp_core_check_avatar_type( $file ) {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_check_avatar_type()' );
+		return bp_attachments_check_avatar_type( $file );
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_check_avatar_type() has been replaced by bp_attachments_check_avatar_type() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Get the max width for original avatar uploads.
+ *
+ * @since BuddyPress (1.5.0)
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @return int The max width for original avatar uploads.
+ */
+function bp_core_avatar_original_max_width() {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_avatar_original_max_width()' );
+		return bp_attachments_avatar_original_max_width();
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_avatar_original_max_width() has been replaced by bp_attachments_avatar_original_max_width() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Get the max filesize for original avatar uploads.
+ *
+ * @since BuddyPress (1.5.0)
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @return int The max filesize for original avatar uploads.
+ */
+function bp_core_avatar_original_max_filesize() {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_avatar_original_max_filesize()' );
+		return bp_attachments_avatar_original_max_filesize();
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_avatar_original_max_filesize() has been replaced by bp_attachments_avatar_original_max_filesize() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Enqueues jCrop library and hooks BP's custom cropper JS.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ */
+function bp_core_add_jquery_cropper() {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_add_jquery_cropper()' );
+		return bp_attachments_add_jquery_cropper();
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_add_jquery_cropper() has been replaced by bp_attachments_add_jquery_cropper() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Output the inline JS needed for the cropper to work on a per-page basis.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ */
+function bp_core_add_cropper_inline_js() {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_add_cropper_inline_js()' );
+		return bp_attachments_add_cropper_inline_js();
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_add_cropper_inline_js() has been replaced by bp_attachments_add_cropper_inline_js() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Output the inline CSS for the BP image cropper.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @package BuddyPress Core
+ */
+function bp_core_add_cropper_inline_css() {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_add_cropper_inline_css()' );
+		return bp_attachments_add_cropper_inline_css();
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_add_cropper_inline_css() has been replaced by bp_attachments_add_cropper_inline_css() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/** xProfile Avatars **********************************************************/
+
+/**
+ * Setup the avatar upload directory for a user.
+ *
+ * @since BuddyPress (1.0.0)
+ * @deprecated BuddyPress (2.2.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 ) {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_xprofile_avatar_upload_dir()' );
+		return bp_attachments_user_avatar_upload_dir( $directory, $user_id );
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'xprofile_avatar_upload_dir() has been replaced by bp_attachments_xprofile_avatar_upload_dir() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Handles the uploading and cropping of a user avatar. Displays the change avatar page.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @package BuddyPress XProfile
+ * @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() {
+	$screen_function = 'bp_xprofile_screen_change_gravatar';
+
+	if ( bp_is_active( 'attachments' ) ) {
+		$screen_function = 'bp_attachments_screen_change_avatar';
+	}
+	_deprecated_function( __FUNCTION__, '2.2.0', sprintf( '%s()', $screen_function ) );
+	return call_user_func( $screen_function );
+}
+
+/**
+ * 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.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @package BuddyPress Xprofile
+ * @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_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_action_xprofile_delete_avatar()' );
+		return bp_attachments_action_xprofile_delete_avatar();
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'xprofile_action_delete_avatar() has been replaced by bp_attachments_action_xprofile_delete_avatar() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/** Group Avatars *************************************************************/
+
+/**
+ * Handle the display of a group's Change Avatar page.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ */
+function groups_screen_group_admin_avatar() {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_screen_group_admin_avatar()' );
+		return bp_attachments_screen_group_admin_avatar();
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'groups_screen_group_admin_avatar() has been replaced by bp_attachments_screen_group_admin_avatar() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Generate the avatar upload directory path for a given group.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @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 ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_groups_avatar_upload_dir()' );
+		return bp_attachments_group_avatar_upload_dir( $group_id );
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'groups_avatar_upload_dir() has been replaced by bp_attachments_groups_avatar_upload_dir() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
+
+/**
+ * Get the avatar storage directory for use during registration.
+ *
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @return string|bool Directory path on success, false on failure.
+ */
+function bp_core_signup_avatar_upload_dir() {
+	if ( bp_is_active( 'attachments' ) ) {
+		_deprecated_function( __FUNCTION__, '2.2.0', 'bp_attachments_signup_avatar_upload_dir()' );
+		return bp_attachments_signup_avatar_upload_dir();
+	} else {
+		_doing_it_wrong(
+			__FUNCTION__,
+			__( 'bp_core_signup_avatar_upload_dir() has been replaced by bp_attachments_signup_avatar_upload_dir() which requires the Attachments component to be active.', 'buddypress' ),
+			'2.2.0'
+		);
+	}
+}
diff --git src/bp-groups/bp-groups-actions.php src/bp-groups/bp-groups-actions.php
index 163e407..07bfc3d 100644
--- src/bp-groups/bp-groups-actions.php
+++ src/bp-groups/bp-groups-actions.php
@@ -283,34 +283,12 @@ function groups_action_create_group() {
 		bp_core_redirect( bp_get_root_domain() . '/' . bp_get_groups_root_slug() . '/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
+	/**
+	 * Group avatar is handled in Attachments component
+	 * Please do not use this action, for internal use only
+	 */
+	do_action( 'bp_groups_avatar_create_step' );
 
-			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' ) );
-		}
-	}
 
 	bp_core_load_template( apply_filters( 'groups_template_create_group', 'groups/create' ) );
 }
diff --git src/bp-groups/bp-groups-functions.php src/bp-groups/bp-groups-functions.php
index fb837df..b71482a 100644
--- src/bp-groups/bp-groups-functions.php
+++ src/bp-groups/bp-groups-functions.php
@@ -696,34 +696,6 @@ function groups_get_current_group() {
 	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 ) {
-	global $bp;
-
-	if ( !$group_id )
-		$group_id = $bp->groups->current_group->id;
-
-	$path    = bp_core_avatar_upload_path() . '/group-avatars/' . $group_id;
-	$newbdir = $path;
-
-	if ( !file_exists( $path ) )
-		@wp_mkdir_p( $path );
-
-	$newurl    = bp_core_avatar_url() . '/group-avatars/' . $group_id;
-	$newburl   = $newurl;
-	$newsubdir = '/group-avatars/' . $group_id;
-
-	return apply_filters( 'groups_avatar_upload_dir', array( 'path' => $path, 'url' => $newurl, 'subdir' => $newsubdir, 'basedir' => $newbdir, 'baseurl' => $newburl, 'error' => false ) );
-}
-
 /** Group Member Status Checks ************************************************/
 
 /**
diff --git src/bp-groups/bp-groups-loader.php src/bp-groups/bp-groups-loader.php
index d12712f..2314ee9 100644
--- src/bp-groups/bp-groups-loader.php
+++ src/bp-groups/bp-groups-loader.php
@@ -269,14 +269,6 @@ class BP_Groups_Component extends BP_Component {
 			)
 		) );
 
-		// If avatar uploads are not disabled, add avatar option
-		if ( ! (int) $bp->site_options['bp-disable-avatar-uploads'] && $bp->avatar->show_avatars ) {
-			$this->group_creation_steps['group-avatar'] = array(
-				'name'     => _x( 'Photo', 'Group screen nav', 'buddypress' ),
-				'position' => 20
-			);
-		}
-
 		// If friends component is active, add invitations
 		if ( bp_is_active( 'friends' ) ) {
 			$this->group_creation_steps['group-invites'] = array(
@@ -531,14 +523,6 @@ class BP_Groups_Component extends BP_Component {
 					'position'        => 10,
 				), $default_params );
 
-				if ( ! (int) bp_get_option( 'bp-disable-avatar-uploads' ) && buddypress()->avatar->show_avatars ) {
-					$sub_nav[] = array_merge( array(
-						'name'        => __( 'Photo', 'buddypress' ),
-						'slug'        => 'group-avatar',
-						'position'    => 20,
-					), $default_params );
-				}
-
 				$sub_nav[] = array_merge( array(
 					'name'            => __( 'Members', 'buddypress' ),
 					'slug'            => 'manage-members',
diff --git src/bp-groups/bp-groups-screens.php src/bp-groups/bp-groups-screens.php
index e89c72b..b81d667 100644
--- src/bp-groups/bp-groups-screens.php
+++ src/bp-groups/bp-groups-screens.php
@@ -660,84 +660,6 @@ function groups_screen_group_admin_settings() {
 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() || (int) bp_get_option( 'bp-disable-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' ) );
-		}
-	}
-
-	do_action( 'groups_screen_group_admin_avatar', $bp->groups->current_group->id );
-
-	bp_core_load_template( apply_filters( 'groups_template_group_admin_avatar', 'groups/single/home' ) );
-}
-add_action( 'bp_screens', 'groups_screen_group_admin_avatar' );
-
-/**
  * This function handles actions related to member management on the group admin.
  */
 function groups_screen_group_admin_manage_members() {
diff --git src/bp-loader.php src/bp-loader.php
index 41c13e0..822253d 100644
--- src/bp-loader.php
+++ src/bp-loader.php
@@ -301,7 +301,7 @@ class BuddyPress {
 		/** Versions **********************************************************/
 
 		$this->version    = '2.2-alpha';
-		$this->db_version = 8311;
+		$this->db_version = 9131;
 
 		/** Loading ***********************************************************/
 
@@ -453,6 +453,7 @@ class BuddyPress {
 			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' );
 		}
 	}
 
diff --git src/bp-members/bp-members-functions.php src/bp-members/bp-members-functions.php
index 2f9b1b3..506ac16 100644
--- src/bp-members/bp-members-functions.php
+++ src/bp-members/bp-members-functions.php
@@ -1168,7 +1168,11 @@ function bp_core_delete_account( $user_id = 0 ) {
  * @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(
+	if ( ! bp_is_active( 'attachments' ) ) {
+		return;
+	}
+
+	return bp_attachments_delete_existing_avatar( array(
 		'item_id' => $user_id,
 		'object'  => 'user',
 	) );
@@ -1903,39 +1907,6 @@ function bp_core_map_user_registration( $user_id ) {
 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;
-	}
-
-	$path  = bp_core_avatar_upload_path() . '/avatars/signups/' . $bp->signup->avatar_dir;
-	$newbdir = $path;
-
-	if ( ! file_exists( $path ) ) {
-		@wp_mkdir_p( $path );
-	}
-
-	$newurl = bp_core_avatar_url() . '/avatars/signups/' . $bp->signup->avatar_dir;
-	$newburl = $newurl;
-	$newsubdir = '/avatars/signups/' . $bp->signup->avatar_dir;
-
-	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.
diff --git src/bp-members/bp-members-screens.php src/bp-members/bp-members-screens.php
index 80f5565..aa5ead3 100644
--- src/bp-members/bp-members-screens.php
+++ src/bp-members/bp-members-screens.php
@@ -235,7 +235,7 @@ function bp_core_screen_activation() {
 
 	// 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();
@@ -245,7 +245,7 @@ function bp_core_screen_activation() {
 	if ( empty( $key ) ) {
 		return;
 	}
-  
+
 	// Activate the signup
 	$user = apply_filters( 'bp_core_activate_account', bp_core_activate_signup( $key ) );
 
@@ -257,11 +257,10 @@ function bp_core_screen_activation() {
 
 	$hashed_key = wp_hash( $key );
 
-	// Check if the avatar folder exists. If it does, move rename it, move
-	// it and delete the signup avatar dir
-	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 );
-	}
+	/**
+	 * Please don't use this action, for internal use only
+	 */
+	do_action( 'bp_core_activate_account_avatar', $hashed_key, $user );
 
 	bp_core_add_message( __( 'Your account is now active!', 'buddypress' ) );
 	$bp->activation_complete = true;
diff --git src/bp-xprofile/bp-xprofile-actions.php src/bp-xprofile/bp-xprofile-actions.php
index d45211e..362fcea 100644
--- src/bp-xprofile/bp-xprofile-actions.php
+++ src/bp-xprofile/bp-xprofile-actions.php
@@ -15,36 +15,6 @@
 if ( !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.
- *
- * @package BuddyPress Xprofile
- * @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 BuddyPress (1.9)
diff --git src/bp-xprofile/bp-xprofile-admin.php src/bp-xprofile/bp-xprofile-admin.php
index 8c907a4..2eae34f 100644
--- src/bp-xprofile/bp-xprofile-admin.php
+++ src/bp-xprofile/bp-xprofile-admin.php
@@ -585,15 +585,17 @@ class BP_XProfile_User_Admin {
 			);
 		}
 
-		// 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'
-		);
+		if ( buddypress()->avatar->show_avatars && ! bp_is_active( 'attachments' ) ) {
+			// 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'
+			);
+		}
 	}
 
 	/**
@@ -606,24 +608,8 @@ class BP_XProfile_User_Admin {
 	 * @since BuddyPress (2.0.0)
 	 */
 	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'] ) ) {
+		if ( isset( $_POST['field_ids'] ) ) {
 
 			// Check the nonce
 			check_admin_referer( 'edit-bp-profile_' . $user_id );
@@ -834,24 +820,6 @@ class BP_XProfile_User_Admin {
 				'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></li>
-
-			<?php endif; ?>
-
 		</div>
 		<?php
 	}
diff --git src/bp-xprofile/bp-xprofile-functions.php src/bp-xprofile/bp-xprofile-functions.php
index f6f2dee..6bede39 100644
--- src/bp-xprofile/bp-xprofile-functions.php
+++ src/bp-xprofile/bp-xprofile-functions.php
@@ -624,51 +624,6 @@ function xprofile_override_user_fullnames() {
 add_action( 'bp_setup_globals', 'xprofile_override_user_fullnames', 100 );
 
 /**
- * Setup the avatar upload directory for a user.
- *
- * @since BuddyPress (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;
-
-	if ( ! file_exists( $path ) ) {
-		@wp_mkdir_p( $path );
-	}
-
-	$newurl    = bp_core_avatar_url() . '/' . $directory. '/' . $user_id;
-	$newburl   = $newurl;
-	$newsubdir = '/' . $directory. '/' . $user_id;
-
-	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 BuddyPress (2.0.0)
diff --git src/bp-xprofile/bp-xprofile-loader.php src/bp-xprofile/bp-xprofile-loader.php
index 4e9fd67..7a3f596 100644
--- src/bp-xprofile/bp-xprofile-loader.php
+++ src/bp-xprofile/bp-xprofile-loader.php
@@ -206,13 +206,13 @@ class BP_XProfile_Component extends BP_Component {
 		);
 
 		// Change Avatar
-		if ( buddypress()->avatar->show_avatars ) {
+		if ( buddypress()->avatar->show_avatars && ! bp_is_active( 'attachments' ) ) {
 			$sub_nav[] = array(
 				'name'            => _x( 'Change Profile Photo', 'Profile header sub menu', 'buddypress' ),
 				'slug'            => 'change-avatar',
 				'parent_url'      => $profile_link,
 				'parent_slug'     => $this->slug,
-				'screen_function' => 'xprofile_screen_change_avatar',
+				'screen_function' => 'bp_xprofile_screen_change_gravatar',
 				'position'        => 30,
 				'user_has_access' => bp_core_can_edit_settings()
 			);
@@ -301,7 +301,7 @@ class BP_XProfile_Component extends BP_Component {
 			);
 
 			// Edit Avatar
-			if ( buddypress()->avatar->show_avatars ) {
+			if ( buddypress()->avatar->show_avatars && ! bp_is_active( 'attachments' ) ) {
 				$wp_admin_nav[] = array(
 					'parent' => 'my-account-' . $this->id,
 					'id'     => 'my-account-' . $this->id . '-change-avatar',
@@ -309,7 +309,6 @@ class BP_XProfile_Component extends BP_Component {
 					'href'   => trailingslashit( $profile_link . 'change-avatar' )
 				);
 			}
-
 		}
 
 		parent::setup_admin_bar( $wp_admin_nav );
@@ -376,4 +375,4 @@ function bp_setup_xprofile() {
 	if ( !isset( $bp->profile->id ) )
 		$bp->profile = new BP_XProfile_Component();
 }
-add_action( 'bp_setup_components', 'bp_setup_xprofile', 2 );
\ No newline at end of file
+add_action( 'bp_setup_components', 'bp_setup_xprofile', 2 );
diff --git src/bp-xprofile/bp-xprofile-screens.php src/bp-xprofile/bp-xprofile-screens.php
index 2b9412e..d53e49a 100644
--- src/bp-xprofile/bp-xprofile-screens.php
+++ src/bp-xprofile/bp-xprofile-screens.php
@@ -154,87 +154,37 @@ function xprofile_screen_edit_profile() {
 }
 
 /**
- * Handles the uploading and cropping of a user avatar. Displays the change avatar page.
+ * Show the xprofile settings template
  *
- * @package BuddyPress XProfile
- * @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)
+ * @since BuddyPress (2.0.0)
  */
-function xprofile_screen_change_avatar() {
-
-	// Bail if not the correct screen
-	if ( !bp_is_my_profile() && !bp_current_user_can( 'bp_moderate' ) )
-		return false;
+function bp_xprofile_screen_settings() {
 
-	// Bail if there are action variables
-	if ( bp_action_variables() ) {
+	// Redirect if no privacy settings page is accessible
+	if ( bp_action_variables() || ! bp_is_active( 'xprofile' ) ) {
 		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 {
-			do_action( 'xprofile_avatar_uploaded' );
-			bp_core_add_message( __( 'Your new profile photo was uploaded successfully.', 'buddypress' ) );
-			bp_core_redirect( bp_displayed_user_domain() );
-		}
-	}
-
-	do_action( 'xprofile_screen_change_avatar' );
-
-	bp_core_load_template( apply_filters( 'xprofile_template_change_avatar', 'members/single/home' ) );
+	// Load the template
+	bp_core_load_template( apply_filters( 'bp_settings_screen_xprofile', '/members/single/settings/profile' ) );
 }
 
 /**
- * Show the xprofile settings template
+ * Show the xprofile change Gravatar template
  *
- * @since BuddyPress (2.0.0)
+ * @since BuddyPress (2.2.0)
  */
-function bp_xprofile_screen_settings() {
+function bp_xprofile_screen_change_gravatar() {
+	if ( ! bp_is_my_profile() && ! bp_current_user_can( 'bp_moderate' ) ) {
+		return false;
+	}
 
-	// Redirect if no privacy settings page is accessible
-	if ( bp_action_variables() || ! bp_is_active( 'xprofile' ) ) {
+	// Bail if there are action variables
+	if ( bp_action_variables() ) {
 		bp_do_404();
 		return;
 	}
 
-	// Load the template
-	bp_core_load_template( apply_filters( 'bp_settings_screen_xprofile', '/members/single/settings/profile' ) );
+	bp_core_load_template( apply_filters( 'bp_xprofile_screen_change_gravatar', 'members/single/home' ) );
 }
diff --git tests/phpunit/includes/install.php tests/phpunit/includes/install.php
index 2c297e3..d7a0398 100644
--- tests/phpunit/includes/install.php
+++ tests/phpunit/includes/install.php
@@ -26,7 +26,7 @@ define( 'BP_ROOT_BLOG', 1 );
 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, );
+	return array( 'activity' => 1, 'attachments' => 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' );
 
diff --git tests/phpunit/testcases/core/avatars.php tests/phpunit/testcases/core/avatars.php
index 0c5254a..d822c9f 100644
--- tests/phpunit/testcases/core/avatars.php
+++ tests/phpunit/testcases/core/avatars.php
@@ -28,7 +28,7 @@ class BP_Tests_Avatars extends BP_UnitTestCase {
 			$avatar_dir = 'group-avatars';
 		}
 
-		$this->rrmdir( bp_core_avatar_upload_path() . '/' . $avatar_dir );
+		$this->rrmdir( bp_attachments_get_upload_dir() . '/' . $avatar_dir );
 	}
 
 	private function rrmdir( $dir ) {
@@ -79,7 +79,7 @@ class BP_Tests_Avatars extends BP_UnitTestCase {
 		$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() );
+		$this->assertEquals( $upload_dir['baseurl'], bp_attachments_get_upload_dir( 'url' ) );
 
 		// reset globals
 		$this->go_to( '/' );
