Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I'm trying to display a WP_List_table inside a metabox. The metabox is for a custom taxonomy called 'service' which is registered to a custom post type called 'office'. The table columns contain input fields which manipulate the taxonomy and allow me to link additional data to each individual taxonomy term. I don't think the fact that I'm trying to this for a custom taxonomy which is on a custom post type edit screen is relevant. I only merely mention to for clarity sake.

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, it I remove the bulk actions then it displays a Wordpress Failure Notice "Are you sure you want to do this? - Please try again", which is a nonce validation error. And in both cases, the none of the data I change for the post is saved.

I've included a link below to the full script that registers the taxonomy, creates the table and saves the metabox data. Feel free to fiddle with it to get it working if you like.

http://www.writeurl.com/text/aawwgq1od5913fmyfjyu/0y84g3vcyptvrkvvfxq4/0dow449a06lj6ctkqjqo

A NOTE TO THE STACKOVERFLOW EDITORS: I have already checked through questions suggested while I type mine and also all questions tagged with wp-list-table plus scoured the depths of Google and none of them seem relevant to my situation.

UPDATE: If possible I'd prefer to not to have to use any bulk_actions. I've no need for them on this table. I only added the delete action for testing purposes. And the class method "process_bulk_action" doesn't even get called on submit anyway.

<?php
if(!class_exists('WP_List_Table')) {
    require_once(ABSPATH.'wp-admin/includes/class-wp-list-table.php');
}

class servicesTable extends WP_List_Table {
    private $allServices;
    private $currentServices;
    private $currentServicesData;

    public function __construct(){
        parent::__construct(
            array(
                'singular' => 'service',
                'plural' => 'services',
                'ajax' => false
            )
        );
    }

    private function table_data() {
        $this->currentServices = wp_get_object_terms(get_the_ID(),array('service'),array('fields' => 'ids'));
        $this->currentServicesData = (array) maybe_unserialize(get_post_meta(get_the_ID(),'currentServices',true));
        $this->allServices = array();
        foreach((array) get_terms(array('service'),array('hide_empty' => false)) as $service) {
            $this->allServices[] = array(
                'id' => $service->term_id,
                'serviceTitle' => $service->name,
                'attachments' => array_key_exists($service->term_id,$this->currentServicesData) ? explode(',',$this->currentServicesData[$service->term_id]['attachments']) : array(),
                'lastServiced' =>  (array_key_exists($service->term_id,$this->currentServicesData) && strlen($this->currentServicesData[$service->term_id]['last_serviced']) > 0) ? date('Y-m-d',strtotime($this->currentServicesData[$service->term_id]['last_serviced'])) : '',
                'nextService' =>  (array_key_exists($service->term_id,$this->currentServicesData) && strlen($this->currentServicesData[$service->term_id]['next_service']) > 0) ? date('Y-m-d',strtotime($this->currentServicesData[$service->term_id]['next_service'])) : '',
                'invoices' => ''
            );
        }
        return $this->allServices;
    }

    public function column_default($item,$columnName) {
        return array_key_exists($columnName,$item) ? $item[$columnName] : print_r($item,true);
    }

    public function get_columns(){
        return array(
            'cb' => '<input type="checkbox" />',
            'id' => 'ID',
            'serviceTitle' => __('Title','brokertech'),
            'attachments' => __('Attachments','brokertech'),
            'lastServiced' => __('Last Service Date','brokertech'),
            'nextService' => __('Next Service Date','brokertech'),
            'invoices' => __('Invoices','brokertech')
        );
    }

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

    public function get_sortable_columns() {
        return array(
            'lastServiced' => array('lastServiced',true),
            'nextService' => array('nextService',true)
        );
    }

    private function sort_data($a,$b) {
        $orderby = empty($_GET['orderby']) ? 'nextService' : $_GET['orderby'];
        $order = empty($_GET['order']) ? 'asc' : $_GET['order'];
        $result = strcmp($a[$orderby],$b[$orderby]);
        return ($order === 'asc') ? $result : -$result;
    }

    public function prepare_items() {
        usort($this->table_data(),array(&$this,'sort_data'));
        $this->_column_headers = array($this->get_columns(),$this->get_hidden_columns(),$this->get_sortable_columns());
        $this->items = $this->allServices;
        wp_enqueue_script('services',JS_URI.'servicesSelector.js',array('thickbox','media-upload'));
        wp_enqueue_style('thickbox');
        wp_localize_script(
        'services',
        'servicesData',
            array(
                'editAttachment' => __('Edit'),
                'removeAttachment' => __('Remove')
            )
        );
    }

    public function column_cb($item) {
        return sprintf('<input type="checkbox" name="services[%s][enable]" value="%s" %s />',$item['id'],$item['serviceTitle'],checked(true,in_array($item['id'],$this->currentServices),false));
    }

    public function column_id($item) {
        return sprintf('<input type="hidden" name="services[%s][id]" value="%s" />',$item['id'],$item['id']);
    }

    public function column_attachments($item) {
        $html  = sprintf(
            '<input type="hidden" name="services[%s][attachments]" value="%s" class="listAttachments" />',
            $item['id'],
            empty($item['attachments']) ? '' : implode(',',$item['attachments'])
        );
        $html .= sprintf('<input type="button" value="%s" class="button button-secondary addServiceAttachment" data-service-id="%s" />',__('Add'),$item['id']);
        $html .= "&nbsp;";
        if(empty($item['attachments'])) {
            $html .= sprintf('<input type="button" value="%s" class="button button-secondary clearAll" data-service-id="%s" disabled="disabled" />',__('Clear'),$item['id']);
        }
        else {
            $html .= sprintf('<input type="button" value="%s" class="button button-secondary clearAll" data-service-id="%s" />',__('Clear'),$item['id']);
            $html .= "<br /><br />";
            foreach($item['attachments'] as $attachment) {
                if(is_numeric($attachment)) {
                    $html .= sprintf(
                        "<div class='serviceAttachment' style='margin-bottom: 1em;'><span>%s</span><br /><a href='%s' target='_blank' class='button button-secondary'>%s</a><input type='button' class='removeServiceAttachment button button-secondary' value='%s' style='float:right;' /><div style='clear:both;'></div></div>",
                        get_the_title($attachment),
                        get_admin_url(get_current_blog_id(),'post.php?post='.$attachment.'&action=edit'),
                        __('Edit'),
                        __('Remove')
                    );
                }
            }
        }
        return $html;
    }

    public function column_lastServiced($item) {
        return sprintf('<input type="date" name="services[%s][last_serviced]" value="%s" />',$item['id'],$item['lastServiced']);
    }

    public function column_nextService($item) {
        return sprintf('<input type="date" name="services[%s][next_service]" value="%s" />',$item['id'],$item['nextService']);
    }

    public function column_invoices($item) {
        $html  = sprintf(
            '<input type="hidden" name="services[%s][invoices]" value="%s" class="listInvoices" />',
            $item['id'],
            empty($item['invoices']) ? '' : implode(',',$item['invoices'])
        );
        $html .= sprintf('<input type="button" value="%s" class="button button-secondary addInvoice" data-service-id="%s" />',__('Add'),$item['id']);
        $html .= "&nbsp;";
        if(!empty($item['invoices'])) {
            $html .= "<br /><br />";
            foreach($item['invoices'] as $invoice) {
                if(is_numeric($invoice)) {
                    $html .= sprintf(
                        "<div class='serviceInvoice' style='margin-bottom: 1em;'><span>%s</span><br /><a href='%s' target='_blank' class='button button-secondary'>%s</a><input type='button' class='removeInvoice button button-secondary' value='%s' style='float:right;' /><div style='clear:both;'></div></div>",
                        get_the_title($invoice),
                        get_admin_url(get_current_blog_id(),'post.php?post='.$attachment.'&action=edit'),
                        __('Edit'),
                        __('Remove')
                    );
                }
            }
        }
        return $html;
    }
}

function saveServices($postID) {
    if(empty($_POST) || (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)) {
        return;
    }
    $servicesData = array();
    $services = array();
    foreach($_POST['services'] as $service) {
        if(isset($service['enable'])) {
            $services[] = (string) $service['enable'];
                $servicesData[$service['id']] = $service;
        }
    }
    empty($servicesData) ? delete_post_meta($postID,'currentServices') : update_post_meta($postID,'currentServices',$servicesData);
    wp_set_object_terms($postID,$services,'service');
}
add_action('save_post','saveServices');

function servicesMetaBox() {
    $servicesTable = new servicesTable();
    $servicesTable->prepare_items();
    $servicesTable->display();
}

$serviceTaxonomy = new brokertechPostType;
$serviceTaxonomy->createTaxonomy(
    'service',
    array(
        'office'
    ),
    __('Service'),
    __('Services'),
    array(
        'public' => true,
        'hierarchical' => false,
        'query_var' => true,
        'update_count_callback' => '_update_post_term_count',
        'show_ui' => true,
        'rewrite' => array(
            'slug' => __('services')
        ),
        'meta_box_cb' => 'servicesMetaBox'
    )
);

function addServicesTaxonomyPage() {
    add_submenu_page(
        'edit.php?post_type=company',
        __('Services'),
        __('Services'),
        'edit_posts',
        'edit-tags.php?taxonomy=service&post_type=office'
    );
}
add_action('admin_menu','addServicesTaxonomyPage');

function changeServicesMetaBoxPosition() {
    global $wp_meta_boxes;
    unset($wp_meta_boxes['office']['side']['core']['tagsdiv-service']);
    add_meta_box(
        'tagsdiv-service',
        __('Services'),
        'servicesMetaBox',
        'office',
        'normal',
        'low'
    );
}
add_action('add_meta_boxes','changeServicesMetaBoxPosition',0);
share|improve this question
    
Removing the nonce field via javascript is a temporary fix, although it's not ideal and I feel I shouldn't have to do that anyway as it's meant to have a nonce field. –  Ben Mar 19 at 10:51
    
Ben, take a look at my Internal Links Checker Plugin on GitHub. I'm using a list table in a meta box there. Btw, the code in the link is unreadable and we prefer code to be in the question instead of an outside resource. Could you please move it over here? Thanks. –  kaiser Mar 19 at 11:14
    
Thanks @kaiser, I've added my latest code as I've made some changes to the columns, although still having the same issue. I always thought that large amounts of code is preferred to be referenced externally. Your plugin does work on my site however I'm not able to test it on my custom post type, so I'm therefore not able to tell if that's the cause of the problem. –  Ben Mar 19 at 14:24
    
Ben, just change this line in your copy of the Internal Link Checker. And could you please rework your code - the indentation seems to be gone. –  kaiser Mar 19 at 16:29
    
@kaiser, yes, your list_table does work perfectly on my post_type however I can see that you don't have the nonce or referer fields added to yours, actually you don't have any input fields at all whereas mine does. Could it possibly be that the metabox is for a custom taxonomy instead a standard metabox? I could quite easily that and get back to you. Indentation has been fixed for you btw. –  Ben Mar 20 at 10:22

1 Answer 1

up vote 1 down vote accepted

You just need to override the function display_tablenav() with a blank function in you parent class.

It is explained here.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.