I have a problem with adding action to 'save_post_product' hook.
I want to send stock data to my external API when product is changed(updated) in woocommerce admin.
The problem is that the stock values i get from simple product after the action is called are one 'iteration' old. By that i mean that the stock data i get seem to be the data before update. so if i call the update product 2 times, first time i get old data, second time i get the new ones.
add_action('save_post_product', array($this, 'product_changed'), 99, 3);
function product_changed($post_id, $post, $update)
{
if ('product' != $post->post_type || $update != true) {
return;
}
$_pf = new WC_Product_Factory();
$product = $_pf->get_product($post_id);
$items = array();
if ($product->product_type == 'simple') {
$product->get_total_stock();
$inStock = $product->is_in_stock();
$qty = $product->get_stock_quantity();
$managing = $product->managing_stock();
$items[$product->id] = [
...
];
} elseif ($product->product_type == 'variable') {
$variations = $product->get_available_variations();
/*For variations it works properly*/
}
}
$itemsJson = json_encode($items);
$this->sendData($itemsJson, '/products-changed/');
}
TLDR (example):
Lets say that product is set to manage stock, stock quantity 500 and is in stock.
Now i change the product not to manage stock, and set that it is out of stock.
I hit update.
Everything runs and wordpress gets to my code. When i get the values i still get
$product->is_in_stock(); //true
$product->get_stock_quantity(); //500
$product->managing_stock(); //yes
Now, when i hit the update again, everything runs the second time, but now i get the correct values.
$product->is_in_stock(); //false
$product->get_stock_quantity(); //0
$product->managing_stock(); //no
I assume that the product stock update runs after 'save_post_product' hook however, i was not able to find any other hook that might solve my problem.
NOTE: It works well with variations in the first 'iteration'. I think it has to do something with the $product->get_available_variations() code.
Seems like hooking the action to wp_insert_post hook did the trick :
add_action('wp_insert_post', array($this, 'product_changed'), 99, 3);
save_post is triggered BEFORE the update. So you would have to use the $_POST, $_GET or the global $post_data, as specified in https://codex.wordpress.org/Plugin_API/Action_Reference/save_post
You already resolved this by using wp_insert_post, so that's okay. Just a clarification for any one seeing this
Related
I want only allow one purchase for each product, so I want to disable the add to cart for the user who already purchased that product.
I've been reading and I think I should use the woocommerce_is_purchasable hook
but I have no idea how to do it, I would appreciate the help, thank you.
Assuming you're talking about simple products*, you can do this by hooking into is_purchasable.
For the logged in customer, the following code gets the product ID's of all past orders. If the current product ID is in that collection, it returns false for is_purchasable.
Add it to the functions.php file of your child theme.
add_filter('woocommerce_is_purchasable', 'preventPurchaseIfAlreadyOrdered', 10, 2);
function preventPurchaseIfAlreadyOrdered($is_purchasable, $product) {
$productId = $product->get_id();
$orderedItemIdArray = getOrdersItemIdsForCurrentUser();
$is_purchasable = !in_array($productId, $orderedItemIdArray);
return $is_purchasable;
}
function getOrdersItemIdsForCurrentUser() {
$orders = wc_get_orders(['author' => get_current_user_id()]);
if (empty($orders)) return;
$orderedItemIdArray = [];
foreach ($orders as $order) {
$items = $order->get_items();
foreach ($items as $item) {
$orderedItemIdArray[] = $item->get_product_id();
}
}
return $orderedItemIdArray;
}
The code has been tested and works.
* for variable products, the selected variation can change without reloading the page, i.e. via Ajax. That process is more involved (but also possible).
I'm working with a custom product where customers can type their custom text that will be added to cart.
Depending on the size of the text, a different price will be set, the logic is done and I can see the new price in the product object.
It seems that I can change the product object with the new price but I get status 500 back from admin-ajax.php when I do $woocommerce->setup_product_data($product_id).
I have found several topics but none of them seems to work in my case.
I'm not able to update the cart with the new price.
Here is my ajax function in functions.php:
// Adjust new price
function applyNewPrice() {
global $woocommerce;
// From JS
$product_id = (int) $_POST['id'];
// From JS
$price = (float) $_POST['price'];
$product_data = get_post($product_id);
// Code returning status 500 here...
$product = $woocommerce->setup_product_data($product_data);
$product->set_price($price);
update_post_meta($product_id,'_price',$price);
update_post_meta($product_id,'_regular_price',$price);
$woocommerce->clear_product_transients( $product_id );
}
add_action('wp_ajax_applyNewPrice', 'applyNewPrice');
add_action('wp_ajax_nopriv_applyNewPrice', 'applyNewPrice');
The function you are trying to call does not exist. This is the function you need.
$product = wc_setup_product_data( $product_id );
No sure where you are coming up with those functions. I could not find this function either?
$woocommerce->clear_product_transients( $product_id );
everyone, i am trying to stop a plugin function from executing if a certain product id is in the cart. I have tried and written the following code and put them in my theme functions.php, but it doesn't seem to work. Instead, the whole page become blank. Can anyone help me out?
$found = false;
global $woocommerce;
foreach($woocommerce->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
if( $_product->id == 1097 ) {
$found = true;
}
}
if ($found){
remove_action('woocommerce_before_order_notes', array($wdt, 'show_field'), 20);
}
By itself, remove_action('woocommerce_before_order_notes', array($wdt, 'show_field'), 20); works. But once i tried to link it to work base on product id, the whole website just cannot load. Thank you!
The variable $wdt does not seam to be initialized. You should ether use the class name(string) or an instance of that class here.
An other guess is that your code runs before the action is added. Make sure you run your code on a later hook in the execution see this for reference: http://codex.wordpress.org/Plugin_API/Action_Reference#Actions_Run_During_a_Typical_Request
So if the code that add the hook runs at init, you can try to run your code at wp_loaded. Like this:
add_action('wp_loaded','my_remove_order_notes');
function my_remove_order_notes()
{
//Your code
}
Also, make sure that the priority parameter matches with the code where the action hook is added. The default priority is 10.
Edit:
Ok, you should not remove the filter. Filters are supposed to be used for manipulating the data and the return the new data back. Like this:
add_filter( 'woocommerce_checkout_fields' , 'custom_remove_order_comments', 99 );
function custom_remove_order_comments( $fields )
{
global $woocommerce;
foreach($woocommerce->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
if( $_product->id == 1097 ) {
unset($fields['order']['order_comments']); //Remove the order comments field
}
return $fields; //Return the data back to WooCommerce
}
I did not want to post here but I could not find the answer I was looking for and I do not have enough reputation to comment on other VERY SIMILAR questions to get my exact answer.
I found an almost perfect answer from this post: WooCommerce: Add product to cart with price override?
using the code:
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price');
function add_custom_price( $cart_object ) {
$custom_price = 10; // This will be your custome price
foreach ( $cart_object->cart_contents as $key => $value ) {
$value['data']->price = $custom_price;
}
}
and it works great...if you set a hard coded custom price.
My question is: How can I set a custom price based on user input?
I have tried every way I can think of to pass information (I even tried using cookies, globals, sessions) and none of them worked how I wanted and all of them were, at BEST, hacks.
The specific product in question is a donation and the customer wants the user to be able to set the donation price (rather than just creating a variable product with set price points).
On the donation page when the user submits the donation form I am adding the donation product to the cart like so:
$donation_success = $woocommerce->cart->add_to_cart($donation_id);
My donation product has a set price of 0.00 so when it is added to the cart it has a price of 0.00 (I don't know if the price is set at this point or later)
I have tried passing in information at this point using the last variable in the add_to_cart method which accepts an array of arguments but I couldn't seem to get that to work either.
I am sure the answer is simple but I have been trying for hours to do this right and I cannot get it to work. I am out of ideas.
The actual code I am using at the moment is slightly different than was suggested by the answerer of the above post but works basically the same:
add_action( 'woocommerce_before_calculate_totals', 'woo_add_donation');
function woo_add_donation() {
global $woocommerce;
$donation = 10;
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if($cart_item['data']->id == 358 || $cart_item['data']->id == 360){
$cart_item['data']->set_price($donation);
}
}
}
Thanks in advance for any helpful advice!
I found a solution which is not elegant but works for my purposes.
I was trying to use cookies before but I didn't set the cookie path so it was only available to the page it was set on.
I am now setting a cookie with the donation price:
setcookie("donation", $_GET['donation'], 0, "/");
Then I am setting the price like so:
add_action( 'woocommerce_before_calculate_totals', 'woo_add_donation');
function woo_add_donation() {
global $woocommerce;
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if($cart_item['data']->id == 358 && ! empty($_COOKIE['donation'])){
$cart_item['data']->set_price($_COOKIE['donation']);
}
}
}
I have been looking for exactly the same thing. I found this WooCommerce plugin (not free) for this
name your price plugin
Initially I wasn't sure what search terms to use to find plugins like this but it looks like "WooCommerce name your price" brings up links to other sources of similar plugins.
[this is a comment] Where do you set the cookie? My first guess is that the refreshes in the same page, using the GET method, and provides a PHP code-block with the $_GET['donation'] to set the cookie with.
And then, once the cookie is set, you redirect the page to Woocommerce Cart page to continue the shopping process. If you're doing it easier way, please let me know. Thanks.
Sorry, I couldn't post this as a comment to the selected answer due to the lack of points.
This code will create order with custom price:
$product = wc_get_product($product_id);
$order = wc_create_order();
try {
$order->add_product($product);
//$order->set_customer_id($user->ID);
$order->set_billing_email($customer_email);
} catch (WC_Data_Exception $e) {
wp_send_json_error("Failed to create order");
}
$order->calculate_totals();
try {
$order->set_total($custom_price); // $custom_price should be float value
} catch (WC_Data_Exception $e) {
wp_send_json_error("Failed to change order details");
}
$order->save();
$order->update_status( 'completed' );
I am currently working on an online shop with WooCommerce. I faced the problem that I want to grant a discount to customers who chose a specific shipping method. The discount is 0.50 for every single product. I basically solved this problem with the following code in my "functions.php":
add_action('woocommerce_before_calculate_totals', 'woo_add_cart_fee');
function woo_add_cart_fee() {
global $woocommerce;
$cart = $woocommerce->cart->get_cart();
//Calculating Quantity
foreach ($cart as $cart_val => $cid) {
$qty += $cid['quantity'];
}
if ($woocommerce->cart->shipping_label == "specific shipping method") {
$woo_fee = $qty * (-0.5);
$woo_name = "discount for specific shipping method";
}
$woocommerce->cart->add_fee(__($woo_name, 'woocommerce'), $woo_fee, true);
}
The code technically works, the only problem I have now is that if a customer changes the shipping method i.e. from the "specific shipping method" to a "normal one" (without any discount) or the other way round, it always displays and calculates the discount value from the previously chosen shipping method. In other words it is always one step back and therefore displays the customer the wrong total amount.
Does anyone has an idea to solve this problem?
This solutions is for Woocommerce 2.1.X!
I am not sure if this might help. I was facing a similar problem, where I needed to retrieve the chosen shipping method. In the file \wp-content\plugins\woocommerce\includes\wc-cart-functions.php I found a method called wc_cart_totals_shipping_html().
Within this method there is a check of the current selected shipping method that contains the following code:
$packages = WC()->shipping->get_packages();
foreach ( $packages as $i => $package ) {
$chosen_method = isset( WC()->session->chosen_shipping_methods[ $i ] ) ? WC()->session->chosen_shipping_methods[ $i ] : '';
}
I used this code in my own functions.php to check for the currently selected shipping method and it works. Example:
add_filter( 'woocommerce_billing_fields', 'wc_change_required_fields');
function wc_change_required_fields($address_fields) {
$packages = WC()->shipping->get_packages();
foreach ( $packages as $i => $package ) {
$chosen_method = isset( WC()->session->chosen_shipping_methods[ $i ] ) ? WC()->session->chosen_shipping_methods[ $i ] : '';
}
if ($chosen_method == 'local_delivery') {
$address_fields['billing_address_1']['required'] = true;
// place your changes that depend on the shipping method here...
}
}
Hope that helps!
This is very old, but I ran into this issue myself and had to work out the solution.
Woocommerce stores pre-calculated cart totals in the database, rather than calculating them on the fly. But the shipping method choice is stored as a session variable. So shipping changes are not reflected immediately at checkout without a visit or refresh of a cart page.
With the original posted code, the shipping changes were not reflected because they aren't recalculated and stored. To do this, the function needs to be tricked into thinking it's a cart page first, and then recalculating the totals to be stored.
GLOBAL $woocommerce;
if ( ! defined('WOOCOMMERCE_CART') ) {
define( 'WOOCOMMERCE_CART', true );
}
And then at the end of the function, after all the desired changes have been made refresh and store.
WC()->cart->calculate_totals();
See also CODEX for WC_AJAX::update_shipping_method()
http://docs.woothemes.com/wc-apidocs/source-class-WC_AJAX.html#148-174
Mark's answer worked for me, however I had to delete all transient values prior to running the code. Otherwise, it would simply restore the saved values.
public function clear_shipping_transients() {
global $wpdb;
$wpdb->query( "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE ('_transient_cp_quote_%') OR `option_name` LIKE ('_transient_timeout_cp_quote_%') OR `option_name` LIKE ('_transient_wc_ship_%')" );
}