I've added a couple of custom field columns to our Woocommerce orders list in the admin of WordPress using the methods below, but the sort is not working....
add_filter( 'manage_edit-shop_order_columns', 'my_wc_columns' );
function my_wc_columns($columns){
$new_columns = (is_array($columns)) ? $columns : array();
unset( $new_columns['order_actions'] );
$new_columns['program_id'] = 'Program';
$new_columns['constituent_id'] = 'Constituent ID';
$new_columns['order_actions'] = $columns['order_actions'];
return $new_columns;
}
add_action( 'manage_shop_order_posts_custom_column', 'my_wc_column_values', 2 );
function my_wc_column_values($column){
global $post;
if ( $column == 'program_id' ) {
$program = get_post_meta( $post->ID, '_program_id', true );
$program_title = get_the_title($program);
$column_val = (isset($program) && $program>0 ? $program_title : 'All');
echo '<span>' . my_programs_get_name( $column_val ) . ' (' . $program . ')</span>';
}
if ( $column == 'constituent_id' ) {
$consid = get_post_meta( $post->ID, 'constituent_id', true );
$column_val = (isset($consid) && $consid != "") ? $consid : "";
echo '<span>' . $column_val . '</span>';
}
}
// Make column sortable
add_filter( "manage_edit-shop_order_sortable_columns", 'my_wc_column_sort' );
function my_wc_column_sort( $columns ) {
$custom = array(
'program_id' => '_program_id',
'constituent_id' => 'constituent_id',
);
return wp_parse_args( $custom, $columns );
}
I expected to have an issue perhaps with the program name, since it is an id that needs to be translated via a custom function to a name, but neither column is sorting properly. The records change order after clicking their column titles, but I cannot tell how the sort is being done. The program is not sorting on name or ID and both are seem random but consistent. Keep in mind both fields are custom fields that may or may not have a value defined. How can I make this sortable?
Here's a good tutorial on custom sortable columns. After you register the column, you need to handle the actual sorting. Sadly, that part doesn't happen automagically. Untested, but adapted from the above tutorial:
add_action( 'pre_get_posts', 'manage_wp_posts_be_qe_pre_get_posts', 1 );
function manage_wp_posts_be_qe_pre_get_posts( $query ) {
/**
* We only want our code to run in the main WP query
* AND if an orderby query variable is designated.
*/
if ( $query->is_main_query() && ( $orderby = $query->get( 'orderby' ) ) ) {
switch( $orderby ) {
// If we're ordering by 'program_id'
case 'program_id':
// set our query's meta_key, which is used for custom fields
$query->set( 'meta_key', '_program_id' );
/**
* Tell the query to order by our custom field/meta_key's
* value
*
* If your meta value are numbers, change 'meta_value'
* to 'meta_value_num'.
*/
$query->set( 'orderby', 'meta_value' );
break;
}
}
}
Related
I have a site that has tens of thousands of orders, which I need to compare the billing and customer/user emails and show a flag if they don't match. One of the stipulations is that I'm unable to add any metadata to the orders. So my solution is to just add a custom column, and compare the emails on the fly when the orders list is rendered. That works just fine.
add_filter( 'manage_edit-shop_order_columns', 'mismatched_orders_column' );
function mismatched_orders_column( $columns ) {
$columns['mismatched'] = 'Mismatched';
return $columns;
}
add_action( 'manage_shop_order_posts_custom_column', 'mismatched_orders_column_data' );
function mismatched_orders_column_data( $column ) {
global $post;
if ( 'mismatched' === $column ) {
$order = new WC_Order( $post->ID );
$customer = $order->get_user();
$result = '';
$billing_email = strtolower ( $order->get_billing_email() );
$customer_email = '';
if ($customer) $customer_email = strtolower ( $customer->user_email );
if ( $customer && ( $billing_email != $customer_email ) ) {
$result = '<span class="mismatched-order" title="Possible order mismatch">Yes</span>';
}
echo $result;
}
}
My issue is when trying to add sorting. Because I'm not accessing any post metadata, I don't have any easy data to sort via the main query. My solution here was originally to hook into pre_get_posts, grab all the orders in a new WP_Query, then loop through them and add the ones that had mismatched emails to an array for use in post__in.
This works/worked fine on my small dev site, but throws fatal memory errors when trying to loop over any more than about 8 or 9 thousand posts (out of a total of 30-40 thousand). Increasing memory isn't really an option.
add_filter( 'manage_edit-shop_order_sortable_columns', 'mismatched_orders_column_sortable');
function mismatched_orders_column_sortable( $columns ) {
$columns['mismatched'] = 'mismatched';
return $columns;
}
add_action( 'pre_get_posts', 'mismatched_emails_posts_orderby' );
function mismatched_emails_posts_orderby( $query ) {
if( ! is_admin() || ! $query->is_main_query() ) {
return;
}
//Remove the pre_get_posts hook so we don't get stuck in a loop
add_action( 'pre_get_posts', 'mismatched_emails_posts_orderby' );
//Make sure we're only looking at our custom column
if ( 'mismatched' === $query->get( 'orderby') ) {
//Set our initial array for 'post__in'
$mismatched = array();
$orders_list = get_posts(array(
'post_type' => 'shop_order',
'posts_per_page' => -1,
'post_status' => 'any',
'fields' => 'ids'
));
//And here is our problem
foreach( $orders_list as $order_post ) :
//Get our order and customer/user object
$order_object = new WC_Order( $order_post );
$customer = $order_object->get_user();
//Check that billing and customer emails don't match, and also that we're not dealing with a guest order
if ( ( $order_object->get_billing_email() != $customer->user_email ) && $order_object->get_user() != false ) {
$mismatched[] = $order_post;
}
endforeach; wp_reset_postdata();
$query->set( 'post__in', $mismatched );
}
}
I would seriously appreciate any insight into how I could either reduce the expense of the query I'm trying to run, or an alternate approach. Again, just for clarification, adding metadata to the orders isn't an option.
Thanks!
I'm trying to get my admin columns on edit-tags.php to be sortable by ACF field value, but I'm missing something. I can click the column heading to sort the column, but it only sorts the table by tag name and not the ACF field value in the column. If anyone can help, I'd really appreciate it!
I have set up the columns that I want to add:
/**
* Add columns to the post_tags admin table
*/
function add_post_tag_columns($columns){
$columns['featured'] = 'Featured';
$columns['order'] = 'Order';
$columns['group'] = 'Group';
// Remove description column from table
unset( $columns['description'] );
return $columns;
}
add_filter('manage_edit-post_tag_columns', 'add_post_tag_columns');
I have set the value for the tags in each column:
/**
* Set values for new post_tags admin table columns
*/
function add_post_tag_column_content( $content, $column_name, $term_id ){
$term= get_term($term_id, 'post_tag');
switch ( $column_name ) {
// display the value of ACF fields
case 'featured' :
$content = the_field( 'kw_featured_tag', $term );
break;
case 'order' :
$content = the_field( 'kw_tag_order', $term );
break;
case 'group' :
$content = the_field( 'kw_group_tags', $term );
break;
}
return $content;
}
add_filter('manage_post_tag_custom_column', 'add_post_tag_column_content', 10, 3);
I have set up column sorting
/**
* Make columns on post_tags admin table sortable
*/
function set_custom_post_tag_sortable_columns( $sortable_columns ) {
$sortable_columns['featured'] = 'kw_featured_tag';
$sortable_columns['order'] = 'kw_tag_order';
$sortable_columns['group'] = 'kw_group_tags';
return $sortable_columns;
}
add_filter( 'manage_edit-post_tag_sortable_columns', 'set_custom_post_tag_sortable_columns' );
And here's where [I think] I'm having the issue:
/**
* Set up the orderby for the column sorting
*/
function post_tag_custom_orderby( $query ) {
if ( ! is_admin() )
return;
$orderby = $query->get( 'orderby');
if ( 'featured' == $orderby ) {
$query->set( 'meta_key', 'kw_feature_tag' );
$query->set( 'orderby', 'meta_value_num' );
}
elseif ( 'order' == $orderby ) {
$query->set( 'meta_key', 'kw_tag_order' );
$query->set( 'orderby', 'meta_value_num' );
}
elseif ( 'group' == $orderby ) {
$query->set( 'meta_key', 'kw_group_tags' );
$query->set( 'orderby', 'meta_value' );
}
}
add_action( 'pre_get_posts', 'post_tag_custom_orderby' );
If anyone could help narrow down what I've done wrong, I would be a very happy Wordpress developer :P. Thanks in advance!
--
Edit: Here's a couple screenshots of my admin table:
Featured (kw_featured_tag) is a true/false
Order (kw_tag_order) is a number field
Group (kw_group_tags) is a
checkbox array (I changed it to return an array and now I need to fix
my code to get the list of items in the array to be displayed instead
of just "array")
I am trying to sort my taxonomy terms table by a custom field in Wordpress. But I am missing something.
I have a custom taxonomy. Let’s call it education in this example.
I added a field sort-order and save a value to that field using update_term_meta().
This works.
Next I added a column to the admin table for the taxonomy:
// Add the column to the table with terms
add_filter('manage_edit-education_columns', array( $this, 'addSortOrderColumn' ));
function addSortOrderColumn( $columns ){
$columnsBefore = array_slice( $columns, 0, 2, true); // NOTE: First column is the checkbox
$columnsAfter = array_slice( $columns, 2, count( $columns ), true);
$columnsInsert = array('sort-order' => ‘Sort’ );
$columns = array_merge($columnsBefore, $columnsInsert, $columnsAfter);
return $columns;
}
// Display the contents for the column
add_filter('manage_education_custom_column',array( $this, 'addSortOrderColumnContent'), 10, 3 );
function addSortOrderColumnContent( $content, $columnName, $termId ){
if( $columnName !== 'sort-order' ){
return $content;
}
$termId = absint( $termId );
$order = get_term_meta( $termId, 'sort-order', true );
if( !empty( $order ) ){
$content .= esc_attr( $order );
}
return $content;
}
// Make the column sortable
add_filter( 'manage_edit-education_sortable_columns', array( $this, 'makeSortOrderColumnSortable' ));
function makeSortOrderColumnSortable( $sortable ){
$sortable[ 'sort-order' ] = 'sort-order';
return $sortable;
}
I see the column, I can click its header and it flips the table rows on click ( a c z e <-> e z c a ), but it does not sort it based on the contents.
What do I need to add to make the sorting aware of the column contents?
You need to tweak the underlying sql that the get_terms function runs to retrieve the taxonomy terms list. It defaults to the term name, clicking & changing the sort will change the orderby=xxx in the url, but will flow through the switch statement in the code and return to the default. The way to do it is through the terms_clauses filter:
add_filter( 'terms_clauses', 'filter_terms_clauses', 10, 3 );
/**
* Filter WP_Term_Query meta query
*
* #param object $query WP_Term_Query
* #return object
*/
function filter_terms_clauses( $pieces, $taxonomies, $args ) {
global $pagenow, $wpdb;
// Require ordering
$orderby = ( isset( $_GET['orderby'] ) ) ? trim( sanitize_text_field( $_GET['orderby'] ) ) : '';
if ( empty( $orderby ) ) { return $pieces; }
// set taxonomy
$taxonomy = $taxonomies[0];
// only if current taxonomy or edit page in admin
if ( !is_admin() || $pagenow !== 'edit-tags.php' || !in_array( $taxonomy, [ 'education' ] ) ) { return $pieces; }
// and ordering matches
if ( $orderby === 'sort-order' ) {
$pieces['join'] .= ' INNER JOIN ' . $wpdb->termmeta . ' AS tm ON t.term_id = tm.term_id ';
$pieces['where'] .= ' AND tm.meta_key = "sort-order"';
$pieces['orderby'] = ' ORDER BY tm.meta_value ';
}
return $pieces;
}
The WP_Term_Query is only from WP4.6 so this functionality is pretty fresh.
I need a "get products A, B and C for $xxx" special offer, products A, B and C must be available on their own, and the bundle is a special offer accessible through a coupon code.
On a marketing page hosting outside my site, I would like a button leading to my site that carries a query string like ?add-to-cart=244,249,200 so that once on my site, all bundle products are already added to the cart (instead of adding them one by one which sounds unacceptably tedious).
If not possible, then at least I'd like a landing page on my site with a single button adding all bundle products to cart at once.
I couldn't find working solutions googling around (here's one example). Any suggestion?
After some research I found that DsgnWrks wrote a hook that does exactly this. For your convenience, and in case the blog goes offline, I bluntly copied his code to this answer:
function woocommerce_maybe_add_multiple_products_to_cart( $url = false ) {
// Make sure WC is installed, and add-to-cart qauery arg exists, and contains at least one comma.
if ( ! class_exists( 'WC_Form_Handler' ) || empty( $_REQUEST['add-to-cart'] ) || false === strpos( $_REQUEST['add-to-cart'], ',' ) ) {
return;
}
// Remove WooCommerce's hook, as it's useless (doesn't handle multiple products).
remove_action( 'wp_loaded', array( 'WC_Form_Handler', 'add_to_cart_action' ), 20 );
$product_ids = explode( ',', $_REQUEST['add-to-cart'] );
$count = count( $product_ids );
$number = 0;
foreach ( $product_ids as $id_and_quantity ) {
// Check for quantities defined in curie notation (<product_id>:<product_quantity>)
// https://dsgnwrks.pro/snippets/woocommerce-allow-adding-multiple-products-to-the-cart-via-the-add-to-cart-query-string/#comment-12236
$id_and_quantity = explode( ':', $id_and_quantity );
$product_id = $id_and_quantity[0];
$_REQUEST['quantity'] = ! empty( $id_and_quantity[1] ) ? absint( $id_and_quantity[1] ) : 1;
if ( ++$number === $count ) {
// Ok, final item, let's send it back to woocommerce's add_to_cart_action method for handling.
$_REQUEST['add-to-cart'] = $product_id;
return WC_Form_Handler::add_to_cart_action( $url );
}
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->get_type(), $adding_to_cart );
// Variable product handling
if ( 'variable' === $add_to_cart_handler ) {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_variable', $product_id );
// Grouped Products
} elseif ( 'grouped' === $add_to_cart_handler ) {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_grouped', $product_id );
// Custom Handler
} elseif ( has_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler ) ){
do_action( 'woocommerce_add_to_cart_handler_' . $add_to_cart_handler, $url );
// Simple Products
} else {
woo_hack_invoke_private_method( 'WC_Form_Handler', 'add_to_cart_handler_simple', $product_id );
}
}
}
// Fire before the WC_Form_Handler::add_to_cart_action callback.
add_action( 'wp_loaded', 'woocommerce_maybe_add_multiple_products_to_cart', 15 );
/**
* Invoke class private method
*
* #since 0.1.0
*
* #param string $class_name
* #param string $methodName
*
* #return mixed
*/
function woo_hack_invoke_private_method( $class_name, $methodName ) {
if ( version_compare( phpversion(), '5.3', '<' ) ) {
throw new Exception( 'PHP version does not support ReflectionClass::setAccessible()', __LINE__ );
}
$args = func_get_args();
unset( $args[0], $args[1] );
$reflection = new ReflectionClass( $class_name );
$method = $reflection->getMethod( $methodName );
$method->setAccessible( true );
$args = array_merge( array( $class_name ), $args );
return call_user_func_array( array( $method, 'invoke' ), $args );
}
It works just like you'd expect, by providing a comma separated list of products. It even works with quantities using ?add-to-cart=63833:2,221916:4
I was, and am still looking for a 'pure' solution that allows to add multiple products to the cart without having to install a plugin or add custom actions. But for many, the above might be an appropriate solution
We have two plugins in a WordPress website
1) Plugin A has the code below
add_filter( 'manage_edit-book_columns', 'set_custom_edit_book_columns' );
add_action( 'manage_book_posts_custom_column' , 'custom_book_column', 10, 2 );
function set_custom_edit_book_columns($columns) {
unset( $columns['author'] );
$columns['book_author'] = __( 'Author', 'your_text_domain' );
$columns['publisher'] = __( 'Publisher', 'your_text_domain' );
return $columns;
}
function custom_book_column( $column, $post_id ) {
switch ( $column ) {
case 'book_author' :
$terms = get_the_term_list( $post_id , 'book_author' , '' , ',' , '' );
if ( is_string( $terms ) )
echo $terms;
else
_e( 'Unable to get author(s)', 'your_text_domain' );
break;
case 'publisher' :
echo get_post_meta( $post_id , 'publisher' , true );
break;
}
}
2) Now, I want to add one more column in this grid by adding code in plugin B , I don't want to edit plugin A to make fixes
3) Is it possible that we copy the code in plugin B? When I did so then there has double data in all columns. From plugin A and plugin B.
You simply have to add different data, at least different column IDs.
<?php
/**
* Plugin Name: Plugin B
*/
add_filter( 'manage_edit-book_columns', 'set_custom_edit_book_columns' );
add_action( 'manage_book_posts_custom_column' , 'custom_book_column', 10, 2 );
function set_custom_edit_book_columns($columns) {
$columns['new_column'] = __( 'Brand new column' ); // <-- Column ID
return $columns;
}
function custom_book_column( $column, $post_id ) {
switch ( $column ) {
case 'new_column' : // <-- Column ID
echo "Anything you want";
break;
}
}