I am trying to add some functionality to woocommerce as per a clients request. The functionality involves me having to modify woocommerce source code. I have explained to the client why this is a bad idea, however they insist, so I do it.
But I am having some trouble adding some values to POST.
This is all done from the woocommerce backend >> orders.
Essentially I am adding a width and height to a product that is added via the back-end by a shop admin.
The only problem that I have is getting the width and height into POST.
I have this code: (backbone modal popup)
<script type="text/template" id="wc-modal-add-products">
<div class="wc-backbone-modal">
<div class="wc-backbone-modal-content">
<section class="wc-backbone-modal-main" role="main">
<header class="wc-backbone-modal-header">
<a class="modal-close modal-close-link" href="#"><span class="close-icon"><span class="screen-reader-text">Close media panel</span></span></a>
<h1><?php _e( 'Add products', 'woocommerce' ); ?></h1>
</header>
<article style="min-height:70px;">
<form action="" method="post">
<input type="hidden" id="add_item_id" name="add_order_items" class="wc-product-search" style="width: 100%;" data-placeholder="<?php _e( 'Search for a product…', 'woocommerce' ); ?>" data-multiple="true" />
<input name="wpti_x" placeholder="Width" class="wpti-product-size" id="wpti-product-x" type="number">
<input name="wpti_y" placeholder="Height" class="wpti-product-size" id="wpti-product-y" type="number">
</form>
</article>
<footer>
<div class="inner">
<button id="btn-ok" class="button button-primary button-large"><?php _e( 'Add', 'woocommerce' ); ?></button>
</div>
</footer>
</section>
</div>
</div>
<div class="wc-backbone-modal-backdrop modal-close"> </div>
</script>
and this is the PHP which controls the data:
<?php
public static function add_order_item() {
function new_prices($backend_prod_id){
$pluginpath = "/home/#####/wp-content/plugins/codecanyon-7104096-woo-table-based-pricing/";
include_once $pluginpath . 'woocommerce-price-table.php';
$height = $_POST['wpti_x']; //This is empty??? Hardcoded works fine
$width = $_POST['wpti_y']; //This is empty??? Hardcoded works fine
$prices = get_prices($width, $height, $backend_prod_id);
$json_string = json_encode($prices); //json encode prices
$obj = json_decode($json_string, true);
return $obj['product_price'];
}
check_ajax_referer( 'order-item', 'security' );
$item_to_add = sanitize_text_field( $_POST['item_to_add'] );
$order_id = absint( $_POST['order_id'] );
// Find the item
if ( ! is_numeric( $item_to_add ) ) {
die();
}
$post = get_post( $item_to_add );
if ( ! $post || ( 'product' !== $post->post_type && 'product_variation' !== $post->post_type ) ) {
die();
}
$_product = wc_get_product( $post->ID );
$order = wc_get_order( $order_id );
$order_taxes = $order->get_taxes();
$class = 'new_row';
// Set values
$item = array();
$item['product_id'] = $_product->id;
$item['variation_id'] = isset( $_product->variation_id ) ? $_product->variation_id : '';
$item['variation_data'] = $item['variation_id'] ? $_product->get_variation_attributes() : '';
$item['name'] = $_product->get_title();
$item['tax_class'] = $_product->get_tax_class();
$item['qty'] = 1;
$item['line_subtotal'] = new_prices($_product->id);
$item['line_subtotal_tax'] = '';
$item['line_total'] = new_prices($_product->id);
$item['line_tax'] = '';
// Add line item
$item_id = wc_add_order_item( $order_id, array(
'order_item_name' => $item['name'],
'order_item_type' => 'line_item'
) );
// Add line item meta
if ( $item_id ) {
wc_add_order_item_meta( $item_id, '_qty', $item['qty'] );
wc_add_order_item_meta( $item_id, '_tax_class', $item['tax_class'] );
wc_add_order_item_meta( $item_id, '_product_id', $item['product_id'] );
wc_add_order_item_meta( $item_id, '_variation_id', $item['variation_id'] );
wc_add_order_item_meta( $item_id, '_line_subtotal', $item['line_subtotal'] );
wc_add_order_item_meta( $item_id, '_line_subtotal_tax', $item['line_subtotal_tax'] );
wc_add_order_item_meta( $item_id, '_line_total', $item['line_total'] );
wc_add_order_item_meta( $item_id, '_line_tax', $item['line_tax'] );
// Since 2.2
wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
// Store variation data in meta
if ( $item['variation_data'] && is_array( $item['variation_data'] ) ) {
foreach ( $item['variation_data'] as $key => $value ) {
wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
}
}
do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item );
}
$item = apply_filters( 'woocommerce_ajax_order_item', $item, $item_id );
include( 'admin/meta-boxes/views/html-order-item.php' );
// Quit out
die();
}
?>
I cant figure out why it does not add the width ($_POST['wpti_x']) and height ($_POST['wpti_x']) to POST. If the values are hardcoded, it works fine.
Any help would be appreciated.
You will need to edit meta-boxes-order-min.js, as that is where the functionality you are trying to use exists in woocommerce.
If you want to POST those values, you may need to edit the add_item function.
I hope this helps, and remember if you update woocommerce, you edits will be lost. so this is really not advised.
Related
I have a problem on the "my account" page of my woocommerce store.
Indeed, when I log in with a new customer account that has never placed an order, I have 3 content containers that disappear.
After testing, they only appear if I place an order with the account in question.
Do you have any idea where this could be coming from?
The 3 divs in question correspond to :
the navigation menu of the page my account that disappears only on the dashboard tab (it reappears on all other tabs - my orders, my addresses ...)
a div with the recently viewed products (see code n°1 below)
a div with the articles recently written by the customer (see code n°2 below)
When an order is placed, all the content reappears.
I also have a div with the recent orders which strangely, does not have any problem of display (see code n°3 below)
Do not hesitate to ask me for clarifications, or if you think that the error comes from a tag elsewhere in my code (a tag of authorization according to the role for example?)
In advance, Thank you very much for your help.
add_shortcode( 'recently_viewed_products', 'bbloomer_recently_viewed_shortcode' );
function bbloomer_recently_viewed_shortcode() {
$viewed_products = ! empty( $_COOKIE['woocommerce_recently_viewed'] ) ? (array) explode( '|', wp_unslash( $_COOKIE['woocommerce_recently_viewed'] ) ) : array();
$viewed_products = array_reverse( array_filter( array_map( 'absint', $viewed_products ) ) );
if ( empty( $viewed_products ) ) return;
?>
<div class="col-23">
<?php $title = '<h3>Produits consultés récemment</h3>';?>
<?php $product_ids = implode( ",", $viewed_products );?>
<?php return $title . do_shortcode("[products ids='$product_ids' limit='4' columns='4' orderby='post__in' class='recentes-vues']");?>
</div>
<?php }
function custom_track_product_view() {
if ( ! is_singular( 'product' ) ) {
return;
}
global $post;
if ( empty( $_COOKIE['woocommerce_recently_viewed'] ) )
$viewed_products = array();
else
$viewed_products = (array) explode( '|', $_COOKIE['woocommerce_recently_viewed'] );
if ( ! in_array( $post->ID, $viewed_products ) ) {
$viewed_products[] = $post->ID;
}
if ( sizeof( $viewed_products ) > 15 ) {
array_shift( $viewed_products );
}
// Store for session only
wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ) );
}
add_action( 'template_redirect', 'custom_track_product_view', 20 );
// PRODUIT RECENT PAGE COMPTE
add_action( 'woocommerce_account_dashboard' , 'recentes_vues', 5 );
function recentes_vues() {
echo do_shortcode('[recently_viewed_products]');
}
add_action( 'woocommerce_account_dashboard' , 'recent_posts', 3 );
function recent_posts() {
if ( is_user_logged_in()) :
global $current_user;
wp_get_current_user();
$author_query = array('posts_per_page' => '-1','author' => $current_user->ID);
$author_posts = new WP_Query($author_query);
?><div id="recentposts">
<?php
if ($author_posts->found_posts) {
?>
<ul class="liststylenone">
<?php
while($author_posts->have_posts()) : $author_posts->the_post();
?>
<li><?php the_title(); ?></li>
<?php
endwhile;
?></ul><?php
}
else {
echo '<p>Aucun article trouvé 😞</p>';
}
else :
echo "not logged in";
endif;
?>
add_action( 'woocommerce_account_dashboard', 'recent_order', 2 );
function recent_order(){
// For logged in users only
if ( is_user_logged_in() ) :
$user_id = get_current_user_id(); // The current user ID
// Get the WC_Customer instance Object for the current user
$customer = new WC_Customer( $user_id );
// Get the last WC_Order Object instance from current customer
$last_order = $customer->get_last_order();
if ( is_a( $last_order, 'WC_Order' ) ) {
$order_id = $last_order->get_id(); // Get the order id
$order_data = $last_order->get_data(); // Get the order unprotected data in an array
$order_status = $last_order->get_status(); // Get the order status
$date_created = $last_order->get_date_created();
$order_total = $last_order->get_total();
}
?>
<div class="row last-order">
<div class="col-md-7">
<div class="col-3">
<div class="col-5">
<div class="col-md-4 order-status-box">
<h6 class="status">État : <?php echo esc_html( wc_get_order_status_name( $order_status ) ); ?></h6>
</div>
<p class="totalcmdd"><?php echo $order_total."€"; ?></p>
<?php
setlocale(LC_TIME, 'fr_FR');
date_default_timezone_set('Europe/Paris');
echo utf8_encode(strftime('%d %B %Y', strtotime($date_created)));
//echo date('d-F-Y', strtotime($date_created)); ?>
</p>
</div>
<div style="display: flex;flex-direction: row;justify-content: space-evenly;align-items: center;margin-right: 1.5em;margin-left: 1.5em;padding: 0.3em;background-color: white;text-transform: uppercase;text-decoration: none;font-size: 13px;">
<?php foreach ($last_order->get_items() as $item) :
$product = $item->get_product();
$thumbnail = $product->get_image(array(50, 50));
if ($product->get_image_id() > 0) {
$item_name = '<div class="item-thumbnail">' . $thumbnail . '</div>'; // You had an extra variable here
}
echo $item_name . $item->get_name();
endforeach;
endif;
?>
I've added an additional variation option to my products with the following code:
add_action('woocommerce_variation_options', 'he_add_to_variation_option', 10, 3);
function he_add_to_variation_option( $loop, $variation_data, $variation){
$is_trial = (get_post_meta($variation->ID, '_trialversion', true)) ? ' checked' : '';
?>
<label class="tips" data-tip="<?php esc_attr_e( 'Enable this option to make as a trial version', 'woocommerce' ); ?>">
<?php esc_html_e( 'Trial Version?', 'woocommerce' ); ?>
<input type="checkbox" class="checkbox variable_is_trial_version" name="_trialversion[<?php echo esc_attr( $variation->ID ); ?>]"<?php echo $is_trial;?>/>
</label>
<?php
}
add_action( 'woocommerce_save_product_variation', 'save_trialversion_option_fields' );
function save_trialversion_option_fields( $post_id ) {
if ( isset( $_POST['_trialversion'] ) ){
foreach ( $_POST['_trialversion'] as $productid=>$checked ){
update_post_meta( $productid, '_trialversion', 'yes' );
}
}
}
This works great, it's displayed within the variants and it's saved correctly in the database.
So far, so good.
Now, I would like to add an additional checkbox at checkout, if a product is flagged as "trial version". I'm using the "Germanized" plugin as well, which has options for custom checkboxes, but I can't get it to recognize the changes I've made with the above code.
How would I accomplish the custom checkbox for my trial version variants? With or without Germanized, at this point I just want to get it to work. Maybe there's a free plugin, but if I can just do it by adding some code, that would probably be easier.
The checkbox would have to be a required one to complete the purchase of the trial version.
Hopefully someone has an idea on how to do this. Looking forward to your replies!
When a product variant has the "trialversion" enabled, a new checkbox will be added at the checkout page
woocommerce_save_product_variation should not contain a foreach loop, the 2nd parameter of the function already contains a counter $i
Normally the problem with the checkboxes should also be solved
function add_to_variation_option( $loop, $variation_data, $variation){
$is_trial = get_post_meta( $variation->ID, '_trialversion', true);
if ( $is_trial == 'yes' ) {
$is_trial = 'checked';
} else {
$is_trial = '';
}
?>
<label class="tips" data-tip="<?php esc_attr_e( 'Enable this option to make as a trial version', 'woocommerce' ); ?>">
<?php esc_html_e( 'Trial Version?', 'woocommerce' ); ?>
<input type="checkbox" class="checkbox variable_is_trial_version" name="_trialversion[<?php echo esc_attr( $loop ); ?>]"<?php echo $is_trial;?>/>
</label>
<?php
}
add_action('woocommerce_variation_options', 'add_to_variation_option', 10, 3);
function save_trialversion_option_fields( $variation_id, $i ) {
if ( !empty($_POST['_trialversion']) && !empty( $_POST['_trialversion'][$i] ) ) {
update_post_meta( $variation_id, '_trialversion', 'yes' );
} else {
update_post_meta( $variation_id, '_trialversion', 'no' );
}
}
add_action( 'woocommerce_save_product_variation', 'save_trialversion_option_fields', 10, 2 );
/**
* Add checkbox field to the checkout
**/
function my_custom_checkout_field( $checkout ) {
// Get $product object from Cart object
$cart = WC()->cart->get_cart();
foreach( $cart as $cart_item ) {
// The WC_Product object
$product = wc_get_product( $cart_item['product_id'] );
// Checks the product type, 'variable', returns boolean
if ( $product->is_type( 'variable' ) ) {
// Get variation id
$variation_id = $cart_item['data']->get_id();
// Get post meta
$trialversion = get_post_meta( $variation_id, '_trialversion', true);
// Found
if ( $trialversion == 'yes' ) {
$trialversion = 'found';
// Break loop
break;
}
}
}
// Found
if ( isset($trialversion) && $trialversion == 'found' ) {
echo '<div id="my-new-field">';
woocommerce_form_field( 'my_checkbox', array(
'type' => 'checkbox',
'class' => array('input-checkbox'),
'label' => __('I agree'),
'required' => true,
), $checkout->get_value( 'my_checkbox' ));
echo '</div>';
}
}
add_action('woocommerce_after_order_notes', 'my_custom_checkout_field', 10, 1 );
I'm using woocommerce for my website, but as I only have one main product and two accessories (related products), I don't need a classic shop page, neither single-product pages for each of my product. My main product has a color variation.
I want to have an add-to-cart button, with the color variation dropdown and the quantity field in one of my regular post page. Exactly like on a single-product page, but embedded in my own page, and without the parts of the single-product page I don't need (description, ...).
I finally decided to achieve this using two custom shortcodes I created: [my_vc_product_price id="xxx"] and [my_vc_add2cart_variable_product id="xxx"]. So I can put them where I want.
But my problem is that the behavior of the dropdown menu + variation availability + add-to-cart button is not the same that this elements have in the single-product page:
- the availability of he variation doesn't show up when I choose the color in the dropdown menu;
- the add-to-cart button is not disable when no color is chosen in the dropdown menu (it should be disable and active only when a color is chosen).
Displaying the price was easy, using some code found on internet:
/**
* Add shortcode to allow to display product price in a page
*/
function my_vc_display_product_price( $args ) {
$product_id = $args['id'];
$product = wc_get_product( $product_id );
echo '<p class="price">' . $product->get_price_html() . '</p>';
}
add_shortcode( 'my_vc_product_price', 'my_vc_display_product_price');
To get the same graphical result, I just had to add some CSS classes on the row: "woocommerce" and "product".
The code to display the dropdown menu + the quantity and the add-to-cart button is almost the same than in the variable.php file found in plugins/woocommerce/templates/single-product/add-to-cart/. The only things really change is that you need to get the "variation attributes" of the product, and not the attributes.
$attributes = $product->get_variation_attributes();
$attribute_keys = array_keys( $attributes );
So the full function code is:
/**
* Add shortcode to allow to display an add to cart button with dropdown menu for variation attributes
*/
function my_vc_add_to_cart_button_variable_product( $args ) {
global $product;
$product_id = $args['id'];
$product = wc_get_product( $product_id );
if( $product->is_type( 'variable' )) {
$attributes = $product->get_variation_attributes();
$attribute_keys = array_keys( $attributes );
$available_variations = array( $product->get_available_variations() );
do_action( 'woocommerce_before_add_to_cart_form' ); ?>
<form class="variations_form cart" method="post" enctype='multipart/form-data' data-product_id="<?php echo absint( $product->get_id() ); ?>" data-product_variations="<?php echo htmlspecialchars( wp_json_encode( $available_variations ) ) ?>">
<?php do_action( 'woocommerce_before_variations_form' ); ?>
<?php if ( empty( $available_variations ) && false !== $available_variations ) : ?>
<p class="stock out-of-stock"><?php _e( 'This product is currently out of stock and unavailable.', 'woocommerce' ); ?></p>
<?php else : ?>
<table class="variations" cellspacing="0">
<tbody>
<?php foreach ( $attributes as $attribute_name => $options ) : ?>
<tr>
<td class="value">
<?php
$selected = isset( $_REQUEST[ 'attribute_' . sanitize_title( $attribute_name ) ] ) ? wc_clean( stripslashes( urldecode( $_REQUEST[ 'attribute_' . sanitize_title( $attribute_name ) ] ) ) ) : $product->get_variation_default_attribute( $attribute_name );
wc_dropdown_variation_attribute_options( array( 'options' => $options, 'attribute' => $attribute_name, 'product' => $product, 'selected' => $selected ) );
?>
</td>
</tr>
<?php endforeach;?>
</tbody>
</table>
<?php do_action( 'woocommerce_before_add_to_cart_button' ); ?>
<div class="single_variation_wrap">
<?php
/**
* woocommerce_before_single_variation Hook.
*/
do_action( 'woocommerce_before_single_variation' );
/**
* woocommerce_single_variation hook. Used to output the cart button and placeholder for variation data.
* #since 2.4.0
* #hooked woocommerce_single_variation - 10 Empty div for variation data.
* #hooked woocommerce_single_variation_add_to_cart_button - 20 Qty and cart button.
*/
do_action( 'woocommerce_single_variation' );
?>
<script type="text/template" id="tmpl-variation-template">
<div class="woocommerce-variation-description">{{{ data.variation.variation_description }}}</div>
<div class="woocommerce-variation-price">{{{ data.variation.price_html }}}</div>
<div class="woocommerce-variation-availability">{{{ data.variation.availability_html }}}</div>
</script>
<script type="text/template" id="tmpl-unavailable-variation-template">
<p><?php _e( 'Sorry, this product is unavailable. Please choose a different combination.', 'woocommerce' ); ?></p>
</script>
<?php
/**
* woocommerce_after_single_variation Hook.
*/
do_action( 'woocommerce_after_single_variation' );
?>
</div>
<?php do_action( 'woocommerce_after_add_to_cart_button' ); ?>
<?php endif; ?>
<?php do_action( 'woocommerce_after_variations_form' ); ?>
</form>
<?php
do_action( 'woocommerce_after_add_to_cart_form' );
}
}
add_shortcode( 'my_vc_add2cart_variable_product', 'my_vc_add_to_cart_button_variable_product');
Any idea what is going wrong? I don't understand, if the code is the same, why it didn't execute the same. Is there something missing because this code is outside woocommerce pages?
try using following code
function add_to_cart_form_shortcode( $atts ) {
if ( empty( $atts ) ) {
return '';
}
if ( ! isset( $atts['id'] ) && ! isset( $atts['sku'] ) ) {
return '';
}
$args = array(
'posts_per_page' => 1,
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'no_found_rows' => 1,
);
if ( isset( $atts['sku'] ) ) {
$args['meta_query'][] = array(
'key' => '_sku',
'value' => sanitize_text_field( $atts['sku'] ),
'compare' => '=',
);
$args['post_type'] = array( 'product', 'product_variation' );
}
if ( isset( $atts['id'] ) ) {
$args['p'] = absint( $atts['id'] );
}
$single_product = new WP_Query( $args );
$preselected_id = '0';
if ( isset( $atts['sku'] ) && $single_product->have_posts() && 'product_variation' === $single_product->post->post_type ) {
$variation = new WC_Product_Variation( $single_product->post->ID );
$attributes = $variation->get_attributes();
$preselected_id = $single_product->post->ID;
$args = array(
'posts_per_page' => 1,
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'no_found_rows' => 1,
'p' => $single_product->post->post_parent,
);
$single_product = new WP_Query( $args );
?>
<script type="text/javascript">
jQuery( document ).ready( function( $ ) {
var $variations_form = $( '[data-product-page-preselected-id="<?php echo esc_attr( $preselected_id ); ?>"]' ).find( 'form.variations_form' );
<?php foreach ( $attributes as $attr => $value ) { ?>
$variations_form.find( 'select[name="<?php echo esc_attr( $attr ); ?>"]' ).val( '<?php echo esc_js( $value ); ?>' );
<?php } ?>
});
</script>
<?php
}
$single_product->is_single = true;
ob_start();
global $wp_query;
$previous_wp_query = $wp_query;
$wp_query = $single_product;
wp_enqueue_script( 'wc-single-product' );
while ( $single_product->have_posts() ) {
$single_product->the_post()
?>
<div class="single-product" data-product-page-preselected-id="<?php echo esc_attr( $preselected_id ); ?>">
<?php woocommerce_template_single_add_to_cart(); ?>
</div>
<?php
}
$wp_query = $previous_wp_query;
wp_reset_postdata();
return '<div class="woocommerce">' . ob_get_clean() . '</div>';
}
add_shortcode( 'add_to_cart_form', 'add_to_cart_form_shortcode' );
/*Example Usage [add_to_cart_form id=147]*/
That's just perfect! Many thanks.
Now the hardest to come: I need to understand your code and compare it with mine ;-)
In the WooCommerce backend, I manually create orders for over-the-phone customers and then send them the "Customer payment page" link so that they complete their payment.
On that page ("order-pay", with template templates/checkout/form-pay.php), I've added the following code to display the billing form:
<h3><?php _e( 'Billing details', 'woocommerce' ); ?></h3>
<?php do_action( 'woocommerce_before_checkout_billing_form', $order ); ?>
<div class="woocommerce-billing-fields__field-wrapper">
<?php
$fields = WC()->checkout->get_checkout_fields( 'billing' );
foreach ( $fields as $key => $field ) {
$field_name = $key;
if ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
$field['value'] = $order->{"get_$field_name"}( 'edit' );
} else {
$field['value'] = $order->get_meta( '_' . $field_name );
}
woocommerce_form_field( $key, $field, $field['value'] );
}
?>
</div>
<?php do_action( 'woocommerce_after_checkout_billing_form', $order ); ?>
I'd like customers to be able to edit their billing info (name, email, phone, address) and have it saved upon payment. This is done on the "regular" checkout page using the following line but doesn't work on the order-pay endpoint.
$('body').trigger('update_checkout');
How can I validate & save those fields' values (overwriting exiting billing info) upon payment?
This is a multi-step solution but it has been working for me.
Make sure you have copied the form-pay.php file to your child theme (yourtheme/woocommerce/checkout/form-pay.php) so you don't lose all of your work on an update.
At the top of your form-pay.php add the following two lines of code right after defined( 'ABSPATH' ) || exit;
$tempateURL = get_stylesheet_directory_uri();
$orderID = wc_get_order_id_by_order_key($_GET["key"]);
Next you will add your billing fields inside of the order_review form. I added mine just after the closing table but before the payment fields.
<h3><?php _e( 'Billing details', 'woocommerce' ); ?></h3>
<?php do_action( 'woocommerce_before_checkout_billing_form', $order ); ?>
<div class="woocommerce-billing-fields__field-wrapper">
<?php
$fields = WC()->checkout->get_checkout_fields( 'billing' );
foreach ( $fields as $key => $field ) {
$field_name = $key;
if ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
$field['value'] = $order->{"get_$field_name"}( 'edit' );
} else {
$field['value'] = $order->get_meta( '_' . $field_name );
}
woocommerce_form_field( $key, $field, $field['value'] );
}
?>
</div>
Then create a "next" button just after the closing ?> but before the closing of the billing details like this:
<div class="my-button">
<input type="button" id="update-billing" class="button-next" value="Next">
</div>
It will look like this:
<h3><?php _e( 'Billing details', 'woocommerce' ); ?></h3>
<?php do_action( 'woocommerce_before_checkout_billing_form', $order ); ?>
<div class="woocommerce-billing-fields__field-wrapper">
<?php
$fields = WC()->checkout->get_checkout_fields( 'billing' );
foreach ( $fields as $key => $field ) {
$field_name = $key;
if ( is_callable( array( $order, 'get_' . $field_name ) ) ) {
$field['value'] = $order->{"get_$field_name"}( 'edit' );
} else {
$field['value'] = $order->get_meta( '_' . $field_name );
}
woocommerce_form_field( $key, $field, $field['value'] );
}
?>
<div class="my-button">
<input type="button" id="update-billing" class="button-next" value="Next">
</div>
</div>
I created a js folder in my child theme and created a file called custom-jquery.js .
I enqueued the new js file in my functions.php using this code:
add_action( 'wp_enqueue_scripts', 'my_enqueue_assets' );
function my_enqueue_assets() {
wp_enqueue_script( 'my-jquery', get_stylesheet_directory_uri() . '/js/custom-jquery.js',"", false, false );
}
I then created a folder called woo-functions inside of the js folder and created a file called update-cart.php
I then wrote the following code:
<?php
require_once(rtrim($_SERVER['DOCUMENT_ROOT'], '/') . '/wp-load.php');
print_r($_POST);
$orderID = $_POST["orderID"];
$firstName = $_POST["firstName"];
$lastName = $_POST["lastName"];
$billingCompany = $_POST["billingCompany"];
$billingAddress_1 = $_POST["billingAddress_1"];
$billing_city = $_POST["billing_city"];
$billing_state = $_POST["billing_state"];
$billing_postcode = $_POST["billing_postcode"];
$billing_phone = $_POST["billing_phone"];
$billing_email = $_POST["billing_email"];
update_post_meta( $orderID, '_billing_first_name', $firstName );
update_post_meta( $orderID, '_billing_last_name', $lastName );
update_post_meta( $orderID, '_billing_company', $billingCompany );
update_post_meta( $orderID, '_billing_address_1', $billingAddress_1 );
update_post_meta( $orderID, '_billing_city', $billing_city );
update_post_meta( $orderID, '_billing_state', $billing_state );
update_post_meta( $orderID, '_billing_postcode', $billing_postcode );
update_post_meta( $orderID, '_billing_phone', $billing_phone );
update_post_meta( $orderID, '_billing_email', $billing_email );
echo $firstName;
?>
I then wrote the following jquery in my custom-jquery.js file to update the information when the "next" button I created was clicked.
jQuery(document).ready(function($) {
$("#update-billing").on("click", function(e){
var stylesheetURL = $("#order_review").attr("data-theme-url");
var orderID = $("#order_review").attr("data-order-id");
var firstName = $("#billing_first_name").val();
var lastName = $("#billing_last_name").val();
var billingCompany = $("#billing_company").val();
var billingAddress_1 = $("#billing_address_1").val();
var billing_city = $("#billing_city").val();
var billing_state = $("#billing_state").val();
var billing_postcode = $("#billing_postcode").val();
var billing_phone = $("#billing_phone").val();
var billing_email = $("#billing_email").val();
console.log(firstName);
$.ajax({
method: "POST",
url: stylesheetURL + "/js/woo-functions/update-cart.php",
data: {
orderID: orderID,
firstName: firstName,
lastName: lastName,
billingCompany: billingCompany,
billingCountry: "US",
billingAddress_1: billingAddress_1,
billing_city: billing_city,
billing_state: billing_state,
billing_postcode: billing_postcode,
billing_phone: billing_phone,
billing_email: billing_email
}
})
.done(function( msg ) {
console.log(msg);
$(".page-id-10 table.shop_table, .page-id-10 .the-payment-fields").slideToggle();
$(".woocommerce-billing-fields__field-wrapper").slideToggle();
});
});
I also added a .slideToggle function to hide the billing fields when the "next" button is clicked. Woocommerce dynamically adds the information to the checkout page and your normal checkout page will have the same page number. If you don't make the slideToggle function target a unique class, it will affect your normal checkout page. Because of this, I added a class of ".the-payment-fields" to the div id "payment" in the form-pay.php file.
<div id="payment" class="the-payment-fields">
When the customer clicks the "next" button it saves the data to their profile and you can see the billing information on the order.
Hope this helps!
The following code adds editable short description of the product as order item metadata in Woocommerce. The code is working as expected for simple products but for variable products the $item->get_formatted_meta_data('') returns empty array at first and only after editing the item, the short description shows as expected.
<?php
/**
* Add product description meta to order item
*/
add_action( 'woocommerce_before_order_itemmeta', 'add_order_item_description_meta', 10, 3 );
function add_order_item_description_meta( $item_id, $item, $product ) {
// Add only if not present
$product_description_meta = wc_get_order_item_meta( $item_id, '_product_short_desc', true );
if( empty( $product_description_meta ) ) {
if( $product->is_type('variation') ) {
$parent_product = wc_get_product( $product->get_parent_id() );
$excerpt = $product->get_description();
$excerpt = empty($excerpt) ? $parent_product->get_short_description() : $excerpt;
} else {
$excerpt = $product->get_short_description();
}
wc_add_order_item_meta( $item_id, '_product_short_desc', $excerpt );
}
}
/**
* Hide product description meta
*/
add_filter( 'woocommerce_hidden_order_itemmeta', 'custom_hidden_order_itemmeta' );
function custom_hidden_order_itemmeta( $hidden_order_itemmeta ) {
$hidden_order_itemmeta[] = '_product_short_desc';
return $hidden_order_itemmeta;
}
/**
* Add custom column to display product description meta
*/
add_action( 'woocommerce_admin_order_item_headers', 'custom_admin_order_items_headers', 20, 1 );
function custom_admin_order_items_headers( $order ) {
echo '<th class="item_short_description">';
echo __('Short Description', 'woocommerce') . '</th>';
}
/**
* Custom column content in table
*/
add_action( 'woocommerce_admin_order_item_values', 'custom_admin_order_item_values', 20, 3 );
function custom_admin_order_item_values( $product, $item, $item_id ) {
?>
<td class="product_short_desc">
<?php
if( $meta_data = $item->get_formatted_meta_data( '' ) ) {
$meta = array_filter( $meta_data, function( $value, $key ) {
return '_product_short_desc' === $value->key;
}, ARRAY_FILTER_USE_BOTH );
if( ! empty( $meta ) ) {
// Get the key of first value
$meta_id = 0;
foreach( $meta as $key => $value) {
$meta_id = $key;
break;
}
$product_desc_meta = $meta[$meta_id];
?>
<div class="view">
<?php echo wp_kses_post( force_balance_tags( $product_desc_meta->display_value ) ); ?>
</div>
<div class="edit" style="display: none;">
<input type="hidden" placeholder="<?php esc_attr_e( 'Name (required)', 'woocommerce' ); ?>" name="meta_key[<?php echo esc_attr( $item_id ); ?>][<?php echo esc_attr( $meta_id ); ?>]" value="<?php echo esc_attr( $product_desc_meta->key ); ?>" />
<textarea placeholder="<?php esc_attr_e( 'Short Description', 'woocommerce' ); ?>" name="meta_value[<?php echo esc_attr( $item_id ); ?>][<?php echo esc_attr( $meta_id ); ?>]"><?php echo esc_textarea( rawurldecode( $product_desc_meta->value ) ); ?></textarea>
</div>
<?php
}
} else {
echo '-';
} ?>
</td>
<?php
}
For simple product the Short Description column is displaying value when adding the item to order but for variable product it shows - as it goes into else condition of function custom_admin_order_item_values at first and then after editing the item, it displays the short description correctly.
Here's screenshot for the simple and variable product added without being edited:
Please help to find the underlying bug.
UPDATE: Anyone trying to help on this, I can confirm that problem lies either in first hooked function i.e. add_order_item_description_meta or in the Woocommerce core because even if you comment out the later hooks and their respective functions the same problem is there.
So the meta data for the variable product is not loaded first time when product is added to the order, but when it is refreshed such as by editing it, the meta data loads fine. Also note as I mentioned earlier the meta data loads as expected for simple products at first time without need of refresh of order items.
I have tried your code and it works using
$meta = array_filter( $meta_data, function( $value ) {
return '_product_short_desc' === $value->key;
} );
instead of
$meta = array_filter( $meta_data, function( $value, $key ) {
return '_product_short_desc' === $value->key;
}, ARRAY_FILTER_USE_BOTH );
and adding if statement in add_order_item_description_meta
function add_order_item_description_meta( $item_id, $item, $product ) {
if ( !$product ) return;
/* … rest of the code … */
Here's a screenshot: