PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.5.0
Forumax – AI Powered Advanced Community Forum Plugin v2.5.0
2.5.1 2.5.0 2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 trunk 1.0.8 1.1.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 All 31 releases
bbp-core / includes / features / frmx-profile-avatar.php

frmx-profile-avatar.php in Forumax – AI Powered Advanced Community Forum Plugin 2.5.0, at includes/features/frmx-profile-avatar.php

351 lines 11.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Custom Profile Avatar Upload
4 *
5 * Allows forum users to upload their own profile avatar
6 * from the bbPress user profile edit page.
7 *
8 * @package Forumax
9 * @since 2.2.0
10 */
11
12 defined( 'ABSPATH' ) || exit;
13
14 /**
15 * Class Frmx_Profile_Avatar
16 *
17 * Handles uploading, saving, deleting and displaying
18 * custom user avatars on the front-end profile edit page.
19 */
20 class Frmx_Profile_Avatar {
21
22 /**
23 * User-meta key used to store the custom avatar attachment ID.
24 *
25 * @var string
26 */
27 const META_KEY = 'frmx_profile_avatar';
28
29 /**
30 * Register all hooks.
31 */
32 public function __construct() {
33 // Render the avatar upload field before the Name fieldset.
34 add_action( 'bbp_user_edit_before', array( $this, 'render_avatar_upload_field' ) );
35
36 // Process the uploaded avatar when the profile is saved.
37 add_action( 'personal_options_update', array( $this, 'save_avatar' ) );
38 add_action( 'edit_user_profile_update', array( $this, 'save_avatar' ) );
39 add_action( 'bbp_profile_update', array( $this, 'save_avatar' ) );
40
41 // Override the default Gravatar with the custom avatar.
42 add_filter( 'pre_get_avatar_data', array( $this, 'filter_avatar_data' ), 10, 2 );
43
44 // Handle AJAX avatar upload.
45 add_action( 'wp_ajax_frmx_upload_avatar', array( $this, 'ajax_upload_avatar' ) );
46
47 // Handle AJAX avatar removal.
48 add_action( 'wp_ajax_frmx_remove_avatar', array( $this, 'ajax_remove_avatar' ) );
49
50 // Enqueue scripts on the user edit page.
51 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
52 }
53
54 /**
55 * Enqueue avatar upload scripts and styles on the front-end user edit page.
56 *
57 * @return void
58 */
59 public function enqueue_scripts() {
60 if ( ! bbp_is_single_user_edit() ) {
61 return;
62 }
63
64 wp_enqueue_script(
65 'frmx-avatar-upload',
66 FORUMAX_FRONT_ASS . 'js/avatar-upload.js',
67 array( 'jquery' ),
68 FORUMAX_VERSION,
69 true
70 );
71
72 wp_localize_script( 'frmx-avatar-upload', 'frmx_avatar', array(
73 'ajax_url' => admin_url( 'admin-ajax.php' ),
74 'nonce' => wp_create_nonce( 'frmx_avatar_nonce' ),
75 'max_size' => 2 * MB_IN_BYTES,
76 'max_size_text' => esc_html__( 'Maximum file size: 2 MB', 'forumax' ),
77 'uploading' => esc_html__( 'Uploading…', 'forumax' ),
78 'removing' => esc_html__( 'Removing…', 'forumax' ),
79 'error' => esc_html__( 'Something went wrong. Please try again.', 'forumax' ),
80 'confirm' => esc_html__( 'Are you sure you want to remove your profile photo?', 'forumax' ),
81 ) );
82 }
83
84 /**
85 * Render the avatar upload section on the user edit form.
86 *
87 * @return void
88 */
89 public function render_avatar_upload_field() {
90 $user_id = bbp_get_displayed_user_id();
91 $avatar_id = (int) get_user_meta( $user_id, self::META_KEY, true );
92 $avatar_url = $avatar_id ? wp_get_attachment_image_url( $avatar_id, 'thumbnail' ) : '';
93 $has_avatar = ! empty( $avatar_url );
94
95 // Fall-back to Gravatar when no custom avatar is set.
96 if ( ! $has_avatar ) {
97 $user = get_userdata( $user_id );
98 $avatar_url = get_avatar_url( $user->user_email, array( 'size' => 150 ) );
99 }
100 ?>
101 <fieldset class="bbp-form frmx-avatar-fieldset">
102 <legend><?php esc_html_e( 'Profile Photo', 'forumax' ); ?></legend>
103
104 <div class="frmx-avatar-upload-wrap">
105 <div class="frmx-avatar-preview" id="frmx-avatar-preview">
106 <img
107 src="<?php echo esc_url( $avatar_url ); ?>"
108 alt="<?php esc_attr_e( 'Profile Photo', 'forumax' ); ?>"
109 id="frmx-avatar-img"
110 />
111 <?php if ( $has_avatar ) : ?>
112 <button type="button" class="frmx-avatar-remove" id="frmx-avatar-remove" title="<?php esc_attr_e( 'Remove Photo', 'forumax' ); ?>">
113 <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>
114 </button>
115 <?php endif; ?>
116 </div>
117
118 <div class="frmx-avatar-actions">
119 <label for="frmx-avatar-input" class="frmx-avatar-upload-btn">
120 <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>
121 <?php esc_html_e( 'Upload Photo', 'forumax' ); ?>
122 </label>
123 <input
124 type="file"
125 id="frmx-avatar-input"
126 name="frmx_avatar_file"
127 accept="image/jpeg,image/png,image/gif,image/webp"
128 class="frmx-avatar-file-input"
129 />
130 <p class="frmx-avatar-hint">
131 <?php esc_html_e( 'JPG, PNG, GIF or WebP. Max 2 MB.', 'forumax' ); ?>
132 </p>
133 <div class="frmx-avatar-status" id="frmx-avatar-status"></div>
134 </div>
135 </div>
136
137 <input type="hidden" name="frmx_avatar_id" id="frmx-avatar-id" value="<?php echo esc_attr( $avatar_id ); ?>" />
138 <?php wp_nonce_field( 'frmx_avatar_nonce', 'frmx_avatar_nonce_field' ); ?>
139 </fieldset>
140 <?php
141 }
142
143 /**
144 * Handle avatar upload via AJAX.
145 *
146 * @return void
147 */
148 public function ajax_upload_avatar() {
149 check_ajax_referer( 'frmx_avatar_nonce', 'nonce' );
150
151 if ( ! is_user_logged_in() ) {
152 wp_send_json_error( array( 'message' => esc_html__( 'You must be logged in.', 'forumax' ) ) );
153 }
154
155 $user_id = isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : get_current_user_id();
156 if ( $user_id <= 0 ) {
157 $user_id = get_current_user_id();
158 }
159
160 // Security: only allow users to update their own avatar or admins to update any.
161 if ( $user_id !== get_current_user_id() && ! current_user_can( 'edit_users' ) ) {
162 wp_send_json_error( array( 'message' => esc_html__( 'Permission denied.', 'forumax' ) ) );
163 }
164
165 if ( empty( $_FILES['avatar_file'] ) ) {
166 wp_send_json_error( array( 'message' => esc_html__( 'No file was uploaded.', 'forumax' ) ) );
167 }
168
169 // Validate file type.
170 $allowed_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp' );
171 $file_type = isset( $_FILES['avatar_file']['type'] ) ? sanitize_mime_type( $_FILES['avatar_file']['type'] ) : '';
172
173 if ( ! in_array( $file_type, $allowed_types, true ) ) {
174 wp_send_json_error( array( 'message' => esc_html__( 'Invalid file type. Please upload JPG, PNG, GIF or WebP.', 'forumax' ) ) );
175 }
176
177 // Validate file size (2 MB).
178 $file_size = isset( $_FILES['avatar_file']['size'] ) ? absint( $_FILES['avatar_file']['size'] ) : 0;
179 if ( $file_size > 2 * MB_IN_BYTES ) {
180 wp_send_json_error( array( 'message' => esc_html__( 'File too large. Maximum size is 2 MB.', 'forumax' ) ) );
181 }
182
183 // Include WordPress upload handling.
184 if ( ! function_exists( 'wp_handle_upload' ) ) {
185 require_once ABSPATH . 'wp-admin/includes/file.php';
186 }
187 if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
188 require_once ABSPATH . 'wp-admin/includes/image.php';
189 }
190 if ( ! function_exists( 'media_handle_upload' ) ) {
191 require_once ABSPATH . 'wp-admin/includes/media.php';
192 }
193
194 $attachment_id = media_handle_upload( 'avatar_file', 0 );
195
196 if ( is_wp_error( $attachment_id ) ) {
197 wp_send_json_error( array( 'message' => $attachment_id->get_error_message() ) );
198 }
199
200 // Delete the old avatar attachment if exists.
201 $old_avatar_id = (int) get_user_meta( $user_id, self::META_KEY, true );
202 if ( $old_avatar_id ) {
203 wp_delete_attachment( $old_avatar_id, true );
204 }
205
206 // Save the new avatar.
207 update_user_meta( $user_id, self::META_KEY, $attachment_id );
208
209 $avatar_url = wp_get_attachment_image_url( $attachment_id, 'thumbnail' );
210
211 wp_send_json_success( array(
212 'avatar_url' => esc_url( $avatar_url ),
213 'attachment_id' => $attachment_id,
214 'message' => esc_html__( 'Profile photo updated successfully.', 'forumax' ),
215 ) );
216 }
217
218 /**
219 * Handle avatar removal via AJAX.
220 *
221 * @return void
222 */
223 public function ajax_remove_avatar() {
224 check_ajax_referer( 'frmx_avatar_nonce', 'nonce' );
225
226 if ( ! is_user_logged_in() ) {
227 wp_send_json_error( array( 'message' => esc_html__( 'You must be logged in.', 'forumax' ) ) );
228 }
229
230 $user_id = isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : get_current_user_id();
231 if ( $user_id <= 0 ) {
232 $user_id = get_current_user_id();
233 }
234
235 // Security: only allow users to remove their own avatar or admins to remove any.
236 if ( $user_id !== get_current_user_id() && ! current_user_can( 'edit_users' ) ) {
237 wp_send_json_error( array( 'message' => esc_html__( 'Permission denied.', 'forumax' ) ) );
238 }
239
240 $avatar_id = (int) get_user_meta( $user_id, self::META_KEY, true );
241
242 if ( $avatar_id ) {
243 wp_delete_attachment( $avatar_id, true );
244 delete_user_meta( $user_id, self::META_KEY );
245 }
246
247 // Return the default Gravatar URL.
248 $user = get_userdata( $user_id );
249 $default_gravatar = get_avatar_url( $user->user_email, array( 'size' => 150 ) );
250
251 wp_send_json_success( array(
252 'avatar_url' => esc_url( $default_gravatar ),
253 'message' => esc_html__( 'Profile photo removed.', 'forumax' ),
254 ) );
255 }
256
257 /**
258 * Save avatar on normal (non-AJAX) form submission.
259 *
260 * @param int $user_id The user ID being updated.
261 * @return void
262 */
263 public function save_avatar( $user_id ) {
264 // Verify nonce.
265 if ( ! isset( $_POST['frmx_avatar_nonce_field'] ) ||
266 ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['frmx_avatar_nonce_field'] ) ), 'frmx_avatar_nonce' )
267 ) {
268 return;
269 }
270
271 // Save the hidden input value (set via AJAX).
272 if ( isset( $_POST['frmx_avatar_id'] ) ) {
273 $avatar_id = absint( $_POST['frmx_avatar_id'] );
274
275 if ( $avatar_id ) {
276 update_user_meta( $user_id, self::META_KEY, $avatar_id );
277 } else {
278 delete_user_meta( $user_id, self::META_KEY );
279 }
280 }
281 }
282
283 /**
284 * Filter avatar data to use the custom uploaded avatar.
285 *
286 * @param array $args Avatar data arguments.
287 * @param mixed $id_or_email User identifier (ID, email, WP_User, WP_Comment).
288 * @return array Modified avatar data.
289 */
290 public function filter_avatar_data( $args, $id_or_email ) {
291 $user_id = $this->get_user_id( $id_or_email );
292
293 if ( ! $user_id ) {
294 return $args;
295 }
296
297 $avatar_id = (int) get_user_meta( $user_id, self::META_KEY, true );
298
299 if ( ! $avatar_id ) {
300 return $args;
301 }
302
303 $avatar_url = wp_get_attachment_image_url( $avatar_id, 'thumbnail' );
304
305 if ( $avatar_url ) {
306 $args['url'] = $avatar_url;
307 $args['found_avatar'] = true;
308 }
309
310 return $args;
311 }
312
313 /**
314 * Extract user ID from the various formats passed to get_avatar.
315 *
316 * @param mixed $id_or_email Accepts user ID, email, WP_User or WP_Comment.
317 * @return int|false User ID or false on failure.
318 */
319 private function get_user_id( $id_or_email ) {
320 if ( is_numeric( $id_or_email ) ) {
321 return absint( $id_or_email );
322 }
323
324 if ( is_string( $id_or_email ) ) {
325 $user = get_user_by( 'email', $id_or_email );
326 return $user ? $user->ID : false;
327 }
328
329 if ( $id_or_email instanceof \WP_User ) {
330 return $id_or_email->ID;
331 }
332
333 if ( $id_or_email instanceof \WP_Post ) {
334 return (int) $id_or_email->post_author;
335 }
336
337 if ( $id_or_email instanceof \WP_Comment ) {
338 if ( ! empty( $id_or_email->user_id ) ) {
339 return (int) $id_or_email->user_id;
340 }
341 $user = get_user_by( 'email', $id_or_email->comment_author_email );
342 return $user ? $user->ID : false;
343 }
344
345 return false;
346 }
347 }
348
349 // Initialize the profile avatar feature.
350 new Frmx_Profile_Avatar();
351