PluginProbe
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor / 4.0.3
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor v4.0.3
4.0.3 4.0.2 4.0.1 4.0.0 3.16.6 3.16.5 3.16.4 3.16.3 3.16.2 3.16.1 3.16.0 3.15.9 3.9.9 3.9.5 3.9.6 3.9.7 3.9.8 1.1.7 1.1.8 1.1.9 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 341 releases
← All changes | front-end/default-fields/upload/upload_helper_functions.php +196 -62 3.16.1 → 4.0.3 View file →
@@ -1,51 +1,34 @@
1 1 <?php
2 2 // Exit if accessed directly
3 3 if ( ! defined( 'ABSPATH' ) ) exit;
4 4
5 -/* Set up upload field for frontend */
6 -/* overwrite the two functions for when an upload is made from the frontend so they don't check for a logged in user */
7 -if( strpos( wp_get_referer(), 'wp-admin' ) === false && isset( $_REQUEST['action'] ) && 'upload-attachment' == $_REQUEST['action'] ){
8 -
9 - if( isset( $_REQUEST['wppb_upload'] ) && 'true' == $_REQUEST['wppb_upload'] &&
10 - isset( $_REQUEST['meta_name'] ) && wppb_check_that_field_is_defined( sanitize_text_field( $_REQUEST['meta_name'] ), array( 'Avatar', 'Upload' ) ) ){
11 -
12 - if( !function_exists( 'check_ajax_referer' ) ){
13 - function check_ajax_referer( ) {
14 - return true;
15 - }
16 - }
17 -
18 - if( !function_exists( 'auth_redirect' ) ){
19 - function auth_redirect() {
20 - return true;
21 - }
22 - }
23 -
5 +/** Simple file input when the field is set to it, or the user cannot upload_files. */
6 +function wppb_use_simple_upload_field( $field ) {
7 + if ( ! empty( $field['simple-upload'] ) && $field['simple-upload'] === 'yes' ) {
8 + return true;
24 9 }
25 10
11 + return ! current_user_can( 'upload_files' );
26 12 }
27 13
28 -/* create a fake user with the "upload_posts" capability and assign him to the global $current_user. this is used to bypass the checks for current_user_can('upload_files') in async-upload.php */
29 -add_action( 'current_screen', 'wppb_create_fake_user_when_uploading_and_not_logged_in' );
30 -if( !function_exists( 'wppb_create_fake_user_when_uploading_and_not_logged_in' ) ) {
31 - function wppb_create_fake_user_when_uploading_and_not_logged_in() {
32 - // don't do anything if this request is coming from the back-end
33 - if( !( strpos( wp_get_referer(), 'wp-admin' ) === false ) )
34 - return;
14 +/**
15 + * Whether this request includes a simple-upload for the field.
16 + * Checkout and some payment forms post the hidden attachment ID without $_FILES.
17 + */
18 +function wppb_simple_upload_was_submitted( $field, $request_data ) {
19 + $meta = wppb_handle_meta_name( $field['meta-name'] );
20 + $file_key = 'simple_upload_' . $meta;
35 21
36 - if ( isset($_REQUEST['action']) && 'upload-attachment' == $_REQUEST['action'] &&
37 - isset($_REQUEST['wppb_upload']) && 'true' == $_REQUEST['wppb_upload'] &&
38 - isset( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( $_REQUEST['_wpnonce'] ), 'media-form' ) &&
39 - isset( $_REQUEST['meta_name'] ) && wppb_check_that_field_is_defined( sanitize_text_field( $_REQUEST['meta_name'] ), array( 'Avatar', 'Upload' ) ) ) {
22 + if ( isset( $_FILES[ $file_key ] ) ) {
23 + return true;
24 + }
40 25
41 - if ( !is_user_logged_in() || !current_user_can( 'upload_files' ) || !current_user_can( 'edit_posts' ) ) {
42 - global $current_user;
43 - $current_user = new WP_User( 0, 'frontend_uploader' );
44 - $current_user->allcaps = array( "upload_files" => true, "edit_posts" => true, "edit_others_posts" => true, "edit_pages" => true, "edit_others_pages" => true );
45 - }
46 - }
26 + if ( isset( $request_data['pay_gate'] ) && in_array( $request_data['pay_gate'], array( 'stripe_connect', 'paypal_connect' ), true ) ) {
27 + return true;
47 28 }
29 +
30 + return array_key_exists( $meta, $request_data );
48 31 }
49 32
50 33 /* for a request of a upload from the frontend and no user is logged in don't query for attachments */
51 34 add_action( 'after_setup_theme', 'wppb_modify_query_attachements_when_not_logged_in' );
@@ -176,23 +159,20 @@
176 159 $allowed_upload_extensions = str_replace( '.', '', array_map( 'trim', explode( ",", strtolower( $allowed_upload_extensions ) ) ) );
177 160 } else {
178 161 $allowed = true;
179 162 }
180 - $allowed_by_wordpress = false;
181 - foreach ( $allowed_mime_types as $key => $val ){
182 - if ( $val == $upload[ 'type' ] ){
183 - $possible_extensions = explode( '|', $key );
184 - $allowed_by_wordpress = true;
185 - }
163 + if ( empty( $upload['tmp_name'] ) || empty( $upload['name'] ) ) {
164 + return false;
186 165 }
187 - if ( isset( $possible_extensions ) && $allowed_by_wordpress == true ){
166 +
167 + $checked = wp_check_filetype_and_ext( $upload['tmp_name'], $upload['name'] );
168 + $detected_type = ! empty( $checked['type'] ) ? $checked['type'] : '';
169 + $detected_ext = ! empty( $checked['ext'] ) ? strtolower( $checked['ext'] ) : '';
170 + $allowed_by_wordpress = ( $detected_type !== '' && in_array( $detected_type, $allowed_mime_types, true ) );
171 +
172 + if ( $allowed_by_wordpress && $detected_ext !== '' ) {
188 173 if ( !isset( $allowed ) ){
189 - $allowed = false;
190 - foreach ( $allowed_upload_extensions as $extension ){
191 - if ( in_array( $extension, $possible_extensions ) ){
192 - $allowed = true;
193 - }
194 - }
174 + $allowed = in_array( $detected_ext, $allowed_upload_extensions, true );
195 175 }
196 176 if ( $upload[ 'size' ] > $limit ){
197 177 $allowed = false;
198 178 }
@@ -255,9 +235,9 @@
255 235 }
256 236 else{
257 237 $repeater_count = count( $repeater_group );
258 238 for ( $i = 0; $i < $repeater_count; $i++ ){
259 - if ( $repeater_group[ $i ][ 'field' ] == 'Upload' && isset( $repeater_group[ $i ][ 'simple-upload' ] ) && $repeater_group[ $i ][ 'simple-upload' ] == 'yes' && isset( $_REQUEST[ $form_field[ 'meta-name' ] . '_extra_groups_count' ] ) ){
239 + if ( $repeater_group[ $i ][ 'field' ] == 'Upload' && wppb_use_simple_upload_field( $repeater_group[ $i ] ) && isset( $_REQUEST[ $form_field[ 'meta-name' ] . '_extra_groups_count' ] ) ){
260 240 $groups = absint( $_REQUEST[ $form_field[ 'meta-name' ] . '_extra_groups_count' ] );
261 241 for ( $j = 0; $j <= $groups; $j++ ){
262 242 $name = $repeater_group[ $i ][ 'meta-name' ];
263 243 if ( $j != 0 ){
@@ -300,16 +280,16 @@
300 280 $thumbnail = wp_get_attachment_image($value, array(80, 80), true);
301 281 $file_name = get_the_title($value);
302 282 $file_type = get_post_mime_type($value);
303 283 $attachment_url = wp_get_attachment_url($value);
304 - $upload_button .= '<div id="' . esc_attr($upload_input_id) . '_info_container" class="upload-field-details" data-attachment_id="' . $value . '">';
284 + $upload_button .= '<div id="' . esc_attr($upload_input_id) . '_info_container" class="upload-field-details" data-attachment_id="' . esc_attr( $value ) . '">';
305 285 $upload_button .= '<div class="file-thumb">';
306 - $upload_button .= "<a href='{$attachment_url}' target='_blank' class='wppb-attachment-link'>" . $thumbnail . "</a>";
286 + $upload_button .= "<a href='" . esc_url( $attachment_url ) . "' target='_blank' class='wppb-attachment-link'>" . $thumbnail . "</a>";
307 287 $upload_button .= '</div>';
308 288 $upload_button .= '<p><span class="file-name">';
309 - $upload_button .= $file_name;
289 + $upload_button .= esc_html( $file_name );
310 290 $upload_button .= '</span><span class="file-type">';
311 - $upload_button .= $file_type;
291 + $upload_button .= esc_html( $file_type );
312 292 $upload_button .= '</span>';
313 293 $upload_button .= '<span class="wppb-remove-upload" tabindex="0">' . apply_filters( 'wppb_upload_button_remove_label', __( 'Remove', 'profile-builder' ) ) . '</span>';
314 294 $upload_button .= '</p></div>';
315 295 }
@@ -319,11 +299,11 @@
319 299 else{
320 300 $hide_upload_button = '';
321 301 }
322 302
323 - if ( isset( $field[ 'simple-upload' ] ) && $field[ 'simple-upload' ] == 'yes' ){
303 + if ( wppb_use_simple_upload_field( $field ) ){
324 304 //If selected accordingly in form fields, generate a simple upload button
325 - $upload_button .= '<input type="file" id="upload_' . esc_attr(Wordpress_Creation_Kit_PB::wck_generate_slug($field['meta-name'], $field)) . '_button" class="wppb_simple_upload" name="simple_upload_'. esc_attr( Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) ) .'"';
305 + $upload_button .= '<input type="file" id="upload_' . esc_attr(Wordpress_Creation_Kit_PB::wck_generate_slug($field['meta-name'], $field)) . '_button" class="wppb_simple_upload" data-field_type="'. esc_attr( $field['field'] ) .'" name="simple_upload_'. esc_attr( Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) ) .'"';
326 306 $upload_button .= $hide_upload_button . '>';
327 307 $upload_button .= '<p id="p_simple_upload_'. esc_attr(Wordpress_Creation_Kit_PB::wck_generate_slug($field['meta-name'], $field)) .'"></p>';
328 308 $limit = apply_filters( 'wppb_server_max_upload_size_byte_constant', wppb_return_bytes( ini_get( 'upload_max_filesize' ) ) );
329 309 $all_fields = apply_filters( 'wppb_form_fields', get_option( 'wppb_manage_fields' ), array( 'context' => 'upload_helper', 'upload_meta_name' => $field[ 'meta-name' ] ) );
@@ -355,9 +335,9 @@
355 335 $allowed_extensions = '';
356 336 }
357 337 }
358 338 }
359 - $upload_button .= '<input id="allowed_extensions_simple_upload_'. esc_attr( $upload_input_id ) .'" type="hidden" size="36" name="allowed_extensions_simple_upload_'. esc_attr( Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) ) .'" value="'. $allowed_extensions .'"/>';
339 + $upload_button .= '<input id="allowed_extensions_simple_upload_'. esc_attr( $upload_input_id ) .'" type="hidden" size="36" name="allowed_extensions_simple_upload_'. esc_attr( Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) ) .'" value="'. esc_attr( $allowed_extensions ) .'"/>';
360 340 $upload_button .= '<input id="size_limit_simple_upload_'. esc_attr( $upload_input_id ) .'" type="hidden" name="size_limit_simple_upload_'. esc_attr( Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) ) .'" value="'. esc_attr( $limit ) .'"/>';
361 341 $allowed_mime_types = get_allowed_mime_types();
362 342 $allowed_types = '';
363 343 if ( !empty( $allowed_mime_types ) ) {
@@ -389,9 +369,9 @@
389 369
390 370 $upload_button .= '>' . apply_filters( 'wppb_upload_button_select_label', __( 'Upload ', 'profile-builder' ) ) . '</a>';
391 371 }
392 372
393 - $upload_button .= '<input id="'. esc_attr( $upload_input_id ) .'" type="hidden" size="36" name="'. esc_attr( Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) ) .'" value="'. $input_value .'"/>';
373 + $upload_button .= '<input id="'. esc_attr( $upload_input_id ) .'" type="hidden" size="36" name="'. esc_attr( Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) ) .'" value="'. esc_attr( wp_unslash( $input_value ) ) .'"/>';
394 374 return $upload_button;
395 375 }
396 376
397 377 /**
@@ -427,8 +407,65 @@
427 407 return '';
428 408 }
429 409 }
430 410
411 +/**
412 + * Converts a legacy file URL stored in user meta (versions that predate attachment IDs)
413 + * into an attachment owned by the user and stores the new ID in its place.
414 + *
415 + * The URL must resolve to an existing file inside the uploads directory with an allowed
416 + * mime type; anything else is discarded. Only call this with a value read from user meta,
417 + * never with request data, so that rendering a field cannot persist attacker-controlled input.
418 + *
419 + * @param string $file_url Legacy file URL read from user meta.
420 + * @param array $field Field definition array (must contain 'meta-name').
421 + * @param int $user_id User the attachment and meta belong to.
422 + *
423 + * @return int|string Attachment ID, or '' when the URL could not be converted.
424 + */
425 +function wppb_legacy_file_url_to_attachment( $file_url, $field, $user_id ) {
426 + $wp_upload_dir = wp_upload_dir();
427 + $base_dir = realpath( $wp_upload_dir['basedir'] );
428 + $file_path = str_replace( $wp_upload_dir['baseurl'], $wp_upload_dir['basedir'], $file_url );
429 + $file_path = is_file( $file_path ) ? realpath( $file_path ) : false;
430 +
431 + if ( ! $base_dir || ! $file_path ) {
432 + return '';
433 + }
434 +
435 + $base_dir = trailingslashit( wp_normalize_path( $base_dir ) );
436 + $file_path = wp_normalize_path( $file_path );
437 +
438 + if ( strpos( $file_path, $base_dir ) !== 0 ) {
439 + return '';
440 + }
441 +
442 + $file_type = wp_check_filetype( basename( $file_path ), null );
443 + if ( empty( $file_type['type'] ) ) {
444 + return '';
445 + }
446 +
447 + $attachment_id = wp_insert_attachment( array(
448 + 'guid' => trailingslashit( $wp_upload_dir['baseurl'] ) . substr( $file_path, strlen( $base_dir ) ),
449 + 'post_mime_type' => $file_type['type'],
450 + 'post_title' => sanitize_text_field( preg_replace( '/\.[^.]+$/', '', basename( $file_path ) ) ),
451 + 'post_content' => '',
452 + 'post_status' => 'inherit',
453 + 'post_author' => $user_id,
454 + ), $file_path );
455 +
456 + if ( empty( $attachment_id ) || is_wp_error( $attachment_id ) ) {
457 + return '';
458 + }
459 +
460 + // Make sure that this file is included, as wp_generate_attachment_metadata() depends on it.
461 + require_once ABSPATH . 'wp-admin/includes/image.php';
462 + wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file_path ) );
463 + update_user_meta( $user_id, $field['meta-name'], $attachment_id );
464 +
465 + return $attachment_id;
466 +}
467 +
431 468 // Deferred to plugins_loaded so older Profile Builder Pro versions (which declare
432 469 // wppb_verify_attachment_id unconditionally during their own file load) win the
433 470 // declaration race and our function_exists guard then skips — avoiding a fatal.
434 471 add_action( 'plugins_loaded', 'wppb_register_attachment_ownership_helpers', 20 );
@@ -458,10 +495,18 @@
458 495 // Allow admins to upload files for users
459 496 if ( $is_admin ) {
460 497 return true;
461 498 }
462 - // Only update if the attachment belongs to the user or has no author (post_author = 0)
463 - if ( $attachment->post_author == $user_id || $attachment->post_author == $current_user_id || $attachment->post_author == 0 ) {
499 + // The attachment is claimable when it already belongs to the target
500 + // user, or to the user performing the request. An author-less
501 + // attachment (post_author == 0) is only claimable by an
502 + // unauthenticated request (e.g. a visitor registering, whose upload
503 + // has no author yet). This prevents an authenticated user from
504 + // claiming (IDOR) an author-0 attachment created by someone else's
505 + // anonymous/nopriv upload.
506 + if ( $attachment->post_author == $user_id
507 + || ( $current_user_id && $attachment->post_author == $current_user_id )
508 + || ( 0 === (int) $current_user_id && 0 === (int) $attachment->post_author ) ) {
464 509 return true;
465 510 }
466 511 } else {
467 512 // If no user ID is provided, check if current user is admin
@@ -467,10 +512,13 @@
467 512 // If no user ID is provided, check if current user is admin
468 513 if ( $is_admin ) {
469 514 return true;
470 515 }
471 - // If no user ID is provided, check if the attachment has no author
472 - if ( $attachment->post_author == $current_user_id || $attachment->post_author == 0 ) {
516 + // Without an explicit target user, an authenticated user may only
517 + // reference an attachment they already own; an author-less
518 + // attachment is only claimable by an unauthenticated request.
519 + if ( ( $current_user_id && $attachment->post_author == $current_user_id )
520 + || ( 0 === (int) $current_user_id && 0 === (int) $attachment->post_author ) ) {
473 521 return true;
474 522 }
475 523 }
476 524 }
@@ -500,8 +548,94 @@
500 548 update_user_meta( $user_id, $field['meta-name'], '' );
501 549 }
502 550 }
503 551 }
552 +}
553 +
554 +/**
555 + * Resolves a simple-upload AJAX `name` parameter to a configured form field.
556 + *
557 + * @param string $post_name Sanitized value of $_POST['name'] from the AJAX request.
558 + * @param string|array $field_type Expected field type(s), e.g. 'Avatar' or 'Upload'.
559 + *
560 + * @return array|false Field definition array, or false when not found or not simple-upload.
561 + */
562 +function wppb_resolve_simple_upload_ajax_field( $post_name, $field_type ) {
563 + if ( empty( $post_name ) ) {
564 + return false;
565 + }
566 +
567 + $field_types = is_array( $field_type ) ? $field_type : array( $field_type );
568 + $all_fields = apply_filters( 'wppb_form_fields', get_option( 'wppb_manage_fields' ), array( 'context' => 'simple_upload_ajax', 'upload_post_name' => $post_name ) );
569 +
570 + if ( empty( $all_fields ) ) {
571 + return false;
572 + }
573 +
574 + foreach ( $all_fields as $field ) {
575 + if ( ! in_array( $field['field'], $field_types, true ) ) {
576 + continue;
577 + }
578 + if ( ! wppb_use_simple_upload_field( $field ) ) {
579 + continue;
580 + }
581 +
582 + $field_slug = str_replace( '-', '_', Wordpress_Creation_Kit_PB::wck_generate_slug( $field['meta-name'], $field ) );
583 + if ( $field_slug === $post_name ) {
584 + return $field;
585 + }
586 + }
587 +
588 + // The field was not found among the top-level form fields. Repeater fields store
589 + // their inner Upload fields in a separate option keyed by the repeater's
590 + // meta-name, so those fields are never part of the wppb_manage_fields list scanned
591 + // above. Scan the repeater groups as well, otherwise Simple Upload inside a
592 + // Repeater field is silently rejected (the lookup fails and the file input clears).
593 + return wppb_resolve_simple_upload_ajax_field_in_repeater( $post_name, $field_types, $all_fields );
594 +}
595 +
596 +/**
597 + * Resolves a simple-upload AJAX `name` parameter to an Upload field nested inside a
598 + * Repeater field.
599 + *
600 + * Repeater sub-fields are stored unindexed in an option keyed by the repeater's
601 + * meta-name. On the front-end each group posts either "<slug>" (the first group) or
602 + * "<slug>_N" (the Nth extra group), where <slug> is the dash-normalized wck slug of
603 + * the inner field's meta-name.
604 + *
605 + * @param string $post_name Sanitized value of $_POST['name'] from the AJAX request.
606 + * @param array $field_types Expected field type(s), e.g. array( 'Upload' ).
607 + * @param array $all_fields The already-resolved top-level form fields.
608 + *
609 + * @return array|false Inner field definition array, or false when not found.
610 + */
611 +function wppb_resolve_simple_upload_ajax_field_in_repeater( $post_name, $field_types, $all_fields ) {
612 + foreach ( $all_fields as $form_field ) {
613 + if ( empty( $form_field['field'] ) || $form_field['field'] !== 'Repeater' ) {
614 + continue;
615 + }
616 +
617 + $repeater_group = get_option( $form_field['meta-name'], 'not_set' );
618 + if ( $repeater_group === 'not_set' || ! is_array( $repeater_group ) ) {
619 + continue;
620 + }
621 +
622 + foreach ( $repeater_group as $inner_field ) {
623 + if ( empty( $inner_field['field'] ) || ! in_array( $inner_field['field'], $field_types, true ) ) {
624 + continue;
625 + }
626 + if ( ! wppb_use_simple_upload_field( $inner_field ) ) {
627 + continue;
628 + }
629 +
630 + $base_slug = str_replace( '-', '_', Wordpress_Creation_Kit_PB::wck_generate_slug( $inner_field['meta-name'], $inner_field ) );
631 + if ( $base_slug === $post_name || preg_match( '/^' . preg_quote( $base_slug, '/' ) . '_[0-9]+$/', $post_name ) ) {
632 + return $inner_field;
633 + }
634 + }
635 + }
636 +
637 + return false;
504 638 }
505 639
506 640 function wppb_check_that_field_is_defined( $meta_name, $field_types = array() ){
507 641