'order', 'plural' => 'orders', 'ajax' => false ) ); // Use registered types $types = array_keys( edd_get_order_types() ); if ( ! empty( $_GET['order_type'] ) && in_array( $_GET['order_type'], $types, true ) ) { $this->type = sanitize_key( $_GET['order_type'] ); // Default to 'sale' if type is unrecognized } else { $this->type = 'sale'; } $this->set_base_url(); $this->filter_bar_hooks(); $this->get_payment_counts(); } /** * Set the base URL. * * This retains the current order-type, or 'sale' by default. * * @since 3.0 */ private function set_base_url() { // Carry the type over to the base URL $this->base_url = edd_get_admin_url( array( 'page' => 'edd-payment-history', 'order_type' => sanitize_key( $this->type ), ) ); } /** * Hook in filter bar actions * * @since 3.0 */ private function filter_bar_hooks() { add_action( 'edd_admin_filter_bar_orders', array( $this, 'filter_bar_items' ) ); add_action( 'edd_after_admin_filter_bar_orders', array( $this, 'filter_bar_searchbox' ) ); } /** * Display advanced filters. * * @since 1.4 * @since 3.0 Add a filter for modes. * Display 'Advanced Filters' */ public function advanced_filters() { // Hide when viewing Refunds. if ( 'refund' === $this->type ) { return; } edd_admin_filter_bar( 'orders' ); } /** * Output filter bar items * * @since 3.0 */ public function filter_bar_items() { // Get values $start_date = isset( $_GET['start-date'] ) ? sanitize_text_field( $_GET['start-date'] ) : null; $end_date = isset( $_GET['end-date'] ) ? sanitize_text_field( $_GET['end-date'] ) : null; $gateway = isset( $_GET['gateway'] ) ? sanitize_key( $_GET['gateway'] ) : 'all'; $mode = isset( $_GET['mode'] ) ? sanitize_key( $_GET['mode'] ) : 'all'; $order_total_filter_type = isset( $_GET['order-amount-filter-type'] ) ? sanitize_text_field( $_GET['order-amount-filter-type'] ) : false; $order_total_filter_amount = isset( $_GET['order-amount-filter-value'] ) ? sanitize_text_field( $_GET['order-amount-filter-value'] ) : ''; $country = isset( $_GET['order-country-filter-value'] ) ? sanitize_text_field( $_GET['order-country-filter-value'] ) : ''; $region = isset( $_GET['order-region-filter-value'] ) ? sanitize_text_field( $_GET['order-region-filter-value'] ) : ''; $product_id = ! empty( $_GET['product-id'] ) ? sanitize_text_field( $_GET['product-id'] ) : false; $status = $this->get_status(); $clear_url = $this->base_url; // Filters $all_modes = edd_get_payment_modes(); $all_gateways = edd_get_payment_gateways(); // No modes if ( empty( $all_modes ) ) { $modes = array(); // Add "All" and pluck labels } else { $modes = array_merge( array( 'all' => __( 'All modes', 'easy-digital-downloads' ) ), wp_list_pluck( $all_modes, 'admin_label' ) ); } // No gateways if ( empty( $all_gateways ) ) { $gateways = array(); // Add "All" and pluck labels } else { $gateways = array_merge( array( 'all' => __( 'All gateways', 'easy-digital-downloads' ) ), wp_list_pluck( $all_gateways, 'admin_label' ) ); } /** * Allow gateways that aren't registered the standard way to be displayed in the dropdown. * * @since 2.8.11 */ $gateways = apply_filters( 'edd_payments_table_gateways', $gateways ); // Output the items if ( ! empty( $modes ) ) : ?> html->select( array( 'options' => $modes, 'name' => 'mode', 'id' => 'mode', 'selected' => $mode, 'show_option_all' => false, 'show_option_none' => false ) ); ?> html->date_field( array( 'id' => 'start-date', 'name' => 'start-date', 'placeholder' => _x( 'From', 'date filter', 'easy-digital-downloads' ), 'value' => $start_date ) ); echo EDD()->html->date_field( array( 'id' => 'end-date', 'name' => 'end-date', 'placeholder' => _x( 'To', 'date filter', 'easy-digital-downloads' ), 'value' => $end_date ) ); ?> html->select( array( 'options' => $gateways, 'name' => 'gateway', 'id' => 'gateway', 'selected' => $gateway, 'show_option_all' => false, 'show_option_none' => false ) ); ?>
__( 'equal to', 'easy-digital-downloads' ), 'gt' => __( 'greater than', 'easy-digital-downloads' ), 'lt' => __( 'less than', 'easy-digital-downloads' ), ); echo EDD()->html->select( array( 'id' => 'order-amount-filter-type', 'name' => 'order-amount-filter-type', 'options' => $options, 'selected' => $order_total_filter_type, 'show_option_all' => false, 'show_option_none' => false, ) ); ?>
html->product_dropdown( array( 'id' => 'orders-filter-product-id', 'name' => 'product-id', 'chosen' => true, 'selected' => $product_id, 'variations' => true ) ); ?>
html->country_select( array( 'name' => 'order-country-filter-value', ), esc_html( $country ) ); echo EDD()->html->region_select( array( 'name' => 'order-region-filter-value', ), esc_html( $country ), esc_html( $region ) ); ?>
search_box( esc_html__( 'Search', 'easy-digital-downloads' ), 'edd-payments' ); } /** * Show the search field. * * @since 1.4 * * @param string $text Label for the search box. * @param string $input_id ID of the search box. */ public function search_box( $text, $input_id ) { // Bail if no customers and no search. if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) { return; } $input_id = $input_id . '-search-input'; if ( ! empty( $_REQUEST['orderby'] ) ) { echo ''; } if ( ! empty( $_REQUEST['order'] ) ) { echo ''; } ?>

get_var( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'edd_payment' LIMIT 1" ); if ( ! empty( $orders ) ) { esc_html_e( 'Easy Digital Downloads needs to upgrade the database. Orders will be available when that has completed.', 'easy-digital-downloads' ); return; } } $status = $this->get_status(); if ( ! empty( $status ) ) { $status_label = edd_get_status_label( $status ); switch ( $status ) { case 'trash': /* Translators: %s is for the status of 'Trash', telling the user no items were found in the trash. */ $message = sprintf( __( 'No orders found in %s.', 'easy-digital-downloads' ), $status_label ); break; default: /* Translators: %s is for the currently viewed order status filter */ $message = sprintf( __( 'No %s orders found.', 'easy-digital-downloads' ), $status_label ); break; } echo esc_html( $message ); } else { esc_html_e( 'No orders found.', 'easy-digital-downloads' ); } } /** * Retrieve the table columns. * * @since 1.4 * * @return array $columns Array of all the list table columns. */ public function get_columns() { $columns = array( 'cb' => '', // Render a checkbox instead of text 'number' => __( 'Number', 'easy-digital-downloads' ), 'customer' => __( 'Customer', 'easy-digital-downloads' ), 'gateway' => __( 'Gateway', 'easy-digital-downloads' ), 'amount' => __( 'Total', 'easy-digital-downloads' ), 'date' => __( 'Date', 'easy-digital-downloads' ), 'status' => __( 'Status', 'easy-digital-downloads' ), ); if ( 'refund' === $this->type ) { unset( $columns['status'] ); } /** * Filters the columns for Orders and Refunds table. * * @since unknown * * @param array $columns Table columns. */ $columns = apply_filters( 'edd_payments_table_columns', $columns ); return $columns; } /** * Retrieve the sortable columns. * * @since 1.4 * * @return array Array of all the sortable columns. */ public function get_sortable_columns() { return apply_filters( 'edd_payments_table_sortable_columns', array( 'number' => array( 'id', true ), 'status' => array( 'status', false ), 'customer' => array( 'customer_id', false ), 'gateway' => array( 'gateway', false ), 'amount' => array( 'total', false ), 'date' => array( 'date_created', false ) ) ); } /** * Gets the name of the primary column. * * @since 2.5 * @access protected * * @return string Name of the primary column. */ protected function get_primary_column_name() { return 'number'; } /** * This function renders most of the columns in the list table. * * @since 1.4 * @since 3.0 Updated to use the new EDD\Orders\Order class. * * @param EDD\Orders\Order $order Order object. * @param string $column_name The name of the column. * * @return string Column name. */ public function column_default( $order, $column_name ) { $timezone_abbreviation = edd_get_timezone_abbr(); switch ( $column_name ) { case 'amount': $currency = ! empty( $order->currency ) ? $order->currency : edd_get_currency(); $value = edd_display_amount( $order->total, $currency ); break; case 'date': $value = ''; break; case 'gateway': $value = edd_get_gateway_admin_label( $order->gateway ); if ( empty( $value ) ) { $value = '—'; } break; case 'status': $value = edd_get_order_status_badge( $order->status ); break; default: $value = method_exists( $order, 'get_' . $column_name ) ? call_user_func( array( $order, 'get_' . $column_name ) ) : ''; break; } return apply_filters( 'edd_payments_table_column', $value, $order->id, $column_name ); } /** * Render the checkbox column. * * @since 1.4 * @since 3.0 Updated to use the new EDD\Orders\Order class. * * @param EDD\Orders\Order $order Order object. * @return string Displays a checkbox. */ public function column_cb( $order ) { $order_number = 'sale' === $order->type ? $order->get_number() : $order->order_number; return sprintf( '', 'order', absint( $order->id ), /* translators: the order number */ esc_html( sprintf( __( 'Select %s', 'easy-digital-downloads' ), $order_number ) ) ); } /** * Render the ID column. * * @since 2.0 * @since 3.0 Updated to use the new EDD\Orders\Order class. * * @param EDD\Orders\Order $order Order object. * @return string Displays a checkbox. */ public function column_number( $order ) { $status = $this->get_status(); // View URL. $view_url = edd_get_admin_url( array( 'page' => 'edd-payment-history', 'view' => 'sale' === $order->type ? 'view-order-details' : 'view-refund-details', 'id' => absint( $order->id ), ) ); // Default row actions. $row_actions = array( 'view' => '' . esc_html__( 'Edit', 'easy-digital-downloads' ) . '', ); // View receipt. if ( 'sale' === $order->type && edd_can_view_receipt( $order ) ) { $row_actions['receipt'] = sprintf( '%s', edd_get_receipt_page_uri( $order->id ), __( 'View Receipt', 'easy-digital-downloads' ) ); } // Resend Receipt. if ( 'sale' === $this->type && 'complete' === $order->status && ! empty( $order->email ) ) { $row_actions['email_links'] = '' . __( 'Resend Receipt', 'easy-digital-downloads' ) . ''; } // Keep Delete at the end. if ( edd_is_order_trashable( $order->id ) ) { $trash_url = wp_nonce_url( add_query_arg( array( 'edd-action' => 'trash_order', 'purchase_id' => absint( $order->id ), ), $this->base_url ), 'edd_payment_nonce' ); $row_actions['trash'] = '' . esc_html__( 'Trash', 'easy-digital-downloads' ) . ''; } elseif ( edd_is_order_restorable( $order->id ) ) { $restore_url = wp_nonce_url( add_query_arg( array( 'edd-action' => 'restore_order', 'purchase_id' => absint( $order->id ), ), $this->base_url ), 'edd_payment_nonce' ); $row_actions['restore'] = '' . esc_html__( 'Restore', 'easy-digital-downloads' ) . ''; $delete_url = wp_nonce_url( add_query_arg( array( 'edd-action' => 'delete_order', 'purchase_id' => absint( $order->id ), ), $this->base_url ), 'edd_payment_nonce' ); $row_actions['delete'] = '' . esc_html__( 'Delete Permanently', 'easy-digital-downloads' ) . ''; unset( $row_actions['view'] ); } if ( has_filter( 'edd_payment_row_actions' ) ) { $payment = edd_get_payment( $order->id ); /** * Filters the row actions. * * @deprecated 3.0 * * @param array $row_actions * @param EDD_Payment|false $payment */ $row_actions = apply_filters_deprecated( 'edd_payment_row_actions', array( $row_actions, $payment ), '3.0', 'edd_order_row_actions' ); } /** * Filters the row actions. * * @param array $row_actions Array of row actions. * @param EDD\Orders\Order $order Order object. * * @since 3.0 */ $row_actions = apply_filters( 'edd_order_row_actions', $row_actions, $order ); // Row actions $actions = $this->row_actions( $row_actions ); // Primary link $order_number = 'sale' === $order->type ? $order->get_number() : $order->order_number; $link = edd_is_order_restorable( $order->id ) ? '' . esc_html( $order_number ) . '' : '' . esc_html( $order_number ) . ''; // Concatenate & return the results return $link . $actions; } /** * Render the Customer column. * * @since 2.4.3 * @since 3.0 Updated to use the new EDD\Orders\Order class. * * @param EDD\Orders\Order $order Order object. * @return string Data shown in the Customer column. */ public function column_customer( $order ) { $customer_id = $order->customer_id; $customer = edd_get_customer( $customer_id ); // Actions if exists if ( ! empty( $customer ) ) { // Use customer name, if exists $name = ! empty( $customer->name ) ? $customer->name : $order->email; // Link to View Customer $url = edd_get_admin_url( array( 'page' => 'edd-customers', 'view' => 'overview', 'id' => absint( $customer_id ), ) ); $name = '' . esc_html( $name ) . ''; if ( ! empty( $customer->name ) ) { $name .= '
' . $order->email . '
'; } } else { $name = $order->email; } /** * Filters the output of the Email column in the Payments table. * * @since 1.4 * @since 3.0 Run manually inside of the `customer` column for backwards compatibility. * * @param string $name Customer name. * @param int $order_id ID of the Payment/Order. * @param string $column_name Name of the current column (email). */ $name = apply_filters( 'edd_payments_table_column', $name, $order->id, 'email' ); return $name; } /** * Retrieve the bulk actions. * * @since 1.4 * * @return array $actions Bulk actions. */ public function get_bulk_actions() { if ( 'refund' !== $this->type ) { $action = array( 'set-status-complete' => __( 'Mark Completed', 'easy-digital-downloads' ), 'set-status-pending' => __( 'Mark Pending', 'easy-digital-downloads' ), 'set-status-processing' => __( 'Mark Processing', 'easy-digital-downloads' ), 'set-status-revoked' => __( 'Mark Revoked', 'easy-digital-downloads' ), 'set-status-failed' => __( 'Mark Failed', 'easy-digital-downloads' ), 'set-status-abandoned' => __( 'Mark Abandoned', 'easy-digital-downloads' ), 'set-status-preapproval' => __( 'Mark Preapproved', 'easy-digital-downloads' ), 'set-status-cancelled' => __( 'Mark Cancelled', 'easy-digital-downloads' ), 'resend-receipt' => __( 'Resend Receipts', 'easy-digital-downloads' ), ); } else { $action = array(); } if ( 'trash' === $this->get_status() ) { $action = array( 'restore' => __( 'Restore', 'easy-digital-downloads' ), ); if ( current_user_can( 'delete_shop_payments' ) ) { $action['delete'] = __( 'Delete permanently', 'easy-digital-downloads' ); } } else { $action['trash'] = __( 'Move to Trash', 'easy-digital-downloads' ); } return apply_filters( 'edd_payments_table_bulk_actions', $action ); } /** * Process the bulk actions. * * @since 1.4 * @since 3.0 Updated to display _doing_it_wrong(). * * @see edd_orders_list_table_process_bulk_actions() */ public function process_bulk_action() { _doing_it_wrong( __FUNCTION__, 'Orders list table bulk actions are now handled by edd_orders_list_table_process_bulk_actions(). Please do not call this method directly.', 'EDD 3.0' ); } /** * Retrieve the payment counts. * * @since 1.4 */ public function get_payment_counts() { // Get the args (without pagination) $args = $this->parse_args( false ); unset( $args['status'], $args['status__not_in'], $args['status__in'] ); // Get order counts by type $this->counts = edd_get_order_counts( $args ); } /** * Retrieves all the data for all the orders. * * @since 1.4 * @deprecated 3.0 Use get_data() * * @return array $payment_data Array of all the data for the orders. */ public function payments_data() { _edd_deprecated_function( __METHOD__, '3.0', 'EDD_Payment_History_Table::get_data()' ); return $this->get_data(); } /** * Retrieves all of the orders data based on current filters. * * @since 3.0 * * @return array Orders table data. */ public function get_data() { // Parse args (with pagination) $this->args = $this->parse_args( true ); // Force EDD\Orders\Order objects to be returned $this->args['output'] = 'orders'; if ( empty( $this->args['status'] ) ) { $this->args['status__not_in'] = array( 'trash' ); } // Get data $items = edd_get_orders( $this->args ); // Get customer IDs and count from payments $customer_ids = array_unique( wp_list_pluck( $items, 'customer_id' ) ); $cust_count = count( $customer_ids ); // Maybe prime customer objects (if more than number of queries) if ( $cust_count > 1 ) { edd_get_customers( array( 'id__in' => $customer_ids, 'no_found_rows' => true, 'number' => $cust_count ) ); } // Return items return $items; } /** * Retrieves the Payments table views. * * @since 1.4 * * @return array $views Available views. */ public function get_views() { $views = parent::get_views(); /** * Filters the Payment table's views. * * @since 1.4 * * @param array $views Payment table's views. */ $views = apply_filters( 'edd_payments_table_views', $views ); return $views; } /** * Builds an array of arguments for getting orders for the list table, counts, and pagination. * * @since 3.0 * * @param bool $paginate Whether to add pagination arguments * * @return array Array of arguments to use for querying orders. */ private function parse_args( $paginate = true ) { $status = $this->get_status(); $user = isset( $_GET['user'] ) ? absint( $_GET['user'] ) : null; $customer = isset( $_GET['customer'] ) ? absint( $_GET['customer'] ) : null; $search = isset( $_GET['s'] ) ? sanitize_text_field( $_GET['s'] ) : null; $start_date = isset( $_GET['start-date'] ) ? sanitize_text_field( $_GET['start-date'] ) : null; $end_date = isset( $_GET['end-date'] ) ? sanitize_text_field( $_GET['end-date'] ) : $start_date; $gateway = isset( $_GET['gateway'] ) ? sanitize_text_field( $_GET['gateway'] ) : null; $mode = isset( $_GET['mode'] ) ? sanitize_text_field( $_GET['mode'] ) : null; $type = isset( $_GET['order_type'] ) ? sanitize_text_field( $_GET['order_type'] ) : 'sale'; /** * Introduced as part of #6063. Allow a gateway to specified based on the context. * * @see https://github.com/easydigitaldownloads/easy-digital-downloads/issues/6063 * @since 2.8.11 * * @param string $gateway */ $gateway = apply_filters( 'edd_payments_table_search_gateway', $gateway ); if ( $gateway === 'all' ) { $gateway = null; } if ( $mode === 'all' ) { $mode = null; } $args = array_filter( array( 'user_id' => $user, 'customer_id' => $customer, 'status' => $status, 'gateway' => $gateway, 'mode' => $mode, 'type' => $type, ) ); // Update args by search query. if ( ! empty( $search ) ) { $args = $this->parse_search( $search, $args ); } // Date query if ( ! empty( $start_date ) || ! empty( $end_date ) ) { // start AND end $args['date_query'] = array( 'relation' => 'AND' ); // Start (of day) if ( ! empty( $start_date ) ) { $start_date_obj = EDD()->utils->date( $start_date, edd_get_timezone_id(), false )->copy()->startOfDay(); $query_start_date = edd_get_utc_equivalent_date( $start_date_obj->copy() ); $args['date_query'][] = array( 'column' => 'date_created', 'after' => $query_start_date->format( 'mysql' ), ); } // End (of day) if ( ! empty( $end_date ) ) { $end_date_obj = EDD()->utils->date( $end_date, edd_get_timezone_id(), false )->copy()->endOfDay(); $query_end_date = edd_get_utc_equivalent_date( $end_date_obj->copy() ); $args['date_query'][] = array( 'column' => 'date_created', 'before' => $query_end_date->format( 'mysql' ), ); } } // Maybe filter by order amount. if ( isset( $_GET['order-amount-filter-type'] ) && ! empty( $_GET['order-amount-filter-value'] ) ) { $filter_amount = floatval( sanitize_text_field( $_GET['order-amount-filter-value'] ) ); switch ( $_GET['order-amount-filter-type'] ) { case 'lt': $filter_type = '<'; break; case 'gt': $filter_type = '>'; break; default: $filter_type = '='; break; } $args['compare_query'] = array( array( 'key' => 'total', 'value' => $filter_amount, 'compare' => $filter_type, ), ); } // Maybe filter by product. if ( ! empty( $_GET['product-id'] ) ) { if ( false !== strpos( $_GET['product-id'], '_' ) ) { $product_pieces = explode( '_', $_GET['product-id'] ); $args['product_id'] = absint( $product_pieces[0] ); $args['product_price_id'] = absint( $product_pieces[1] ); } else { $args['product_id'] = absint( $_GET['product-id'] ); } } // Maybe filter by country. if ( ! empty( $_GET['order-country-filter-value'] ) ) { $args['country'] = sanitize_text_field( $_GET['order-country-filter-value'] ); } // Maybe filter by region. if ( ! empty( $_GET['order-region-filter-value'] ) ) { $args['region'] = sanitize_text_field( $_GET['order-region-filter-value'] ); } // Maybe filter by discount ID. if ( ! empty( $_GET['discount_id'] ) ) { $args['discount_id'] = absint( $_GET['discount_id'] ); } /** * Filters array of arguments for getting orders for the list table, counts, and pagination. * * @since 3.0 * * @param array $args Array of arguments to use for querying orders. * @param bool $paginate $paginate Whether to add pagination arguments */ $args = apply_filters( 'edd_payments_table_parse_args', $args, $paginate ); // Return args, possibly with pagination return ( true === $paginate ) ? $this->parse_pagination_args( $args ) : $args; } /** * Parse the search query. * * @since 3.0.2 * @param string $search * @param array $args * @return array */ private function parse_search( $search, $args ) { // Transaction ID if ( is_string( $search ) && ( false !== strpos( $search, 'txn:' ) ) ) { $args['txn'] = trim( str_replace( 'txn:', '', $search ) ); return $args; } // Email if ( is_email( $search ) ) { $args['email'] = $search; return $args; } // Download ID if ( is_string( $search ) && ( false !== strpos( $search, '#' ) ) ) { $args['product_id'] = intval( trim( str_replace( '#', '', $search ) ) ); return $args; } // Order ID if ( is_numeric( $search ) ) { $args['id'] = $search; return $args; } // The customer’s name or ID prefixed by customer: if ( ! is_array( $search ) && ( false !== strpos( $search, 'customer:' ) ) ) { $search = trim( str_replace( 'customer:', '', $search ) ); // Search by customer ID. if ( is_numeric( $search ) ) { $args['customer_id'] = absint( $search ); return $args; } // Get customer IDs that match the search string. $customers = edd_get_customers( array( 'search' => $search, 'fields' => 'ids', ) ); if ( ! empty( $customers ) ) { $args['customer_id__in'] = $customers; } else { $args['customer_id__in'] = array( null ); } return $args; } // The user ID prefixed by user: if ( ! is_array( $search ) && ( false !== strpos( $search, 'user:' ) ) ) { $search = trim( str_replace( 'user:', '', $search ) ); if ( is_numeric( $search ) ) { $args['user_id'] = absint( $search ); return $args; } } // The Discount Code prefixed by discount: if ( is_string( $search ) && ( false !== strpos( $search, 'discount:' ) ) ) { $discount = edd_get_discount_by_code( trim( str_replace( 'discount:', '', $search ) ) ); if ( ! empty( $discount->id ) ) { $args['discount_id'] = $discount->id; } else { // If no discount object is found, we force the results to be empty. $args['id__in'] = array( null ); } return $args; } // Default search $args['search'] = $search; return $args; } /** * Setup the final data for the table. * * @since 1.4 */ public function prepare_items() { wp_reset_vars( array( 'action', 'order', 'orderby', 'order', 's' ) ); $hidden = array(); // No hidden columns $columns = $this->get_columns(); $sortable = $this->get_sortable_columns(); $status = $this->get_status( 'total' ); $this->items = $this->get_data(); $this->_column_headers = array( $columns, $hidden, $sortable ); if ( empty( $this->counts[ $status ] ) ) { return; } $this->set_pagination_args( array( 'total_pages' => ceil( $this->counts[ $status ] / $this->per_page ), 'total_items' => $this->counts[ $status ], 'per_page' => $this->per_page, ) ); } /** * Generate the table navigation above or below the table. * We're overriding this to turn off the referer param in `wp_nonce_field()`. * * @param string $which * @since 3.1.0.4 * @since 3.1.1 Outputs the dialogs for deleting orders. */ protected function display_tablenav( $which ) { if ( 'top' === $which ) : wp_nonce_field( 'bulk-' . $this->_args['plural'], '_wpnonce', false ); ?>
has_items() ) : ?>
bulk_actions( $which ); ?>
extra_tablenav( $which ); $this->pagination( $which ); ?>