Woocommerce REST API v2: Issue with adding custom data to orders - woocommerce

I have a site running an old version of Woo v2.5.5 using legacy v3 for the API. I was able to use the action woocommerce_api_order_response to add data to the orders.
Something like:
add_action( 'woocommerce_api_order_response', 'add_testing_api_function', 10, 1 );
function add_testing_api_function( $order_data ) {
$order_data['foo'] = "testing";
return $order_data;
}
This works fine over the older API link:
https://example.com/wc-api/v3/orders?consumer_key=KEY&consumer_secret=SECRET
However, I need to update to Woo v3.3+ and the REST API is server up as:
https://example.com/wp-json/wc/v2/orders?consumer_key=KEY&consumer_secret=SECRET
My custom data no longer appears, and the hook does not appear to work. Is there another I can use?

WC API is indeed a great tool. Adding your own custom data to WC API shop order's response in WooCommerce 3.x can still be achieved as easily as it used to be with the legacy version of the API.
WooCommerce has these prepare filters for most of their API responses (see). Note that the format of them is woocommerce_rest_prepare_{$post_type}, where $post_type is a post type or taxonomy name like shop_orders or product_cat. In WooCommerce 2.7+ some of these filters also have a _object suffix.
As long as our intent is to add custom data to orders, the right filter to use will be woocommerce_rest_prepare_shop_order_object, where shop_order is our {$post_type} followed the _object suffix (as described above).
The following function conditionally gets user meta and returns the user's social profile's avatar url if available.
/**
* Add custom data to WC API shop order response
* Overriding "$object" here with $order so it's easier to access its properties
*/
function my_wc_rest_prepare_order( $response, $order, $request ) {
if( empty( $response->data ) )
return $response;
$order_id = $order->get_id();
// Get an instance of the WC_Order object
$order = wc_get_order($order_id);
// Get the user ID from WC_Order methods
$user_id = $order->get_customer_id(); // $order->get_user_id(); // or $order->get_customer_id();
// check for WooCommerce Social Login User Avatar
if( class_exists( 'WC_Social_Login' ) ) {
$fb_avatar = get_user_meta( $user_id, '_wc_social_login_facebook_profile_image', true );
$gplus_avatar = get_user_meta( $user_id, '_wc_social_login_google_profile_image', true );
}
$social_data = array();
$avatar_url = array();
$customer_picture = array(
'default' => get_avatar_url( $user_id ),
'facebook' => ( $fb_avatar ) ? esc_url( $fb_avatar ) : '',
'google' => ( $gplus_avatar ) ? esc_url( $gplus_avatar ) : ''
);
$response->data['social_data']['avatar_url'] = $customer_picture;
return $response;
}
add_filter( 'woocommerce_rest_prepare_shop_order_object', 'my_wc_rest_prepare_order', 10, 3 );
Result:
[{
"social_data": {
"avatar_url": {
"default": "https://secure.gravatar.com/avatar/6e27402273b47316097247a2057492f8?s=96&d=mm&r=g",
"facebook": "https://graph.facebook.com/2028542057604385/picture?width=150&height=150",
"google": ""
}
},
}]

Related

Redirecting customers to custom page after sale in WooCommerce

I'm trying to learn WooCommerce and WordPress plugins so I'm tweaking around. I'm trying to create a plugin that redirects customer to a custom page after checkout. The custom page/url can be defined when I create the product. Here is my code:
<?php
/*
Plugin Name: Custom Redirect After Sale
Description: Redirects customers to a custom page after a successful sale.
*/
// Register a new meta field for products
add_action( 'add_meta_boxes', 'custom_redirect_meta_box' );
function custom_redirect_meta_box() {
add_meta_box( 'custom_redirect_meta_box', 'Custom Redirect URL', 'custom_redirect_meta_box_callback', 'product', 'side' );
}
function custom_redirect_meta_box_callback( $post ) {
$value = get_post_meta( $post->ID, '_custom_redirect_url', true );
echo '<label for="custom_redirect_url">Custom Redirect URL:</label>';
echo '<input type="text" id="custom_redirect_url" name="custom_redirect_url" value="' . esc_attr( $value ) . '" style="width:100%">';
}
// Save the meta field value when the product is saved
add_action( 'save_post_product', 'save_custom_redirect_meta_box_data' );
function save_custom_redirect_meta_box_data( $post_id ) {
if ( isset( $_POST['custom_redirect_url'] ) ) {
update_post_meta( $post_id, '_custom_redirect_url', sanitize_text_field( $_POST['custom_redirect_url'] ) );
}
}
// Redirect to the custom page after a successful sale
add_action( 'woocommerce_payment_complete', 'custom_redirect_after_sale' );
function custom_redirect_after_sale( $order_id ) {
$order = wc_get_order( $order_id );
//$order->update_status( 'completed' );
$items = $order->get_items();
// Get the first product in the order
$product = reset($items);
// Get the custom redirect URL for the product
//$redirect_url = get_post_meta( $product->get_product_id(), '_custom_redirect_url', true );
$redirect_url = get_post_meta( $product->get_id(), '_custom_redirect_url', true );
//echo "Meta retrieved: " . $redirect_url;
//error_log("callback fired");
//echo "Payment complete ho ho ho";
if( $redirect_url ) {
wp_redirect( $redirect_url );
exit;
}
}
It seems the woocommerce_payment_complete hook is not firing. I tried to echo out the redirect url and text but it doesn't seem to work.
I'm on localhost and I'm using the cash on delivery payment method.
Basing this answer on the great https://rudrastyh.com/ - specifically this tutorial https://rudrastyh.com/woocommerce/thank-you-page.html#redirects this is the code that should work for what you are trying to do.
First, you hook into the template_redirect action to determine the URL where the customer needs to go
Getting the Order ID, you can get the products purchased for that order
Once you have the purchased products, you can get their ID and meta data, the redirect URL you saved for each. Note that while you use WP functions for handling meta, when working with WooCommerce it is best practice to use its CRUD methods. In case in the future they port products to custom tables, your code will continue working.
Implement the redirect with the WP function wp_safe_redirect
Note that what you are trying to achieve will have problems if customers purchase orders with more than 1 product, and you have more than 1 redirect URL. In this implementation, the first product in the order that has a saved redirect URL will override all others
add_action( 'template_redirect', 'purchased_product_redirect');
function purchased_product_redirect(){
if( !function_exists( 'is_wc_endpoint_url' )){
return;
}
// do nothing if we are not on the order received page
if( ! is_wc_endpoint_url( 'order-received' ) || empty( $_GET[ 'key' ] ) ) {
return;
}
// Get the order ID
$order_id = wc_get_order_id_by_order_key( $_GET[ 'key' ] );
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
// Get and Loop Over Order Items
foreach ( $order->get_items() as $item_id => $item ) {
$product_id = $item->get_product_id();
$product = wc_get_product($product_id);
if(!$product){
continue;
}
//Get the first product's redirect URL
$product_redirect_url = $product->get_meta('_custom_redirect_url');
if(!$product_redirect_url){
continue;
}
wp_safe_redirect( $product_redirect_url );
exit; // always exit after using wp_safe_redirect
}
}

Extending search in WooCommerce admin orders list

I'm looking for a way to extend the search field in WooCommerce admin orders list for a custom meta key. Currently i'm using the woocommerce_shop_order_search_fields filter hook.
Resulting in this code, which allows me to search by the user id, order total and order number.
add_filter( 'woocommerce_shop_order_search_fields', 'woocommerce_shop_order_search_order_total' );
function woocommerce_shop_order_search_order_total( $search_fields ) {
$search_fields[] = '_order_total';
$search_fields[] = '_user_id';
$search_fields[] = '_order_number';
return $search_fields;
}
However, these are all existing meta keys, what if I want to search for meta data that doesn't yet exist? Any adivce?
Expanding the search in WooCommerce admin orders list can be done very easily by using the woocommerce_shop_order_search_fields filter hook, you just need to add some post_meta fields for order (item(s)). As long as this data exists of course!
By default, the following metakeys are present:
_billing_address_index
_shipping_address_index
_billing_last_name
_billing_email
So, for example, if you want to extend the search by the billing first name, you can do this by simply adding the metakey _billing_first_name, and then you get:
function filter_woocommerce_shop_order_search_fields( $search_fields ) {
// Metakey
$search_fields[] = '_billing_first_name';
return $search_fields;
}
add_filter( 'woocommerce_shop_order_search_fields', 'filter_woocommerce_shop_order_search_fields', 10, 1 );
Now, what if this metadata is not present for the orders? You could add that specific metadata for future orders, when the order is created. But wait! what about the existing orders? for these orders this data would not exist!
Wouldn't it be useful if we could add this data to existing orders, or even newer orders. Well this is possible, namely by using wc_get_orders(), to get (and update) the existing orders before running the search.
So you get: (where we in this example add the user's nickname as meta data to orders)
function filter_woocommerce_shop_order_search_fields( $search_fields ) {
// The desired meta key
$meta_key = '_user_nickname';
// Get ALL orders where a certain meta key not exists
$orders = wc_get_orders( array(
'limit' => -1, // Query all orders
'meta_key' => $meta_key, // Post meta_key
'meta_compare' => 'NOT EXISTS', // Comparison argument
));
// NOT empty
if ( ! empty ( $orders ) ) {
// Loop through the orders
foreach ( $orders as $order ) {
// Get the desired information via the order object
// Get user
$user = $order->get_user();
// User is NOT empty
if ( ! empty ( $user ) ) {
// Get nickname from user
$meta_value = $user->nickname;
// Meta value is NOT empty
if ( ! empty ( $meta_value ) ) {
// Add the meta data
$order->update_meta_data( $meta_key, $meta_value );
$order->save();
}
}
}
}
// Metakey
$search_fields[] = $meta_key;
return $search_fields;
}
add_filter( 'woocommerce_shop_order_search_fields', 'filter_woocommerce_shop_order_search_fields', 10, 1 );

How to limit the response's fields from woocommerce rest API?

I need to limit the response's fields of woo commerce Rest API. For example. When I need to show the products of a specific category. I just need product id, image, and slug. So, I wanna get only specific fields. Any way to solve my problem?
You can use the filter hook woocommerce_rest_prepare_product_object to adjust the response.
$wc_rest_api->get('products', array('category' => '1234', 'fields_in_response' => array(
'id',
'images',
'slug'
) ) );
The argument fields_in_response is not there by default.
The following code must be placed on server-side (i.e. in functions.php) to work with it.
add_filter('woocommerce_rest_prepare_product_object', 'at_wc_rest_api_adjust_response_data', 10, 3);
function at_wc_rest_api_adjust_response_data( $response, $object, $request ) {
$params = $request->get_params();
if ( ! $params['fields_in_response'] ) {
return $response;
}
$data = $response->get_data();
$cropped_data = array();
foreach ( $params['fields_in_response'] as $field ) {
$cropped_data[ $field ] = $data[ $field ];
}
$response->set_data( $cropped_data );
return $response;
}

Woocommerce Get Vendor Information with Product ID

I am using Woo Commerce with WC Vendor and WC Booking plugin. I want to send booking notification to vendor. Currently it sends notification to Customer and Administrator and when admin changes product status to processing & completed, then it sends notification to vendor. However, I want to send vendor notification along with admin notification.
I tried this hook:
add_action( 'woocommerce_new_booking', 'new_order_email_to_vendor', 10, 4 );
function new_order_email_to_vendor( $order ){
$emails = WC()->mailer()->get_emails();
if ( ! empty( $emails ) ) {
$emails['WC_Product_Vendors_Order_Email_To_Vendor']->trigger( $order );
}
}
But it throws an error:
Fatal error: Call to a member function get_date_created() on boolean in /wp-content/plugins/woocommerce-product-vendors/includes/emails/class-wc-product-vendors-order-email-to-vendor.php on line 56
So, now I am trying to hook vendor email directly along with customer and administrator email in this line:
$this->recipient = $this->get_option( 'recipient', get_option( 'admin_email' ), 'WILL ADD VENDOR EMAIL HERE' );
in file:
wp-content/plugins/woocommerce-bookings/includes/emails/class-wc-email-new-booking.php
At this stage, I have bookable product id available and I am trying to pull vendor information using product id but I don't found any information.
I tried:
get_post()
get_post_meta()
get_post_meta_by_id()
The Question is: How to get vendor information (specifically email) using product id?
I don't have and I have never used WC Vendors plugin as this is a non official commercial plugin (not made by automatic).
To get the vendor ID (after searching a bit) you can get it this way from a product ID:
$vendor_id = get_post_field( 'post_author', $product_id );
Now you can get the vendor email this way:
$vendor_id = get_post_field( 'post_author', $product_id );
$vendor = get_userdata( $vendor_id );
$email = $vendor->user_email;
May be a good turn around:
You can use the dedicated filter hook woocommerce_email_recipient_{$this->id} where $this->id is the ID of the notification type, for new_booking email ID (and also for testing new_order email ID too).
This will allow you to add additional email recipients.
In email notifications hooks, the Order object is nearly always defined. As In an order you can have many items (different products rom different vendors), you will need to get the vendor ID from each.
In the code below I add to the recipients the vendors emails for new_booking and new_order email notifications:
add_filter( 'woocommerce_email_recipient_new_booking', 'additional_customer_email_recipient', 10, 2 );
add_filter( 'woocommerce_email_recipient_new_order', 'additional_customer_email_recipient', 10, 2 ); // Optional (testing)
function additional_customer_email_recipient( $recipient, $order ) {
if ( ! is_a( $order, 'WC_Order' ) ) return $recipient;
$additional_recipients = array(); // Initializing…
// Iterating though each order item
foreach( $order->get_items() as $item_id => $line_item ){
// Get the vendor ID
$vendor_id = get_post_field( 'post_author', $line_item->get_product_id());
$vendor = get_userdata( $vendor_id );
$email = $vendor->user_email;
// Avoiding duplicates (if many items with many emails)
// or an existing email in the recipient
if( ! in_array( $email, $additional_recipients ) && strpos( $recipient, $email ) === false )
$additional_recipients[] = $email;
}
// Convert the array in a coma separated string
$additional_recipients = implode( ',', $additional_recipients);
// If an additional recipient exist, we add it
if( count($additional_recipients) > 0 )
$recipient .= ','.$additional_recipients;
return $recipient;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This should work without errors.

WP REST API orderby meta_value

Need to be able to sort the results of a REST API custom post query by a meta value.
Having difficulty doing so.
I have made my post type available to the REST API and can order by the Date, Title, etc...
But when I try the Post Meta it doesn't work.
I have added the following code to try and enable the functionality but defaults to ordering by date.
function my_add_meta_vars ($current_vars) {
$current_vars = array_merge ($current_vars, array('meta_key', 'meta_value'));
return $current_vars;
}
add_filter ('query_vars', 'my_add_meta_vars');
add_filter ('rest_query_vars', 'my_add_meta_vars');
My REST API query is
mysite.com/wp-json/wp/v2/hh_equipment?filter[orderby]=meta_value_num&meta_key=equipment_price&order=desc
I have tried following the instructions here to no avail.
Running WordPress 4.8 and tried testing on 4.7 to no avail
I've got it working with the rest_' . $post_type . '_collection_params filter and rest_' . $post_type . '_query filter like so (change $post_type to needed post type slug):
// Add meta your meta field to the allowed values of the REST API orderby parameter
add_filter(
'rest_' . $post_type . '_collection_params',
function( $params ) {
$params['orderby']['enum'][] = 'YOUR_META_KEY';
return $params;
},
10,
1
);
// Manipulate query
add_filter(
'rest_' . $post_type . '_query',
function ( $args, $request ) {
$order_by = $request->get_param( 'orderby' );
if ( isset( $order_by ) && 'YOUR_META_KEY' === $order_by ) {
$args['meta_key'] = $order_by;
$args['orderby'] = 'meta_value'; // user 'meta_value_num' for numerical fields
}
return $args;
},
10,
2
);
The first filter adds your meta field to the possible values of the ordeby parameters, as by default REST API supports only: author, date, id, include, modified, parent, relevance, slug, include_slugs, title (check the ordeby param in the WP REST API handbook)
The second filter allows you to manipulate the query that returns the results when you have your meta key inside the orderby. Here we need to reset orderby to 'meta_value' or 'meta_value_num' (read more about this in WP Query class description) and set the meta key to your custom field key.
Refer below method,
I modified the existing routes to add a new args entry which validates the meta_key values which are permitted. No need to modify the rest query vars this way either.
add_filter('rest_endpoints', function ($routes) {
// I'm modifying multiple types here, you won't need the loop if you're just doing posts
foreach (['some', 'types'] as $type) {
if (!($route =& $routes['/wp/v2/' . $type])) {
continue;
}
// Allow ordering by my meta value
$route[0]['args']['orderby']['enum'][] = 'meta_value_num';
// Allow only the meta keys that I want
$route[0]['args']['meta_key'] = array(
'description' => 'The meta key to query.',
'type' => 'string',
'enum' => ['my_meta_key', 'another key'],
'validate_callback' => 'rest_validate_request_arg',
);
}
return $routes;
});
REF: https://github.com/WP-API/WP-API/issues/2308

Resources