# bbp-core/2.5.1/includes/features/frmx-profile-avatar.php

Forumax – AI Powered Advanced Community Forum Plugin, version 2.5.1. 405 lines.

- Page: https://pluginprobe.com/plugins/bbp-core/2.5.1/code/includes/features/frmx-profile-avatar.php
- Raw: https://pluginprobe.com/plugins/bbp-core/2.5.1/raw/includes/features/frmx-profile-avatar.php
- Modified: 2026-10-12T12:43:18+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/bbp-core/2.5.1/code/includes/features/frmx-profile-avatar.php#L10-L20`.

```php
<?php
/**
 * Custom Profile Avatar Upload
 *
 * Allows forum users to upload their own profile avatar
 * from the bbPress user profile edit page.
 *
 * @package Forumax
 * @since   2.2.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class Frmx_Profile_Avatar
 *
 * Handles uploading, saving, deleting and displaying
 * custom user avatars on the front-end profile edit page.
 */
class Frmx_Profile_Avatar {

	/**
	 * User-meta key used to store the custom avatar attachment ID.
	 *
	 * @var string
	 */
	const META_KEY = 'frmx_profile_avatar';

	/**
	 * Attachment meta marking an image as a user's uploaded avatar (value: user ID).
	 *
	 * @var string
	 */
	const OWNER_META_KEY = '_frmx_avatar_owner';

	/**
	 * Register all hooks.
	 */
	public function __construct() {
		// Render the avatar upload field before the Name fieldset.
		add_action( 'bbp_user_edit_before', array( $this, 'render_avatar_upload_field' ) );

		// Process the uploaded avatar when the profile is saved.
		add_action( 'personal_options_update', array( $this, 'save_avatar' ) );
		add_action( 'edit_user_profile_update', array( $this, 'save_avatar' ) );
		add_action( 'bbp_profile_update', array( $this, 'save_avatar' ) );

		// Override the default Gravatar with the custom avatar.
		add_filter( 'pre_get_avatar_data', array( $this, 'filter_avatar_data' ), 10, 2 );

		// Handle AJAX avatar upload.
		add_action( 'wp_ajax_frmx_upload_avatar', array( $this, 'ajax_upload_avatar' ) );

		// Handle AJAX avatar removal.
		add_action( 'wp_ajax_frmx_remove_avatar', array( $this, 'ajax_remove_avatar' ) );

		// Enqueue scripts on the user edit page.
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
	}

	/**
	 * Enqueue avatar upload scripts and styles on the front-end user edit page.
	 *
	 * @return void
	 */
	public function enqueue_scripts() {
		if ( ! bbp_is_single_user_edit() ) {
			return;
		}

		wp_enqueue_script(
			'frmx-avatar-upload',
			FORUMAX_FRONT_ASS . 'js/avatar-upload.js',
			array( 'jquery' ),
			FORUMAX_VERSION,
			true
		);

		wp_localize_script( 'frmx-avatar-upload', 'frmx_avatar', array(
			'ajax_url'      => admin_url( 'admin-ajax.php' ),
			'nonce'         => wp_create_nonce( 'frmx_avatar_nonce' ),
			'max_size'      => 2 * MB_IN_BYTES,
			'max_size_text' => esc_html__( 'Maximum file size: 2 MB', 'forumax' ),
			'uploading'     => esc_html__( 'Uploading…', 'forumax' ),
			'removing'      => esc_html__( 'Removing…', 'forumax' ),
			'error'         => esc_html__( 'Something went wrong. Please try again.', 'forumax' ),
			'confirm'       => esc_html__( 'Are you sure you want to remove your profile photo?', 'forumax' ),
		) );
	}

	/**
	 * Render the avatar upload section on the user edit form.
	 *
	 * @return void
	 */
	public function render_avatar_upload_field() {
		$user_id    = bbp_get_displayed_user_id();
		$avatar_id  = (int) get_user_meta( $user_id, self::META_KEY, true );
		$avatar_url = $avatar_id ? wp_get_attachment_image_url( $avatar_id, 'thumbnail' ) : '';
		$has_avatar = ! empty( $avatar_url );

		// Fall-back to Gravatar when no custom avatar is set.
		if ( ! $has_avatar ) {
			$avatar_url = get_avatar_url( $user_id, array( 'size' => 150 ) );
		}
		?>
		<fieldset class="bbp-form frmx-avatar-fieldset">
			<legend><?php esc_html_e( 'Profile Photo', 'forumax' ); ?></legend>

			<div class="frmx-avatar-upload-wrap">
				<div class="frmx-avatar-preview" id="frmx-avatar-preview">
					<img
						src="<?php echo esc_url( $avatar_url ); ?>"
						alt="<?php esc_attr_e( 'Profile Photo', 'forumax' ); ?>"
						id="frmx-avatar-img"
					/>
					<?php if ( $has_avatar ) : ?>
						<button type="button" class="frmx-avatar-remove" id="frmx-avatar-remove" title="<?php esc_attr_e( 'Remove Photo', 'forumax' ); ?>">
							<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
						</button>
					<?php endif; ?>
				</div>

				<div class="frmx-avatar-actions">
					<label for="frmx-avatar-input" class="frmx-avatar-upload-btn">
						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
						<?php esc_html_e( 'Upload Photo', 'forumax' ); ?>
					</label>
					<input
						type="file"
						id="frmx-avatar-input"
						name="frmx_avatar_file"
						accept="image/jpeg,image/png,image/gif,image/webp"
						class="frmx-avatar-file-input"
					/>
					<p class="frmx-avatar-hint">
						<?php esc_html_e( 'JPG, PNG, GIF or WebP. Max 2 MB.', 'forumax' ); ?>
					</p>
					<div class="frmx-avatar-status" id="frmx-avatar-status"></div>
				</div>
			</div>

			<input type="hidden" name="frmx_avatar_id" id="frmx-avatar-id" value="<?php echo esc_attr( $avatar_id ); ?>" />
			<?php wp_nonce_field( 'frmx_avatar_nonce', 'frmx_avatar_nonce_field' ); ?>
		</fieldset>
		<?php
	}

	/**
	 * Handle avatar upload via AJAX.
	 *
	 * @return void
	 */
	public function ajax_upload_avatar() {
		check_ajax_referer( 'frmx_avatar_nonce', 'nonce' );

		if ( ! is_user_logged_in() ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You must be logged in.', 'forumax' ) ) );
		}

		$user_id = isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : get_current_user_id();
		if ( $user_id <= 0 ) {
			$user_id = get_current_user_id();
		}

		// Security: only allow users to update their own avatar or admins to update any.
		if ( $user_id !== get_current_user_id() && ! current_user_can( 'edit_users' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Permission denied.', 'forumax' ) ) );
		}

		if ( empty( $_FILES['avatar_file'] ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'No file was uploaded.', 'forumax' ) ) );
		}

		// Validate file type.
		$allowed_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp' );
		$file_type     = isset( $_FILES['avatar_file']['type'] ) ? sanitize_mime_type( $_FILES['avatar_file']['type'] ) : '';

		if ( ! in_array( $file_type, $allowed_types, true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Invalid file type. Please upload JPG, PNG, GIF or WebP.', 'forumax' ) ) );
		}

		// Validate file size (2 MB).
		$file_size = isset( $_FILES['avatar_file']['size'] ) ? absint( $_FILES['avatar_file']['size'] ) : 0;
		if ( $file_size > 2 * MB_IN_BYTES ) {
			wp_send_json_error( array( 'message' => esc_html__( 'File too large. Maximum size is 2 MB.', 'forumax' ) ) );
		}

		// Include WordPress upload handling.
		if ( ! function_exists( 'wp_handle_upload' ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
		}
		if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
			require_once ABSPATH . 'wp-admin/includes/image.php';
		}
		if ( ! function_exists( 'media_handle_upload' ) ) {
			require_once ABSPATH . 'wp-admin/includes/media.php';
		}

		$attachment_id = media_handle_upload( 'avatar_file', 0 );

		if ( is_wp_error( $attachment_id ) ) {
			wp_send_json_error( array( 'message' => $attachment_id->get_error_message() ) );
		}

		update_post_meta( $attachment_id, self::OWNER_META_KEY, $user_id );

		// Delete the old avatar attachment if exists.
		$this->delete_avatar_file( $user_id, (int) get_user_meta( $user_id, self::META_KEY, true ) );

		// Save the new avatar.
		update_user_meta( $user_id, self::META_KEY, $attachment_id );

		$avatar_url = wp_get_attachment_image_url( $attachment_id, 'thumbnail' );

		wp_send_json_success( array(
			'avatar_url'    => esc_url( $avatar_url ),
			'attachment_id' => $attachment_id,
			'message'       => esc_html__( 'Profile photo updated successfully.', 'forumax' ),
		) );
	}

	/**
	 * Handle avatar removal via AJAX.
	 *
	 * @return void
	 */
	public function ajax_remove_avatar() {
		check_ajax_referer( 'frmx_avatar_nonce', 'nonce' );

		if ( ! is_user_logged_in() ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You must be logged in.', 'forumax' ) ) );
		}

		$user_id = isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : get_current_user_id();
		if ( $user_id <= 0 ) {
			$user_id = get_current_user_id();
		}

		// Security: only allow users to remove their own avatar or admins to remove any.
		if ( $user_id !== get_current_user_id() && ! current_user_can( 'edit_users' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Permission denied.', 'forumax' ) ) );
		}

		$avatar_id = (int) get_user_meta( $user_id, self::META_KEY, true );

		if ( $avatar_id ) {
			$this->delete_avatar_file( $user_id, $avatar_id );
			delete_user_meta( $user_id, self::META_KEY );
		}

		// Return the default Gravatar URL.
		$default_gravatar = get_avatar_url( $user_id, array( 'size' => 150 ) );

		wp_send_json_success( array(
			'avatar_url' => esc_url( $default_gravatar ),
			'message'    => esc_html__( 'Profile photo removed.', 'forumax' ),
		) );
	}

	/**
	 * Save avatar on normal (non-AJAX) form submission.
	 *
	 * @param int $user_id The user ID being updated.
	 * @return void
	 */
	public function save_avatar( $user_id ) {
		// Verify nonce.
		if ( ! isset( $_POST['frmx_avatar_nonce_field'] ) ||
			! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['frmx_avatar_nonce_field'] ) ), 'frmx_avatar_nonce' )
		) {
			return;
		}

		// Save the hidden input value (set via AJAX).
		if ( isset( $_POST['frmx_avatar_id'] ) ) {
			$avatar_id = absint( $_POST['frmx_avatar_id'] );

			if ( $avatar_id ) {
				// Only accept an avatar uploaded for this user, not any media ID.
				if ( $this->is_user_avatar( $user_id, $avatar_id ) ) {
					update_user_meta( $user_id, self::META_KEY, $avatar_id );
				}
			} else {
				delete_user_meta( $user_id, self::META_KEY );
			}
		}
	}

	/**
	 * Whether an attachment was uploaded as this user's avatar.
	 *
	 * Older avatars (before the owner meta existed) are accepted when they
	 * are the user's current avatar.
	 *
	 * @param int $user_id       User ID.
	 * @param int $attachment_id Attachment ID.
	 * @return bool
	 */
	private function is_user_avatar( $user_id, $attachment_id ) {
		if ( ! $attachment_id || ! wp_attachment_is_image( $attachment_id ) ) {
			return false;
		}

		$owner = get_post_meta( $attachment_id, self::OWNER_META_KEY, true );
		if ( '' !== $owner ) {
			return (int) $owner === (int) $user_id;
		}

		return (int) get_user_meta( $user_id, self::META_KEY, true ) === (int) $attachment_id;
	}

	/**
	 * Delete a user's previous avatar file, never other media.
	 *
	 * @param int $user_id       User ID.
	 * @param int $attachment_id Attachment ID.
	 * @return void
	 */
	private function delete_avatar_file( $user_id, $attachment_id ) {
		if ( ! $attachment_id ) {
			return;
		}

		$owner = get_post_meta( $attachment_id, self::OWNER_META_KEY, true );

		// Avatars saved before the owner meta existed: the user uploaded them themselves.
		if ( '' === $owner ) {
			$owner = (int) get_post_field( 'post_author', $attachment_id );
		}

		if ( (int) $owner === (int) $user_id ) {
			wp_delete_attachment( $attachment_id, true );
		}
	}

	/**
	 * Filter avatar data to use the custom uploaded avatar.
	 *
	 * @param array $args        Avatar data arguments.
	 * @param mixed $id_or_email User identifier (ID, email, WP_User, WP_Comment).
	 * @return array Modified avatar data.
	 */
	public function filter_avatar_data( $args, $id_or_email ) {
		$user_id = $this->get_user_id( $id_or_email );

		if ( ! $user_id ) {
			return $args;
		}

		$avatar_id = (int) get_user_meta( $user_id, self::META_KEY, true );

		if ( ! $avatar_id ) {
			return $args;
		}

		$avatar_url = wp_get_attachment_image_url( $avatar_id, 'thumbnail' );

		if ( $avatar_url ) {
			$args['url']          = $avatar_url;
			$args['found_avatar'] = true;
		}

		return $args;
	}

	/**
	 * Extract user ID from the various formats passed to get_avatar.
	 *
	 * @param mixed $id_or_email Accepts user ID, email, WP_User or WP_Comment.
	 * @return int|false User ID or false on failure.
	 */
	private function get_user_id( $id_or_email ) {
		if ( is_numeric( $id_or_email ) ) {
			return absint( $id_or_email );
		}

		if ( is_string( $id_or_email ) ) {
			$user = get_user_by( 'email', $id_or_email );
			return $user ? $user->ID : false;
		}

		if ( $id_or_email instanceof \WP_User ) {
			return $id_or_email->ID;
		}

		if ( $id_or_email instanceof \WP_Post ) {
			return (int) $id_or_email->post_author;
		}

		if ( $id_or_email instanceof \WP_Comment ) {
			if ( ! empty( $id_or_email->user_id ) ) {
				return (int) $id_or_email->user_id;
			}
			$user = get_user_by( 'email', $id_or_email->comment_author_email );
			return $user ? $user->ID : false;
		}

		return false;
	}
}

// Initialize the profile avatar feature.
new Frmx_Profile_Avatar();

```
