WooCommerce - Update All Product Descriptions (site-wide) on Button Press - php

I have been playing around with the following code in the hope that I will be able to create a button - press it - and then all the descriptions on my website will auto generate. The dream.
My question: Why is my code not updating ALL of the products as intended?
So far I have the following code, which although it works to an extent, doesn't work correctly when the button is located on a product page - It will update that product pages description along with all empty description instances for product with an ID greater than the current product, but doesn't include anything below?!
I have been using the following post to try and help me get to an answer: Woocommerce: function to update all products however I have so far been unsuccessful!
My current code:
// EDIT ALL PRODUCTS //
add_action( 'woocommerce_admin_process_product_object', 'update_products_by_x' );
function update_products_by_x(){
if(isset($_POST['button_all_descriptions']) && $_POST['button_all_descriptions'] == 'All_Descriptions'){
$product_all = get_posts( array(
'post_type' => 'product','product_variation',
'post_status' => 'publish','future','draft','pending','private','trash','auto-draft','inherit',
'fields' => 'ids'
) );
// Loop through product Ids
foreach ( $product_all as $product_id ) {
// Get the WC_Product object
$WCproduct = wc_get_product($product_id);
if($WCproduct->is_type('simple') or $WCproduct->is_type('variable')){
$title = $WCproduct->get_name();
$mpn = $WCproduct->get_meta('sp_wc_barcode_field');
$description = $WCproduct->get_description();
$output = '';
if(!empty($title)){
$output .= "<p>A " . $title;
}if(!empty($mpn)){
$output .= " (MPN: " . $mpn . ").";
}if(empty($description)){
$WCproduct->set_description($output);
$WCproduct->save();
}
}
}
}
}

You might have a default limit of results restricting number of products your get_posts call returns
$product_all = get_posts( array(
'post_type' => 'product','product_variation',
'post_status' => 'publish','future','draft','pending','private','trash','auto-draft','inherit',
'fields' => 'ids',
'numberposts' => -1
) );

Related

Woocommerce how to display relevant tags under category title

I want to display meta tags under each category title. I can see there is this code to display product tag lists for each product, but what I really want is product tags for each category and then display it under the category title in the page.
<?php
global $product;
?>
<div class="product-tags">
<?php
echo wc_get_product_tag_list( $product->get_id(), ', ' );
?>
</div>
Example screenshot:
Well, you already know/have the category name (i.e 'Coffee Equipment'), so you could use that to get the relevant tags, but in order to do so, we'll create a function in the functions.php of your active theme, like so:
The following code goes to your functions.php file of your active theme:
function your_theme_get_tags_based_on_cat($cat_name)
{
$cat_id = get_cat_ID($cat_name);
$tag_query = new WP_Query(array(
'post_type' => 'product',
'posts_per_page' => -1,
'post_status' => 'publish',
'tax_query' => array(
array(
'taxonomy' => 'product_cat',
'field' => 'term_id',
'terms' => $cat_id,
'operator' => 'IN'
)
)
));
$all_tags = array();
while ($tag_query->have_posts()) {
$tag_query->the_post();
$producttags = get_the_tags();
if ($producttags) {
foreach ((array)$producttags as $tag_obj) {
$all_tags[] = $tag_obj->term_id . '-' . $tag_obj->name;
}
}
};
$tags_array = array_unique($all_tags);
$new_array = array_map(function ($val) {
return explode("-", $val);
}, $tags_array);
return new_array;
}
The function above will return an associative array containing tag id and tag name of the corresponding tags of your PRODUCT category.
Side Note:
if you need to use it for the blog posts of your wordpress site, then you could change/modify the query by swapping 'post_type' => 'product' argument with 'post_type' => 'posts'. So your query for blog posts would be something like this:
$blog_tag_query = new WP_Query(array('post_type'=>'post','post_status' =>'publish','cat'=>$cat_id,'posts_per_page'=>-1));
If you decide to use it for your blog posts, also remember to change the get_term_link((int)$tag[0], 'product_tag') with get_term_link((int)$tag[0], 'post_tag') in your template.
Now you have a magical function :) that you can use anywhere that you need a list of tags for a specific category!
So let's use our function in your page template to populate the corresponding tags for the current category, like so:
$cat_name = 'Coffee Equipment';
$tags_array = your_theme_get_tags_based_on_cat($cat_name);
foreach ($tags_array as $tag)
{
echo '<a class="item" href="' . get_term_link((int)$tag[0], 'product_tag') . '">' . $tag[1] . '</a>';
};
Just tested and it works seamlessly fine! Feel free to customize it as needed on your html template/markup.

WooCommerce get_name() function throws an error even though the product exists

I would be so greatful for any assistance as this is driving me mad. After many hours of troubleshooting, I have narrowed the issue but have no idea why it occurs...
My code iterates through all the products within the woocommmerce store and create an order with a relevant product for a customer. It has stopped working when I updated.
The error log displays
PHP Fatal error: Uncaught Error: Call to a member function get_name()
my code [in its simplest] to diagnose the issue:
//declarations
global $wpdb;
global $woocommerce;
//get all product (ids) on system
$all_ids = get_posts( array(
'post_type' => 'product',
'numberposts' => -1,
'post_status' => 'publish',
'fields' => 'ids',
) );
//create an array of the product ids found
$counterOfProducts=0;
foreach ( $all_ids as $id ) {
$arrayOfProdIds[] = $id;
$counterOfProducts=$counterOfProducts+1;
}
foreach ($arrayOfProdIds as $IdOfproductFromArray) {
echo '<p>array id value:: '.$IdOfproductFromArray.'</p>';
//$product = wc_get_product( $IdOfRoductFromArray );
$product = wc_get_product( 30302 ); // this is a test id that I know to exist
$theTestproductName = $product->get_name();
echo $theTestproductName;
}
The exception is thrown when get_name is used but the above product is a product that I know exists.
All help is really appreciated.
Use instead wc_get_products() function to query your products and you will get directly an array of WC_Product Objects where you will be able to use the WC_Product method get_name() like:
// Get all published products
$products = wc_get_products( array(
'limit' => -1,
'status' => 'publish',
) );
$product_count = count($products); // Get the product count
echo '<p>Count: '. $product_count .'</p>'; // Output
// Loop through the array of product objects
foreach ( $products as $product ) {
$product_name = $product->get_name(); // Get product name
echo '<p>'. $product_name .'</p>'; // Output
}
It should better work.
Note: There is no need to declare anything.
as per LoicTheAztec, my answer worked but was not the correct way to resolve this issue so I have removed in case it gives an issue to someone else

Create a shortcode to display related posts (posts in the same category) in the sidebar

I want to display in the article sidebar (in wordpress) a list of 5 recent articles from that category to which it belongs. I'm using the code below to (using a shortcode) show the 5 posts (from category 62 in this case). Is there a way to write this code in functions.php so that it is optimised, and I don't have to rewrite everything for each new category?
/**
* function to add recent posts widget
*/
function wpcat_postsbycategory_musculacao() {
// the query
$the_query = new WP_Query( array( 'cat' => '62', 'posts_per_page' => 5 ) );
// The Loop
if ( $the_query->have_posts() ) {
$string .= '<ul class="postsbytag widget_recent_entries">';
while ( $the_query->have_posts() ) {
$the_query->the_post();
if ( has_post_thumbnail() ) {
$string .= '<li>';
$string .= '' . get_the_post_thumbnail($post_id, array( 80, 80) ) . get_the_title() .'</li>';
} else {
// if no featured image is found
$string .= '<li>' . get_the_title() .'</li>';
}
}
} else {
// no posts found
}
$string .= '</ul>';
return $string;
/* Restore original Post Data */
wp_reset_postdata();
}
// Add a shortcode
add_shortcode('categoryposts-musculacao', 'wpcat_postsbycategory_musculacao');
/**
* function to change search widget placeholder
*/
function db_search_form_placeholder( $html ) {
$html = str_replace( 'placeholder="Pesquisar ', 'placeholder="Buscar ', $html );
return $html;
}
add_filter( 'get_search_form', 'db_search_form_placeholder' );
The complication here is that in WP a post can have multiple categories. You need to decide how to handle that - get from all categories, or if you only want to show posts from one category, how do you choose which?
I've given a few answers below depending on how you want to handle that.
1. Get posts in any of the categories of the current post
As posts can have multiple categories, you can get all of the ids and use this to query for posts that are in any of those categories:
// 1. get the categories for the current post
global $post;
$post_categories = get_the_category( $post->ID );
// 2. Create an array of the ids of all the categories for this post
$categoryids = array();
foreach ($post_categories as $category)
$categoryids[] = $category->term_id;
// 3. use the array of ids in your WP_Query to get posts in any of these categories
$the_query = new WP_Query( array( 'cat' => implode(",",$categoryids), 'posts_per_page' => 5 ) );
1a. Get posts in any of the categories but not their children
Note cat will include children of those category ids. If you want to include those exact categories only and not children, use category__in instead of cat:
$the_query = new WP_Query( array('category__in' => $categoryids, 'posts_per_page' => 5) );
2. Get posts that have all of the categories of the current post
If you want posts that have all the same categories as the current one, this is done in the same way as above, except we use category__and instead of cat (Note that this does not include children of those categories) :
$the_query = new WP_Query( array('category__in' => $categoryids, 'posts_per_page' => 5) );
3. If you know your post will only have one category
If you know you only have one category per post, then you can just use the first element from the category array:
// 1. Just use the id of the first category
$categoryid = $post_categories[0]->term_id;
$the_query = new WP_Query( array( 'cat' => $categoryid, 'posts_per_page' => 5 ) );
4. Pass the category into the shortcode
If you want to specify which category to show, you can pass the category id into the shortcode, letting you choose whichever category you want. (FYI you can also get it to work with slug instead of id, which might be a bit more user-friendly)
Your shortcode is currently used like this, I assume:
[categoryposts-musculacao]
We can change the function so you can pass the category like this:
[categoryposts-musculacao category=62]
Shortcode function can accept an $attributes argument that has the information or "attributes" we're adding to the shortcode, e.g. category. We use this to get the category passed in as a variable called $category and then just use it instead of the hardcoded value in the rest of your function.
// 1. include the $attributes argument
function wpcat_postsbycategory_musculacao( $attributes ) {
// 2. get the value passed in as category - this will save it into a variable called `$category`
extract( shortcode_atts( array(
'category' => ''
), $attributes ) );
// 3. if there is no category don't do anything (or sohw a message or whatever you want
if (!$category) return "";
// 4. Now just use your variable instead of the hardcoded value in the rest of the code
$the_query = new WP_Query( array( 'cat' => $category, 'posts_per_page' => 5 ) );
// do the rest of your stuff!
}
Reference: Wordpress Codex Shortcode API
4a. Pass the category into the shortcode by slug
If you want the category attribute in your shortcode to work with a slug instead of the id, you just need to change WP_Query to use category_name instead of cat:
$the_query = new WP_Query( array( 'category_name' => $category, 'posts_per_page' => 5 ) );

Woocommerce wp_query get order by ID

I am trying to produce a plain output of order data. First step is a WP_QUery (perhaps) so I write this code;
$args = array (
'post_type' =>'shop_order',
'posts_per_page' => -1,
'post_status' => 'any',
//'p' => $post_id,
);
$order_query = new WP_Query( $args );
while ( $order_query->have_posts() ) :
$order_query->the_post();
echo the_ID();
echo ' : ';
the_title();
echo '<br/><br/>';
endwhile;
It obliging products a list of all orders, if I set the 'p' => $post_id where $post_id is a valid post ID, the query returns nothing.
Any idea why, hive mind?
Alternatively is there a Woocommerce way of producing a plain page with a layout like;
Order ID: 836
Order Status: ....
I assumed a WP_Query would be the obvious way but it is appearing like getting woocommerce order data is anything but straightforward.
Update 2
To get the order data for one order, you don't need WP_query. You can use directly:
$order = wc_get_order( $order_id );
$order->id; // order ID
$order->post_title; // order Title
$order->post_status; // order Status
// getting order items
foreach($order->get_items() as $item_id => $item_values){
// Getting the product ID
$product_id = $item_values['product_id'];
// .../...
}
Update 1
You should try this, as with array_keys( wc_get_order_statuses() you will get all order statuses and with 'numberposts' => -1, all existing orders.
Here is an alternative way (without WP_query or you can use thoses args in the WP_query array):
$customer_orders = get_posts( array(
'numberposts' => -1,
'post_type' => 'shop_order',
'post_status' => array_keys( wc_get_order_statuses() )
) );
// Going through each current customer orders
foreach ( $customer_orders as $customer_order ) {
// Getting Order ID, title and status
$order_id = $customer_order->ID;
$order_title = $customer_order->post_title;
$order_status = $customer_order->post_status;
// Displaying Order ID, title and status
echo '<p>Order ID : ' . $order_id . '<br>';
echo 'Order title: ' . $order_title . '<br>';
echo 'Order status: ' . $order_status . '<br>';
// Getting an instance of the order object
$order = wc_get_order( $order_id );
// Going through each current customer order items
foreach($order->get_items() as $item_id => $item_values){
// Getting the product ID
$product_id = $item_values['product_id'];
// displaying the product ID
echo '<p>Product ID: '.$product_id.'</p>';
}
}
The strict answer to this question is change 'p'=>$post_id' to 'post__in' => array($post_id)
...but the real answer, should anyone be treading this path, is that parsing the email is so much easier, woocommerce has done most of the work for you. There are other pratfalls along the way; 1. The post is protected and the order notes are in the excerpt so can't be displayed ( I could move them to meta but...) 2. The order items are within comments and have to be looped then parsed to produce meaningful output, so I might as well parse the email direct.

WooCommerce Display Purchased Items Only

So I have done a bunch of looking around the web and couldn't find a solution for this...
Basically what I am trying to do is display a product loop of all the products the user has purchased in the store just like displaying normal products.
If you still don't understand maybe this will help you get what I mean..
Here is the example product loop on the WooCommerce documentation...
<ul class="products">
<?php
$args = array(
'post_type' => 'product',
'posts_per_page' => 12
);
$loop = new WP_Query( $args );
if ( $loop->have_posts() ) {
while ( $loop->have_posts() ) : $loop->the_post();
woocommerce_get_template_part( 'content', 'product' );
endwhile;
} else {
echo __( 'No products found' );
}
wp_reset_postdata();
?>
</ul><!--/.products-->
So what if I wanted to display basically this same exact product loop however filter it out so that it only displays products that the user has already purchased.
I honestly do not know where to go with this one and I am sure there are others that have done research on this in the past so maybe this will help out a bunch of people!
Thanks in advance!
There are at least two different approaches you can take to solve this problem.
The first is to get the product from each post, and then get the product ID from each product and then use an if statement to filter using wc_customer_bought_product or woocommerce_customer_bought_product (if you are using old WooCommerece).
The second is to pass the correct arguments to filter the WP_Query to only include orders purchased by a user and then filter products only in those orders. More information on the second approach is available at Get All User Orders and Products bought by user in WooCommerce based shop (archive.org).
An example of the first approach is something like
<!-- code started -->
<ul class="products">
<?php
$user_id = get_current_user_id();
$current_user= wp_get_current_user();
$customer_email = $current_user->email;
$args = array(
'post_type' => 'product',
'posts_per_page' => 12
);
$loop = new WP_Query( $args );
if ( $loop->have_posts() ) {
while ( $loop->have_posts() ) : $loop->the_post(); $_product = get_product( $loop->post->ID );
if (wc_customer_bought_product($customer_email, $user_id,$_product->id)){
woocommerce_get_template_part( 'content', 'product' );
}
endwhile;
} else {
echo __( 'No products found' );
}
wp_reset_postdata();
?>
</ul><!--/.products-->
Kudos to Appleman1234 for providing two answers, both of which will work.
ApppleMan1234's first answer that he provided an example for is to loop through all products and then filter them by calling wc_customer_bought_product(). This certainly will work. If you have n products then you are going to make n+1 database queries.
His second suggestion is a link to a post written by Brajesh Singh who, on June 2, 2013, published a solution on fusedpress.com. The original post is no longer available. I found a cached copy at Google.
Brajesh Singh's solution queries the user's orders, then queries the order details, and last queries the product id in the order item's metadata. This solution then is always only 3 queries. Unless your shop only has 1 or 2 products, this solution is far better.
Here is a slightly edited version of Brajesh Singh's code.
/**
* Get all Products Successfully Ordered by the user
* #return bool|array false if no products otherwise array of product ids
*/
function so28362162_get_all_products_ordered_by_user() {
$orders = so28362162_get_all_user_orders(get_current_user_id(), 'completed');
if(empty($orders)) {
return false;
}
$order_list = '(' . join(',', $orders) . ')';//let us make a list for query
//so, we have all the orders made by this user that were completed.
//we need to find the products in these orders and make sure they are downloadable.
global $wpdb;
$query_select_order_items = "SELECT order_item_id as id FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id IN {$order_list}";
$query_select_product_ids = "SELECT meta_value as product_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_key=%s AND order_item_id IN ($query_select_order_items)";
$products = $wpdb->get_col($wpdb->prepare($query_select_product_ids, '_product_id'));
return $products;
}
/**
* Returns all the orders made by the user
* #param int $user_id
* #param string $status (completed|processing|canceled|on-hold etc)
* #return array of order ids
*/
function so28362162_get_all_user_orders($user_id, $status = 'completed') {
if(!$user_id) {
return false;
}
$args = array(
'numberposts' => -1,
'meta_key' => '_customer_user',
'meta_value' => $user_id,
'post_type' => 'shop_order',
'post_status' => 'publish',
'tax_query' => array(
array(
'taxonomy' => 'shop_order_status',
'field' => 'slug',
'terms' => $status
)
)
);
$posts = get_posts($args);
//get the post ids as order ids
return wp_list_pluck($posts, 'ID');
}
Combining that with a product loop from the question, plus a non-deprecated wc_get_template_part() and an addition of posts_per_page=-1 gives us
<ul class="products">
<?php
$args = array(
'post_type' => 'product',
'post__in' => so28362162_get_all_products_ordered_by_user(),
'posts_per_page' => -1
);
$loop = new WP_Query($args);
if($loop->have_posts()) {
while($loop->have_posts()) : $loop->the_post();
wc_get_template_part('content', 'product');
endwhile;
}
else {
echo __('No products found');
}
wp_reset_postdata();
?>
</ul><!--/.products-->
Not sure if this helps you out at all, but there is a plugin developed by WooThemes to support purchase history.

Categories