I need to add a new role to my recently registered user (upon buying any of my four specific subscription products). Until now, every recently registered user (those who buy a subscription product) get a Subscriber role. While I want them to be Subscriber + Advertiser if they buy any of my 4 target subscription products.
I have tried to use woocommerce_order_status_completed, woocommerce_order_status_processing and woocommerce_order_status_changed hooks, but none of them are working with my code.
I have modified the function and code inside these hooks several times but I got nothing special.
Until now, I have used this code.
add_action( 'woocommerce_order_status_completed', 'so_29647785_convert_customer_role' );
function so_29647785_convert_customer_role( $order_id ) {
$order = new WC_Order( $order_id );
if ( $order->user_id > 0 ) {
foreach ( $order->get_items() as $order_item ) {
if( 4008 == $order_item[ 'product_id' ] ) {
$user = new WP_User( $order->user_id );
// Add new role
$user->add_role( 'advertiser' );
}
}
}
}
I will appreciate any help or track.
I have also tried this code and it is helpful in creating a user with both Subscriber + Advertiser roles but I can't do so in my case. Because I need users to be registered with both Subscriber + Advertiser roles only if they will buy four of my target subscription products. While this code is adding both Subscriber + Advertiser to every new user regardless of the product they choose.
add_filter('woocommerce_new_customer_data', 'bbloomer_assign_custom_role', 10, 1);
function bbloomer_assign_custom_role($args) {
$args['role'] = 'advertiser';
return $args;
}
Any help will highly be appreciated!
Since Woocommerce 3, your code is outdated and there are some errors and mistakes in your code, like $order_item['product_id'] will not work… Try the following instead:
add_action( 'woocommerce_order_status_processing', 'order_status_change_add_user_role', 10, 2 );
add_action( 'woocommerce_order_status_completed', 'order_status_change_add_user_role', 10, 2 );
function order_status_change_add_user_role( $order_id, $order ) {
if ( $order->get_user_id() > 0 ) {
$user = $order->get_user(); // Get an instance of the WP_User object
foreach ( $order->get_items() as $item ) {
// Check that user role is not set yet and that is matching with a product ID
if( 4008 == $item->get_product_id() && ! in_array('advertiser', $user->roles) ) {
$user->add_role( 'advertiser' ); // Add new role
break; // Stop the loop
}
}
}
}
Code goes in function.php file of your active child theme (or active theme). It should works now.
Order and order items related since Woocommerce 3:
How to get WooCommerce order details
Get Order items and WC_Order_Item_Product in Woocommerce 3
add_action( 'woocommerce_order_status_completed', 'add_advertiser_role' );
function add_advertiser_role( $order_id ) {
$order = new WC_Order( $order_id );
if ( $order->get_user_id() > 0 ) {
foreach ( $order->get_items() as $order_item ) {
if( 4008 == $order_item->get_product_id() ) {
$user = new WP_User( $order->get_user_id() );
// Add new role
$user->add_role( 'advertiser' );
}
}
}
}
Programatically get WooCommerce Order details
Related
I am developing a wordpress and woocommerce-based website where information on cooking-related training is provided and various kitchen materials are sold.
Those who want to participate in the trainings apply by filling out a form. Kitchen supplies are also sold through woocommerce.
Trainings are added to the website with a type of content called training.
Some trainings are requested to be sold over the woocommerce structure. However, these "Trainings" that want to be sold are wanted to remain in the form of educational content. In addition, it is requested not to be added or moved as a product.
First of all, I created a virtual product called Education. I hid the product in the store.
Then I added a custom field for Tutorials called price. The price of each training to be sold will be entered here.
I have a button "Register for Training" on the training detail page, I changed it to "Buy" for the trainings wanted to sell and the link
?add-to-cart=340&custom_price=600&quantity=1
I gave in the form.
Here 340 is the id of the virtual product I created.
When the Buy button is clicked, the virtual product called Education is added to the basket. But I want to update the name and price of this training according to which training detail page is printed.
The codes I added to functions.php.
add_action( 'woocommerce_before_calculate_totals', 'before_calculate_totals' );
function before_calculate_totals( $_cart ){
// loop through the cart_contents
foreach ( $_cart->cart_contents as $cart_item_key => &$item ) {
// you will need to determine the product id you want to modify, only when the "donation_amount" is passed
if ( $item['product_id'] == 340 && isset( $_GET['custom_price'] ) ){
// custom price from POST
$custom_price = $_GET['custom_price'] > 0 ? $_GET['custom_price'] : 0;
// save to the cart data
//$item['data']->price = $custom_price;
// new versions of WooCommerce may require (instead of line above)...
$item['data']->set_price($custom_price);
}
}
}
function ipe_product_custom_price( $cart_item_data, $product_id ) {
if( isset( $_POST['custom_price'] ) && !empty($_POST['custom_price'])) {
$cart_item_data[ "custom_price" ] = $_POST['custom_price'];
}
return $cart_item_data;
}
add_filter( 'woocommerce_add_cart_item_data', 'ipe_product_custom_price', 99, 2 );
I wanted to update the price with these codes, but it didn't work.
How do I dynamically update the information of the virtual product? Or what different method would you suggest?
There are some mistakes in your code and some missing things to make it work. Try the following:
// Set custom data as custom cart data in the cart item
add_filter( 'woocommerce_add_cart_item_data', 'add_custom_price_as_custom_cart_item_data', 30, 3 );
function add_custom_price_as_custom_cart_item_data( $cart_item_data, $product_id, $variation_id ) {
if( ! isset($_GET['custom_price']) ) {
$cart_item_data['custom_price'] = (float) esc_attr( $_GET['custom_price'] );
$cart_item_data['unique_key'] = md5( microtime().rand() ); // Make each item unique
}
return $cart_item_data;
}
// Change cart item price
add_action( 'woocommerce_before_calculate_totals', 'change_cart_item_price' );
function change_cart_item_price( $cart ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// loop through the cart_contents
foreach ( $cart->get_cart() as $cart_item ) {
// you will need to determine the product id you want to modify, only when the "donation_amount" is passed
if ( isset($cart_item['custom_price']) ) {.
$item['data']->set_price($cart_item['custom_price']);
}
}
}
// Display the custom price on minicart too
add_filter( 'woocommerce_cart_item_price', 'change_minicart_item_price', 10, 3 );
function change_minicart_item_price( $price_html, $cart_item, $cart_item_key ) {
if( ! is_cart() && isset( $cart_item['custom_price'] ) ) {
$args = array( 'price' => floatval($cart_item['custom_price']) );
if ( WC()->cart->display_prices_including_tax() ) {
$product_price = wc_get_price_including_tax( $cart_item['data'], $args );
} else {
$product_price = wc_get_price_excluding_tax( $cart_item['data'], $args );
}
return wc_price( $product_price );
}
return $price_html;
}
Code goes in functions.php file of your active child theme (or active theme). It should works.
I am using a plugin, WooCommerce Custom Post Type Manager, along with WooCommerce that enables the use of custom post types as products. This is working pretty well for the most part, but I want to be able to control inventory and I am able to see the problem. In the database under the order_item_meta table, the product_id = 0. Because the product_id is empty, there is no way to update the stock on a completed purchase.
I know that WooCommerce made some changes to where it searched if a post_type was a 'product' and if not, certain things failed. I am wondering if there is a filter hook or another way to add the product id with a custom function at checkout?
This is the function I am trying to create to control a custom type of inventory, we are selling event "seats". And no, even the regular "stock" does not work, probably because of the same reason.
function update_course_seats( $order_id ){
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item->get_product_id(); // this returns 0
if($product_id != 0) {
wc_update_order_item_meta( $item, '_seats', 10 ); // example number
}
}
//add_action( 'woocommerce_payment_complete', 'update_course_seats');
First you should use woocommerce_add_cart_item_data action hook, to store your Custom Post Type (CTP) Id on add to cart… But for that you will need to display, on your CTP pages, inside the add to cart form, a hidden field with the CTP Id like:
<input type="hidden" name="ctpost_id" value="<?php echo get_the_id(); ?>">
Now you can add this hooked function, that will add as custom cart item data your CTP Id:
add_filter( 'woocommerce_add_cart_item_data', 'add_custom_cart_item_data', 10, 3 );
function add_custom_cart_item_data( $cart_item_data, $product_id, $variation_id ){
if( isset( $_POST['ctpost_id'] ) ) {
$cart_item_data['ctpost_id'] = wc_clean( $_POST['ctpost_id'] );
}
return $cart_item_data;
}
The Second hook to be used is woocommerce_checkout_create_order_line_item action hook, to add custom order item meta data (or to make changes to order item data). This hook is triggered before payment gateways process during order creation.
You can use WC_data add_meta_data() method to save your CTP Id as a custom CTP ID like:
add_action( 'woocommerce_checkout_create_order_line_item', 'save_cpt_id_to_order_item_data', 10, 4 );
function save_cpt_id_to_order_item_data( $item, $cart_item_key, $cart_item, $order ){
if( isset($cart_item['ctpost_id']) && $cart_item['ctpost_id'] > 0 ) {
// Add the custom CTP post ID
$item->add_meta_data('_ctpost_id', $cart_item['ctpost_id'] );
}
// And here for example you add seats as custom cart item data
if( isset($cart_item['quantity']) ) {
$item->add_meta_data( 'Seats', $cart_item['quantity'], true );
}
}
When the order will get paid, you will be able to update everything required, as you will have your CTP post ID as custom order item data.
Then finally, you can use woocommerce_payment_complete as follow:
add_action( 'woocommerce_payment_complete', 'action_payment_complete_callback', 10, 1 );
function action_payment_complete_callback( $order_id ){
$order = wc_get_order();
// Loop through order items
foreach ( $order->get_items() as $item_id => $item ) {
$ctp_id = $item->get_meta('_ctpost_id'); // Get the CTP post ID
// Your code goes here
}
}
Or woocommerce_order_status_changed hooks like:
add_action( 'woocommerce_order_status_changed', 'action_order_status_changed_callback', 10, 4 );
function action_order_status_changed_callback( $order_id, $status_from, $status_to, $order ){
if( in_array( $status_to, ['processing','completed'] ) ) {
// Loop through order items
foreach ( $order->get_items() as $item_id => $item ) {
$ctp_id = $item->get_meta('_ctpost_id'); // Get the CTP post ID
// Your code goes here
}
}
}
Related: Get Order items and WC_Order_Item_Product in Woocommerce 3.
I am creating a membership site and totally created static pages for each Membership plans (have only 3 plans). However, I have added products for each plan and when I hit SELECT PLAN button I redirect to some custom form where I ask users range of info we are going to use to fulfil the plan (same as sneakertub.com).
I have written code into the PHP page which will handle SUBMIT action of the form. This PHP file, infopage.php, will process POST data I sent via POST call and stores these all data into WC session.
$customer_name = $_POST["customer_name"];
$customer_email = $_POST["customer_email"];
$customer_sex = $_POST["customer_sex"];
$customer_age = $_POST["customer_age"];
$product_id = $_POST["product_id"];
global $wp_session;
$data = array(
'customer_name' => $customer_name,
'customer_email' => $customer_email,
'customer_sex' => $customer_sex,
'customer_age' => $customer_age);
$wp_session['custom_SESSION_child']=$data;
WC()->session->set('custom_data_child', $data);
//Add product to WooCommerce cart.
WC()->cart->add_to_cart( $product_id )
However, I don't think the above code works. As I don't find values into session with any of the above technique. I have used wp_session, WC()->session and $_SESSION but no approach is working.
I am trying to access these values into functions.php this way,
add_action( 'woocommerce_before_calculate_totals', 'twf_additional_price', 1, 3 );
function twf_additional_price( $cart_object ) {
global $wpdb;
global $wp_session;
$session_data_2 = $wp_session['custom_SESSION_child'];
$session_data = WC()->session->get('custom_data_child');
var_dump($session_data);
var_dump($session_data2);
foreach ( $cart_object->cart_contents as $key => $value ) {
$extra_charge = 0;
if(isset($value['twf_user_custom_datas'])){
$extra_charge = 100;
}
$value['data']->set_price($value['data']->price + $extra_charge);
}
}
For now ignore the for loop. Main thing is
var_dump($session_data);
var_dump($session_data2);
both dumps only NULL.
My main goal is to add the all above fields into Woocommerce checkout and order pages.
Please let me know what is wrong here. I know I might be working on very bad approach but I want Plan selection to checkout process same as sneakertub.com. Please let me know if there is any tutorial on this or proper way to do this. I prefer doing this without plugins but I am ready to use plugins as well.
I appreciate your attention.
Updated - Instead of using sessions, you should use the last available argument in WC_Cart add_to_cart() method, which will allow you to add any custom cart item data.
For cart item price change based on calculations, is better to make the new price calculation before and to set it in that custom cart item data.
Try the following instead:
1) For your code in the php page:
$custom_data = array(); // Initializing
// Set the posted data as cart item custom data
if( isset($_POST['customer_name']) && ! empty($_POST['customer_name']) )
$custom_data['custom_data']['name'] = sanitize_text_field( $_POST['customer_name'] );
if( isset($_POST['customer_email']) && ! empty($_POST['customer_email']) )
$custom_data['custom_data']['email'] = sanitize_text_field( $_POST['customer_email'] );
if( isset($_POST['customer_sex']) && ! empty($_POST['customer_sex']) )
$custom_data['custom_data']['sex'] = sanitize_text_field( $_POST['customer_sex'] );
if( isset($_POST['customer_age']) && ! empty($_POST['customer_age']) )
$custom_data['custom_data']['age'] = sanitize_text_field( $_POST['customer_age'] );
// Set the calculated item price as custom cart item data
if( isset($custom_data['custom_data']) && sizeof($custom_data['custom_data']) > 0 && $product_id > 0 ) {
// Get an instance of the WC_Product object
$product = wc_get_product( $product_id );
// Save the new calculated price as custom cart item data
$custom_data['custom_data']['new_price'] = $product->get_price() + 100;
}
// Add product to cart with the custom cart item data
WC()->cart->add_to_cart( $product_id, '1', '0', array(), $custom_data );
Code goes in function.php file of your active child theme (or active theme). Tested and works.
2) Your revisited function that will change the cart item price:
add_action( 'woocommerce_before_calculate_totals', 'custom_cart_item_price', 30, 1 );
function custom_cart_item_price( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
foreach ( $cart->get_cart() as $cart_item ) {
if( isset($cart_item['custom_data']['new_price']) )
$cart_item['data']->set_price( $cart_item['custom_data']['new_price'] );
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
All other custom cart item data is available under the cart item key 'custom_data' as an indexed array… So you will be able to get that data easily from the cart object, to save it in the order.
I made a new post type named "sub_products" containing the meta tag "unit_price".
After assigning a new field for every Woocommerce product containing a list of all the "sub_products" posts, the goal was to update every product price based on the selected "sub_products" meta "unit_price".
function kulcskron_edit_post( $p1, $p2 )
{
if ( !is_admin() )
return;
if ( get_post_type() != 'product' )
return;
$sub_product_ids = $p2->get_meta( 'sub_products' );
if ( empty($sub_product_ids) )
return;
$product_regular_price = 0;
foreach ( $sub_product_ids as $id )
$product_regular_price += get_post_meta( $id, 'unit_price', true );
if ( $p1 == $product_regular_price )
return;
$p2->set_regular_price( $product_regular_price );
$p2->save();
}
add_action( 'woocommerce_product_get_price', 'kulcskron_edit_post', 10, 2 );
I tried every possible hook to make this work:
add_action( 'the_post', 'kulcskron_edit_post', 9, 1 );
add_action( 'edit_post', 'kulcskron_edit_post', 10, 2 );
add_action( 'pre_get_posts', 'kulcskron_edit_post' );
add_action( 'save_post', 'kulcskron_edit_post' );
This code updates the price but in a strange way:
Admin single product edit view: The price is not updated right away, just after I revisit the edit screen.
Admin product listing view: Every price is 0.
Front-end product page view: The displayed price is 0.
How do I update the product price based on the assigned "sub_products" "unit_price" post meta when a single product is saved in admin view?
You may use the hook "woocommefrce_get_price_html" it should work
I managed to solve the price update problem.
I used the save_post action:
save_post is an action triggered whenever a post or page is created or updated, which could be from an import, post/page edit form, xmlrpc, or post by email.
The final code:
function kulcskron_update_product( $product_obj )
{
if ( !is_admin() )
return;
if ( get_post_type() != 'product' )
return;
$product = wc_get_product( $product_obj );
if ( !$product->meta_exists( 'sub_products' ) )
return;
$sub_product_ids = $product->get_meta( 'sub_products' );
if ( empty($sub_product_ids) )
return;
_update_product_price( $product_obj, $sub_product_ids );
}
add_action( 'save_post', 'kulcskron_update_product' );
And for the sake of completeness here is the rest of code:
function _update_product_price( $product_obj, $sub_product_ids )
{
$product = wc_get_product( $product_obj );
$product_regular_price = 0;
foreach ( $sub_product_ids as $id )
$product_regular_price += get_post_meta( $id, 'kulcskron_unit_price', true );
$product->set_regular_price( $product_regular_price );
$product->set_price( $product_regular_price );
$product->save();
}
This way basically everything can be edited and updated. Example:
function _update_product_stock( $product_obj, $sub_product_ids )
{
$product = wc_get_product( $product_obj );
$sub_product_stocks = array();
foreach ( $sub_product_ids as $id )
$sub_product_stocks[] = get_post_meta( $id, 'kulcskron_free_stock_quantity', true );
$product->set_manage_stock( true );
$product->set_stock_quantity( min($sub_product_stocks) );
$product->save();
}
I would love to post a link to all the methods to edit a product but I have no rep for that.
But sadly this is not all...
In order to fully update the price, we need to filter the price HTML to return the newly updated price.
function kulcskron_price_html( $priceHtml, $product )
{
$symbol = get_woocommerce_currency_symbol();
$price = $product->get_regular_price();
$html = '<span class="woocommerce-Price-amount amount">'. $price .' <span class="woocommerce-Price-currencySymbol">'. $symbol .'</span></span>';
return $html;
};
add_filter( 'woocommerce_get_price_html', 'kulcskron_price_html', 10, 2 );
A little backstory. We need to sync all the products from an external database through an XML file. But there is a catch, the XML file only contains product parts and they are not displayed, searched, filtered and certainly can't be ordered individually. None of the Woocommerce build in functionality meet these requirements.
In order to solve this, I registered a new post type and imported all the product parts into this newly created post type. After that, I registered a new field for every WC Product with the Advanced Custom Fields plugin.
It looks like this:
Newly registered custom post type with the custom fields
The code above makes easy to update the WC Products (e.g. Prices) based on the selected Sub products.
I found this thread but it doesn't exactly do what I'm looking for.
My question is, how can I change the default Woocommerce role "customer" to, e.g. "Subscriber" for new registered users.
And then, if user checks out (purchases product), change the role from "Subscriber" to "Customer".
I'm asking this as I want to show different content per user roles: "registered customer" and "subscribed (paid) customer".
The first part of your question is changing the default role of user that is created by WooCommerce. Honestly, I'd probably leave the default role as customer. And then create a new role/capability for people who purchase your specific product.
add_filter( 'woocommerce_new_customer_data', 'so_29647785_default_role' );
function so_29647785_default_role( $data ){
$data['role'] = 'subscriber'; // the new default role
return $data;
}
As you saw in my other answer (well my re-posted answer), you can do things once the user has finished paying, but hooking into the woocommerce_order_status_completed hook.
To adapt that code to do something specific to a particular product, you need to loop through the order items and check them against a product ID. Replace 999 with the ID of the product in question.
add_action( 'woocommerce_order_status_completed', 'so_29647785_convert_customer_role' );
function so_29647785_convert_customer_role( $order_id ) {
$order = new WC_Order( $order_id );
if ( $order->user_id > 0 ) {
foreach ( $order->get_items() as $order_item ) {
if( 999 == $order_item[ 'product_id' ] ) {
$user = new WP_User( $order->user_id );
// Remove existing role
$user->remove_role( 'customer' );
// Add new role
$user->add_role( 'subscriber' );
}
}
}
}
Note: Totally untested, but seems right in theory.