I'm trying to generate a unique random value from an array and store it in another array so it can't be used again.
I've managed to generate the random value based on a meta field but I'm not sure how I would go about making this unique and ensuring the same values aren't being generated again. I've created an empty array $tickethistory to save the values into. Is it possible the next time it runs to run a validation check so the $lottery_max_tickets don't include the $tickethistory values?
I'm using the function below which returns the number and I', calling it when customers purchase a product in Woocommerce.
function add_lottery_ticket_number( $postid ) {
$tickethistory = array();
$lottery_max_tickets = get_post_meta( $postid, '_max_tickets', true );
$max_tickets = range(1, $lottery_max_tickets);
$ticketallocated = array_rand($max_tickets, 1);
$tickethistory[] = $ticketallocated;
return $ticketallocated;
}
for ( $i = 0; $i < $item_meta['_qty'][0]; $i++ ) {
add_post_meta( $product_id, '_participant_id', $order->get_user_id() );
$participants = get_post_meta( $product_id, '_lottery_participants_count', true ) ? get_post_meta( $product_id, '_lottery_participants_count', true ) : 0;
update_post_meta( $product_id, '_lottery_participants_count', intval( $participants ) + 1 );
$this->add_lottery_to_user_metafield( $product_id, $order->get_user_id() );
$ticketnumber = $this->add_lottery_ticket_number($product_id);
$log_ids[] = $this->log_participant( $product_id, $order->get_user_id(), $ticketnumber, $order_id, $item );
}
As you can see here, you can use storing array in the metadata - in your case, the tickethistory array.
But, for your case I would take different approach - create ticket options once and assign the first element each time.
Consider the following:
function add_lottery_ticket_number( $postId ) {
if (metadata_exists('post', $postId, 'optionsToGive')) {
$ticketOptions = get_post_meta( $postId, 'optionsToGive', true );
} else {
$lottery_max_tickets = get_post_meta( $postid, '_max_tickets', true );
$ticketOptions = range(1, $lottery_max_tickets);
shuffle($ticketOptions ); //random order of all number
}
$ticketAllocated = array_shift($ticketOptions); //take the first element
update_post_meta( $postId, 'optionsToGive', $ticketOptions ); //update all the rest
return $ticketAllocated;
}
Notice, if all numbers has been assign this will return null.
As I never tested this code please consider it as pseudo.
Related
I have created an action with the below function and I can display the number of views of each post
But every time the page is refreshed, one view is added to this
I want the actual number of views of the post to be displayed
Thank you for your guidance
/* Counter PostViews*/
function set_post_view_custom_field() {
if ( is_single() ) {
global $post;
$post_id = $post->ID;
$count = 1;
$post_view_count = get_post_meta( $post_id, 'post_view_count', true );
if ( $post_view_count ) {
$count = $post_view_count + 1;
}
update_post_meta( $post_id, 'post_view_count', $count );
}
}
add_action( 'wp_head', 'wpb_track_post_views');
You can do this using cookies. Basically set a cookie that has a lifetime of 1 hour, so that when refreshing the page, if the cookie exists, it means the user has already been on this article in the last hour :
function set_post_view_custom_field() {
if ( is_single() ) {
global $post;
$post_id = $post->ID;
$count = 1;
if ( !isset( $COOKIE['post_view_count' . $post_id] ) ) {
$post_view_count = get_post_meta( $post_id, 'post_view_count', true );
if ( $post_view_count ) {
$count = $post_view_count + 1;
}
update_post_meta( $post_id, 'post_view_count', $count );
setcookie( 'post_view_count_' . $post_id, $post_id, time() + 3600 );
}
}
}
add_action( 'wp_head', 'wpb_track_post_views');
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
}
});
I have a custom infinite scroll that is working perfectly but it's really slow. Here is the script that handles the ajax request:-
function ga_infinite_scroll() {//trigger this on infinite scroll
add_filter( 'woocommerce_get_price_html', 'ga_show_price' );//filter to fix price range
if(empty($_POST['search_term'] )){
$params = json_decode( stripslashes( $_POST['query'] ), true );
$params['post_status'] = 'publish';
$params['posts_per_page'] = get_option('posts_per_page');
$params['post_type'] = 'product';
$params['paged'] = $_POST['page'] + 1; // we need next page to be loaded
}
else{//search logic here
$search_query = json_decode( stripslashes( $_POST['search_posts'] ), true );
$search_query['post_status'] = 'publish';
$search_query['posts_per_page'] = get_option('posts_per_page');
$search_query['paged'] = $_POST['page'] + 1;
wc_set_loop_prop( 'total', $_POST['search_count'] );
$params = $search_query;
}
ob_start();
query_posts( $params);
if ( have_posts() ) {//product loop
if ( wc_get_loop_prop( 'total' ) ) {
while ( have_posts() ) {
the_post();
wc_get_template_part( 'content', 'product' );
}
}
}
$data = ob_get_clean();
die($data);
exit;
}
add_action( 'wp_ajax_ga_infinite_scroll', 'ga_infinite_scroll' );
add_action( 'wp_ajax_nopriv_ga_infinite_scroll', 'ga_infinite_scroll' );
Here's another post with my brief description of the problem Improving the performance of a custom developed scroll. Here is the code for ga_show_price.
function ga_show_price( $price ) {
global $post, $product, $reg_price_field_slug, $sale_price_field_slug, $user_currency, $wp_query,$wp_object_cache;
if( count($product->get_children()) !== 0 ) {
$variations = $product->get_children();
$regularPriceList = [];
$salePriceList = [];
$lowestPrice;
$salePrice;
// die("here");
if( $product->is_on_sale() ) {
// NOTE: ADD caching HERE!!
if( false === get_transient( 'sales_price' ) ) {
foreach( $variations as $variation ) {
array_push($salePriceList, get_post_meta( $variation, $reg_price_field_slug, true ) );
}
set_transient( 'sales_price', $salePriceList, 12 * HOUR_IN_SECONDS );
}
else{
$salePriceList = get_transient( 'sales_price');
}
$salePrice = min($salePriceList);
$price = add_proper_decimal($salePrice);
return get_woocommerce_currency_symbol() . $price . ' ' . $user_currency;
} else {
// NOTE: ADD caching HERE!!
if( false === get_transient( 'reg_price' ) ) {
foreach( $variations as $variation ) {
array_push($regularPriceList, get_post_meta( $variation, $reg_price_field_slug, true ) );
}
set_transient( 'reg_price', $regularPriceList, 12 * HOUR_IN_SECONDS );
}
else{
$regularPriceList = get_transient( 'reg_price');
}
$lowestPrice = min($regularPriceList);
$price = add_proper_decimal($lowestPrice);
return get_woocommerce_currency_symbol() . $price . ' ' . $user_currency;
}
} else {
$price = get_post_meta( $post->ID, $reg_price_field_slug, true );
$price = add_proper_decimal($price); // pr( $price );
if ( $price == '0.00' ) {
return 'Call for Price';
}
return get_woocommerce_currency_symbol() . $price . ' ' . $user_currency;
}
}
My javascript is here:-
jQuery(document).ready( function($) {
var url = window.location.origin + '/wp-admin/admin-ajax.php',
canBeLoaded=true,
bottomOffset = 2000; // the distance (in px) from the page bottom when you want to load more posts
$(window).scroll(function(){
var data = {
'action': 'ga_infinite_scroll',
'query': my_ajax_object.posts,
'page' : my_ajax_object.current_page,
//'search_results' : my_ajax_object.ga_search_results,
'search_count' : my_ajax_object.ga_search_count,
'search_posts': my_ajax_object.ga_search_posts,
'search_term' : my_ajax_object.ga_search_term,
'user_currency': my_ajax_object.user_currency,
'reg_price_slug': my_ajax_object.reg_price_field_slug
};
if( $(document).scrollTop() > ( $(document).height() - bottomOffset ) && canBeLoaded == true ){
$.ajax({//limit the ajax calls
url : url,
data:data,
type:'POST',
beforeSend: function( xhr ){
// you can also add your own preloader here
// you see, the AJAX call is in process, we shouldn't run it again until complete
//console.log(data.search_term);
$('#ajax-loader').show();
canBeLoaded = false;
},
success:function(data){
if( data ) {
$('#multiple-products .columns-3 .products ').find('li:last-of-type').after( data ); // where to insert posts
//console.log(url);
canBeLoaded = true; // the ajax is completed, now we can run it again
my_ajax_object.current_page++;
$('#ajax-loader').hide();
}
else{
$('#ajax-loader').html('End of products...').delay(1000).fadeOut();
return;
}
}
});
}
});
//setting if it's a search
});
Is there a way that i can use this woocommerce_get_price_html filter outside of the ajax request handling script(ga_infinite_scroll) as it's really costly in terms of speed to use it inside the ajax handling script? I tried using transients at the ga_show_price(). How to implement other types of caching here to increase the speed of the infinite scroll?
So using transients is probably the best "simple" answer here without doing some major rework. However there's a couple issues with your ga_show_price() function.
So you want to always minimise the amount of database calls or long lengthy functions you call from your code to make things faster.
Transients have GLOBAL names. So if you use something called sales_price for one product, as soon as you use it for another product it will still hold the value of the previous product. What you'll probably have to do is generate a unique name for all your transients. Something like: set_transient('price_'.$product->getSKU(), ...).
$variations = $product->get_children(); - You're loading the $variations variable with all the children of the product, which probably takes quite a while and involves quite a few db calls, then if you already have a transient for this product, the variations are never used!. Only run this line if you dont already have a cached value for the product.
A smaller issue, but you are calling get_transient twice every time you have a cached value. Once to check that it's not false, and then again to actually retrieve the value. Might seem like a small thing, but if you have 100+ products loading in, it adds up.
I like to do this with my transients:
$value = get_transient('something');
if ($value === false)
{
$value = some_long_calculation();
set_transient('something', $value, ...);
}
//Now use $value here.
Get even more aggressive with your caching. How often do items change from being on sale to not being on sale? Not more than once a day? Then just cache the entire function's calculation instead of first checking if it has a sales price or regular price.
A word of warning: Transients have a maximum length for their names and values so you cant store too much in them. Also they're designed for only storing a few values, not a price for every single product in your system.
If that is the case you're probably better off thinking about caching the value in a custom field for each product? You could attach hooks so that every time the product is updated, it updates the calculated price custom field automatically.
#Mikepote's suggestions for ga_price increased the speed but editing the main product loop based on unique transient increased speed more. I hereby attach my code:-
if( empty(get_transient('ga_loop_products_'.md5(serialize($params))))){ //using md5 and serialize(for 32digit) to assign a unique name to the given set of params
query_posts( $params);
ob_start();
add_filter( 'woocommerce_get_price_html', 'ga_show_price' );//filter to fix price range
if ( have_posts() ) {//product loop
if ( wc_get_loop_prop( 'total' ) ) {
while ( have_posts() ) {
the_post();
wc_get_template_part( 'content', 'product' );
}
}
}
$data = ob_get_clean();
// $ga_loop = get_transient('ga_loop_products_'.md5(serialize($params)));
set_transient( 'ga_loop_products_'.md5(serialize($params)), $data, 24 * 60 ); // 1 day cache
}
else{
$data= get_transient('ga_loop_products_'.md5(serialize($params)));
}
wp_reset_query();
I'm trying to add meta data to each product when an order has been created by using the woocommerce_checkout_create_order_line_item.
However, I can't seem to access the ID of the order.
I've used print_r($order) and can see the order details in there but I can't see the ID of the order within the object. Is this because it hasn't been generated yet?
add_action('woocommerce_checkout_create_order_line_item', array($this, 'ticket_meta_to_line_item'), 20, 4 );
function ticket_meta_to_line_item( $item, $cart_item_key, $values, $order )
{
$_p = $item->get_product();
$key = 'Draw #';
$order_id = $order->id;
error_log( print_r( $order, true ) );
if ( false !== ( $value = $_p->get_meta( $key, true ) ) )
{
$numbers = $this->add_tickets_to_order_meta($order_id, $order->get_user_id(), $_p->id);
error_log( print_r( $numbers, true ) );
$item->add_meta_data( $key , 1 , true );
}
}
If you wondering to add meta data, then there is no need to find the Order_ID, from below code you can easily do so.
function _woocommerce_add_order_item_meta_new_ver($item,$cart_key,$values) {
//HERE product_meta is just a random key I have used here, you have to use your key here
if (isset ( $values ['product_meta'] )) {
foreach ( $values ['product_meta'] as $key => $val ) {
$order_val = stripslashes( $val );
if($val) {
if($key == 'your_cart_item_key') {
$item->add_meta_data('Your Key',$order_val);
}
}
}
}
}
//This will add "Your Key" in your order_item_meta, just make sure you have used the same key "your_cart_item_key" in your cart_item_meta key too.
You can access order id using below code.
$order_id = $order->get_order_number();
Tested and works well
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.