Search code examples
javascriptphpwordpressadmin-ajax

WordPress Attachment Taxonomy Checkbox Group Not Saving In Grid View


I created a taxonomy and attached it to the attachments or Media.

I am using Advanced Custom Fields to add a taxonomy field which is displayed as checkbox group. It adds the fields correctly to the attachments pages in both grid and list views.

In list view, the fields update properly but in the grid view, it only saves the last checkbox clicked. This is caused by some sort of onclick function which fires admin-ajax when each checkbox is clicked. Due to this it only sends the value of the box is clicked. I am trying to find a way to alter the js functions to add in checkbox groups and multi-select.

Update: I have tracked the function down to an onchange.

function: save

file: media-views.min.js

Code to replicate the issue:

Taxonomy:

function cptui_register_my_taxes() {
    $labels = array(
        "name" => __( "Image Tags", "" ),
        "singular_name" => __( "Image Tag", "" ),
    );
    $args = array(
        "label" => __( "Image Tags", "" ),
        "labels" => $labels,
        "public" => true,
        "hierarchical" => false,
        "label" => "Image Tags",
        "show_ui" => true,
        "show_in_menu" => true,
        "show_in_nav_menus" => false,
        "query_var" => true,
        "rewrite" => false,
        "show_admin_column" => false,
        "show_in_rest" => false,
        "rest_base" => "imagetags",
        "show_in_quick_edit" => false,
    );
    register_taxonomy( "imagetags", array( "attachment" ), $args );
}
add_action( 'init', 'cptui_register_my_taxes' );

Custom Fields:

if( function_exists('acf_add_local_field_group') ):
    acf_add_local_field_group(array(
        'key' => 'group_5bc3f242c39e3',
        'title' => 'Gallery - Image Tags',
        'fields' => array(
            array(
                'key' => 'field_5bc3f249f009c',
                'label' => 'Image Tags',
                'name' => 'new_image_tags',
                'type' => 'taxonomy',
                'instructions' => '',
                'required' => 0,
                'conditional_logic' => 0,
                'wrapper' => array(
                    'width' => '',
                    'class' => '',
                    'id' => '',
                ),
                'taxonomy' => 'imagetags',
                'field_type' => 'checkbox',
                'add_term' => 1,
                'save_terms' => 1,
                'load_terms' => 1,
                'return_format' => 'id',
                'multiple' => 0,
                'allow_null' => 0,
            ),
        ),
        'location' => array(
            array(
                array(
                    'param' => 'attachment',
                    'operator' => '==',
                    'value' => 'all',
                ),
            ),
        ),
        'menu_order' => 0,
        'position' => 'normal',
        'style' => 'default',
        'label_placement' => 'top',
        'instruction_placement' => 'label',
        'hide_on_screen' => '',
        'active' => 1,
        'description' => '',
    ));

endif;

You can also remove the default taxonomy field if you want with the following code. It does not effect the code but removes the text field:

// Remove taxonomy from the attachment pages so the acf taxonomy can work
add_action( 'admin_menu', function (){ remove_meta_box('imagetagsdiv', 'attachment', 'side' ); } );

// Add this in to remove it from the popup editor
add_filter( 'attachment_fields_to_edit', function( $fields ){
    unset($fields['imagetags']); 
    return $fields;
} );

Problem Found Now to find a solution

Open wp-includes/js/media-views.js - Line: 8353

_.each( this.$el.serializeArray(), function( pair ) {
    data[ pair.name ] = pair.value;
});

It appears they used this code to prevent duplicates but in effect it is deleting everything that can be submitted as an array.

Note: This is the full file WP loads the .min version by default


Solution

  • The issue can be fixed by overriding the prototype function (wp.media.view.AttachmentCompat.prototype.save()), like so:

    add_action( 'admin_print_footer_scripts', 'so52810006', 11 );
    function so52810006() {
        // Make sure the media-views script has been enqueued.
        if ( ! did_action( 'wp_enqueue_media' ) ) {
            return;
        }
        ?>
    <script>
    wp.media.view.AttachmentCompat.prototype.save = function( event ) {
        var data = {};
    
        if ( event ) {
            event.preventDefault();
        }
    
        _.each( this.$el.serializeArray(), function( pair ) {
            if ( /\[\]$/.test( pair.name ) ) {
                if ( undefined === data[ pair.name ] ) {
                    data[ pair.name ] = [];
                }
                data[ pair.name ].push( pair.value );
            } else {
                data[ pair.name ] = pair.value;
            }
        });
    
        this.controller.trigger( 'attachment:compat:waiting', ['waiting'] );
        this.model.saveCompat( data ).always( _.bind( this.postSave, this ) );
    };
    </script>
        <?php
    }
    

    As you can see, the main part is here, which properly builds the data by taking into account the field name which is pair.name — i.e. whether it ends with a [] which indicates an array value.

    _.each( this.$el.serializeArray(), function( pair ) {
        if ( /\[\]$/.test( pair.name ) ) {
            if ( undefined === data[ pair.name ] ) {
                data[ pair.name ] = [];
            }
            data[ pair.name ].push( pair.value );
        } else {
            data[ pair.name ] = pair.value;
        }
    });
    

    Tried and tested working on WordPress 4.9.8 and ACF 5.7.7.