Woocommerce Order Again won't copy custom fields - php

I've managed to code my plugin to add extra custom fields to woocommerce products. This works all the way through from cart to completing an order. Going to my account and viewing past orders, the custom fields are displayed correctly.
However, when I choose to click "order again" on a past order, the new cart doesn't contain the custom fields and their values.
Here is what I currently have to attempt this:
// order again
add_filter( 'woocommerce_order_again_cart_item_data', 'woocommerce_order_again_cart_item_data', 10, 3 );
function woocommerce_order_again_cart_item_data($cart_item_meta, $product, $order){
global $woocommerce;
// Disable validation
remove_filter( 'woocommerce_add_to_cart_validation', array( $this, 'validate_add_cart_item' ), 10, 3 );
if ( ! array_key_exists( 'item_meta', $cart_item_meta ) || ! is_array( $cart_item_meta['item_meta'] ) )
$cart_item_meta['item_meta'] = array();
foreach ( array( 'jhpc_toppings', 'jhpc_sauce', 'jhpc_toppings_half', 'jhpc_sauce_half', 'jhpc_garnish' ) as $key )
$cart_item_meta['item_meta'][$key] = $product['item_meta'][$key];
return $cart_item_meta;
}

replace
$cart_item_meta['item_meta'][$key] = $product['item_meta'][$key];
by
$cart_item_meta[$key] = $product[$key];
Otherwise, why are you removing the validation ?

Here is the code to add all custom field data for order again. Use the given code in your theme's function.php file and replace the custom field keys of $customfields array with your keys.
<?php
add_filter( 'woocommerce_order_again_cart_item_data', 'wpso2523951_order_again_cart_item_data', 10, 3 );
function wpso2523951_order_again_cart_item_data($cart_item_meta, $product, $order){
//Create an array of all the missing custom field keys that needs to be added in cart item.
$customfields = [
'customfield_key1',
'customfield_key2',
'customfield_key3',
'customfield_key4',
];
global $woocommerce;
remove_all_filters( 'woocommerce_add_to_cart_validation' );
if ( ! array_key_exists( 'item_meta', $cart_item_meta ) || ! is_array( $cart_item_meta['item_meta'] ) )
foreach ( $customfields as $key ){
if(!empty($product[$key])){
$cart_item_meta[$key] = $product[$key];
}
}
return $cart_item_meta;
}
?>
Replace the values of array $customfields with the keys of custom fields that are missing or are not being added automatically.

Related

Remove particular variation from dropdown in WooCommerce frontend

I am using WooCommerce and WooCommerce Subscriptions and its working as per my expectations.
Now I am creating a variable subscription product having multiple attributes like this.
Now I want to remove/hide particular item from dropdown hence I am trying to use below code / hook which I believe might help me to achieve.
add_filter('woocommerce_dropdown_variation_attribute_options_args', 'hide_variations_for_mindesk_users');
function hide_variations_for_mindesk_users( $args ){
print_r($args);
return $args;
}
Now my question is, how can I remove or hide particular variation product from dropdown? Do I need to remove from variation id or from somewhere?
For example:
Here I want to remove/hide 2nd variation from dropdown which has variation id #4171 having "Monthly- Professional". This should work with single attribute as well.
Can anyone point me in the right direction to achieve this?
In add-to-cart/variable.php template file, we find foreach ( $attributes as $attribute_name => $options ). However, the intention is to hide 1 attribute, so let's see where these are passed to the template file.
In includes/wc-template-functions.php, we can see that the template file is called and an array is passed with some options. One of these options is available_variations' => $get_variations ? $product->get_available_variations()
The get_available_variations() function is then found in includes/class-wc-product-variable.php which in turn $variation_ids = $this->get_children(); contains.
The get_children() function can then be found in includes/class-wc-product-variable.php, which contains apply_filters( 'woocommerce_get_children', $this->children, $this, false );
And that filter hook can be used to remove one or more childIDs (variantions)
So you get:
function filter_woocommerce_get_children( $children, $product, $false ) {
// NOT backend
if ( is_admin() ) return $children;
// Variation ID
$variation_id = 4171;
// Delete by value: Searches the array for a given value and returns the first corresponding key if successful
if ( ( $key = array_search( $variation_id, $children ) ) !== false ) {
unset( $children[$key] );
}
return $children;
}
add_filter( 'woocommerce_get_children', 'filter_woocommerce_get_children', 10, 3 );
If you want to apply it for multiple variantion IDs, use:
function filter_woocommerce_get_children( $children, $product, $false ) {
// NOT backend
if ( is_admin() ) return $children;
// Variation IDs, multiple IDs can be entered, separated by a comma
$variation_ids = array( 4171, 36, 38 );
// Loop through variation IDs
foreach ( $variation_ids as $variation_id ) {
// Delete by value: Searches the array for a given value and returns the first corresponding key if successful
if ( ( $key = array_search( $variation_id, $children ) ) !== false ) {
unset( $children[$key] );
}
}
return $children;
}
add_filter( 'woocommerce_get_children', 'filter_woocommerce_get_children', 10, 3 );
Used in this answer: PHP array delete by value (not key)

Add unique line item meta as order meta data in WooCommerce

I have meta data that is stored below the order line items, many times the data is that same. I am trying to save the unique meta values as order meta data, separated by commas and I if possible to add a text "these are your numbers" before this data.
So far I tried the code below but nothing is happening.
add_action( 'woocommerce_update_order', 'add_unique_order_nummers_to_order', 10, 2 );
function add_unique_order_nummers_to_order( $order_id, $order ) {
$allenummers = array();
$items = $order->get_items();
foreach ( $order->get_items() as $item_id => $item ) {
$allenummers[] = $item->get_meta( '_org_ordernummer', true );
}
$ordernummers_array = array_unique($allenummers);
$ordernummers_unique = implode(',', $ordernummers_array);
update_post_meta( $order_id, '_Unieke_nummers', $ordernummers_unique );
}
I hope someone can help.
Update: Use the following simplified code with a different hook, based on your comment, when order status is changed to "we-fact" custom order status:
add_action('woocommerce_order_status_we-fact', 'add_unique_order_nummers_to_order', 20, 2 );
function add_unique_order_nummers_to_order( $order_id, $order ) {
$allenummers = array(); // Initializing
// Loop through order
foreach ( $order->get_items() as $item ) {
$allenummers[] = $item->get_meta( '_org_ordernummer' );
}
if ( ! empty($allenummers) ) {
$order->update_meta_data('_Unieke_nummers', sprintf( __("These are your numbers: %s"), implode(', ', array_unique($allenummers) ) ) );
}
}
Code goes in functions.php file of the active child theme (or active theme). It should works.

Store custom data using WC_Cart add_to_cart() method in Woocommerce 3

I am creating a membership site and totally created static pages for each Membership plans (have only 3 plans). However, I have added products for each plan and when I hit SELECT PLAN button I redirect to some custom form where I ask users range of info we are going to use to fulfil the plan (same as sneakertub.com).
I have written code into the PHP page which will handle SUBMIT action of the form. This PHP file, infopage.php, will process POST data I sent via POST call and stores these all data into WC session.
$customer_name = $_POST["customer_name"];
$customer_email = $_POST["customer_email"];
$customer_sex = $_POST["customer_sex"];
$customer_age = $_POST["customer_age"];
$product_id = $_POST["product_id"];
global $wp_session;
$data = array(
'customer_name' => $customer_name,
'customer_email' => $customer_email,
'customer_sex' => $customer_sex,
'customer_age' => $customer_age);
$wp_session['custom_SESSION_child']=$data;
WC()->session->set('custom_data_child', $data);
//Add product to WooCommerce cart.
WC()->cart->add_to_cart( $product_id )
However, I don't think the above code works. As I don't find values into session with any of the above technique. I have used wp_session, WC()->session and $_SESSION but no approach is working.
I am trying to access these values into functions.php this way,
add_action( 'woocommerce_before_calculate_totals', 'twf_additional_price', 1, 3 );
function twf_additional_price( $cart_object ) {
global $wpdb;
global $wp_session;
$session_data_2 = $wp_session['custom_SESSION_child'];
$session_data = WC()->session->get('custom_data_child');
var_dump($session_data);
var_dump($session_data2);
foreach ( $cart_object->cart_contents as $key => $value ) {
$extra_charge = 0;
if(isset($value['twf_user_custom_datas'])){
$extra_charge = 100;
}
$value['data']->set_price($value['data']->price + $extra_charge);
}
}
For now ignore the for loop. Main thing is
var_dump($session_data);
var_dump($session_data2);
both dumps only NULL.
My main goal is to add the all above fields into Woocommerce checkout and order pages.
Please let me know what is wrong here. I know I might be working on very bad approach but I want Plan selection to checkout process same as sneakertub.com. Please let me know if there is any tutorial on this or proper way to do this. I prefer doing this without plugins but I am ready to use plugins as well.
I appreciate your attention.
Updated - Instead of using sessions, you should use the last available argument in WC_Cart add_to_cart() method, which will allow you to add any custom cart item data.
For cart item price change based on calculations, is better to make the new price calculation before and to set it in that custom cart item data.
Try the following instead:
1) For your code in the php page:
$custom_data = array(); // Initializing
// Set the posted data as cart item custom data
if( isset($_POST['customer_name']) && ! empty($_POST['customer_name']) )
$custom_data['custom_data']['name'] = sanitize_text_field( $_POST['customer_name'] );
if( isset($_POST['customer_email']) && ! empty($_POST['customer_email']) )
$custom_data['custom_data']['email'] = sanitize_text_field( $_POST['customer_email'] );
if( isset($_POST['customer_sex']) && ! empty($_POST['customer_sex']) )
$custom_data['custom_data']['sex'] = sanitize_text_field( $_POST['customer_sex'] );
if( isset($_POST['customer_age']) && ! empty($_POST['customer_age']) )
$custom_data['custom_data']['age'] = sanitize_text_field( $_POST['customer_age'] );
// Set the calculated item price as custom cart item data
if( isset($custom_data['custom_data']) && sizeof($custom_data['custom_data']) > 0 && $product_id > 0 ) {
// Get an instance of the WC_Product object
$product = wc_get_product( $product_id );
// Save the new calculated price as custom cart item data
$custom_data['custom_data']['new_price'] = $product->get_price() + 100;
}
// Add product to cart with the custom cart item data
WC()->cart->add_to_cart( $product_id, '1', '0', array(), $custom_data );
Code goes in function.php file of your active child theme (or active theme). Tested and works.
2) Your revisited function that will change the cart item price:
add_action( 'woocommerce_before_calculate_totals', 'custom_cart_item_price', 30, 1 );
function custom_cart_item_price( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
foreach ( $cart->get_cart() as $cart_item ) {
if( isset($cart_item['custom_data']['new_price']) )
$cart_item['data']->set_price( $cart_item['custom_data']['new_price'] );
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
All other custom cart item data is available under the cart item key 'custom_data' as an indexed array… So you will be able to get that data easily from the cart object, to save it in the order.

Woo-commerce 3.0 single product price is not changing properly

I made a new post type named "sub_products" containing the meta tag "unit_price".
After assigning a new field for every Woocommerce product containing a list of all the "sub_products" posts, the goal was to update every product price based on the selected "sub_products" meta "unit_price".
function kulcskron_edit_post( $p1, $p2 )
{
if ( !is_admin() )
return;
if ( get_post_type() != 'product' )
return;
$sub_product_ids = $p2->get_meta( 'sub_products' );
if ( empty($sub_product_ids) )
return;
$product_regular_price = 0;
foreach ( $sub_product_ids as $id )
$product_regular_price += get_post_meta( $id, 'unit_price', true );
if ( $p1 == $product_regular_price )
return;
$p2->set_regular_price( $product_regular_price );
$p2->save();
}
add_action( 'woocommerce_product_get_price', 'kulcskron_edit_post', 10, 2 );
I tried every possible hook to make this work:
add_action( 'the_post', 'kulcskron_edit_post', 9, 1 );
add_action( 'edit_post', 'kulcskron_edit_post', 10, 2 );
add_action( 'pre_get_posts', 'kulcskron_edit_post' );
add_action( 'save_post', 'kulcskron_edit_post' );
This code updates the price but in a strange way:
Admin single product edit view: The price is not updated right away, just after I revisit the edit screen.
Admin product listing view: Every price is 0.
Front-end product page view: The displayed price is 0.
How do I update the product price based on the assigned "sub_products" "unit_price" post meta when a single product is saved in admin view?
You may use the hook "woocommefrce_get_price_html" it should work
I managed to solve the price update problem.
I used the save_post action:
save_post is an action triggered whenever a post or page is created or updated, which could be from an import, post/page edit form, xmlrpc, or post by email.
The final code:
function kulcskron_update_product( $product_obj )
{
if ( !is_admin() )
return;
if ( get_post_type() != 'product' )
return;
$product = wc_get_product( $product_obj );
if ( !$product->meta_exists( 'sub_products' ) )
return;
$sub_product_ids = $product->get_meta( 'sub_products' );
if ( empty($sub_product_ids) )
return;
_update_product_price( $product_obj, $sub_product_ids );
}
add_action( 'save_post', 'kulcskron_update_product' );
And for the sake of completeness here is the rest of code:
function _update_product_price( $product_obj, $sub_product_ids )
{
$product = wc_get_product( $product_obj );
$product_regular_price = 0;
foreach ( $sub_product_ids as $id )
$product_regular_price += get_post_meta( $id, 'kulcskron_unit_price', true );
$product->set_regular_price( $product_regular_price );
$product->set_price( $product_regular_price );
$product->save();
}
This way basically everything can be edited and updated. Example:
function _update_product_stock( $product_obj, $sub_product_ids )
{
$product = wc_get_product( $product_obj );
$sub_product_stocks = array();
foreach ( $sub_product_ids as $id )
$sub_product_stocks[] = get_post_meta( $id, 'kulcskron_free_stock_quantity', true );
$product->set_manage_stock( true );
$product->set_stock_quantity( min($sub_product_stocks) );
$product->save();
}
I would love to post a link to all the methods to edit a product but I have no rep for that.
But sadly this is not all...
In order to fully update the price, we need to filter the price HTML to return the newly updated price.
function kulcskron_price_html( $priceHtml, $product )
{
$symbol = get_woocommerce_currency_symbol();
$price = $product->get_regular_price();
$html = '<span class="woocommerce-Price-amount amount">'. $price .' <span class="woocommerce-Price-currencySymbol">'. $symbol .'</span></span>';
return $html;
};
add_filter( 'woocommerce_get_price_html', 'kulcskron_price_html', 10, 2 );
A little backstory. We need to sync all the products from an external database through an XML file. But there is a catch, the XML file only contains product parts and they are not displayed, searched, filtered and certainly can't be ordered individually. None of the Woocommerce build in functionality meet these requirements.
In order to solve this, I registered a new post type and imported all the product parts into this newly created post type. After that, I registered a new field for every WC Product with the Advanced Custom Fields plugin.
It looks like this:
Newly registered custom post type with the custom fields
The code above makes easy to update the WC Products (e.g. Prices) based on the selected Sub products.

Add custom field data to WooCommerce order

I have a custom field on my WooCommerce single product. It sends to the cart fine, it displays on checkout fine, it shows in the order in the dashboard fine.
What I am now trying to do is set the value as a custom field in the order page so I am able to amend the text when I need to. For some reason when I submit the form this step isn't working.
The code that i use in my functions.phpfile:
// Add the field to the product
add_action('woocommerce_before_add_to_cart_button', 'my_custom_checkout_field');
function my_custom_checkout_field() {
echo '<div id="my_custom_checkout_field"><h3>'.__('My Field').'</h3>';
echo '<label>fill in this field</label> <input type="text" name="my_field_name">';
echo '</div>';
}
// Store custom field
function save_my_custom_checkout_field( $cart_item_data, $product_id ) {
if( isset( $_REQUEST['my_field_name'] ) ) {
$cart_item_data[ 'my_field_name' ] = $_REQUEST['my_field_name'];
/* below statement make sure every add to cart action as unique line item */
$cart_item_data['unique_key'] = md5( microtime().rand() );
}
return $cart_item_data;
}
add_action( 'woocommerce_add_cart_item_data', 'save_my_custom_checkout_field', 10, 2 );
// Render meta on cart and checkout
function render_meta_on_cart_and_checkout( $cart_data, $cart_item = null ) {
$custom_items = array();
/* Woo 2.4.2 updates */
if( !empty( $cart_data ) ) {
$custom_items = $cart_data;
}
if( isset( $cart_item['my_field_name'] ) ) {
$custom_items[] = array( "name" => 'My Field', "value" => $cart_item['my_field_name'] );
}
return $custom_items;
}
add_filter( 'woocommerce_get_item_data', 'render_meta_on_cart_and_checkout', 10, 2 );
// Display as order meta
function my_field_order_meta_handler( $item_id, $values, $cart_item_key ) {
if( isset( $values['my_field_name'] ) ) {
wc_add_order_item_meta( $item_id, "my_field_name", $values['my_field_name'] );
}
}
add_action( 'woocommerce_add_order_item_meta', 'my_field_order_meta_handler', 1, 3 );
/** THIS IS WHERE I'M STUCK **/
add_action('woocommerce_checkout_process', 'my_custom_checkout_field_process');
function my_custom_checkout_field_process() {
global $woocommerce;
// Check if set, if its not set add an error. This one is only requite for companies
if ($_POST['billing_company'])
if (!$_POST['my_field_name'])
$woocommerce->add_error( __('Please enter your XXX.') );
}
// Update the user meta with field value
add_action('woocommerce_checkout_update_user_meta', 'my_custom_checkout_field_update_user_meta');
function my_custom_checkout_field_update_user_meta( $user_id ) {
if ($user_id && $_POST['my_field_name']) update_user_meta( $user_id, 'my_field_name', esc_attr($_POST['my_field_name']) );
}
// Update the order meta with field value
add_action('woocommerce_checkout_update_order_meta', 'my_custom_checkout_field_update_order_meta');
function my_custom_checkout_field_update_order_meta( $order_id ) {
if ($_POST['my_field_name']) update_post_meta( $order_id, 'My Field', esc_attr($_POST['my_field_name']));
}
Screenshot of what currently happens:
What I would like to happen:
Any help would be greatly appreciated.
Updated: compatibility with Woocommerce version 3+
You have missing the function to display this custom field value on the order edit page:
/**
* Display field value on the order edit page
*/
add_action( 'woocommerce_admin_order_data_after_billing_address', 'my_custom_checkout_field_display_admin_order_meta', 10, 1 );
function my_custom_checkout_field_display_admin_order_meta( $order ){
$order_id = method_exists( $order, 'get_id' ) ? $order->get_id() : $order->id;
echo '<p><strong>'.__('My Field Name').':</strong> ' . get_post_meta( $order_id, 'my_field_name', true ) . '</p>';
}
On the reference link below, you have all original wooThemes functional working code snippets. It's an excellent fully functional tutorial.
Reference: [Customizing checkout fields using actions and filters][1]
Edit: Get a custom label displayed with your custom field value in Order item meta
To get a custom label like "MY field name" with your custom field value (in order items meta) instead of a slug like my_field_name, refer to this treads:
Saving a product custom field and displaying it in cart page
Displaying product custom fields values in the order once processed
Adding user custom field value to order items details
I don't know if this still is relevant, I have tried to do this with code, unfortunately, I got stuck in the way. I have tried this woocommerce checkout field editor plugin, which did well to add custom field data to woocomemrce order.

Categories