Make WordPress Core

Opened 3 years ago

#54720 new defect (bug)

WP_List_Table Inside Metabox With Bulk Actions Not Working on Submit

Reported by: muhammadfaizanhaidar's profile muhammadfaizanhaidar Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version: 5.8.2
Component: Posts, Post Types Keywords: 2nd-opinion needs-dev-note
Focuses: Cc:

Description

I'm trying to display a WP_List_table inside a metabox. The metabox is for questions which are from assessment_question custom post type.The metabox is being displayed on an other custom post type 'cs_questionnaire'. The table columns display some data taken from questions. Also I am using bulk actions to link questions to a questionnaire.

What's happening is that it all looks fine until I click the Publish/Update button on the custom post type edit screen. If the WP_List_Table has bulk actions it will redirect back to the /wp-admin/edit.php page, if I remove the bulk actions then it Works fine. And in both cases, the nonce stays the same and no extra nonce is created.

I've whole code below. I have already overridden the display_tablenav function by commenting the nonce generating code. It stops working when I provide bulk actions else it works fine with the following code.

<?php
/**
 * Generates The User Grade Listing for Admin
 */
if ( ! class_exists( 'WP_List_Table' ) ) {
    require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}


class Class_Conditional_Shortcode_Questions_Listing extends WP_List_Table {
    // define dataset for WP_List_Table => data

    /** Class constructor */
    public function __construct() {

        parent::__construct(
            array(
                'singular' => __( 'Question', 'conditional-shortcode' ), // singular name of the listed records
                'plural'   => __( 'Questions', 'conditional-shortcode' ), // plural name of the listed records
                'ajax'     => false, // does this table support ajax?
            )
        );
    }


    /**
     * Function to filter data based on order , order_by & searched items
     *
     * @param string $orderby
     * @param string $order
     * @param string $search_term
     * @return array $users_array()
     */
    public function list_table_data_fun( $orderby = '', $order = '', $search_term = '' ) {

        $args            = array();
        $questions_array = array();
        $questions       = '';
        $flag            = false;
        if ( ! empty( $search_term ) ) {

            $args = array(
                'fields'         => 'ids',
                'orderby'        => $orderby,
                'order'          => $order,
                'search'         => intval( sanitize_text_field( $_REQUEST['s'] ) ),
                'post_type'      => 'assessment_question',
                'posts_per_page' => -1,
            );
        } else {
            if ( $order == 'asc' && $orderby == 'id' ) {
                $args = array(

                    'orderby'        => 'ID',
                    'order'          => 'ASC',
                    'fields'         => 'ids',
                    'post_type'      => 'assessment_question',
                    'posts_per_page' => -1,
                );
            } elseif ( $order == 'desc' && $orderby == 'id' ) {
                    $args = array(
                        'orderby'        => 'ID',
                        'order'          => 'DESC',
                        'fields'         => 'ids',
                        'post_type'      => 'assessment_question',
                        'posts_per_page' => -1,
                    );

            } elseif ( $order == 'desc' && $orderby == 'title' ) {
                    $args = array(
                        'orderby'        => 'name',
                        'order'          => 'DESC',
                        'fields'         => 'ids',
                        'post_type'      => 'assessment_question',
                        'posts_per_page' => -1,
                    );
            } elseif ( $order == 'asc' && $orderby == 'title' ) {
                $args = array(
                    'orderby'        => 'name',
                    'order'          => 'ASC',
                    'fields'         => 'ids',
                    'post_type'      => 'assessment_question',
                    'posts_per_page' => -1,
                );
            } else {
                $args = array(
                    'orderby'        => 'ID',
                    'order'          => 'DESC',
                    'fields'         => 'ids',
                    'post_type'      => 'assessment_question',
                    'posts_per_page' => -1,
                );
                $flag = true;
            }
        }
        $questions = get_transient( 'pd_questions' );
        if ( $flag == false ) {
            $questions = get_posts( $args );
        } elseif ( $flag == true && ! $questions ) {
            $questions = get_posts( $args );
            set_transient( 'pd_questions', $questions, 1 * DAY_IN_SECONDS );
        }
        if ( count( $questions ) > 0 ) {
            foreach ( $questions as $question_id ) {
                $question          = get_post_meta( $question_id ?? 0, CONDITIONAL_SHORTCODE_ASSESSMENT_QUESTION_META, true )['question'] ?? 'NA';
                $questions_array[] = array(
                    'id'       => $question_id,
                    'title'    => '<b>' . get_the_title( $question_id ) . '</b>',
                    'question' => $question,
                );

            }
        }

        return $questions_array;
    }
    // prepare_items
    public function prepare_items() {

        $orderby = sanitize_text_field( isset( $_GET['orderby'] ) ? trim( $_GET['orderby'] ) : '' );
        $order   = sanitize_text_field( isset( $_GET['order'] ) ? trim( $_GET['order'] ) : '' );

        $search_term = sanitize_text_field( isset( $_POST['s'] ) ? trim( $_POST['s'] ) : '' );
        if ( $search_term == '' ) {

            $search_term = sanitize_text_field( isset( $_GET['s'] ) ? trim( $_GET['s'] ) : '' );
        }

        $datas = $this->list_table_data_fun( $orderby, $order, $search_term );

        $per_page     = 30;
        $current_page = $this->get_pagenum();
        $total_items  = count( $datas );

        $this->set_pagination_args(
            array(
                'total_items' => $total_items,
                'per_page'    => $per_page,
            )
        );

        $this->items = array_slice( $datas, ( ( $current_page - 1 ) * $per_page ), $per_page );

        $columns  = $this->get_columns();
        $hidden   = $this->get_hidden_columns();
        $sortable = $this->get_sortable_columns();

        $this->_column_headers = array( $columns, $hidden, $sortable );
        $this->process_bulk_action();
    }

    public function get_bulk_actions() {

        return array(
            'add_questions'    => __( 'Add Questions', 'conditional-shortcode' ),
            'remove_questions' => __( 'Remove Questions', 'conditional-shortcode' ),
        );

    }
        // get_columns
    public function get_columns() {

        $columns = array(
            'cb'       => '<input type="checkbox" />',
            'id'       => __( 'ID', 'conditional-shortcode' ),
            'title'    => __( 'Title', 'conditional-shortcode' ),
            'question' => __( 'Questions', 'conditional-shortcode' ),
            'action'   => __( 'Action', 'conditional-shortcode' ),
        );

        return $columns;
    }

    public function get_hidden_columns() {
        return array( '' );
    }

    public function get_sortable_columns() {
        return array(
            'title' => array( 'title', true ),
            'id'    => array( 'id', true ),
        );

    }

    /**
     * Generate the table navigation above or below the table.
     *
     * @since 3.1.0
     * @access protected
     *
     * @param string $which
     */
    protected function display_tablenav( $which ) {

        // REMOVED NONCE -- INTERFERING WITH SAVING POSTS ON METABOXES
        // Add better detection if this class is used on meta box or not.
        /*
        if ( 'top' == $which ) {
            wp_nonce_field( 'bulk-' . $this->_args['plural'] );
        }
        */
    
        ?>
        <div class="tablenav <?php echo esc_attr( $which ); ?>">
    
            <div class="alignleft actions bulkactions">
                <?php $this->bulk_actions( $which ); ?>
            </div>
            <?php
            $this->extra_tablenav( $which );
            $this->pagination( $which );
            ?>
    
            <br class="clear"/>
        </div>
        <?php
    }

    // column_default
    public function column_default( $item, $column_name ) {
        $post_id = get_the_ID();
        switch ( $column_name ) {
            case 'cb':
            case 'id':
            case 'title':
            case 'question':
                return $item[ $column_name ];
            case 'action':
                return '<a href="?post=' . $post_id . '&action=edit&action1=add_question&question_id=' . $item['id'] . '&questionnaire_id=' . $post_id . '">Add Question</a>';
            default:
                return 'no value';

        }

    }

    public function column_title( $item ) {
        $post_id = get_the_ID();
        $action  = array(
            'edit' => sprintf( '<a href="?post=%d&action=%s&action1=%s&question_id=%d&questionnaire_id=%d">Add Question</a>', $post_id, 'edit', 'add_question', $item['id'], $post_id ),
        );
        return sprintf( '%1$s %2$s', $item['title'], $this->row_actions( $action ) );
    }

    function column_cb( $item ) {
        return sprintf(
            '<input type="checkbox" name="add-questions[]" value="%d" />',
            $item['id']
        );
    }

    function no_items() {
        esc_html_e( 'No Questions Found.', 'conditional-shortcode' );
    }

    public function process_bulk_action() {
        
        // security check!
        if ( isset( $_POST['_wpnonce'] ) && ! empty( $_POST['_wpnonce'] ) ) {

            $nonce  = filter_input( INPUT_POST, '_wpnonce', FILTER_SANITIZE_STRING );
            $action = 'bulk-' . $this->_args['plural'];

            if ( ! wp_verify_nonce( $nonce, $action ) ) {
                wp_die( 'Nope! Security check failed!' );
            }
        }

        $action = $this->current_action();

        switch ( $action ) {

            case 'delete_questions':
                wp_die( 'Delete something' );
                break;

            case 'add_questions':
                wp_die( 'Save something' );
                break;

            default:
                // do nothing or something else
                return;
                break;
        }
        wp_redirect( esc_url( add_query_arg() ) );
        exit;
        return;
    }

}

/**
 * Shows the List table for all questions.
 *
 * @return void
 */
function conditional_shortcode_questions_list_table_layout() {
    $table = new Class_Conditional_Shortcode_Questions_Listing();

    printf( '<div class="wrap" id="wpse-list-table"><h2>%s</h2>', __( '', 'conditional-shortcode' ) );

    echo '<form id="wpse-list-table-form" method="post">';

    $page  = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRIPPED );
    $paged = filter_input( INPUT_GET, 'paged', FILTER_SANITIZE_NUMBER_INT );

    printf( '<input type="hidden" name="page" value="%s" />', $page );
    printf( '<input type="hidden" name="paged" value="%d" />', $paged );

    $table->prepare_items(); // this will prepare the items AND process the bulk actions
    $table->search_box( __( 'Search question by id' ), 'conditional-shortcode' ); // Needs To be called after $myRequestTable->prepare_items()
    $table->display();

    echo '</form>';

    echo '</div>';

}

conditional_shortcode_questions_list_table_layout();

I was able to resolve this issue by changing the value of name attribute of bulk actions select field. Like from name to names. What I think the issue could be is when here the name="action" for this select and it has some value xyz on the other side WordPress save post looks for action to be equal to edit.
Can we provide a way of changing this name attributes value either by providing a filter or by changing it. But I think changing name attribute would require a lot of other code changes so its better to provide a filter for custom use. Or add a comment on top so someone else using WP LIST Table in a metabox must override this function with custom value to name attribute.
For clarity I changed
This echo '<select name="action' . $two . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n";

To this echo '<select name="actions' . $two . '" id="bulk-action-selector-' . esc_attr( $which ) . "\">\n";
Thanks!

Change History (0)

Note: See TracTickets for help on using tickets.