1

I was trying to add an image field to the woocommerce account settings section so that users could upload their avatars.

With a bit of research here on stack I came across this question that worked and gave the solution to my problem: Add a profile picture (file upload) on My account > edit account in WooCommerce

However, other questions have arisen and I have thought about extending the question to try to improve the final result as I believe it is a very common situation.

So I hope someone can answer this.

  1. Will the uploaded image not appear for the post comments section or woocommerce product reviews? Do we need to change the meta image for this? It would be very useful to be able to view the uploaded image anywhere on the site, including the comments and reviews section.

  2. If the user wants to remove the image and go back to the default how can he do it? There is no remove image button. Is there a way to insert a button and remove the image uploaded ?

  3. Is there a way to set upload limit? For example, uploaded images must be jpeg or png and must not exceed 1mb in size.

  4. The biggest problem is the directory where the images are saved, can it be different from the default media library? Furthermore, when the user changes multiple images, the previous one is not deleted and will remain forever in the media library taking up space unnecessarily.

I believe the answer to these questions completes something that the woocommerce world lacks by default. This may be a standard solution for most users.

For convenience, I report the code of the previous question:

// Add field
function action_woocommerce_edit_account_form_start() {
    ?>
    <p class="woocommerce-form-row woocommerce-form-row--wide form-row form-row-wide">
        <label for="image"><?php esc_html_e( 'Image', 'woocommerce' ); ?>&nbsp;<span class="required">*</span></label>
        <input type="file" class="woocommerce-Input" name="image" accept="image/x-png,image/gif,image/jpeg">
    </p>
    <?php
}
add_action( 'woocommerce_edit_account_form_start', 'action_woocommerce_edit_account_form_start' );

// Validate
function action_woocommerce_save_account_details_errors( $args ){
    if ( isset($_POST['image']) && empty($_POST['image']) ) {
        $args->add( 'image_error', __( 'Please provide a valid image', 'woocommerce' ) );
    }
}
add_action( 'woocommerce_save_account_details_errors','action_woocommerce_save_account_details_errors', 10, 1 );

// Save
function action_woocommerce_save_account_details( $user_id ) {  
    if ( isset( $_FILES['image'] ) ) {
        require_once( ABSPATH . 'wp-admin/includes/image.php' );
        require_once( ABSPATH . 'wp-admin/includes/file.php' );
        require_once( ABSPATH . 'wp-admin/includes/media.php' );

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

        if ( is_wp_error( $attachment_id ) ) {
            update_user_meta( $user_id, 'image', $_FILES['image'] . ": " . $attachment_id->get_error_message() );
        } else {
            update_user_meta( $user_id, 'image', $attachment_id );
        }
   }
}
add_action( 'woocommerce_save_account_details', 'action_woocommerce_save_account_details', 10, 1 );

// Add enctype to form to allow image upload
function action_woocommerce_edit_account_form_tag() {
    echo 'enctype="multipart/form-data"';
} 
add_action( 'woocommerce_edit_account_form_tag', 'action_woocommerce_edit_account_form_tag' );

To display the image (can be used anywhere, provided you adjust the desired hook)

// Display
function action_woocommerce_edit_account_form() {
    // Get current user id
    $user_id = get_current_user_id();

    // Get attachment id
    $attachment_id = get_user_meta( $user_id, 'image', true );

    // True
    if ( $attachment_id ) {
        $original_image_url = wp_get_attachment_url( $attachment_id );

        // Display Image instead of URL
        echo wp_get_attachment_image( $attachment_id, 'full');
    }
} 
add_action( 'woocommerce_edit_account_form', 'action_woocommerce_edit_account_form' );
Snorlax
  • 183
  • 4
  • 27

3 Answers3

2
// Save
function action_woocommerce_save_account_details( $user_id ) {  
    if ( isset( $_FILES['image'] ) ) {
        require_once( ABSPATH . 'wp-admin/includes/image.php' );
        require_once( ABSPATH . 'wp-admin/includes/file.php' );
        require_once( ABSPATH . 'wp-admin/includes/media.php' );

        function wp_set_custom_upload_folder($uploads) {
            $uploads['path'] = $uploads['basedir'] . '/custom-folder';
            $uploads['url'] = $uploads['baseurl'] . '/custom-folder';    
            if (!file_exists($uploads['path'])) {
                mkdir($uploads['path'], 0755, true);
            }
            return $uploads;
        }
        add_filter('upload_dir', 'wp_set_custom_upload_folder');    
        
        
        $attachment_id = media_handle_upload( 'image', 0 );

        if ( is_wp_error( $attachment_id ) ) {
            update_user_meta( $user_id, 'image', $_FILES['image'] . ": " . $attachment_id->get_error_message() );
        } else {
            $old_attachment_id = get_user_meta( $user_id, 'image', true );
            wp_delete_attachment($old_attachment_id);
            update_user_meta( $user_id, 'image', $attachment_id );
        }
   }
}
add_action( 'woocommerce_save_account_details', 'action_woocommerce_save_account_details', 10, 1 );

Set custom upload directory for profile image uploads

// Display function

function action_woocommerce_edit_account_form() {
    // Get current user id
    $user_id = get_current_user_id();

    // Get attachment id
    $attachment_id = get_user_meta($user_id, 'image', true);

    // True
    if ($attachment_id) {
        $original_image_url = wp_get_attachment_url($attachment_id);

        // Display Image instead of URL
        echo wp_get_attachment_image($attachment_id, 'full');

        if (isset($_GET['rm_profile_image_id'])) {
            if ($attachment_id == $_GET['rm_profile_image_id']) {
                wp_delete_attachment($attachment_id);
                delete_user_meta($user_id, 'image');
    ?> <script>
                                window.location='<?php echo wc_get_account_endpoint_url('edit-account') ?>';
                              </script> 
                <?php
                exit();
            }
        } else {

            echo '<a href=' . wc_get_account_endpoint_url('edit-account') . '?rm_profile_image_id=' . $attachment_id . '> ' . __('Remove') . ' </a>';
        }
    }
}
mujuonly
  • 11,370
  • 5
  • 45
  • 75
  • Thanks for your contribution, I appreciate it very much. One of the known problems is the persistence of avatars that are not deleted. For example, you upload image 1, then later if you decide to replace the avatar and upload image 2, image 1 remains in the library forever. This takes up space unnecessarily. Let's assume that within a month the user changes 10 avatars, then there will be a lot of unused images of that user in the directory. Do you think there is a way to automatically delete the previous image when the user uploads a new image ? – Snorlax Aug 19 '22 at 09:20
  • Thank you for your time, I am truly grateful for that. I'm not at home right now to check if it works, but I'll do it as soon as I get back. The last remaining question is problem n2, which is whether it is possible to put a button that allows the user to remove the currently loaded image. Even just the removal action from the form would be enough, then the save settings button should do the rest. But it's just an idea... I'm not sure if that's the right way. Do you have any suggestion ? – Snorlax Aug 19 '22 at 10:43
  • Tested and working, thank you very much for your contribution, I believe many questions about woocommerce avatar will now be answered. As soon as I have some time I will put the post in order. Anyway, I intended to save the files in `/wp-content/custom-folder` while they are currently saved in `/wp-content/uploads/custom-folder`. Change this because uploaded avatars are still displayed in the media library mixing with other post and page images. Is it possible to do this? – Snorlax Aug 19 '22 at 18:32
  • I just discovered a small bug. If the user saves the account details without uploading the image they get a white layout. The error displayed with active wp-debug is `Array to string conversion in: update_user_meta($user_id, 'image', $_FILES ['image']. ":". $attachment_id->get_error_message ());` – Snorlax Aug 19 '22 at 18:50
  • Also, after receiving the mentioned error, the avatar is deleted when you return to your account. – Snorlax Aug 19 '22 at 19:00
  • If I do `var_dump($ attachment_id);` I get `object (WP_Error) #25169 (3) {["errors"] => array(1) {["upload_error"] => array(1) {[0] => string(21) "No file was uploaded. " }} ["error_data"] => array(0) {} ["additional_data": protected] => array (0) {}}` – Snorlax Aug 19 '22 at 19:11
  • 1
    If I do var_dump ($_FILES); i see it contains an array with element 'error'. So, just doing `$_FILES ['image']` accesses the array, but you have to call the element inside it as well because you can't concatenate `$_FILES` with a string. I realized that I needed to access the `$_FILES` array element to resolve the error. So I changed `$_FILES ['image']` to `$_FILES ['image']['error']` and solved the problem. – Snorlax Aug 20 '22 at 17:09
  • Is this still working? I can get the old answer from 2.5 years ago to work, but this does not add anything to the edit account form? – cactusboat Sep 26 '22 at 04:45
1

When I have some free time I want to work on these issues. I'm not very good with codes I'm just a fan, but with study, research and practice I hope to make my contribution to this topic.

I publish this answer to update it every time I find a new solution. I'm sure someone can offer more efficient solutions, so corrections and answers that improve the topic are welcome.


Solution to problem n1 - Set the uploaded image anywhere on the site

Set the get_avatar filter (responsible for displaying the avatar in the comments section and other section of website) and assign it the url stored in the image meta_key.

add_filter( 'get_avatar', 'my_custom_avatar', 10, 6 );
function my_custom_avatar( $avatar, $id_or_email, $size, $default, $alt, $args ) {
    
  // What is the custom image field's meta key?
  // Set this value to match the meta key of your custom image field.
  $meta_key = "image";
  
  // Nothing really to change below here, unless
  // you want to change the <img> tag HTML.
  $user = false;
  if ( is_numeric( $id_or_email ) ) {
    $user = get_user_by( 'id' , (int)$id_or_email );
  } 
  
  elseif ( is_object( $id_or_email ) ) {
    if ( ! empty( $id_or_email->user_id ) ) {
      $id = (int)$id_or_email->user_id;
      $user = get_user_by( 'id' , $id );
    }
  } else {
    $user = get_user_by( 'email', $id_or_email );    
  }

  if ( $user && is_object( $user ) ) {
    $post_id = get_user_meta( $user->ID, $meta_key, true );
    if ( $post_id ) {
      $attachment_url = wp_get_attachment_url( $post_id );
    
      // HTML for the avatar <img> tag.  This is WP default.
      $avatar = wp_get_attachment_image($post_id, $size = array('50', '50'));
    }
  }

 return $avatar;
}

Solution to problem n3 - Set a size limit for files

Thanks to this question Limit the size of a file upload (html input element) I found the solution to limit the file size in bytes. So here's what I did:

I assigned the input type file the id file

<input id="file" type="file" class="woocommerce-Input" name="image" accept="image/x-png,image/gif,image/jpeg">

I then applied the script below

/* Limit File Size for Avatar Upload (size in bytes) */
var uploadField = document.getElementById("file");
uploadField.onchange = function() {
  // Put size in Bytes
    if(this.files[0].size > 100000){
       alert("File is too big!");
       this.value = "";
    };
};
Snorlax
  • 183
  • 4
  • 27
1

The code provided by @mujuonly looks promising, however, please take the following into consideration:

  1. Add a remove_filter after the else block in 1st code. Else all the website uploads will happen in that directory.

To do so, use:

remove_filter('upload_dir', 'wp_set_custom_upload_folder');

after else block.

2nd code should check if the user meta was deleted successfully or not. The line delete_user_meta($user_id, 'image'); should be wrapped in if condition like so:

if (delete_user_meta($user_id, 'image')){
    wp_delete_attachment($attachment_id);
}

This way you don't delete the image first and make it unavailable for failed delete_user_meta.

Vinay Jain
  • 862
  • 1
  • 3
  • 11
  • 1
    Thanks for your contribution, I appreciate it. I've actually made the changes from your suggestions and everything works fine. Thank you so much. – Snorlax Aug 19 '22 at 17:58