Ajax in checkout + select2 - how to fix the address field problematic update? - ajax

For checkout I use one script to show and hide billing fields depending on the shipping way.
Earlier it worked fine, but that time - I think because of using select2 in the city field instead of the text input - the address text field is making issues.
When user writes their address not so fast, the form is starting to update too quick, can delete the new written after that short pause characters or not delete, randomly, and when I want to delete something - the select 2 can appear on the top of the page and not closing.
add_filter( 'woocommerce_update_order_review_fragments', 'awoohc_add_update_form_billing', 99 );
function awoohc_add_update_form_billing( $fragments ) {
$checkout = WC()->checkout();
ob_start();
?>
<div class="woocommerce-billing-fields__field-wrapper">
<?php
$fields = $checkout->get_checkout_fields( 'billing' );
foreach ( $fields as $key => $field ) {
if ( isset( $field['country_field'], $fields[ $field['country_field'] ] ) ) {
$field['country'] = $checkout->get_value( $field['country_field'] );
}
woocommerce_form_field( $key, $field, $checkout->get_value( $key ) );
}
?>
</div>
<?php
$art_add_update_form_billing = ob_get_clean();
$fragments['.woocommerce-billing-fields'] = $art_add_update_form_billing;
return $fragments;
}
add_filter( 'woocommerce_checkout_fields' , 'override_billing_checkout_fields', 20, 1 );
function override_billing_checkout_fields( $fields ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
if ( 'local_pickup:1' === $chosen_methods[0] ) {
unset( $fields['billing']['billing_address_1'] );
unset( $fields['billing']['billing_city'] );
unset( $fields['billing']['billing_state'] );
}
return $fields;
}
add_filter( 'woocommerce_checkout_fields' , 'b_override_billing_checkout_fields', 20, 1 );
function b_override_billing_checkout_fields( $fields ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
if ( 'boxberry_self:4' === $chosen_methods[0] || 'boxberry_self_after:6' === $chosen_methods[0] ) {
unset( $fields['billing']['billing_address_1'] );
}
return $fields;
}
// Just hide woocommerce billing country
add_action( 'woocommerce_before_checkout_form', 'hide_checkout_billing_country', 5 );
function hide_checkout_billing_country() {
echo '<style>#billing_country_field{display:none;}</style>';
}
add_filter('woocommerce_billing_fields', 'customize_checkout_fields', 100 );
function customize_checkout_fields( $fields ) {
if ( is_checkout() ) {
// HERE set the required key fields below
$chosen_fields = array( 'postcode', 'country', 'company','last_name', 'address_2');
foreach( $chosen_fields as $key ) {
if( isset($fields['billing_'.$key]) && $key !== 'country') {
unset($fields['billing_'.$key]); // Remove all define fields except country
}
}
}
return $fields;
}
/*
* Updating the form
*/
add_action( 'wp_footer', 'awoohc_add_script_update_shipping_method' );
function awoohc_add_script_update_shipping_method() {
if ( is_checkout() ) {
?>
<script>
jQuery(document).ready(function ($) {
$(document.body).on('updated_checkout updated_shipping_method', function (event, xhr, data) {
$('input[name^="shipping_method"]').on('change', function () {
$('.woocommerce-billing-fields__field-wrapper').block({
message: null,
overlayCSS: {
background: '#fff',
'z-index': 1000000,
opacity: 0.3
}
});
$('select#billing_city').select2();
});
var first_name = $('#billing_first_name').val(),
phone = $('#billing_phone').val(),
email = $('#billing_email').val(),
city = $('#billing_city').val(),
address_1 = $('#billing_address_1').val(),
$(".woocommerce-billing-fields__field-wrapper").html(xhr.fragments[".woocommerce-billing-fields"]);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_first_name"]').val(first_name);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_phone"]').val(phone);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_email"]').val(email);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_city"]').val(city);
$('select#billing_city').select2();
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_address_1"]').val(address_1);
$('.woocommerce-billing-fields__field-wrapper').unblock();
});
});
</script>
<?php
}
}
Yes, I needed to use select2 twice, in other ways it didn't work as needed. But when even I use it once, it didn't help me with that issue. I can't disable select2, the client needs it, and needs to fix changing the address.
How I replaced the city input to select
add_filter( 'woocommerce_checkout_fields' , 'override_checkout_city_fields' );
function override_checkout_city_fields( $fields ) {
// Define here in the array your desired cities (Here an example of cities)
$option_cities = array(
'city1' => 'city1',
'city2' => 'city2',
);
$fields['billing']['billing_city']['type'] = 'select';
$fields['billing']['billing_city']['options'] = $option_cities;
$fields['shipping']['shipping_city']['type'] = 'select';
$fields['shipping']['shipping_city']['options'] = $option_cities;
return $fields;
}
Or maybe it's a delivery service plugin. Maybe you see what I don't see.
I know that exist many plugins, but I need to find what's wrong manually.
I tried to /comment/ different parts of the script, so I think it's something with select, or with delicery service plugin if not it.

Related

How to update cart item quantities and price based on custom select field on checkout in woocommerce

I'm trying to update cart item quantities and its price based on the custom selection field on checkout. If someone selects "Single person" option from the dropdown then the quantity will be 1 and if someone select "Two person" from the Dropdown then the cart quantity will update to 2 and the price will update as well.
Users can only add one product to the cart, Here is the reference link from where I got some help.
https://www.webroomtech.com/woocommerce-checkout-change-quantity-and-delete-products/
Im using wordpress ajax call to acheive this.
In functions.php I have this code
function customjs_script_add_quanity_js() {
wp_enqueue_script( 'checkout_script', get_stylesheet_directory_uri() . '/assets/js/add_quantity.js', array('jquery') );
wp_localize_script( 'checkout_script', 'add_quantity', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
}
add_action( 'wp_enqueue_scripts', 'customjs_script_add_quanity_js' );
function custom_addqty_load_ajax() {
if ( !is_user_logged_in() ){
add_action( 'wp_ajax_nopriv_update_order_review', 'anp_update_order_review' );
} else{
add_action( 'wp_ajax_update_order_review', 'anp_update_order_review' );
}
}
add_action( 'init', 'custom_addqty_load_ajax' );
function anp_update_order_review() {
$numberof_people = intval($_POST['number_of_people']);
$values = array();
parse_str($_POST['post_data'], $values);
$cart = $values['cart']; //This have no values in it,
foreach ( $cart as $cart_key => $cart_value ){
WC()->cart->set_quantity( $cart_key, $numberof_people);
WC()->cart->calculate_totals();
woocommerce_cart_totals();
}
wp_die();
}
In my **add_quantity.js** file
jQuery(function() {
jQuery( "form.checkout" ).on( "change", ".woocommerce-additional-fields select#______NumberOfTravelers______", function( ) {
//console.log('on chage called');
var number_of_people = jQuery(this).val();
var data = {
action: 'update_order_review',
security: wc_checkout_params.update_order_review_nonce,
number_of_people: number_of_people,
post_data: jQuery( 'form.checkout' ).serialize()
};
jQuery.post( add_quantity.ajax_url, data, function( response )
{
jQuery( 'body' ).trigger( 'update_checkout' );
console.log('success');
});
});
});
My $cart variable in the ajax function shows empty and the cart is not updating on the selection field change. Any help will be appreciated. Thanks
I've updated my code function.php and add_quantity.js
function customjs_script_add_quanity_js() {
wp_enqueue_script( 'checkout_script', get_stylesheet_directory_uri() . '/assets/js/add_quantity.js', array('jquery') );
wp_localize_script( 'checkout_script', 'add_quantity', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
}
add_action( 'wp_enqueue_scripts', 'customjs_script_add_quanity_js' );
function anp_update_order_review() {
$numberof_people = intval($_POST['number_of_people']);
$cart = WC()->cart->get_cart();
foreach ( $cart as $cart_item_key => $item ){
WC()->cart->set_quantity( $cart_item_key, $numberof_people);
WC()->cart->calculate_totals();
woocommerce_cart_totals(); //checkout order total not calculating correctly and cart items quantity and price not changing
}
wp_die();
}
add_action( 'wp_ajax_nopriv_update_order_review', 'anp_update_order_review' );
add_action( 'wp_ajax_update_order_review', 'anp_update_order_review' );
updated add_quantity.js file
jQuery(function() {
jQuery( "form.checkout" ).on( "change", ".woocommerce-additional-fields select#________NumberOfTravelers________", function( ) {
var number_of_people = jQuery(this).val();
//console.log('nop: ' + number_of_people);
var data = {
action: 'update_order_review',
number_of_people: number_of_people,
security: wc_checkout_params.update_order_review_nonce
};
jQuery.post( add_quantity.ajax_url, data, function( response )
{
jQuery( 'body' ).trigger( 'update_checkout' );
console.log('success');
});
});
});
Now this start working but it calculating wrong price on checkout order table at bottom and cart item quantity and price is not updating on dropdown selection.
I think you don't need serialize checkout form data.
just in your ajax function use the following code:
$cart = WC()->cart;
for your ajax function you don't have to check if the user is logged in. By default, WordPress checks the hook if its admin side or front end side. In addition, you are calling the hooks from inside a function.
function anp_update_order_review() {
$numberof_people = intval($_POST['number_of_people']);
$values = array();
parse_str($_POST['post_data'], $values);
$cart = $values['cart']; //This have no values in it,
foreach ( $cart as $cart_key => $cart_value ){
WC()->cart->set_quantity( $cart_key, $numberof_people);
WC()->cart->calculate_totals();
woocommerce_cart_totals();
}
wp_die();
}
add_action( 'wp_ajax_nopriv_update_order_review', 'anp_update_order_review' );
add_action( 'wp_ajax_update_order_review', 'anp_update_order_review' );
I've resolved the issue and managed to update the cart quantity and its price without an ajax call.
My functions.php file only includes a custom add_quantity.js file
function customjs_script_add_quanity_js() {
wp_enqueue_script( 'checkout_script', get_stylesheet_directory_uri() . '/assets/js/add_quantity.js', array('jquery') );
wp_localize_script( 'checkout_script', 'add_quantity', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
}
add_action( 'wp_enqueue_scripts', 'customjs_script_add_quanity_js' );
Above enqueue scripts Code goes in function.php file of your active child theme (or active theme).
updated add_quantity.js file
jQuery(function() {
jQuery( "form.checkout" ).on( "change", ".woocommerce-additional-fields select#_number_of_travlers_", function( ) {
var number_of_people = parseInt(jQuery(this).val());
if(number_of_people){
jQuery( 'input.qty' ).attr('value', number_of_people);
}else{
jQuery( 'input.qty' ).attr('value', 1);
}
jQuery(".cart [name='update_cart']").removeAttr('disabled').attr('aria-disabled','false').trigger("click");
});
});
Tested and works perfectly!

Creating a custom product sort option in woocommerce categories by meta if exists

I am able to create a custom product sorting option using the official woo documentation. However, when chosen, only products that have the custom meta appear. I'd like to add order by modified time if the custom meta does not exist for a product. The end result would be products with the custom meta will appear on top sorted, all products without the custom meta will appear after them sorted by modified time.
This is the function:
add_filter( 'woocommerce_get_catalog_ordering_args', 'custom_woocommerce_get_catalog_ordering_args' );
function custom_woocommerce_get_catalog_ordering_args( $args ) {
$orderby_value = isset( $_GET['orderby'] ) ? wc_clean( $_GET['orderby'] ) : apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby' ) );
if ( 'custom_meta' == $orderby_value ) {
$args['orderby'] = 'custom_meta';
$args['order'] = 'DESC';
$args['meta_key'] = 'meta_key_num';
}
return $args;
}
add_filter( 'woocommerce_default_catalog_orderby_options', 'custom_woocommerce_catalog_orderby' );
add_filter( 'woocommerce_catalog_orderby', 'custom_woocommerce_catalog_orderby' );
function custom_woocommerce_catalog_orderby( $sortby ) {
$sortby['custom_meta'] = 'Custom';
return $sortby;
}

Add custom options while adding grouped product to cart

I've got a problem when adding a grouped product to cart.
I need to set a custom option for all products that are added to cart while adding a grouped product to cart.
What I have tried last (with a little bit of success):
<checkout_cart_product_add_after>
<observers>
<customoptions>
<type>singleton</type>
<class>Company_CustomOptions_Model_Observer</class>
<method>addCustomOptionGroupSku</method>
</customoptions>
</observers>
</checkout_cart_product_add_after>
and
public function addCustomOptionGroupSku(Varien_Event_Observer $observer) {
$product = $observer->getProduct ();
if ($product->isGrouped ()) {
$quoteItem = $observer->getQuoteItem ();
$additionalOptions = array (
'options' => array (
'label' => 'GROUPSKU',
'value' => $product->getSku ()
)
);
$quoteItem->addOption ( new Varien_Object ( array (
'product' => $quoteItem->getProduct (),
'code' => 'additional_options',
'value' => serialize ( $additionalOptions )
) ) );
}
}
I have created one grouped product, containing two products.
But that code only adds the custom option "GROUPSKU" to one of the items in the cart. The other one is untouched.
How do I get all the QuoteItems that are about to be added to the cart?
PS: I have also added this question to the Magento part of StackExchange: https://magento.stackexchange.com/questions/51883/add-custom-options-while-adding-grouped-product-to-cart
I found a solution which does not need an observer.
I had to do a rewrite to Mage_Sales_Model_Quote. More specific the method addProductAdvanced()
Bad news: You can not simply use parent::addProductAdvanced() and then do your own stuff. You have to copy the original code and fill it up with your code.
Here is what I did (look out for the comment starting with /********):
public function addProductAdvanced(Mage_Catalog_Model_Product $product, $request = null, $processMode = null) {
if ($request === null) {
$request = 1;
}
if (is_numeric ( $request )) {
$request = new Varien_Object ( array (
'qty' => $request
) );
}
if (! ($request instanceof Varien_Object)) {
Mage::throwException ( Mage::helper ( 'sales' )->__ ( 'Invalid request for adding product to quote.' ) );
}
$cartCandidates = $product->getTypeInstance ( true )->prepareForCartAdvanced ( $request, $product, $processMode );
/**
* Error message
*/
if (is_string ( $cartCandidates )) {
return $cartCandidates;
}
/**
* If prepare process return one object
*/
if (! is_array ( $cartCandidates )) {
$cartCandidates = array (
$cartCandidates
);
}
$parentItem = null;
$errors = array ();
$items = array ();
foreach ( $cartCandidates as $candidate ) {
// Child items can be sticked together only within their parent
$stickWithinParent = $candidate->getParentProductId () ? $parentItem : null;
$candidate->setStickWithinParent ( $stickWithinParent );
$item = $this->_addCatalogProduct ( $candidate, $candidate->getCartQty () );
/******** own modification for custom option GROUPSKU*/
if($product->isGrouped()){
$additionalOptions = array (
'options' => array (
'label' => 'GROUPSKU',
'value' => $product->getSku ()
)
);
$item->addOption ( new Varien_Object ( array (
'product' => $item->getProduct (),
'code' => 'additional_options',
'value' => serialize ( $additionalOptions )
) ) );
}
/******** own modification end*/
if ($request->getResetCount () && ! $stickWithinParent && $item->getId () === $request->getId ()) {
$item->setData ( 'qty', 0 );
}
$items [] = $item;
/**
* As parent item we should always use the item of first added product
*/
if (! $parentItem) {
$parentItem = $item;
}
if ($parentItem && $candidate->getParentProductId ()) {
$item->setParentItem ( $parentItem );
}
/**
* We specify qty after we know about parent (for stock)
*/
$item->addQty ( $candidate->getCartQty () );
// collect errors instead of throwing first one
if ($item->getHasError ()) {
$message = $item->getMessage ();
if (! in_array ( $message, $errors )) { // filter duplicate messages
$errors [] = $message;
}
}
}
if (! empty ( $errors )) {
Mage::throwException ( implode ( "\n", $errors ) );
}
Mage::dispatchEvent ( 'sales_quote_product_add_after', array (
'items' => $items
) );
return $item;
}
This way all the products that are added to the cart using a grouped product now have the custom option.

Woocommerce - Cannot delete a product variation

Using WooCommerce 2.6.1
I cannot delete a product variation for a variable product: the record is still in the database after the ajax call.
It seems the ajax call doesn't go through: putting error_log(print_r('remove_variation', true)); doesn't output anything (line 387 in class-wc-ajax.php).
The action is added in the constructor of the class. The function public function remove_variation() is just not called.
Has anyone had the same issue, and found a way to make it work?
/**
* Trash a variation, don't delete it permanently.
*
* This is hooked to
* Hijack WooCommerce's WC_AJAX::remove_variation() "Delete Variation" Trash a variation if it is a subscription variation via ajax function
*/
public static function remove_variations() {
if ( isset( $_POST['variation_id'] ) ) { // removing single variation
error_log("here3");
check_ajax_referer( 'delete-variation', 'security' );
$variation_ids = array( $_POST['variation_id'] );
error_log($_POST['variation_id']);
} else { // removing multiple variations
error_log("here4");
check_ajax_referer( 'delete-variations', 'security' );
$variation_ids = (array) $_POST['variation_ids'];
}
foreach ( $variation_ids as $variation_id ) {
$variation_post = get_post( $variation_id );
error_log(print_r($variation_post, ));
if ( $variation_post && $variation_post->post_type == 'product_variation' ) {
$variation_product = get_product( $variation_id );
if ( $variation_product && $variation_product->is_type( 'subscription_variation' ) ) {
wp_trash_post( $variation_id );
}
}
}
die();
}
remove && $variation_product->is_type( 'subscription_variation' ) to solve the problem of un-deletable variations. http://support.woothemes.com/requests/162693 should provide a patch, issue has been reported.
Deleting the variation completely
This is for WooCommerece version 3+
(this code to be put in functions or a custom plugin: not Rest api)
Trashing the variation does not remove the swatch,
it just makes the swatch disabled.(which may be a litle bit embarasing)
If you don't want the variation in your product portfolio anymore, you should delete it.
If you have created your variation using recognizable string in the slug (SKU), you can use below code.
function delete_variation($product_cat="all",$Sku__search_string="",$force_delete=false,$document_it=false){
// 'posts_per_page' => -1: goes through all posts
if($product_cat=="all") {
$args = array(
'post_type' => 'product',
'posts_per_page' => -1,
);
} else {
$args = array(
'post_type' => 'product',
'posts_per_page' => -1,
'product_cat' => $product_cat
);
}
$query = new WP_Query($args);
while ( $query->have_posts() ) {
$query->the_post();
$post_id = get_the_ID();
$product = wc_get_product($post_id);
$product_id = $product->get_id(); //same as post_id
//if you want to see what you are doing put $document_it to true;
if($document_it) echo "<br>*********** $product_id ****************<br>";
if($Sku__search_string!="") {
$variations = $product->get_available_variations();
foreach($variations as $variation){
//get the SKU slug of the variation
$sku=$variation["sku"];
//get the variation id
$variation_id=$variation['variation_id'];
if($document_it) echo "varid $variation_id <br>";
//and then get the actual variation product
$var_product=wc_get_product($variation_id);
//Check if the search_string is in the slug
//You can modify this, or modify the $args above,
//if you have other criteria
if(stripos($sku,$Sku__search_string)>0){
$is_deleted=false;
//here the variation is deleted
$is_deleted=$var_product->delete($force_delete);
if($is_deleted){
if($document_it) echo "<br>Deleted: $sku";
} else {
if($document_it) echo "<br>Not deleted: $sku";
}
}
}
}
}
}
If you want to delete all variations you may use this

Joomla 1.5 - JTable queries always doing UPDATE instead of INSERT

The following code is straight from the docs and should insert a row into the "test" table.
$row = &JTable::getInstance('test', 'Table');
if (!$row->bind( array('user_id'=>123, 'customer_id'=>1234) )) {
return JError::raiseWarning( 500, $row->getError() );
}
if (!$row->store()) {
JError::raiseError(500, $row->getError() );
}
My table class looks like this:
class TableTest extends JTable
{
var $user_id = null;
var $customer_id = null;
function __construct(&$db)
{
parent::__construct( '#__ipwatch', 'user_id', $db );
}
}
SELECT queries work fine, but not INSERT ones. Through debugging I find that the query being executed is UPDATE jos_test SET customer_id='1234' WHERE user_id='123', which fails because the row doesn't exist yet in the database (should INSERT instead of UPDATE).
While digging through the Joomla code I find this:
function __construct( $table, $key, &$db )
{
$this->_tbl = $table;
$this->_tbl_key = $key;
$this->_db =& $db;
}
.....
.....
function store( $updateNulls=false )
{
$k = $this->_tbl_key;
if( $this->$k)
{
$ret = $this->_db->updateObject( $this->_tbl, $this, $this->_tbl_key, $updateNulls );
}
else
{
$ret = $this->_db->insertObject( $this->_tbl, $this, $this->_tbl_key );
}
if( !$ret )
{
$this->setError(get_class( $this ).'::store failed - '.$this->_db->getErrorMsg());
return false;
}
else
{
return true;
}
}
_tbl_key here is "user_id" that I passed in my TableTest class, so it would appear that it will always call updateObject() when this key is set. Which is baffling.
Anyone have any insight?
function store( $updateNulls=false )
{
// You set user_id so this branch is executed
if( $this->$k)
{
$ret = $this->_db->updateObject( $this->_tbl, $this, $this->_tbl_key, $updateNulls );
}
// ...
Looking at the code it seems to me that store() check if the primary key field is present and if it use updateObject(). This means if you want to store a new user you can't specify the user_id.

Resources