php snippet breaks down the site - php

My products have a custom meta 'wccaf_virtual_quantity'. Now I want to calculate and add another custom meta 'actual_stock'. Value of 'actual_stock' = stock - wccaf_virtual_quantity
The code I am trying breaks down my site It gives error of 'The site is experiencing technical difficulties. Please check your site admin email inbox for instructions.' while accessing admin panel. but when I disable the code from the database and check the products table for 'actual_stock', I can see the values of 'actual_stock' are updated.
That means the code works as it should but it breaks down the site in process.
I have tried adding following code to functions.php. I am adding php snippet using 'Code Snippets' plugin
$args = array(
'post_type' => 'product',
'posts_per_page' => -1,
);
$products_array = get_posts($args);
if (!empty($products_array)) {
// loop through each product
foreach ($products_array as $product)
{
update_actual_stock($product->ID);
}
}
function update_actual_stock($post_id) {
$post_type = get_post_type($post_id);
if ($post_type == 'product') {
$product = wc_get_product($post_id);
$virtual_stock = get_post_meta( $post_id,
'wccaf_virtual_quantity', true );
$visible_stock = $product->get_stock_quantity();
$actual_quantity = $visible_stock - $virtual_stock;
update_post_meta( $post_id, 'actual_stock',$actual_quantity);
}
}
Please check what am I doing wrong.

Why do you have to run the function on every request?
Off-course, your code can kill your server, its being triggered for every request either admin or front-end, and its queries and loops through all posts then update all product post,
You should hook it somewhere, like when post is created/updated
checkout save_post function
//Your function to update the meta
function update_actual_stock($post_id) {
$post_type = get_post_type($post_id);
if ($post_type == 'product') {
$product = wc_get_product($post_id);
$virtual_stock = get_post_meta( $post_id, 'wccaf_virtual_quantity', true );
$visible_stock = $product->get_stock_quantity();
$actual_quantity = $visible_stock - $virtual_stock;
update_post_meta( $post_id, 'actual_stock',$actual_quantity);
}
}
// hook it on 'save_post' action hook so it only updates meta of specific post if its updated/created
function _update_blabla_meta( $post_id ) {
update_actual_stock($post_id)
}
add_action( 'save_post', '_update_blabla_meta' );
if you need your function to run after order is place you have to hook it on woocommerce_checkout_order_processed, there are three parameter being pass on that action do_action( 'woocommerce_checkout_order_processed', $order_id, $posted_data, $order ); for you to grab which post to update
check the code here https://docs.woocommerce.com/wc-apidocs/source-class-WC_Checkout.html#1120
EDIT....
This should achieve what you want, or simply modify it to suit your needs;
//run meta update on products only after order is place
add_action( 'woocommerce_checkout_order_processed', function($order_id) {
$order = wc_get_order( $order_id ); // get the order from ID
$items = $order->get_items(); // get order items
//Loop through order each items
foreach ( $items as $item ) {
$porduct_id = $item->get_product_id(); //get the product ID from order item
$virtual_stock = get_post_meta( $porduct_id, 'wccaf_virtual_quantity', true ); // get your own meta value
$visible_stock = get_post_meta( $porduct_id, '_stock', true ); // get the product current stock count
$actual_quantity = $visible_stock - $virtual_stock;
update_post_meta( $porduct_id, 'actual_stock', $actual_quantity); // Update your own meta
}
});

Related

compare woocommerce product meta with customer meta, then hide/show products

I have been trying to implement geolocation for woocommerce products on my store by displaying a specific category of products (rentals) only to customers within the city where the product is posted. For some reason, I do not want to use a plugin; also because I feel there is a way I can achieve this with a few lines of code. I tried to create a custom meta (product location)for each product, then compare this custom meta with logged-in user billing address. If they match, the product should be visible. Here is the code I was trying to put together, but nothing works so far.
add_action( 'woocommerce_product_query', 'myshop_hide_product_if', 9999, 2 );
function myshop_hide_product_if( $q, $query ) {
//product details
global $product;
$product_id->get_id();
$product_location = get_post_meta( $product_id, 'custom_product_location' );
$product_category = array('rentals');
//get user details
$user = wp_get_current_user () ;
$customer_city = ! empty( $user->ID ) ? get_user_meta( $user->ID , 'billing_state' , true ) : '' ;
//hide em all
if ( $customer_city !== $product_location ) {
$q->set( 'post__not_in', $product_category );
}
}
You could use the WC_Geolocation class to get the city if the user city is not set in the metadata. There are also a few minor errors in your snippet. Like you don't actually assign $product_id
Also, I would hook this to the shop loop rather than the get products query. If you try to hook this into the get_products query, the global $product is not defined, so it would throw an error.
This hides the products, but doesn't update the query results. So this is a path to solving your problem.
add_action( 'woocommerce_shop_loop', 'myshop_hide_product_if', 20 );
function myshop_hide_product_if() {
// product details.
global $product;
$product_id = $product->get_id();
$product_location = get_post_meta( $product_id, 'custom_product_location' );
$product_category = array( 'rentals' );
// get user details.
$user = wp_get_current_user();
if ( 0 < $user->ID ) {
$customer_city = get_user_meta( $user->ID, 'billing_state', true );
} else {
$ip = WC_Geolocation::get_ip_address();
$location = WC_Geolocation::geolocate_ip( $ip );
$customer_city = $location['state'];
}
// hide em all.
if ( $customer_city !== $product_location ) {
$product->set_catalog_visibility( 'hidden' );
}
}

Filter products by clickable value in custom column WooCommerce

We add a new column to display the author => store-name form each product. (Add custom meta column to WooCommerce product listing)
I was able to answer the mentioned question by my self. But now we ran into a new problem. The needed information are now in the new column, but we can not click it. We want to have the entries in that new column clickable in order that every product from that author would be filtered. So it's a question about how to make a clickable link in order to show all other product from that author.
This is our code:
add_filter( 'manage_edit-product_columns', 'custom_admin_products_store_name_column', 9999 );
function custom_admin_products_store_name_column( $columns ){
$columns['vendor_store_name'] = __( 'Vendor');
return $columns;
}
add_action( 'manage_product_posts_custom_column', 'custom_admin_products_store_name_column_content', 10, 2 );
function custom_admin_products_store_name_column_content( $column, $product_id ){
$seller = get_post_field( 'post_author', $product_id);
$store_info = dokan_get_store_info( $seller );
$store_name = $store_info['store_name'];
if ( $column == 'vendor_store_name' ) {
echo __($store_name);
}
}
I already tried around with the information from here: WooCommerce: Adding a custom filter to the Product Admin area
But this is more for a new filter than a clickable value in a column which will apply a filter.
I was able to get this to work! :)
Here is my code. Is this a proper solution or where can I improve this? :)
Thanlks
add_filter( 'manage_edit-product_columns', 'custom_admin_products_store_name_column', 9999 );
function custom_admin_products_store_name_column( $columns ){
$columns['vendor_store_name'] = __( 'Vendor');
return $columns;
}
add_action( 'manage_product_posts_custom_column', 'custom_admin_products_store_name_column_content', 10, 2 );
function custom_admin_products_store_name_column_content( $column, $product_id ){
$seller = get_post_field( 'post_author', $product_id);
$store_info = dokan_get_store_info( $seller );
$store_name = $store_info['store_name'];
$vendor_products = get_admin_url() . 'edit.php?post_type=product&author=' . $seller;
if ( $column == 'vendor_store_name' ) {
printf('%s', $vendor_products, $store_name);
}
}

Display custom product image on WooCommerce email notifications

I am trying to modify the product images in the auto generated WooCommerce order e-mails. Note, I am trying to do this with hooks, rather than creating a modified e-mail template.
To start, I have a hidden input on the single product page. I have some JS that sets the ID of the image, which is based on the product's colour (a custom attribute I made with ACF).
<input class="js-col-img-id" type="hidden" name="product-col-img-id" value="">
For reference, here's what I've done to get this to work on the cart page:
// Add custom attributes to cart item data
add_action('woocommerce_add_cart_item_data', 'jwd_add_custom_attr_to_cart', 10, 3);
function jwd_add_custom_attr_to_cart($cart_item_data, $product_id, $variation_id) {
$attrs = array(...,'product-col-img-id');
foreach ($attrs as $attr) {
$san_attr = filter_input( INPUT_POST, $attr );
if ( !empty( $san_attr ) ) {
$cart_item_data[$attr] = $san_attr;
}
}
return $cart_item_data;
}
// This function sets the image ID in the cart, and it displays the image properly
add_action( 'woocommerce_before_calculate_totals', 'bwa_new_price' );
function bwa_new_price($cart) {
// ...
$cart_items = $cart->get_cart();
foreach($cart_items as $item) {
$data = $item['data'];
// product-col-img-id is sent through the hidden input when the add to cart form is submitted
$colour_img_ID = $item['product-col-img-id'] ?? false;
if ($colour_img_ID) {
$data->set_image_id($colour_img_ID);
}
}
}
// Add custom attributes to order
add_action( 'woocommerce_checkout_create_order_line_item', 'jwd_add_attr_to_order_items', 10, 4 );
function jwd_add_attr_to_order_items( $item, $cart_item_key, $values, $order ) {
if ( !empty( $values['product-colour'] ) ) {
$item->add_meta_data( 'Colour', $values['product-colour'] );
}
// I tried replicating this for the img ID. It does display it in the var dump mentioned below, and it does display it as an actual label in the e-mail (which I'd want to get rid of). The difficulty then becomes reading the ID through code and setting the product to display that image
if ( !empty( $values['product-col-img-id'] ) ) {
$item->add_meta_data( 'col_img_id', $values['product-col-img-id'] );
}
}
That works fine, but I'm having difficulty replicating this in the order e-mail. So far I've come across the following. This shows images in the e-mail, but it's the product's featured image. Of note is the "show_image" key being set to true.
// Add prod IMG to e-mails
function jwd_add_images_woocommerce_emails( $output, $order ) {
// set a flag so we don't recursively call this filter
static $run = 0;
// if we've already run this filter, bail out
if ( $run ) {
return $output;
}
$args = array(
'show_image' => true,
//'image_size' => array( 300, 300 ),
'image_size' => 'full',
);
// increment our flag so we don't run again
$run++;
// if first run, give WooComm our updated table
return wc_get_email_order_items($order, $args);
}
add_filter( 'woocommerce_email_order_items_table', 'jwd_add_images_woocommerce_emails', 10, 2 );
To modify the image, I found this filter, but I'm just having a difficult time figuring out what I need to do to replicate what I've done on the cart page. The closest I've got is having $item_data work, and I do see the product-col-img-id key in the var dump, though it's in a protected object, which I'm not sure how to access. Even if I could access, I'm not sure how I could even set the image from here.
add_filter('woocommerce_order_item_thumbnail', 'jwd_add_colour_img', 10, 2);
function jwd_add_colour_img($image, $item) {
$item_id = $item->get_id();
//var_dump($item->get_current_data());
$product = $item->get_product();
$product_id = $item->get_product_id();
$item_data = $item->get_data();
$col_img_id = false;
$item->set_image_id(1);
foreach ($item_data as $key => $item) {
//current_data
var_dump($item);
//echo "<br><br>";
$col_img_id = $item['product-col-img-id'] ?? false;
if ($col_img_id) {
break;
}
}
//var_dump($col_img_id);
//$item_id = $item->get_product_id();
//var_dump(wc_get_order_item_meta( $item_id, 'col_img_id', true ) );
return $image;
}
To get WooCommerce custom meta data, you can use WC_Data method get_meta().
Try the following instead to display your custom image on email notifications:
// Save custom image ID as order item meta
add_action( 'woocommerce_checkout_create_order_line_item', 'save_custom_image_id_to_order_item', 10, 4 );
function save_custom_image_id_to_order_item( $item, $cart_item_key, $values, $order ) {
if ( isset($values['product-colour']) && ! empty($values['product-colour']) ) {
$item->add_meta_data('Colour', $values['product-colour'] );
}
if ( isset($values['product-col-img-id']) && ! empty($values['product-col-img-id']) ) {
$item->add_meta_data('_col_img_id', $values['product-col-img-id'] );
}
}
// (optional) Force display item image on emails
add_filter( 'woocommerce_email_order_items_args', 'show_image_on_email_notifications' );
function show_image_on_email_notifications( $args ) {
$args['show_image'] = true;
return $args;
}
// Display custom image on emails
add_filter( 'woocommerce_order_item_thumbnail', 'display_email_order_item_custom_image', 10, 2 );
function display_email_order_item_custom_image( $image, $item ) {
// Only on email notifications
if( is_wc_endpoint_url() )
return $image;
$image_id = $item->get_meta('_col_img_id');
if( $image_id ) {
$image = wp_get_attachment_image( $image_id, array( 32, 32 ), false, array() );
}
return $image;
}
Code goes in functions.php file of the active child theme (or active theme). It should work.

Add columns in admin order list on woocommerce 3.3

Related to:
Add columns to admin orders list in WooCommerce backend
Is it possible to add only one new column in admin Order list instead. For the content of this column my-column1 I will like to have 2 custom fields like:
$my_var_one = get_post_meta( $post_id, '_the_meta_key1', true );
$my_var_two = get_post_meta( $post_id, '_the_meta_key2', true );
To finish is it possible to position this new column after the order_number?
Any help is appreciated.
Updated - It can be done this way:
// ADDING 1 NEW COLUMNS WITH THEIR TITLES
add_filter( 'manage_edit-shop_order_columns', 'custom_shop_order_column',11);
function custom_shop_order_column($columns)
{
$reordered_columns = array();
foreach( $columns as $key => $column){
$reordered_columns[$key] = $column;
if( $key == 'order_number' ){
$reordered_columns['my-column1'] = __( 'Title1','theme_slug');
}
}
return $reordered_columns;
}
// Adding the data for the additional column (example)
add_action( 'manage_shop_order_posts_custom_column' , 'custom_orders_list_column_content', 10, 2 );
function custom_orders_list_column_content( $column, $post_id )
{
if( 'my-column1' == $column )
{
// Get custom post meta data 1
$my_var_one = get_post_meta( $post_id, '_the_meta_key1', true );
if(!empty($my_var_one))
echo $my_var_one;
// Get custom post meta data 2
$my_var_two = get_post_meta( $post_id, '_the_meta_key2', true );
if(!empty($my_var_two))
echo $my_var_two;
// Testing (to be removed) - Empty value case
if( empty($my_var_one) && empty($my_var_two) )
echo '<small>(<em>no value</em>)</small>';
}
}
The other function stays unchanged…
Code goes in function.php file of your active child theme (or active theme). Tested and works.

Hook woocommerce price in backend order edition

I need to change item product prices in Woocommerce Backend Order. I tried tu use the following hook, but I have a problem trying to obtain the order id. Any suggestion? Thanks in advance!
function my_custom_prices ($price, $product)
{
if ( !is_admin() || ( is_admin() && is_post_type_archive() ) ) return $price;
global $post, $woocommerce;
$order = new WC_Order( $post_id );
$user_id = $order->user_id;
$user = get_user_by('id', $user_id);
if ( in_array( 'my_role', (array) $user->roles ) ) {
return $price * 2;
}
else {
return $price;
}
}
add_filter('woocommerce_get_price', 'my_custom_prices ', 10, 2);
Complete problem:
The complete problem is as follows. I am using a plugin that adds a field to the product called wholesale price. If the customer has the wholesale customer role, the order uses those prices. The plugin works fine, but it does not take the price in the backend. I talked to the author and it's something they do not plan to change yet. My client needs to modify the orders. But when it enters the backend, it takes the common price, not the wholesaler. I need to do something in the backend that allows me to detect if the order is from a client with a wholesale customer role. If yes, take the correct price when adding products. There is more information on the discussion with the author here. https://wordpress.org/support/topic/wholesale-prices-in-backend-editing-orders/ Thank you very much for the help you can give me.
Options:
woocommerce_get_price: does not work because I cannot obtain the customer id
woocommerce_ajax_add_order_item_meta: nice option, but I could not find a sample
Button: nice option, but I do not know how can I change the price. I tryed the follow:
add_action( 'woocommerce_order_item_add_action_buttons', 'action_aplicar_mayoristas', 10, 1);
function action_aplicar_mayoristas( $order )
{
echo '<button type="button" onclick="document.post.submit();" class="button button-primary generate-items">Aplicar precios mayoristas</button>';
echo '<input type="hidden" value="1" name="aplicar_mayoristas" />';
};
add_action('save_post', 'aplicar_mayoristas', 10, 3);
function aplicar_mayoristas($post_id, $post, $update){
$slug = 'shop_order';
if(is_admin()){
if ( $slug != $post->post_type ) {
return;
}
if(isset($_POST['aplicar_mayoristas']) && $_POST['aplicar_mayoristas']){
$order = wc_get_order( $post_id);
//$order_id = $order->get_user_id();
// Iterating through each "line" items in the order
foreach ($order->get_items() as $item_id => $item_data) {
//$item_data->set_subtotal("798");
$item_data->set_price("798");
//->set_price($custom_price);
}
}
}
}
Updated
The hook that you are using is not for orders, but only for products, and is made to change the displayed prices only. So you will not get the order ID with it.
You could change the price display in many hooks, but if you want to change order item prices for real (not only the displayed formatted prices), you should trigger this prices changes when order is updated for example.
In this case you can use a custom function hooked in save_post action hook:
add_action( 'save_post', 'change_order_item_prices', 11, 1 );
function change_order_item_prices( $post_id ) {
// If this is an autosave (has not been submitted).
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return $post_id;
// Check the user's permissions.
if ( 'shop_order' == $_POST[ 'post_type' ] ){
if ( ! current_user_can( 'edit_shop_order', $post_id ) )
return $post_id;
} else {
if ( ! current_user_can( 'edit_post', $post_id ) )
return $post_id;
}
## ------------------- Changing prices Start code ------------------- ##
## ===> HERE define the targeted user role
$user_role = 'my_role';
## ===> HERE define the rate multiplier (for price calculations)
$multiplier = 2;
// If this Order items prices have already been updated, we exit
$items_prices_updated = get_post_meta( $post_id, 'line_item_updated', true );
if( ! empty( $items_prices_updated ) ) return $post_id; // Exit
$order = new WC_Order( $post_id ); // Get the order object
$user_id = $order->get_user_id(); // Get the user ID
$user_data = get_userdata( $user_id ); // Get the user data
// Check the user role
if ( ! in_array( $user_role, $user_data->roles ) ) return;
// Loop through order items
foreach( $order->get_items() as $item_id => $item ){
$item_data = $item->get_data(); // The item data
$taxes = array();
foreach( $item_data['taxes'] as $key_tax => $values ){
if( ! empty( $values ) ){
foreach( $values as $key => $tax_price ){
$taxes[$key_tax][$key] = floatval($tax_price) * $multiplier;
}
}
}
$new_line_subtotal = floatval( $item_data['subtotal'] ) * $multiplier;
$new_line_subt_tax = floatval( $item_data['subtotal_tax'] ) * $multiplier;
$new_line_total = floatval( $item_data['total'] ) * $multiplier;
$new_line_total_tax = floatval( $item_data['total_tax'] ) * $multiplier;
// Update Order item prices
$item->set_subtotal($new_line_subtotal);
$item->set_subtotal_tax($new_line_subt_tax);
$item->set_total($new_line_total);
$item->set_total_tax($new_line_total_tax);
$item->set_taxes($taxes);
// Save the updated data
$item->save();
}
// Udpate order totals and cache
$order->calculate_totals();
// We mark the order as updated (to avoid repetition)
update_post_meta( $post_id, 'line_item_updated', true );
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and and finally works.
I have added a security to avoid the order items to be updated twice.
The method $order->calculate_totals(); slow down the process a little bit… It's normal as it will calculate totals, update data and refresh caches.
Your code needs to be debugged.
$post_id - there is not such variable or parameter inside your function. So, use $post->ID instead.
do
var_dump( (array) $user->roles);
before the
if (in_array( 'my_role', (array) $user->roles ) )
line. And make sure that my_role exists in that array.
Temporary comment this line for debugging purpose:
// if (is_admin() && is_post_type_archive())
Then you will see the reason and able to be fix it.

Categories