WP_list_table bulk_action get edit and delete

I am really apparently not getting documentation about this. Even other stack exchanges about it do not really get it clear to me. I think it’s because I just do not get how this works.

So I am creating an admin page and I am reading the database table that I created. I just don’t get how I can make single deletions (and edits) and bulk deletions (and edits) to work within the WordPress framework.

So here is the code:

class Designit_Countries_Table extends Designit_List_Table
{

    private $order;
    private $orderby;
    private $posts_per_page = 25;

    public function __construct()
    {
        global $status, $page;

        parent :: __construct(array(
            'singular' => "country",
            'plural' => "countries",
            'ajax' => true
        ));

        $this->set_order();
        $this->set_orderby();
    }

    private function get_sql_results()
    {
        global $wpdb;
        $args = array('id', 'name', 'title', 'country', 'image', 'time');
        $sql_select = implode(', ', $args);
        $sql_results = $wpdb->get_results("SELECT " . $sql_select . " FROM " . $wpdb->prefix . "designitcountries ORDER BY $this->orderby $this->order ");
        return $sql_results;
    }

    public function set_order()
    {
        $order = 'ASC';
        if (isset($_GET['order']) AND $_GET['order'])
            $order = $_GET['order'];
        $this->order = esc_sql($order);
    }

    public function set_orderby()
    {
        $orderby = 'id';
        if (isset($_GET['orderby']) AND $_GET['orderby'])
            $orderby = $_GET['orderby'];
        $this->orderby = esc_sql($orderby);
    }

    public function ajax_user_can()
    {
        return current_user_can('edit_posts');
    }

    public function no_items()
    {
        _e('No countries found.');
    }

    public function get_views()
    {
        return array();
    }

    public function get_columns()
    {
        $columns = array(
            'cb' => '<input type="checkbox" />',
            'name' => __('Name'),
            'title' => __('Title'),
            'country' => __('Country'),
            'image' => __('Image'),
            'time' => __('Created on'),
            'id' => __('id')
        );
        return $columns;
    }

    function get_bulk_actions() {
        $actions = array(
            'delete'    => 'Delete'
        );
        return $actions;
    }
    public function get_sortable_columns()
    {
        $sortable = array(
            'id' => array('id', true),
            'title' => array('Title', true),
            'name' => array('Name', true),
            'country' => array('Country', true),
            'image' => array('Image', true),
            'time' => array('Created on', true),
        );
        return $sortable;
    }

    public function prepare_items()
    {
        $columns = $this->get_columns();
        $hidden = array();
        $sortable = $this->get_sortable_columns();
        $this->_column_headers = array(
            $columns,
            $hidden,
            $sortable
        );

        // SQL results
        $posts = $this->get_sql_results();
        empty($posts) AND $posts = array();

        # >>>> Pagination
        $per_page = $this->posts_per_page;
        $current_page = $this->get_pagenum();
        $total_items = count($posts);
        $this->set_pagination_args(array(
            'total_items' => $total_items,
            'per_page' => $per_page,
            'total_pages' => ceil($total_items / $per_page)
        ));
        $last_post = $current_page * $per_page;
        $first_post = $last_post - $per_page + 1;
        $last_post > $total_items AND $last_post = $total_items;

        // Setup the range of keys/indizes that contain
        // the posts on the currently displayed page(d).
        // Flip keys with values as the range outputs the range in the values.
        $range = array_flip(range($first_post - 1, $last_post - 1, 1));

        // Filter out the posts we're not displaying on the current page.
        $posts_array = array_intersect_key($posts, $range);
        # <<<< Pagination
        // Prepare the data
        $permalink = __('Edit:');
        foreach ($posts_array as $key => $post) {
            $link = get_edit_post_link($post->id);
            $no_name = __('No name set');
            $name = !$post->name ? "<em>{$no_name}</em>" : $post->name;
            $posts[$key]->name = "{$name}";
        }
        $this->items = $posts_array;

        $this->process_bulk_action();

    }

    /**
     * A single column
     */
    public function column_default($item, $column_name)
    {
        return $item->$column_name;
    }


    /**
     * Disables the views for 'side' context as there's not enough free space in the UI
     * Only displays them on screen/browser refresh. Else we'd have to do this via an AJAX DB update.
     *
     * @see WP_List_Table::extra_tablenav()
     */
    public function extra_tablenav($which)
    {
        global $wp_meta_boxes;
        $views = $this->get_views();
        if (empty($views)) return;

        $this->views();
    }

    //edit buttons

    function column_name(){
        global $wpdb;
        $args = array('id', 'name');
        $sql_select = implode(', ', $args);
        $sql_results = $wpdb->get_results("SELECT " . $sql_select . " FROM " . $wpdb->prefix . "designitcountries ORDER BY $this->orderby $this->order ");

        foreach ($sql_results as $sql_result) {
            $name = $sql_result->name;
            $id = $sql_result->id;
            $actions = array(
                'edit' => sprintf('<a href="?page=%s&action=%s&name=%s">Edit</a>', $_REQUEST['page'], 'edit', $id),
                'delete' => sprintf('<a href="?page=%s&action=%s&name=%s">Delete</a>', $_REQUEST['page'], 'delete', $id),
            );
            return sprintf('%1$s %2$s', $name, $this->row_actions($actions));
        }
    }

    function column_cb($item) {
            return sprintf(
                '<input type="checkbox" name="book[]" value="%s" />',
                $this->_args['singular'],
                $item->id
            );
        }

    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':
                foreach($_GET['country'] as $country) {



                }

                wp_die( 'You have deleted this succesfully' );
                break;

            case 'edit':
                wp_die( 'This is the edit page.' );
                break;

            default:
                // do nothing or something else
                return;
                break;
        }

        return;
    }

}

It is a lot of code to go trough so let me cut in in pieces for my problem.

The results shows this :Countries admin page

This shows the country page I created with the actions I have in place right now and the checkboxes

So i implement the checkboxes like this :

public function get_columns()
{
    $columns = array(
        'cb' => '<input type="checkbox" />',
        'name' => __('Name'),
        'title' => __('Title'),
        'country' => __('Country'),
        'image' => __('Image'),
        'time' => __('Created on'),
        'id' => __('id')
    );
    return $columns;
}
    function column_cb($item) {
        return sprintf(
            '<input type="checkbox" name="book[]" value="%s" />',
            $this->_args['singular'],
            $item->id
        );
    }

Then I also have the single edit and delete buttons:

function column_name(){
    global $wpdb;
    $args = array('id', 'name');
    $sql_select = implode(', ', $args);
    $sql_results = $wpdb->get_results("SELECT " . $sql_select . " FROM " . $wpdb->prefix . "designitcountries ORDER BY $this->orderby $this->order ");

    foreach ($sql_results as $sql_result) {
        $name = $sql_result->name;
        $id = $sql_result->id;
        $actions = array(
            'edit' => sprintf('<a href="?page=%s&action=%s&name=%s">Edit</a>', $_REQUEST['page'], 'edit', $id),
            'delete' => sprintf('<a href="?page=%s&action=%s&name=%s">Delete</a>', $_REQUEST['page'], 'delete', $id),
        );
        return sprintf('%1$s %2$s', $name, $this->row_actions($actions));
    }
}

and the bulk action and processing of that:

function get_bulk_actions() {
    $actions = array(
        'delete'    => 'Delete'
    );
    return $actions;
}

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':
                foreach($_GET['country'] as $country) {



                }

                wp_die( 'You have deleted this succesfully' );
                break;

            case 'edit':
                wp_die( 'This is the edit page.' );
                break;

            default:
                // do nothing or something else
                return;
                break;
        }

        return;
    }

Now where i don’t get it

I do get that I need to do the delete in case ‘delete’ and the edit in case ‘edit’. But I don’t get how I can select the row that has been selected with the checkboxes to actually get deleted. Or in case you use the ‘delete button’ and not the bulk action only that row get deleted and how the edit function overall works.

Can’t seem to find proper examples either so I am hoping that one of you guys can help me out!

The page i see when i press ‘delete’ :
enter image description here

Update

So i got it to work by giving everything its own ID (which makes sense)

function column_cb($item) {
    return sprintf(
        '<input type="checkbox" name="country[]" value="%s" />',
        $item->id
    );
}
function column_name($item){
    $item_json = json_decode(json_encode($item), true);
    $actions = array(
        'edit' => sprintf('<a href="?page=%s&action=%s&id=%s">Edit</a>', $_REQUEST['page'], 'edit', $item_json['id']),
        'delete' => sprintf('<a href="?page=%s&action=%s&id=%s">Delete</a>', $_REQUEST['page'], 'delete', $item_json['id']),
    );
    return '<em>' . sprintf('%s %s', $item_json['name'], $this->row_actions($actions)) . '</em>';
}

And then just use that reference to delete the tables with an SQL query :

 case 'delete':
                global $wpdb;
                $table_name = $wpdb->prefix . 'designitcountries';
                $ids = isset($_REQUEST['id']) ? $_REQUEST['id'] : array();
                var_dump($ids);
                    if (is_array($ids)) $ids = implode(',', $ids);
                    if (!empty($ids)) {
                        $wpdb->query("DELETE FROM $table_name WHERE id IN($ids)");
                }

                wp_die( 'You have deleted this succesfully' );
                break;

BUT

It works when i use the delete button but it does not work when i do a bulk action deletion (not even when i select 1)

I do give the ID in the URL of the A href of the ‘delete’ button. No idea how i’m supposed to do that with the checkboxes to act the same

Solutions Collecting From Web of "WP_list_table bulk_action get edit and delete"

So, I found the problem.

First of all, I was not selecting the right ID’s on my checkbox or buttons, so I changed to code to do so:

function column_name($item){
    $item_json = json_decode(json_encode($item), true);
    $actions = array(
        'edit' => sprintf('<a href="?page=%s&action=%s&id=%s">Edit</a>', $_REQUEST['page'], 'edit', $item_json['id']),
        'delete' => sprintf('<a href="?page=%s&action=%s&id=%s">Delete</a>', $_REQUEST['page'], 'delete', $item_json['id']),
    );
    return '<em>' . sprintf('%s %s', $item_json['name'], $this->row_actions($actions)) . '</em>';
}

Notice here that I have $items now who will get all the data needed. I will do an JSON decode and encode to turn it into an array and after that I can call the JSON array id or name in the sprinttf() where I need it.

Same for the checkboxes (without the JSON):

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

After that i added this to my case ‘delete’ :

case 'delete':
                global $wpdb;
                $table_name = $wpdb->prefix . 'designitcountries';

                if ('delete' === $this->current_action()) {
                    $ids = isset($_REQUEST['id']) ? $_REQUEST['id'] : array();
                    if (is_array($ids)) $ids = implode(',', $ids);

                    if (!empty($ids)) {
                        $wpdb->query("DELETE FROM $table_name WHERE id IN($ids)");
                    }
                }

                wp_die( 'You have deleted this succesfully' );
                break;

Now I have a working method to delete from the buttons and from the checkboxes.