WooCommerce subscriptions | All Products for WooCommerce Subscriptions
Does anyone know if there's a way to disable the subscribe option for a specific product variation?
i.e. product with 2x attributes: attr-1, attr-2
All of which can be bought singly or via a sub on the PDP, is there a way to disable the subscribe option for one of the attributes?
There's no succinct way using the wcsatt_product_subscription_scheme filter without a lot of JS logic.
Have reached out to WC and would love to give back if there's a more a elegant solution
add_filter('woocommerce_subscription_variation_is_purchasable', 'conditional_variation_subscription_is_purchasable', 20, 2);
function conditional_variation_subscription_is_purchasable($purchasable, $product) {
$check_attributes = [
'attribute_pa_color' => 'black',
'attribute_pa_slow-connect' => 'very-slow',
];
$matching_variation = find_matching_product_variation_id($product->get_parent_id(), $check_attributes);
if ($matching_variation == $product->get_id()) {
$purchasable = false;
}
return $purchasable;
}
function find_matching_product_variation_id($product_id, $attributes) {
return (new \WC_Product_Data_Store_CPT())->find_matching_product_variation(
new \WC_Product($product_id),
$attributes
);
}
Add the code in to the active theme functions.php file. Change the attribute names and value as required.
Related
I'm creating a WP menu, which should take the user to the current page, and just append a query to end of it.
So, for example I'm on a custom page:
example.com/group/fans
So the current menu items which links to:
/?query1
/?query2
/?query3
Should be appended to the current page:
example.com/group/fans/?query1
And same thing for different pages, with the Same menu items.
I've searched for many solutions but couldn't find one, Any suggestions?
Have you tried the post_link filter? I think something like below might be what you are looking for:
add_filter('post_link', function ($link) {
$pattern = 'group/fans';
$is_custom_query = strpos($link, $pattern) !== false;
if ($is_custom_query) {
return add_query_arg([
'query1' => 'someValue',
'query2' => 'someValue2'
], $link);
}
return $link;
});
I want to be able to hide certain Wordpress Post Categories from Users dependent on their Role.
I've tried the code here:
Wordpress: Hide specific categories from User Role on the Add New page
I think its deprecated, and would really appreciate some help
add_filter('list_terms_exclusions', 'yoursite_list_terms_exclusions', 10, 2);
function yoursite_list_terms_exclusions( $exclusions, $args ) {
global $pagenow;
if (in_array($pagenow,array('post.php','post-new.php')) && !current_user_can('see_special_cats')) {
$exclusions = " {$exclusions} AND t.slug NOT IN ('slug-one','slug-two')";
}
return $exclusions;
}
With this code nothing happens. I've tried 10+ different plugins and am really getting desperate. Thanks in advance.
add_filter('list_terms_exclusions', 'yoursite_list_terms_exclusions', 10, 2);
function yoursite_list_terms_exclusions( $exclusions, $args ) {
global $pagenow;
if (in_array($pagenow,array('post.php','post-new.php')) &&
!current_user_can('see_special_cats')) {
$exclusions = " {$exclusions} AND t.slug NOT IN ('slug-one','slug-two')";
}
return $exclusions;
}
This code presumes that you've used a plugin like the Members plugin to create a capability called 'see_special_cats' and that you've assigned it to every role that you want to have access to the categories except of course 'Contributors'.
Since you found the plugin you may not need this, but maybe it will help someone else.
add_filter('list_terms_exclusions', 'yoursite_list_terms_exclusions', 10, 2);
function yoursite_list_terms_exclusions( $exclusions, $args ) {
$current_user = wp_get_current_user();
// Change 'see_special_cats' to capability user must have to be able see category or categories
if ( $current_user->has_cap('see_special_cats') ) {
$capCheck = 1;
} else {
$capCheck = 0;
}
global $pagenow;
if (in_array($pagenow,array('post.php','post-new.php')) && !$capCheck) {
// Change category-slug-one and two to desired categories to hide. Additional categories may be added
// by separating with a comma. Delete ", 'category-slug-two'" to only hide one category
$exclusions = " {$exclusions} AND t.slug NOT IN ('category-slug-one', 'category-slug-two')";
}
return $exclusions;
}
This code works without using current_user_can(). Paste this code in your functions.php file. If you want to hide a category from everyone except for the Administrator role, as set up by the default hierarchical structure, change 'see_special_cats' to 'create_users'. Change category-slug-one and category-slug-two to the category slugs that you want hidden. There is no additional plugin required (I'm not sure where 'see_special_cats' comes from).
I know we can add meta for woocommerce cart item using woocommerce_add_cart_item_data hook.
Is there any way to update existing cart item meta.?
Yes, but it seems, only via accessing the cart directly:
global $woocommerce;
$woocommerce->cart->cart_contents[$cart_item_key]['whatever_meta'] = 'testing';
$woocommerce->cart->set_session(); // when in ajax calls, saves it.
I would recommend to remove and re-add the product, as other meta data could be lost (as in Phong Tran's answer).
Based on #DanielSalcedos answer, only keeping the absolute minimum required to answer the question.
I know It's been a while, but as it's still not answered, and it took me lots of sweat and a pint of blood, I share my solution here.
First
I'll assume you know how to add metadata to a cart and into an order. If not, you can have a look at pwnewbie's solution but I recommend you the full article at Wisdm labs
Wisdm's method takes many steps. First you create a PHP's session variable trough Ajax. Second, you intercept woocommerce_add_cart_item_data filter to add your Session variable to woocommerce session.
The thing about woocommerce_add_cart_item_data filter is that it gets executed in the middle of the add to cart process, so, if you add your variable to the main $wooocmmerce object, at some point, it get's stored as the add-to-cart event. (Sort of)
The idea
What if I want to edit that metadata and not any of the standard cart properties. The ideal would be to get a filter or an action that runs in the middle of saving something. The problem is that as long as we don't change anything else, there's no hook to run (I tried with woocommerce_update_cart_action_cart_updated hook that runs after coupons, quantities and removals from cart had happened, but it never fired as I never passed the validations)
My approach
In a shell, rebuild as little as possible, as much as needed. I added a synchronous ajax event to the cart form OnSubmit event. (I want my UI to be updated with my changes, so the reload must happen after my update)
AJAX:
var myFlag33322805 = true;
$('form').submit(function(e){
if(myFlag33322805){
myFlag33322805 = false;
e.preventDefault(); // Flag and prevent default to syncronize submits
var kart = []; // Will retrieve every cart item's meta
$('.cartRow').each(function(){
//This object will store your meta data and be pushed into kart array
var kitm = {
'p' : $(this).data('product_id'),
'm' : $(this).find('select[name=myMetaData]').val(),
'k' : $(this).data('key')
};
kart.push(kitm);
});
var data = {
'action': 'Ajax_Update_My_MetaData_33322805',
'k': kart
};
$.post(VKVAjax.ajaxurl, data, function (response) {
// Might do something with the response here
});
$('form').submit(); // This time, the form will submit, but AJAX wont run because of myFlag33322805 = false
}
});
The magic:
The php ajax response is a function that will receive my meta data to update (actually, all of it, updated or not), and will take the global $woocommerceobject to insert the meta data AND save it to the session:
PHP:
function fn_Update_My_MetaData_33322805(){
global $woocommerce;
$cart = $woocommerce->cart->cart_contents;
$updt = Array();
foreach ($_POST['k'] AS $item){
$product = new stdClass();
$updtCL = new stdClass();
$product->{'id'} = $item['p']; //This is product id
$product->{'mymeta'} = $item['m']; //This is metadata
$updtCL->{'krtkey'} = $item['k']; //This is product key in cart
$updtCL->{'meta'} = $product;
$updt[] = $updtCL;
}
// cycle the cart replace the meta of the correspondant key
foreach ($cart as $key => $item) {
foreach($updt as $updtitem){
if($key == $updtitem->krtkey){ // if this kart item corresponds with the received, the meta data is updated
// Update the content of the kart
$woocommerce->cart->cart_contents[$key]['vkv_AlternCart_value'] = $updtitem->meta;
}
}
}
// This is the magic: With this function, the modified object gets saved.
$woocommerce->cart->set_session();
wp_die('{"e":"ok", "Updt": "'.count($arrupdt).'"}');
}
Of course, this should be hooked as any other ajax event.
add_action('wp_ajax_nopriv_Ajax_Update_My_MetaData_33322805', 'fn_Ajax_Update_My_MetaData_33322805');
add_action('wp_ajax_Ajax_Update_My_MetaData_33322805', 'fn_Ajax_Update_My_MetaData_33322805');
Conclussion
You can update the meta data of a cart item with a synchronous Ajax call, overwritting the object directly to the $woocommerce global variable and saving it with the $woocommerce->cart->set_session(); method.
Footnotes
It's not the ideal method, and is quite risky to work directlly with the $woocommerce global. I would love to know of a better approach.
I'm newbie and my English is not good so the answer may be a little confusing.
Thank vlad274 for the advice.
My approach:
Same with Daniel Salcedo, I also try to change the metadata by editing woocommerce_sessions, but there is a problem. When adding an item to the cart, the woocommerce will create a unique cart_item_key for it by md5 (product_id + ... + metadata), then look for this cart_item_key already exists, if available, this item will be updated in quantity, otherwise will create a new item.
This means that if you change the meta value of the hat product from blue to red, then add the hat product with blue, instead of creating a new item blue hat, it will only increase the quantity of red hat, you need to change the cart_item_key if you want the cart to be updated correctly.
Changing cart_item_key is quite risky, instead we can simply remove and re-add the product. Like this
// get cart_item_key of item you want to change
$cart_item_key_old = $_POST['cart_item_key'];
// retrieve its information
$cart_item_old = WC()->cart->cart_contents[ $cart_item_key_old ];
$product_id_old = $cart_item_old['product_id'];
$quantity_old = $cart_item_old['quantity'];
$variation_id_old = $cart_item_old['variation_id'];
$variation_old = $cart_item_old['variation'];
// creating a cart_item_key with the same information except metadata
$cart_item_key_new = WC()->cart->generate_cart_id( $product_id_old, $variation_id_old, $variation_old, ['color'=>'red'] );
// check new cart_item_key already exists
$found = WC()->cart->find_product_in_cart( $cart_item_key_new );
// if true, update its quantity
if ($found != '') {
$new_quantity = $cart_item_old['quantity'] + WC()->cart->cart_contents[ $cart_item_key_new ]['quantity'];
WC()->cart->set_quantity( $cart_item_key_new, $new_quantity );
}
// else, re-add with new metadata
else {
WC()->cart->add_to_cart($product_id_old, $quantity_old, $variation_id_old, $variation_old, ['color'=>'red'] );
}
// finally delete the old item
WC()->cart->remove_cart_item($cart_item_key_old);
wp_die();
Note: If you want to submit cart form instead of page refreshes after running the above ajax, the quantity of item is likely to be overridden by the set_quantity method when woocommerce update_cart. In this case you just need to return new_quantity and change the input value by js before submitting the form.
Full code:
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
...
?>
<!-- place it anywhere within the foreach -->
<div class="box-type-field">
<select class="box-type" name="box-type" cart_item_key="<?php echo $cart_item_key ?>">
<option <?php echo $cart_item['box-type']=='boxes'?"selected":""; ?> value="boxes"><?php _e( 'Boxes', 'woocommerce' ); ?></option>
<option <?php echo $cart_item['box-type']=='bags'?"selected":""; ?> value="bags"><?php _e( 'Bags', 'woocommerce' ); ?></option>
</select>
</div>
<?php
...
}
AJAX:
$('.box-type-field .box-type').live('change', function () {
var cartItemKey = $(this).attr("cart_item_key");
var boxType = $(this).val();
$.ajax({
type : "post",
url : '<?php echo admin_url('admin-ajax.php');?>',
datatype: 'json',
data : {
action : "update_cart_boxtype",
cart_item_key : cartItemKey,
box_type : boxType,
},
success: function(cartItem) {
cartItemKey = cartItem[0];
cartItemQty = cartItem[1];
if (cartItem) $('input[name="cart['+cartItemKey+'][qty]"]').val(cartItemQty); // update quantity
$('.woocommerce-cart-form button[type="submit"]').click(); // submit form
}
})
})
PHP:
add_action( 'wp_ajax_update_cart_boxtype', 'update_cart_boxtype_init' );
add_action( 'wp_ajax_nopriv_update_cart_boxtype', 'update_cart_boxtype_init' );
function update_cart_boxtype_init() {
if ( ! WC()->cart->is_empty() ) {
$cart_item_key = (isset($_POST['cart_item_key']))?$_POST['cart_item_key'] : '';
$cart_item = WC()->cart->cart_contents[ $cart_item_key ];
$box_type = (isset($_POST['box_type']))?$_POST['box_type'] : '';
$cart_updated = false;
$cart_item_key_new = WC()->cart->generate_cart_id( $cart_item['product_id'], $cart_item['variation_id'], $cart_item['variation'], ['box-type'=>$box_type] );
$found = WC()->cart->find_product_in_cart( $cart_item_key_new );
if ($found != '') {
$new_qty = $cart_item['quantity'] + WC()->cart->cart_contents[ $cart_item_key_new ]['quantity'];
WC()->cart->remove_cart_item($cart_item_key);
wp_send_json_success([$cart_item_key_new, $new_qty]);
} else {
WC()->cart->add_to_cart($cart_item['product_id'], $cart_item['quantity'], $cart_item['variation_id'], $cart_item['variation'], ['box-type' => $box_type]);
$cart_updated = true;
WC()->cart->remove_cart_item($cart_item_key);
wp_send_json_success(false);
}
}
wp_die();
}
Step 1: Add Data in a Custom Session, on ‘Add to Cart’ Button Click
For those of you who have worked with WooCommerce might know that on the click of the ‘Add to Cart’ button the product page gets refreshed and the user data is lost. Hence, we should add the custom data from our product page to a custom session created using Ajax. This code is invoked before the WooCommerce session is created.
<?php
add_action('wp_ajax_wdm_add_user_custom_data_options', 'wdm_add_user_custom_data_options_callback');
add_action('wp_ajax_nopriv_wdm_add_user_custom_data_options', 'wdm_add_user_custom_data_options_callback');
function wdm_add_user_custom_data_options_callback()
{
//Custom data - Sent Via AJAX post method
$product_id = $_POST['id']; //This is product ID
$user_custom_data_values = $_POST['user_data']; //This is User custom value sent via AJAX
session_start();
$_SESSION['wdm_user_custom_data'] = $user_custom_data_values;
die();
}
Step 2: Add Custom Data in WooCommerce Session
At this step, the WooCommerce session has been created and is now available for us to add our custom data. We use the following code to add the custom data from the session we have created into the WooCommerce session. At this step, our session is also unset since the data in it has been captured and it is not needed anymore.
add_filter('woocommerce_add_cart_item_data','wdm_add_item_data',1,2);
if(!function_exists('wdm_add_item_data'))
{
function wdm_add_item_data($cart_item_data,$product_id)
{
/*Here, We are adding item in WooCommerce session with, wdm_user_custom_data_value name*/
global $woocommerce;
session_start();
if (isset($_SESSION['wdm_user_custom_data'])) {
$option = $_SESSION['wdm_user_custom_data'];
$new_value = array('wdm_user_custom_data_value' => $option);
}
if(empty($option))
return $cart_item_data;
else
{
if(empty($cart_item_data))
return $new_value;
else
return array_merge($cart_item_data,$new_value);
}
unset($_SESSION['wdm_user_custom_data']);
//Unset our custom session variable, as it is no longer needed.
}
}
Step 3: Extract Custom Data from WooCommerce Session and Insert it into Cart Object
At this stage, we have default product details along with the custom data in the WooCommerce session. The default data gets added to the cart object owing to the functionality provided by the plugin. However, we need to explicitly extract the custom data from the WooCommerce session and insert it into the cart object. This can be implemented with the following code.
add_filter('woocommerce_get_cart_item_from_session', 'wdm_get_cart_items_from_session', 1, 3 );
if(!function_exists('wdm_get_cart_items_from_session'))
{
function wdm_get_cart_items_from_session($item,$values,$key)
{
if (array_key_exists( 'wdm_user_custom_data_value', $values ) )
{
$item['wdm_user_custom_data_value'] = $values['wdm_user_custom_data_value'];
}
return $item;
}
}
Step 4: Display User Custom Data on Cart and Checkout page
Now that we have our custom data in the cart object all we need to do now is to display this data in the Cart and the Checkout page. This is how your cart page should look after the custom data has been added from the WooCommerce session to your Cart.
My-Cart-Page
How to implement following task :-
I have 2 div, In first div i have product name and add link, If user click on add link related product should be add in second div.
In second div at bottom i have add to cart button so on click cart button all added products should be add in drupal commerce add to cart page.
Just for reference please check below link :-
http://buildabagpartyfavours.ca/pages/build-your-own-goodie-bag
If anybody use Drupal 8, and want to solve this question, can in this way:
If you have the product variation id $var_id, you can create an "a" tag with "use-ajax" class:
Add to cart
In routing.yml you have to add the following path with controller:
path: '/add/product/{pid}'
_controller: Drupal\MY_MODULE\Controller\ShopProductController::addToCart
And you have to create controller with ajax response:
namespace Drupal\MY_MODULE\Controller;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_order\Entity\OrderItem;
use Drupal\commerce_order\Entity\Order;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\CssCommand;
class ShopProductController {
public function addToCart($pid) {
$ajax_response = new AjaxResponse();
#### ADD TO CART ####
// variation_id of product.
if (isset($pid) && !empty($pid)) {
$store_id = 1;
$order_type = 'default';
$variation_id = $pid;
$entity_manager = \Drupal::entityManager();
$cart_manager = \Drupal::service('commerce_cart.cart_manager');
$cart_provider = \Drupal::service('commerce_cart.cart_provider');
// Drupal\commerce_store\Entity\Store::load($store_id);
$store = $entity_manager->getStorage('commerce_store')->load($store_id);
$product_variation = $entity_manager->getStorage('commerce_product_variation')->load($variation_id);
// order type and store
$cart = $cart_provider->getCart($order_type, $store);
if (!$cart) {
$cart = $cart_provider->createCart($order_type, $store);
}
//Create new order item
$order_item = $entity_manager->getStorage('commerce_order_item')->create(array(
'type' => 'default',
'purchased_entity' => (string) $variation_id,
'quantity' => 1,
'unit_price' => $product_variation->getPrice(),
));
$order_item->save();
$cart_manager->addOrderItem($cart, $order_item);
$ajax_response->addCommand(new HtmlCommand('.-add-to-cart', '<span class="-added">Added</span>'));
}
return $ajax_response;
}
}
Make your button triggers some AJAX call, pass product id and on other side in your AJAX callback script collect product id, create line item from it and add it to the cart ( https://www.drupal.org/node/1288414 ).
When AJAX call is finished you'll have to call another one, to update cart block.
I am having a bit of an issue with autogenerating shortcodes, based on database entries.
I am able to get a normal shortcode working i.e:
function route_sc5() {
return "<div>Route 5</div>";
}
add_shortcode('route 5','route_sc');
and the following shortcode to activate it would be [route 5]
This works. But what I need is the shortcode to be produced for each database entry. something like:
$routes = $wpdb->get_results( $wpdb->prepare("SELECT * FROM wp_routes") );
foreach($routes as $route)
{
function route_sc$route->id () {
return "<div>Route $route->id</div>";
}
add_shortcode('route $route->id','route_sc$route->id');
}
The above is just an example of how I want it to work. Not literally the code I am using. How would I go about achieving this? ):
Thanks.
Here's an example of dynamic shortcode callbacks using PHP 5.3 anonymous functions:
for( $i = 1; $i <= 5; $i++ ) {
$cb = function() use ($i) {
return "<div>Route $i</div>";
};
add_shortcode( "route $i", $cb );
}
I have to ask, though: can you just accomplish what you need to do using shortcode arguments? ie. [route num=3]. Then you could just have one handle_route() function and one [route] shortcode, which may simplify things.
Also, while technically you can include a shortcode with a space in the name, I think it creates a confusing ambiguity. If you decide you need specific shortcodes for each route, I would recommend "route5" or "route-5" rather than "route 5."
Thanks guys, finally got it working. here is the code for any1 who may need it in the future:
function route_sc($atts, $content = null) {
extract(shortcode_atts(array(
'num' => '',
'bg' => '',
'text' => '',
), $atts));
global $wpdb;
$bus = $wpdb->get_row( $wpdb->prepare("SELECT * FROM wp_route WHERE id = '$num'") );
return "<div class='".$bus->text_colour."' style='background-color:".$bus->bg_colour."'>".$bus->route_id."</div></div>";
}
add_shortcode('route','route_sc');
with the shortcode at [route num="5a"]
Dynamic function names are not possible in PHP.
But you could try eval.
eval('function route_sc'.$route->id.' () { return "<div>Route '.$route->id.'</div>"; }');
Go about it a different way: Shortcodes can take parameters. So instead of [route 5] do [route rt="5"]. This way your shortcode processing function stays generic and the part that changes is meant to be dynamic. It also means that if an unexpected shortcode is encountered during the page load you can handle it properly instead of WordPress just stripping the code and replacing it with nothing.
See here for more info: http://codex.wordpress.org/Shortcode_API