Add custom checkout field as Order custom-meta data in Woocommerce 3 - php

before WooCommerce 3.0 came out my code had worked like a charm to save custom values from the cart into the order on checkout. But since then I'm not able to create custom meta for orders.
Environment: Wordpress 4.9.4 & WooCommerce 3.3.3
Hooks
add_action('woocommerce_checkout_update_order_meta', 'custom_meta_to_order', 20, 1);
add_action('woocommerce_checkout_create_order', 'custom_meta_to_order', 20, 1);
The Hook number 1 is the one I tried the most, the 2 one was just an experiment with some literal changes mentioned in this topic.
Function
The following functions-code is related to hook number 1:
if (!function_exists('custom_meta_to_order')) {
function custom_meta_to_order($order_id, $values) {
$order = wc_get_order( $order_id );
$order->update_meta_data('_TESTKEYstart', 'Hello');
if (isset($values['myValue'])) {
$myValue = $values['myValue'];
if (!empty($myValue)) $order->update_meta_data('_myKey', $myValue);
}
$order->update_meta_data('_TESTKEYend', 'Bye');
$order->save();
}
}
I've checked also in the mySQL table table wp_woocommerce_order_itemmeta if at least the two _TESTKEY*-meta-entrys will be created (because they don't have a condition).
But it seems that the meta-keys and values don't getting created via this hook and function.
The function itself getting called, so at least the hooks itselfs are working.
So my question is: "What am I doing wrong?"

UPDATED: There is some errors in your code…
Both hooks have only 1 argument (not 2, so $values doesn't exist)
To get your custom field you should use $_POST['myValue'] instead.
and other things like each hook has a different argument:
$order_id for woocommerce_checkout_update_order_meta
$order for woocommerce_checkout_create_order
Below I have replaced $_POST['myValue'] by $_POST['billing_country'] as you don't give the code for this custom checkout field…
So here are both ways:
1) The best way for me, as explained here:
if ( ! function_exists('custom_meta_to_order') ) {
add_action( 'woocommerce_checkout_create_order', 'custom_meta_to_order', 20, 1 );
function custom_meta_to_order( $order ) {
$order->update_meta_data('_TESTKEYstart', 'Hello');
if (isset($_POST['billing_country'])) {
$myValue = $_POST['billing_country'];
if (!empty($myValue)) $order->update_meta_data('_my_key', $myValue);
}
$order->update_meta_data('_TESTKEYend', 'Bye');
}
}
Code goes in function.php file of your active child theme (or theme). Tested and works.
2) The other way:
if ( ! function_exists('custom_meta_to_order') ) {
add_action('woocommerce_checkout_update_order_meta', 'custom_meta_to_order', 20, 1);
function custom_meta_to_order( $order_id ) {
// get an instance of the WC_Order object
$order = wc_get_order( $order_id );
$order->update_meta_data('_TESTKEYstart', 'Hello');
if (isset($_POST['billing_country'])) {
$myValue = $_POST['billing_country'];
if (!empty($myValue)) $order->update_meta_data('_my_key', $myValue);
}
$order->update_meta_data('_TESTKEYend', 'Bye');
// Save the order data and meta data
$order->save();
}
}
Code goes in function.php file of your active child theme (or theme). Tested and works.
The proof:
And (in database wp_postmeta table for this order ID):
Tested in WooCommerce version 3.3+
You can use the old way too (which works):
if ( ! function_exists('custom_meta_to_order') ) {
add_action('woocommerce_checkout_update_order_meta', 'custom_meta_to_order', 20, 1);
function custom_meta_to_order( $order_id ) {
update_post_meta( $order_id, '_TESTKEYstart', 'Hello' );
if ( isset( $_POST['billing_country'] ) ) {
$myValue = $_POST['billing_country'];
if (!empty($myValue))
update_post_meta( $order_id, '_my_key', $myValue);
}
update_post_meta( $order_id, '_TESTKEYend', 'Bye');
}
}
Code goes in function.php file of your active child theme (or theme). Tested and works.
Related: Add extra meta for orders in Woocommerce

Because comments are really hard to read (because of to much restricted formatation), this answer is just a response to the answer from LoicTheAztec.
I wrote a longer answer but it seems gone, so I sorry now for the much shorter one!
First our misunderstanding
You understood that I would like to use custom values from products but in my case it was a little bit other. I wrote an external application which included the wp-load.php and posted then data back to the product-page into the cart.
So the problem showed up here was the attempt to write the data from the cart into the order on checkout.
Recommend ways doesn't worked at first
The recommend ways you suggested all doesn't worked. I also stripped them so much down that they have should work and just write something into the meta. I've no clue which plugin/theme-function pranked me this time here.
But I was able to solve the problem
And many more! Just because I found the blog-post where I found out in the past, how to do this and as addition to my personal luck the author wrote already the changes for WP3.0, related to this process.
Still your post helped me
The errors you showed me bugged me since then and because it was hard to follow and inspect everything with Sublime and CodeIntel (and my start with Symfony itself) I decided to buy PHPStorm which showed and allowed me to fix all of my deprecated (legacy-using) functions by updating them properly.
( Finally no more global-variables: Yay. )
I mean, showing up parameters inline and deprecation-strokes already did a great job. But a bug-free working code-intel/reference which doesn't dies on big projects is just awesome.
That's why I marked your answer now as solution, thanks. Otherwise I would have just fixed the problem maybe (thanks to the authors blog-post) but still would sit on a ticking time bomb.

Related

How to update the weight value in the product data, inside an existing woocommerce products without the use of the SAVE/UPDATE button

What I am trying to achieve here is to update all the weight values inside the products that already exist based on the criteria in the if statement.
So I am using the following code:
function update_product_weight_from_dimensions( $product ) {
//$product = wc_get_product();
$squared_dimensions = $product->get_length() * $product->get_width();
$synthetic_product = $product->get_attribute('pa_composition');
if($squared_dimensions <= 2.8 && $synthetic_product == 'steel') {
$product->set_weight(3);
}
}
add_action( 'woocommerce_admin_process_product_object', 'update_product_weight_from_dimensions', 10, 1 );
If i use a hook like do_action( 'update_product_weight_from_dimensions', 10, 1 ); , nothing will happen.
As per this post in StackOverflow , i am already using the $product->set_weight(); and the hook woocommerce_admin_process_product_object. The function already works as expected but only if I SAVE the product. I want to update the values without needing to run inside all products to hit the SAVE/UPDATE button. If this function could run only once then this would also be closer to what I try to achieve.
As far as I know, the weight of standard WooCommerce products is stored solely inside wp_postmeta table with the _weight meta key.
Therefore you should be able to use:
update_post_meta( $product->get_id(), '_weight', $your_value );
I would also recommend to clear WC transients after updating the weights (I am not sure if it is needed though).
Anyway, the $product->set_weight() and $product->save() approach is really the right thing to do. 4000 products really isn't that much and you can always split these into smaller chunks, if it is a one-time action.

Clear Woocommerce cart when sending multiple items by url

I need the Woocommerce cart to be cleaned in case I send more than four items to it via url.
I came up with this code, but it only cleans the cart if there are already five items in it.
//in functions.php
add_filter( 'woocommerce_add_to_cart_validation', 'remove_cart_item_before_add_to_cart', 20, 3 );
function remove_cart_item_before_add_to_cart( $passed, $product_id, $quantity ) {
if( WC()->cart->get_cart_contents_count() >= 5 )
WC()->cart->empty_cart();
return $passed;
}
Another problem is that, even the code above that is not useful to me, does not work if I add multiple courses via url, as in:
https://exemple.net/cart?fill_cart=100,101,102,103,104,105,106
That is, the code in functions.php only works from the website, and not by url.
All I need is clear the cart when sending more than 4 items by url.
I prefer a solution in PHP, but a JS solution will do. Thanks for who can help me.
I found a solution using the free plugin Cart links for WooCommerce, and making a few change in its code.
I will describe below what I did:
1) To start install the plugin, this will allow you to add products using the fill_cart parameter in url (as described in my question)
2) edit the plugin file soft79-cart-links-for-woocommerce.php found at: yoursite/wp-content/plugins/soft79-cart-links-for-woocommerce/
3) go to line 141 or find the declaration of the method fill_cart
Inside the fill_cart method uncomment (or add) the code to clean the cart:
public function fill_cart( $fill_string ) {
$original_notices = wc_get_notices();
wc_clear_notices();
////Clear the cart
//WC()->cart->empty_cart(); <-- uncomment here!
Save the file. Now any new items that arrive via url must clean the cart first.

Send an email notification when order status change from pending to cancelled

In previous versions of Woocommerce, an email notification was sent automatically when an order was changed from pending status to cancelled status (In my case, this happens after an allotted time set in the inventory section of the admin).
In WooCommerce 3.0.8 they have removed this automation and labelled as a fix:
https://github.com/woocommerce/woocommerce/blob/master/CHANGELOG.txt
And the pull request is here:
https://github.com/woocommerce/woocommerce/pull/15170/files
I'm looking to restore this functionality, but obviously copy/pasting this line back in to the Woocommerce core files isn't a good idea as it will get overwritten when the platform updates.
I know the best method would to be to create a function and hook into the cancelled order action via functions.php but after having a look I'm a bit lost about how to do this. Here is the line which was replaced:
add_action( 'woocommerce_order_status_pending_to_cancelled_notification', array( $this, 'trigger' ), 10, 2 );
How can I restore this old automated functionality?
The good new: Using woocommerce_order_status_pending_to_cancelled action hook with a custom function hook in it, solve your problem definitively:
add_action('woocommerce_order_status_pending_to_cancelled', 'cancelled_send_an_email_notification', 10, 2 );
function cancelled_send_an_email_notification( $order_id, $order ){
// Getting all WC_emails objects
$email_notifications = WC()->mailer()->get_emails();
// Sending the email
$email_notifications['WC_Email_Cancelled_Order']->trigger( $order_id );
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and perfectly works in WooCommerce 3+ (still work on version 4.8+)

WooCommerce: How to retain checkout info when client leaves then comes back?

Is there a simple way or a plugin to retain checkout information entered by the client after he/she leaves and comes back?
This plugin retains "fields information for customers when they navigate back and forth" however it has quite a lot of recent bad reviews so I don't think I'll use that for production. Any alternative suggestion?
---- Update ----
The code below is working, but only if data is submitted!
The only possible ways are javascript/jQuery form event detection on checkout fields and worpress Ajax:
Using ajax connected to some session transients function (as in code below).
Using (javascript) web Storage: localStorage, sessionStorage…
I have found some real interesting code in this thread that is using sessions transients to store checkout data.
// this function sets the checkout form data as session transients whenever the checkout page validates
function set_persitent_checkout ( $a ) {
$arr = array();
foreach ( $a as $key => $value )
if ( ! empty($value) )
$arr[$key] = $value;
WC()->session->set( 'form_data', $arr );
return $a;
}
add_action( 'woocommerce_after_checkout_validation', 'set_persitent_checkout' );
// this function hooks into woocommerce_checkout_get_value to substitute standard values with session values if present
function get_persistent_checkout ( $value, $index ) {
$data = WC()->session->get('form_data');
if ( ! $data || empty($data[$index]) )
return $value;
return is_bool($data[$index]) ? (int) $data[$index] : $data[$index];
}
add_filter( 'woocommerce_checkout_get_value', 'get_persistent_checkout', 10, 2 );
// This is a fix for the ship_to_different_address field which gets it value differently if there is no POST data on the checkout
function get_persitent_ship_to_different ( $value ) {
$data = WC()->session->get('form_data');
if ( ! $data || empty($data['ship_to_different_address']) )
return $value;
return is_bool($data['ship_to_different_address']) ? (int) $data['ship_to_different_address'] : $data['ship_to_different_address'];
}
add_action( 'woocommerce_ship_to_different_address_checked', 'get_persitent_ship_to_different' );
Add this code to the functions.php file located in your active child theme or theme.
Explanations from the author:
1. Save the form data:
The first function set_persitent_checkout hooks into woocommerce_after_checkout_validation.
Whenever that hook is fired, any current form data is saved as a WordPress transient via the WC_Session_Handler class (which was recently updated in version 2.5 to be a lot more efficient).
2. Check the saved data on reload:
Next we hook woocommerce_checkout_get_value with get_persitent_checkout. As the name suggests, here we check the session transients and return any matches for the current field if found.
3. Make ship_to_different_address work:
The only difficult was the ship_to_different_address field, which gets its value through a different method.
To get around this the final function was added. This works exactly the same as the previous function, but hooks into woocommerce_ship_to_different_address_checked.
There you have it. It would be nice if the data was saved after every field update on checkout, but the woocommerce_after_checkout_validation hook fires enough to capture the data at all the important points.
Functions.php snipped posted by LoicTheAztec didn't work for me.
I found this plugin which remembers everything I type or select in Woocommerce checkout, including shipping fields and my custom additions to the template:
Save Abandoned Carts – WooCommerce Live Checkout Field Capture
Account passwords, if creating during checkout, are naturally not remembered.

WooCommerce add_to_cart metadata not sticking

I am a WP noob but very comfortable in PHP.
I am working with a client and we have built a product customization tool as an Angular.js single page application. When the product is finished being customized we are seeking to inject it into a WooCommerce cart so the client can check out. To do this we are $_POSTing the data to a PHP file in the root directory of the WP install. The code to catch it looks like:
require_once('./wp-load.php' );
global $woocommerce;
$woocommerce->session->set_customer_session_cookie(true);
$woocommerce->cart->empty_cart();
$id_arr = $_GET['productID'];
$pdfName = $_GET['pdfName'];
for($i=0; $i<count($id_arr); $i++){
$id = $id_arr[$i];
if ($id==0) continue;
if ($i==0){
$ret = $woocommerce->cart->add_to_cart($id, 1, '', '', array('pdfName'=>$pdfName));
}else{
$ret = $woocommerce->cart->add_to_cart($id);
}
}
wp_redirect(site_url().'/cart/');
The products are all correctly added to the cart but after checkout there is no sign of the metadata. After extensive research, I have found an article here: https://wpml.org/forums/topic/woocommerce-add-to-cart-does-not-work-with-wpml-activated/ that shows me that plugins can cause this behavior. So I have two specific questions?
Does my code make sense, am I creating the metadata array correctly?
Do I need to create something in WooCommerce called pdfName before I can do this?
Is there another way that metadata can be added to an order in
WooCommerce that may work around this problem?
It looks like you are adding the metadata correctly. However, as soon as you refresh, WooCommerce re-creates the cart data. Therefore you have to tell WooCommerce to maintain the metadata when it is pulling the cart from the stored session. Well, at least that is my understanding of it. So I think you need to filter thee $cart_item as it is run through the woocommerce_get_cart_item_from_session filter:
add_filter( 'woocommerce_get_cart_item_from_session', 'so_29660316_get_cart_item_from_session', 11, 2 );
function so_29660316_get_cart_item_from_session( $cart_item, $values ) {
if ( isset( $values['pdfName'] ) ) {
$cart_item['pdfName'] = $values['pdfName'];
}
return $cart_item;
}

Categories