Step 1: Add Meta Boxes for Each Field

Step 2: Display Each Meta Box

// Profile Image Meta Box
function profile_image_box_callback($post) {
    $image_ids = get_post_meta($post->ID, 'profile_image_gallery', true);
    $image_ids = is_array($image_ids) ? $image_ids : [];
    
    wp_nonce_field('profile_image_box_nonce', 'profile_image_box_nonce_field');
    ?>
    <div id="profile-image-gallery-wrapper">
        <ul class="custom-images-list">
            <?php foreach ($image_ids as $image_id) : ?>
                <li>
                    <?php echo wp_get_attachment_image($image_id, 'thumbnail'); ?>
                    <a href="#" class="custom-remove-image" data-image-id="<?php echo esc_attr($image_id); ?>">Remove</a>
                </li>
            <?php endforeach; ?>
        </ul>
        <input type="hidden" id="profile_image_gallery" name="profile_image_gallery" value="<?php echo implode(',', $image_ids); ?>">
        <button type="button" class="button" id="profile_upload_images_button">Add Images</button>
    </div>
    <?php
}

// Similar code for biography and photo gallery callbacks...

Step 3: Enqueue JavaScript with Separate Handlers

jQuery(document).ready(function($) {
    function mediaUploaderHandler(buttonId, inputFieldId, galleryWrapperId) {
        var mediaUploader;
        
        $(buttonId).on('click', function(e) {
            e.preventDefault();
            
            if (mediaUploader) {
                mediaUploader.open();
                return;
            }
            
            mediaUploader = wp.media.frames.file_frame = wp.media({
                title: 'Choose Images',
                button: {
                    text: 'Add Images'
                },
                multiple: true
            });

            mediaUploader.on('select', function() {
                var attachments = mediaUploader.state().get('selection').map(function(attachment) {
                    attachment = attachment.toJSON();
                    return attachment.id;
                });

                var imageList = $(galleryWrapperId + ' .custom-images-list');
                var imageField = $(inputFieldId);
                var currentImageIds = imageField.val().split(',').filter(Boolean);

                // Check if total number of images exceeds the limit (20)
                if (currentImageIds.length + attachments.length > 20) {
                    alert('You can only upload a maximum of 20 images.');
                    return;
                }

                $.each(attachments, function(index, imageId) {
                    if (!currentImageIds.includes(imageId.toString())) {
                        currentImageIds.push(imageId);
                        imageList.append('<li>' + wp.media.attachment(imageId).get('sizes').thumbnail.url + '<a href="#" class="custom-remove-image" data-image-id="' + imageId + '">Remove</a></li>');
                    }
                });

                imageField.val(currentImageIds.join(','));
            });

            mediaUploader.open();
        });
    }

    // Initialize for each meta box
    mediaUploaderHandler('#profile_upload_images_button', '#profile_image_gallery', '#profile-image-gallery-wrapper');
    mediaUploaderHandler('#biography_upload_images_button', '#biography_image_gallery', '#biography-image-gallery-wrapper');
    mediaUploaderHandler('#photo_gallery_upload_images_button', '#photo_gallery', '#photo-gallery-wrapper');

    // Remove image from the gallery
    $(document).on('click', '.custom-remove-image', function(e) {
        e.preventDefault();
        var imageId = $(this).data('image-id');
        var imageField = $(this).closest('.custom-image-box').find('input[type="hidden"]');
        var currentImageIds = imageField.val().split(',').filter(Boolean);
        var newImageIds = currentImageIds.filter(function(id) {
            return id !== imageId.toString();
        });

        imageField.val(newImageIds.join(','));
        $(this).closest('li').remove();
    });
});

Step 4: Save the Meta Box Data for Each Field

function custom_save_meta_boxes($post_id) {
    // Profile Image Save
    if (isset($_POST['profile_image_box_nonce_field']) && wp_verify_nonce($_POST['profile_image_box_nonce_field'], 'profile_image_box_nonce')) {
        if (isset($_POST['profile_image_gallery'])) {
            $image_ids = array_map('intval', explode(',', $_POST['profile_image_gallery']));
            if (count($image_ids) > 20) {
                $image_ids = array_slice($image_ids, 0, 20);
            }
            update_post_meta($post_id, 'profile_image_gallery', $image_ids);
        } else {
            delete_post_meta($post_id, 'profile_image_gallery');
        }
    }

    // Biography Image Save
    if (isset($_POST['biography_image_box_nonce_field']) && wp_verify_nonce($_POST['biography_image_box_nonce_field'], 'biography_image_box_nonce')) {
        if (isset($_POST['biography_image_gallery'])) {
            $image_ids = array_map('intval', explode(',', $_POST['biography_image_gallery']));
            if (count($image_ids) > 20) {
                $image_ids = array_slice($image_ids, 0, 20);
            }
            update_post_meta($post_id, 'biography_image_gallery', $image_ids);
        } else {
            delete_post_meta($post_id, 'biography_image_gallery');
        }
    }

    // Photo Gallery Save
    if (isset($_POST['photo_gallery_box_nonce_field']) && wp_verify_nonce($_POST['photo_gallery_box_nonce_field'], 'photo_gallery_box_nonce')) {
        if (isset($_POST['photo_gallery'])) {
            $image_ids = array_map('intval', explode(',', $_POST['photo_gallery']));
            if (count($image_ids) > 20) {
                $image_ids = array_slice($image_ids, 0, 20);
            }
            update_post_meta($post_id, 'photo_gallery', $image_ids);
        } else {
            delete_post_meta($post_id, 'photo_gallery');
        }
    }
}
add_action('save_post', 'custom_save_meta_boxes');

Bài viết liên quan

post-no-image

Add the Meta Box Repeat

post-no-image

Kỹ thuật debounce trong javascript – Trì hoãn nhập từ khóa trong ô input

post-no-image

Thêm VS Code snippets

post-no-image

Query only seach by title

post-no-image

Hướng dẫn tạo form có validate, upload file nhiều bước và xử lý ajax

post-no-image

Chia sẻ một số thư viện loading css đẹp