Playing around with WooCommerce, trying to call remove item in a custom api implementation.
I can successfully get a list of cart items and add items to the cart in similar fashion. Yet, despite the code being nearly identical on every guide I can find, I cannot get the cart item to be removed.
I get the cart items something like this:
$ret = array();
global $woocommerce;
$items = $woocommerce->cart->get_cart();
foreach($items as $item => $values) {
$product = $values['data'];
$p = array(
'cart_item_key' => $item,
'id' => $product->get_id(),
'name' => $product->get_name(),
'slug' => $product->get_slug(),
'price' => $product->get_price(),
'description' => $product->get_description(),
'short_description' => $product->get_short_description(),
'permalink' => get_permalink( $product->get_id() ),
'quantity' => $values['quantity']
);
$ret[] = $p;
}
return $ret;
Gives me a nice clean array back I can see in the response.
I then take the cart_item_key and pass it back in to another method like so:
$payload = $request->get_params();
$ret = array();
global $woocommerce;
if ($payload['cart_item_key']){
$cartItemKey = WC()->cart->find_product_in_cart( $payload['cart_item_key'] );
$woocommerce->cart->remove_cart_item( $cartItemKey );
unset( $woocommerce->cart->cart_contents[$cartItemKey] );
}
return $ret;
I can see that $payload['cart_item_key'] is indeed the string value I expect to see that passed into the call. I can also see that $cartItemKey is the identical string value, so I'm 99.8% certain that I have the right value to pass in.
The remove_cart_item and unset methods are two different implementations I've seen and neither seems to have an effect. I've also tried using WC() in place of the global $woocommerce to zero success. Whether I query the cart items again here and return them, or just refresh the page, all items are still in the cart.
There is zero server caching I'm aware of - everything I know about I have specifically turned off for this sandbox.
Update
This does appear to work as expected in a fresh session. For some reason, when I'm logged in as the admin for the site, I am unable to clear out my cart, and no errors are being logged for anything like this on the server.
Related
I have been trying to make it so that I can update a custom field(per product) I have added on the basket/cart page using WooCommerce Product Add-ons.
I did get a method working as a test, but it is really a good solution, and most of it is a test/bad code. It also only works the second time for some reason - as if the cache is breaking it!
It will be changed via a textarea, but I have set a value for now to see if I can get it to work.
So, to clarify, on submit first time, nothing happens, secound time it updates the product, however it now doesn't update the attribute, it updates quantity etc(when I add a value in the code) but not the attribute.
I have set it to delete the product afterwards but it looses the attribute when I delete it so I have left the duplication in there for now until I fix the double update required for it to update the page!
Is there anything anyone can see that will point me in the direction of a quicker fix? Greatly apprecaite any help!!!
As you can see from the commenting out I have been trying a few ways, but keep failing!!
<?php
if ( ! empty( $_POST['update_cart'] ) ) {
$cart_totals = isset( $_POST['cart'] ) ? $_POST['cart'] : '';
if ( sizeof( $woocommerce->cart->get_cart() ) > 0 ) {
foreach ( $woocommerce->cart->get_cart() as $cart_item_key => $values ) {
if ( isset( $cart_totals[ $cart_item_key ]['addons'] ) ) {
WC()->cart->remove_cart_item($cart_item_key);
#print_r($woocommerce->cart->cart_contents[ $cart_item_key ]['addons']);
#$cart_item['addons'] = array('name' => 'Add your message', 'value' => 'testing the message box', 'price' => 0, 'field_name' => $cart_item['product_id'].'-add-your-message-1', 'field_type' => 'custom_textarea', 'price_type' => 'quantity_based' );
$data = array('name' => 'Add your message', 'value' => 'testing the message box', 'price' => 0, 'field_name' => $cart_item['product_id'].'-add-your-message-1', 'field_type' => 'custom_textarea', 'price_type' => 'quantity_based' );
#WC()->cart->add_to_cart($cart_item['product_id'], $cart_item['quantity'], $cart_item['variation_id'], $cart_item['addons']);
WC()->cart->add_to_cart($cart_item['product_id'], $cart_item['quantity'], $cart_item['variation_id'], $cart_item['addons']);
#$woocommerce->cart->cart_contents[$cart_item_key]['addons'][0]['value'] = 'test';
$woocommerce->cart->cart_contents[$cart_item]['addons'] = $data;
}
}
}
}
EDIT:
The below is updating the message box per item, which is great and exactly what was needed, just trying to get the session to accept the update. The cart is a multipage cart as that is what was requested, so when you go to the next page/checkout page you can see it has reverted back to the original message and you can see it in the cart session. Also, if you just refresh the page it reverts back(after you have already updated the page).
I have tried to edit via WC()->session->set but I think I am setting the wrong bit! I get some of the session set, but it doesn't set the correct entry!
add_action('woocommerce_update_cart_action_cart_updated', function ($cartUpdated) {
// loop through cart items, passing the $item array by reference, so it can be updated
foreach (WC()->cart->cart_contents as $key => &$item) {
// set the values of the addons array as required
$test = $_POST['cart'][$key]['addons'];
$item['addons'][0]['value'] = $test;
print_r(WC()->session->get( 'cart'));
}
});
EDIT - WORKING CODE:
Thank you to #benJ for the solution, very helpful! The session needed to be set, but not the way I thought I had to do it!
// hook on woocommerce_update_cart_action_cart_updated
add_action('woocommerce_update_cart_action_cart_updated', function ($cartUpdated) {
// loop through cart items, passing the $item array by reference, so it can be updated
foreach (WC()->cart->cart_contents as $key => &$item) {
// set the values of the addons array as required
$addon = $_POST['cart'][$key]['addons'];
$item['addons'][0]['value'] = $addon;
// set the session
WC()->cart->set_session();
}
});
Cheers
Iskander
If I understand your question correctly, you want to update an addon field on every product when you update the cart.
This can be achieved by adding a hook on woocommerce_update_cart_action_cart_updated and editing the cart items as required.
// hook on woocommerce_update_cart_action_cart_updated
add_action('woocommerce_update_cart_action_cart_updated', function ($cartUpdated) {
// loop through cart items, passing the $item array by reference, so it can be updated
foreach (WC()->cart->cart_contents as $key => &$item) {
// set the values of the addons array as required
$item['addons'][0]['value'] = 'your message here';
}
});
If you're only looking to do this for individual products, you can check their other values as you iterate over them.
I believe I am close but entirely unsure as my limited php knowledge is based on trial and error. I am trying to pull data from a plugin that creates my shipping label into a plugin that tracks shipping. Specifically Elex EasyPost into Advanced Shipping Tracking.
I have an example snippet for shipstation integration from AST here:
<?php
add_action( 'woocommerce_shipstation_shipnotify', 'add_tracking_information_into_order', 10, 2 );
function add_tracking_information_into_order($order, $tracking_details){
$order_id = $order->get_id();
$args = array(
'tracking_provider' => $tracking_details['carrier'],
'tracking_number' => wc_clean( $tracking_details['tracking_number'] ),
'date_shipped' => wc_clean( $tracking_details['ship_date'] ),
'status_shipped' => 1,
);
$ast = new WC_Advanced_Shipment_Tracking_Actions;
$tracking_item = $ast->insert_tracking_item( $order_id, $args );
}
It should be similar but obviously I need to replace the hook and the items in the array. Here is what Elex sent me when asked for the hooks they are using.
get_post_meta($order_id, ‘wf_easypost_labels’);
Sample output:
array(
0=>(‘url’=>label_url,’tracking_number’=> (string)’tracking code’,’integrator_txn_id’=> ‘integerator_id’,’shipment_id’=>’package_shipment_id’,’order_date’=>order_date,’carrier’=>EX:USPS,”link”=>tracking_link,);//First package
1=>(‘url’=>label_url,’tracking_number’=> (string)’tracking code’,’integrator_txn_id’=> ‘integerator_id’,’shipment_id’=>’package_shipment_id’,’order_date’=>order_date,’carrier’=>EX:USPS,”link”=>tracking_link,);//Second package
)
So here is my attempt to use their array in my own snippet:
<?php
add_action( 'woocommerce_checkout_update_order_meta', 'add_tracking_information_into_order', 10, 2 );
function add_tracking_information_into_order($order, $myship_func){
$myship_func = get_post_meta($order_id, 'wf_easypost_labels');
$order_id = $order->get_id();
$args = array(
'tracking_provider' => $myship_func['carrier'],
'tracking_number' => wc_clean($myship_func['tracking code']),
'date_shipped' => wc_clean($myship_func['order_date']),
'status_shipped' => 1,
);
$ast = new WC_Advanced_Shipment_Tracking_Actions;
$tracking_item = $ast->insert_tracking_item( $order_id, $args );
}
When the order is initially filled and the label is generated Elex grabs all the data from and sticks it in the meta. I need to pull that meta either at the time of generation or post generation and stick it in the AST fields.
My main question is thus - Am I on the right track here? Do I just need to figure out the correct hook or is my method for accessing the Elex array incorrect? Again, I usually compare codes, slice and dice, and get things to "look right" and then they work (or not). I myself don't have too deep of knowledge about arrays or class functions (which I believe the Elex one is).
In this case the code is silently failing so I suspect it is the hook involved or a combination of the hook and my code.
I'm using the WooCommerce REST API to e.g. get all products with variations, but I encounter a fairly large problem regarding the number of fired requests. I need help optimizing the situation below.
Situation:
A webshop with 50 products and 5 variations for each product.
Get all master products (~1 request)
Get all variations for each product (50 requests)
Total count of request = 51
How can I do this without firing of 51 requests? Is't possible to get all products with their variations eager loaded somehow?
Yes, you can do it by customizing the WooCommerce Product REST API Response.
Here I have attached some code that will help you.
add_filter('woocommerce_rest_prepare_product_object', 'custom_change_product_response', 20, 3);
add_filter('woocommerce_rest_prepare_product_variation_object', 'custom_change_product_response', 20, 3);
function custom_change_product_response($response, $object, $request) {
$variations = $response->data['variations'];
$variations_res = array();
$variations_array = array();
if (!empty($variations) && is_array($variations)) {
foreach ($variations as $variation) {
$variation_id = $variation;
$variation = new WC_Product_Variation($variation_id);
$variations_res['id'] = $variation_id;
$variations_res['on_sale'] = $variation->is_on_sale();
$variations_res['regular_price'] = (float)$variation->get_regular_price();
$variations_res['sale_price'] = (float)$variation->get_sale_price();
$variations_res['sku'] = $variation->get_sku();
$variations_res['quantity'] = $variation->get_stock_quantity();
if ($variations_res['quantity'] == null) {
$variations_res['quantity'] = '';
}
$variations_res['stock'] = $variation->get_stock_quantity();
$attributes = array();
// variation attributes
foreach ( $variation->get_variation_attributes() as $attribute_name => $attribute ) {
// taxonomy-based attributes are prefixed with `pa_`, otherwise simply `attribute_`
$attributes[] = array(
'name' => wc_attribute_label( str_replace( 'attribute_', '', $attribute_name ), $variation ),
'slug' => str_replace( 'attribute_', '', wc_attribute_taxonomy_slug( $attribute_name ) ),
'option' => $attribute,
);
}
$variations_res['attributes'] = $attributes;
$variations_array[] = $variations_res;
}
}
$response->data['product_variations'] = $variations_array;
return $response;
}
I have done by this way. I have got all variations in the single parameter product_variations.
Unfortunately this is not possible with their "new" API without having access to the server. So third party companies developing tools for their customers who run WooCommerce are screwed.
The problem of having to make a request per variation is not the only issue. Counting products is also impossible. We now have to load the data of all products just to count them...
If a customer has 10,000 products in their store and 3 variations for each we have to make 30K+ individual requests... (instead of a couple dozen, one per page).
Personally since this is totally impractical, and is most likely to get us blacklisted by some firewall, I decided to use the old API: https://woocommerce.github.io/woocommerce-rest-api-docs/v3.html#view-products-count
Instead of the "new" one: https://woocommerce.github.io/woocommerce-rest-api-docs/
When will the old API be completely removed from WooCommerce? I have no idea but there is no other realistic option anyway.
You can get product variations all in 1 request making
GET /products/<product-id>/variations/
then iterate over the results to find the matching one and retrieve the id
The products in my clients website require certain attributes which I have added via Products -> Attributes in the Wordpress administration. In this import script I'm coding I need to use the function update_post_meta($post_id, $meta_key, $meta_value) to import the proper attributes and values.
Currently I have the function like so:
update_post_meta( $post_id, '_product_attributes', array());
However I'm not sure how to properly pass along the attributes and their values?
Right so it took me a while to figure it out myself but I finally managed to do this by writing the following function:
// #param int $post_id - The id of the post that you are setting the attributes for
// #param array[] $attributes - This needs to be an array containing ALL your attributes so it can insert them in one go
function wcproduct_set_attributes($post_id, $attributes) {
$i = 0;
// Loop through the attributes array
foreach ($attributes as $name => $value) {
$product_attributes[$i] = array (
'name' => htmlspecialchars( stripslashes( $name ) ), // set attribute name
'value' => $value, // set attribute value
'position' => 1,
'is_visible' => 1,
'is_variation' => 1,
'is_taxonomy' => 0
);
$i++;
}
// Now update the post with its new attributes
update_post_meta($post_id, '_product_attributes', $product_attributes);
}
// Example on using this function
// The attribute parameter that you pass along must contain all attributes for your product in one go
// so that the wcproduct_set_attributes function can insert them into the correct meta field.
$my_product_attributes = array('hdd_size' => $product->hdd_size, 'ram_size' => $product->ram_size);
// After inserting post
wcproduct_set_attributes($post_id, $my_product_attributes);
// Woohay done!
I hope this function will help other people if they need to import multiple attributes pro-grammatically in WooCommerce!
I tried Daniel's answer, and it didn't work for me. It might be that the Wordpress/Woocommerce code has changed since, or perhaps I didn't quite understand how to do it, but either way that code did nothing for me. After a lot of work using it as a base, however, I came up with this snippet of code and put it on my theme's functions.php:
function wcproduct_set_attributes($id) {
$material = get_the_terms( $id, 'pa_material');
$material = $material[0]->name;
// Now update the post with its new attributes
update_post_meta($id, '_material', $material);
}
// After inserting post
add_action( 'save_post_product', 'wcproduct_set_attributes', 10);
With this, I can take what I set as "material" on my WooCommerce install as a custom attribute and add it to the formal meta as _material. This in turn allows me to use another snippet of code so the WooCommerce search function extends to meta fields, meaning I can search for a material in the WooCommerce search field and have all items with that material appear.
I hope this is useful to somebody.
#Daniels's answer works, won't decide on right or wrong, however if you want to add the values as a taxonomy term under attributes you have to adapt the code as below (set is_taxonomy = 1). Otherwise Woocommerce sees it as custom meta field(?). It still adds the value under attributes. This will only work for strings. For values that are arrays the code has to be adapted.
Additionally it uses the wp_set_object_terms that #Anand suggests as well. I was using that, because all the documentation I could find led to believe that had to be used. However if one only uses the wp_set_object_terms then I couldn't see the attributes in the edit product screen. Using the information from both answers and reading on the subject resulted in the solution.
You will need to tweak the code for things such as product variations.
/*
* Save Woocommerce custom attributes
*/
function save_wc_custom_attributes($post_id, $custom_attributes) {
$i = 0;
// Loop through the attributes array
foreach ($custom_attributes as $name => $value) {
// Relate post to a custom attribute, add term if it does not exist
wp_set_object_terms($post_id, $value, $name, true);
// Create product attributes array
$product_attributes[$i] = array(
'name' => $name, // set attribute name
'value' => $value, // set attribute value
'is_visible' => 1,
'is_variation' => 0,
'is_taxonomy' => 1
);
$i++;
}
// Now update the post with its new attributes
update_post_meta($post_id, '_product_attributes', $product_attributes);
}
Then call the function:
$custom_attributes = array('pa_name_1' => $value_1, 'pa_name_2' => $value_2, 'pa_name_3' => $value_3);
save_wc_custom_attributes($post_id, $custom_attributes);
Thank you for posting the code Daniel & Anand. It helped me a great deal.
Don't know if this is the "correct" way to do this... But I needed a function to add ACF repeater fields with a date value as a attribute on post save, so this was the function I came up with:
add_action( 'save_post', 'ed_save_post_function', 10, 3 );
function ed_save_post_function( $post_ID, $post, $update ) {
//print_r($post);
if($post->post_type == 'product')
{
$dates = get_field('course_dates', $post->ID);
//print_r($dates);
if($dates)
{
$date_arr = array();
$val = '';
$i = 0;
foreach($dates as $d)
{
if($i > 0)
{
$val .= ' | '.date('d-m-Y', strtotime($d['date']));
}
else{
$val .= date('d-m-Y', strtotime($d['date']));
}
$i++;
}
$entry = array(
'course-dates' => array(
'name' => 'Course Dates',
'value' => $val,
'position' => '0',
'is_visible' => 1,
'is_variation' => 1,
'is_taxonomy' => 0
)
);
update_post_meta($post->ID, '_product_attributes', $entry);
}
}
}
Hope this helps someone.
I searched around for a while and only came up wit solutions that added whole new option sets to products in a Magento store.
What I'm trying to accomplish is a way to add a Simple Product to the cart. This Simple Product has some predifined custom options (free text fields) that has to be filled by a php function.
So, how can I do this? Let's say I have a product with the ID "111" and a one custom option.
$qty = '1';
$product = Mage::getModel('catalog/product')->load("111");
// set option value in product model?
$cart = Mage::helper('checkout/cart')->getCart();
$cart->addProduct($product, $qty);
// set option value while passing product to car?
$cart->save();
Thanks in advance for any hinds.
BTW: setting option values via QueryString is relativly easy as seen here.
You don't set the custom option on the product model, you pass it in through the second argument to $cart->addProduct($product, $params).
The set up we have for a project, that requires an external app to add to the Magento cart, is to use a $params array of the following format:
$params = array(
'product' => 1, // This would be $product->getId()
'qty' => 1,
'options' => array(
34 => "value",
35 => "other value",
53 => "some other value"
)
);
The $params['options'] contains the custom option information. The keys are the custom option ids, you can see them if you inspect the custom options section of the product screen with Firebug, or similar.
The $params['product'] may be redundant, I wrote this script a while ago for a much earlier version of Magento.
Also, I'm fairly sure that the standard add to cart events will fire when you add this way, so you'll need to set them off yourself. There may be side effects.
In Magento 1.7 you have to wrap the params array in a Varien Object.
$params = array(
'product' => $_fancypack->getId(),
'qty' => 1,
'options' => array(
$this->_getOptionId($_fancypack,'Product SKU') => $product->getId() .'/'. $product->getSku()
)
);
$request = new Varien_Object();
$request->setData($params);
$quote->addProduct($_fancypack, $request);
You should write the input parameter for addproduct as the following format, it is tested by myself:
$params = array(
'product' => 1, // This would be $product->getId()
'qty' => 1,
'super_attribute' => array(
34 => "value",
35 => "other value",
53 => "some other value"
)
);
The problem with the current answer is that magento will not add a second line item if the SKU is the same but the options are distinct from the first. If you want a 3" apple and a 4" apple you would like to have separate line items. Or at least I do.
A HTTP call to the following URL
/store/checkout/cart/add?product=23&qty=1&options[41]=4
followed by
/store/checkout/cart/add?product=23&qty=1&options[41]=3
will add two line items.
But still this is just half of the battle, what do these option codes stand for?? Well the following PHP code will tell you. And since we are using an HTTP call the code will return javascript ready JSON.
<?php
include_once '../app/Mage.php';
Mage::app();
echo getProductOptionsIds($_GET['eventcode']);
function getProductOptionsIds($sku)
{
$ProductID = Mage::getModel('catalog/product')->getIdBySku($sku);
$Product = Mage::getModel('catalog/product')->load($ProductID);
$config = array();
$config['ProductID'] = $ProductID;
foreach ($Product->getOptions() as $option) {
// #var $option Mage_Catalog_Model_Product_Option
if ($option->getGroupByType() == Mage_Catalog_Model_Product_Option::OPTION_GROUP_SELECT) {
$_tmpValues = array();
foreach ($option->getValues() as $value) {
// #var $value Mage_Catalog_Model_Product_Option_Value
$_tmpValues[$value->getTitle()] = $value->getId();
}
$config[$option->getTitle().'list'] = $option->getId();
$optionValue = $_tmpValues;
} else {
$optionValue = $option->getId();
}
$config[$option->getTitle()] = $optionValue;
}
return json_encode($config);
}
?>