WooCommerce custom plugin: Error around WC_Customer Object instance - php

I've built a plugin and I have the following issue:
The WooCommerce Dashboard (in the admin side) will not load the data. It hangs and fails. I have tracked the problem code:
The issue in the
if ( is_admin() ) {
//removed
} else if ( !$this->is_login_page() && !wp_doing_ajax() ) {
$public = new Public();
}
It's the public side code that's causing the issue! and neither is_admin or wp_doing_ajax prevent it from happening.
In the public side, I'm calling
add_action( 'init', array('Dynamic_Rules', 'dynamic_rule_tax_exemption') );
Inside the tax exemption function, I have this code in particular which causes the problem:
$woocommerce = WC();
$user_country = $woocommerce->customer->get_billing_country();
$woocommerce->customer->set_is_vat_exempt(true);
So I can only speculate about what happens, perhaps the WC() is somehow sending everything into an infinite loop, which is why the Dashboard does not load the data. Why is_admin() and wp_doing_ajax() don't prevent this from happening I don't know.
Perhaps it's wrong that I'm calling that function on init, but where else could I call it?
Any help is appreciated

Is difficult to find out what your problem can be… Note that "your question should be updated to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem."
What you can try instead, may be:
$customer = WC()->customer;
if( ! is_a( $customer, 'WC_Customer' ) {
global $current_user;
if( $current_user > 0 ) {
$customer = new WC_Customer( $current_user->ID );
}
}
if( is_a( $customer, 'WC_Customer' ) {
$billing_country = $customer->get_billing_country();
if( ! $customer->is_vat_exempt() ) {
$customer->set_is_vat_exempt( true );
}
} else {
// Some code to throw an error or debug trace
}
I hope this will solve your issue. If not you need to pass the User ID to your code in some way.
Maybe useful: Debugging WooCommerce PHP with Javascript console.log doesn't work

Related

How to check if a WooCommerce product type is NOT variable?

I need a code to check wether a woocommerce product type is not "variable"
To check if something IS a variable product, I usually use this:
if ($product->is_type('variable') ) {
// do something
}
But I need to check if it is NOT a variable product, so I thought I was supposed to do it this way but this results in PHP errors...
if (! $product->is_type('variable') ) {
// do something
}
EDIT: I get the following error: "Fatal error: Uncaught Error: Call to a member function is_type() on null"
I thought there was something wrong with my code. But then I tested it in other template files and it worked. I wanted to use my php codition in functions.php to dequeue a style everywhere except for variable products. Like so:
global $product;
if (! $product->is_type('variable') ) {
wp_dequeue_style('sample');
}
So it can not be done this way? Any idea?
Your theme's functions.php file gets called for every page on your WP site. If you have a weak controlled function there you are veri likely going to face some fatal error all over the website. In your case all you need to do is to check if $product is indeed an instance of Wc_Product or null.
So, change your code into this:
global $product;
if (!empty($product) && is_a($product,Wc_Product::class) && ! $product->is_type('variable') ) {
wp_dequeue_style('sample');
}
The explanation is that we check that $product actually has a value and it is really a Wc_Product class instance. If that is ok then you check for the $product->is_type. This way you won't face fatal errors all over when you'r global $product variable has not been set yet by Woocommerce
To avoid this error, you can try the following lightweight way:
if ( is_product() && ! has_term( 'variable', 'product_type', get_the_id() ) ) {
wp_dequeue_style('sample');
}
Or also this heavier way:
if ( is_product() ) {
global $product;
if ( ! is_a($product, 'WC_Product') ) {
$product = wc_get_product( get_the_id() );
}
if ( is_a($product, 'WC_Product') && ! $product->is_type('variable') ) {
wp_dequeue_style('sample');
}
}
How about this?
if ( $product->get_type() !== 'variable' ){
// do something
}

Showing failed wp_remote_post on user registration

When registering user, I am hooking into user_register hook to send some call to the external API.
add_action( 'user_register', 'create_user_on_api' );
inside the create_user_on_api function I am making the call (nonces are there, and tons of security checks, but I'm omitting those for brevity) using wp_remote_post()
function create_user_on_api( $user_id ) {
$user_create_response = wp_remote_post( "someurlgoeshere.api", $curl_args );
if ( is_wp_error( $user_create_response ) ) {
throw new Exception( 'wp error' );
} else {
$code = wp_remote_retrieve_response_code( $user_create_response );
$msg = wp_remote_retrieve_response_message( $user_create_response );
$body = wp_remote_retrieve_body( $user_create_response );
if ( $code !== 200 ) {
throw new Exceprion( 'error' );
}
}
}
This is the gist of it.
The problem is following: using any error handles causes white screen of death, error is logged in my debug.log, and I would like to throw a notice on my admin dashboard that, even though api call failed, the user is created in WP, and you'll have to create it manually on the API or try again.
I tried echoing stuff, custom exception handling mentioned here, but so far no luck.
Any idea how to do that? Can I hook into admin notices in any way?
Ok, found a solution. A hacky one but it works.
On api error I am storing a error message in a transient, which has an expiry of 60 seconds and I created a user_notices function that I hook on admin_notices hook. In it I check two things: if I'm on users screen
$current_screen = get_current_screen();
if ( $current_screen->id === 'users' ) {
}
And then inside this I check if the transients are empty, and then assign them to the $error_message array, which I output in a foreach (in case I have multiple error messages)
if ( ! empty( $error_msg_array ) ) {
foreach ( $error_msg_array as $error_msg_key => $error_msg ) {
?>
<div class="error notice">
<p><?php printf( '<strong>Apigee error:</strong> %s', esc_html( $error_msg ) ); ?></p>
</div>
<?php
}
}
And this seems to be working.

woocommerce_checkout_order_processed hook executing function twice

I have attached a function to the woocommerce_checkout_order_processed hook:
//check if woocommerce is acive
if (in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
add_action('woocommerce_checkout_order_processed', 'wc_on_place_order');
}
The wc_on_place_order function is to be executed after the user clicks on the PLACE ORDER button. However, it's so odd that the function is executed twice.
My wc_on_place_order function calls an external api written in C#:
function wc_on_place_order( $order_id ) {
global $wpdb;
// get order object and order details
$order = new WC_Order( $order_id );
// get product details
$items = $order->get_items();
//return $items;
$products = array();
foreach ($items as $item) {
array_push($products,
array('userid' => $order->user_id, 'descr' => $item['name'], 'amt' => (float)$item['line_total'])
);
}
//passing $products to external api using `curl_exec`
. . . .
//on successful call, the page should be showing an `alert`, however, it does not
// the handle response
if (strpos($response,'ERROR') !== false) {
print_r($response);
} else {
echo "<script type='text/javascript'>alert($response)</script>";
}
}
After debugging on C# API, I noticed that it the service is being called twice, thus, the checkout is being saved twice to the API database.
Is there something wrong with the wc_on_place_order function or is woocommerce_checkout_order_processed called twice when clicking the PLACE ORDER?
Interestingly, adding return $items after $items = $order->get_items() somehow, the C# api was only called once:
// get product details
$items = $order->get_items();
return $items; //this line
Why is that so?
One more question I would like to ask, is woocommerce_checkout_order_processed the right hook I should use? I have been searching the web for the correct hook to use and it seems that woocommerce_checkout_order_processed is used in the most post. I can't use the woocommerce_thankyou hook as it is also calling the API if I refresh the page.
Any idea will be really appreciated.
EDIT:
I used woocommerce_after_checkout_validation hook which fires after pre-validations on checkout. I can't remember though why woocommerce_checkout_order_processed is being fired twice but I just changed some kind of settings in WooCommerce options page. I can't remember which.
Useful Links from the Comments:
Visual Representation of the WooCommerce hooks
WordPress Action References
I always use the hook woocommerce_payment_complete This will fire as the name suggests after the order has been paid.
function order_payment_complete( $order_id ){
$order = wc_get_order( $order_id );
/* Insert your code */
}
add_action( 'woocommerce_payment_complete', 'order_payment_complete' );

Woocommerce login redirect if cart not empty

I want to redirect the user to cart page after he logs in if the cart is not empty. I am trying this:
function woocommerce_custom_redirects() {
if(!WC()->cart->is_empty() )
wp_redirect( "https://edkasa.com/checkout" );
}
add_action('wp_login', 'woocommerce_custom_redirects');
But this is not working, I am using buddypress plugin, any idea where am I going wrong.
With the wp_redirect() Function, it is suggested to follow that up with an explicit call to exit. For further Information on that, see Documentation.
You may thus try adding that to your Code... Something like:
function woocommerce_custom_redirects() {
ob_start(); // COULD BE A GOOD IDEA
if ( WC()->cart->get_cart_contents_count() !== 0 || !WC()->cart->is_empty() ) {
wp_redirect( "https://edkasa.com/checkout" );
exit; // VERY VITAL HERE TO EXPLICITLY CALL exit...
}
}
add_action('wp_login', 'woocommerce_custom_redirects');
I am looking for and can not find anywhere as I can redirect the empty woocommerce cart to the homepage. I only find redirects that go to the store.
This is what I find, but I do not need to redirect to the home:
add_action("template_redirect", 'redirection_function');
function redirection_function(){
global $woocommerce;
if( is_cart() && WC()->cart->cart_contents_count == 0){
wp_safe_redirect( get_permalink( woocommerce_get_page_id( 'shop' ) ) );
}
}
Thanks
just removed static link of previous answer.
function woocommerce_custom_redirects() {
ob_start(); // COULD BE A GOOD IDEA
if ( WC()->cart->get_cart_contents_count() !== 0 || !WC()->cart->is_empty() ) {
global $woocommerce;
$checkout_url = $woocommerce->cart->get_checkout_url();
wp_redirect($checkout_url);
exit; // VERY VITAL HERE TO EXPLICITLY CALL exit...
}
}
add_action('wp_login', 'woocommerce_custom_redirects');

Disable Local Delivery for everyone except "wholesale_customer"

Alright, I've been pulling my hair out the last couple of days trying to figure this out.
I have a wholesale plugin in wordpress install with woocommerce. It gives the user "wholesale_customer" special rates over everyone else. I want to be able to offer local delivery to only the "wholesale_customer" user role but can't seem to figure out how to do it.
I've gotten this code from #mcorkum but it's still not working.
/**
* Add local delivery for wholesale customers
*/
function wholesale_local_delivery($available_methods) {
global $woocommerce;
global $current_user;
$user_roles = $current_user->roles;
$user_role = array_shift($user_roles);
if ( isset( $available_methods['local_delivery'] ) ) {
if ($user_role == 'wholesale_customer' ) {
unset( $available_methods['local_delivery'] );
}
}
return $available_methods;
}
add_filter( 'woocommerce_package_rates', 'wholesale_local_delivery', 10, 1);
I know this achievable with a plugin, but I'd rather not use plugins or pay for it for that matter.
Does anyone see anything that I'm not seeing?
/**
* Add local delivery for wholesale customers
*/
function wholesale_local_delivery($available_methods) {
global $woocommerce;
global $current_user;
if ( isset( $available_methods['local_delivery'] ) ) {
if ( !current_user_can( 'wholesale_customer' ) ) {
unset( $available_methods['local_delivery'] );
}
}
return $available_methods;
}
add_filter( 'woocommerce_package_rates', 'wholesale_local_delivery', 10, 1);
Try the above code by pasting it in your theme's functions.php file. And let me know if this worked for you.
I'm not a wordpress dev, but that code doesn't look like it is providing a user with role "wholesale_customer" the "local_delivery" option. On the contrary in fact, it looks to be removing the local delivery option if the user role IS "wholesale_customer":
if ( isset( $available_methods['local_delivery'] ) ) {
if ($user_role == 'wholesale_customer' ) {
unset( $available_methods['local_delivery'] );
}
}
If I was to take this code simply at face value (As I am not a wordpress dev) I would re-write this function to be easier to understand and read:
function wholesale_local_delivery($available_methods)
{
global $woocommerce;
global $current_user;
// Return early if no local delivery option is available
if (!isset($available_methods['local_delivery'])) {
return $available_methods;
}
// Determine if the user has a user role of wholesale customer
$hasRoleWholeSaleCustomer = false;
foreach ($current_user->roles as $role) {
if ($role === 'wholesale_customer') {
$hasRoleWholeSaleCustomer = true;
break;
}
}
// If the user does not have the role wholesale customer
// And for the code here to be being processed the local delivery
// option must be available
if (!$hasRoleWholeSaleCustomer) {
unset($available_methods['local_delivery']);
}
// Return the available methods applicable to the users roles
return $available_methods;
}
Hope someone else with experience in woocomerce, can give a better answer. But in the meantime, you can try this re-write and see if it works for you.
Goodluck.

Categories