Bulk update all taxonomy terms in wordpress - wordpress

My custom post type "references" has a custom field called "references_count". It has a numeric value.
I have an custom taxonomy called "country" with a custom field called "country_count" for the terms.
Background:
The custom post type "references" saves cities with a number of clients in this city. This value is saved in the field "references_count". In the custom taxonomy there are countries. For each country, there is a total number of references.
Example:
In the city of "Berlin" there are 3 clients. In the city of "Munich" there are 2 clients. The taxonomy term "Germany" includes the sum of all cities in this country. So the value of "country_count" in this example for the taxonomy term "Germany" is 5, being the sum of the references of each city.
I wrote this code which is working, if I'm saving each individual taxonomy term.
add_action( 'edited_country', 'update_counter_for_countries', 10, 2 );
function update_counter_for_countries( $term_id ) {
// Get posts with term
$args = array(
'post_type' => 'reference',
'posts_per_page' => -1,
'tax_query' => array(
array(
'taxonomy' => 'country',
'field' => 'term_id',
'terms' => $term_id
)
)
);
$the_query = new WP_Query( $args );
// sum values in posts
$sumTerm = 0;
if ( $the_query->have_posts() ) {
while ( $the_query->have_posts() ) {
$the_query->the_post();
$number = get_field( 'references_count', get_the_ID() );
$sumTerm = $sumTerm + $number;
}
}
wp_reset_postdata();
// update field in term
update_field( 'country_count', $sumTerm, 'country'.'_'.$term_id );
}
Problem:
I have more than 100 countries (taxonomy terms), so I have to save each term individually to get things going.
What I am looking for: Is there a way to update / save all custom taxonomy terms at once, so I don't have to update each term seperately? I checked out a lot of plugins, but couldn't find any plugin which gives the possibility of "bulk edit" or "bulk save" taxonomy terms. I would prefer a solution without plugin if possible. I am very grateful for any hint, thank you very much.

You can use this code to update all terms in one go.
Just make sure to backup your database in case needed.
This code will loop through all the terms and will only run once. after that you can remove this code.
Just to make this code run only on your IP, change 111.111.111.111 to your IP ADDRESS.
if($_SERVER["REMOTE_ADDR"]=='111.111.111.111'){
//run only my ip
add_action("init","update_all_terms_in_one_go");
}
function update_all_terms_in_one_go(){
session_start();
if(isset($_SESSION['all_terms_updated']) && $_SESSION['all_terms_updated'] == "done"){
return;
}
$taxonomy = "country";
$terms = get_terms([
'taxonomy' => $taxonomy,
'hide_empty' => false,
]);
foreach ($terms as $term) {
update_counter_for_countries( $term->term_id );
}
$_SESSION['all_terms_updated'] = "done";
echo "ALL TAXONOMY TERMS UPDATED";
die();
}
function update_counter_for_countries( $term_id ) {
// Get posts with term
$args = array(
'post_type' => 'reference',
'posts_per_page' => -1,
'tax_query' => array(
array(
'taxonomy' => 'country',
'field' => 'term_id',
'terms' => $term_id
)
)
);
$the_query = new WP_Query( $args );
// sum values in posts
$sumTerm = 0;
if ( $the_query->have_posts() ) {
while ( $the_query->have_posts() ) {
$the_query->the_post();
$number = get_field( 'references_count', get_the_ID() );
$sumTerm = $sumTerm + $number;
}
}
wp_reset_postdata();
// update field in term
update_field( 'country_count', $sumTerm, 'country'.'_'.$term_id );
}

I wanted to have a neat admin page with a button to bulk update all my taxonomy terms. The process should work using AJAX so it can take a while without confusing the user. There should be status messages.
So in the first step I added a new admin page to the wordpress backend.
add_action( 'admin_menu', array( $this, 'my_admin_page' ) );
function my_admin_page() {
add_menu_page(
__('Bulk Terms'), // page title
__('Bulk Terms'), // menu title
'manage_options', // user capabilities
'options-page', // menu slug
'my_output_function', // output function
'dashicons-admin-generic', // menu icon
77 // menu position
);
}
In the output function I put a form with a button to bulk update terms. And some status messages.
function my_output_function() {
echo '<div class="wrap">';
echo '<form action="admin.php?page=options-page" method="post">';
wp_nonce_field( 'ajax_validation', 'nonce' ); // security feature
submit_button('Update Terms', 'primary', 'submitOptions', false);
echo '</form>';
echo '<div id="processing" style="display:none;">Please wait</div>';
echo '<div id="error" style="display:none;">Something went wrong</div>';
echo '<div id="success" style="display:none;">Done!</div>';
echo '</div>';
}
In the second step I had to enqueue script file for ajax calls:
add_action( 'admin_enqueue_scripts', 'my_ajax_scripts' );
function my_ajax_scripts() {
// Check if on specific admin page
global $pagenow;
if (( $pagenow == 'admin.php' ) && ($_GET['page'] == 'options-page')):
wp_enqueue_script( 'ajaxcalls', plugin_dir_url( __FILE__ ).'/js/ajax-calls.js', array('jquery'), '1.0.0', true );
wp_localize_script( 'ajaxcalls', 'ajax_object', array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'ajaxnonce' => wp_create_nonce( 'ajax_validation' )
) );
endif;
}
And create the function to bulk update all my taxnomy terms:
function options_page_action() {
$taxonomy = "country";
$terms = get_terms([
'taxonomy' => $taxonomy,
'hide_empty' => false,
]);
foreach ($terms as $term) {
$term_id = $term->term_id;
// Get posts with term
$args = array(
'post_type' => 'reference',
'posts_per_page' => -1,
'tax_query' => array(
array(
'taxonomy' => 'country',
'field' => 'term_id',
'terms' => $term_id
)
)
);
$the_query = new WP_Query( $args );
// sum values in posts
$sumTerm = 0;
if ( $the_query->have_posts() ) {
while ( $the_query->have_posts() ) {
$the_query->the_post();
$number = get_field( 'references_count', get_the_ID() );
$sumTerm = $sumTerm + $number;
}
}
wp_reset_postdata();
// update field in term
update_field( 'country_count', $sumTerm, 'country'.'_'.$term_id );
}
$result = array( 'status' => 'success' ); // create response
wp_send_json_success( $result ); // send response
wp_die(); // close ajax request
}
As the third step, in my ajax_calls.js file I take the click event to call the function for updating the taxonomy terms using ajax.
$( '#submitOptions' ).click( function(event) {
event.preventDefault();
$('#submitOptions').css('cssText','display:none;');
$('#processing').css('cssText','display: block;');
$.ajax({
type: 'POST',
url: ajax_object.ajaxurl,
data: {
action: 'options_page_action',
nonce: ajax_object.ajaxnonce
},
success: function( response ) {
if( response['data']['status'] == 'success' ) {
$('#processing').css('cssText','display:none;');
$('#success').css('cssText','display:block;');
}
},
error: function() {
$('#processing').css('cssText','display:none;');
$('#error').css('cssText','display:block;');
}
});
});
There are messages indicating that the function is running and it show messages when it's done or has an error. This way the user will always know what's going on when bulk updating terms.

Related

How to get number of a post over the total numbers of posts within a category in wordpress

I need to show a counter within my single page post (I use it as a shortcode).
I have a custom post type (slug = 'portfolio') that has different categories.
When I am in the single post, I want to display a counter such as "n/tot" where
n = number of post (first post within the category = 1; second post= 2 etc)
tot = total number of posts within that category
I found the way to show the total number of posts, but not within a category
add_shortcode( 'tot_posts', 'number_factsheets' );
function number_factsheets () {
$total = wp_count_posts('portfolio')->publish;
$output = '<div class="count">';
$output .= $total;
$output .='</div>';
return $output;
}
Is it possible to obtain the data above when I am in single page post? And how?
WORKING CODE FOR RETRIEVING NUMBER OF SINGLE CUSTOM POST TYPE OVER TOTAL NUMBER OF CPT WITHIN A SPECIFIC CATEGORY
add_shortcode('post-counter', 'get_current_post_num');
function get_current_post_num() {
$url = $_SERVER['REQUEST_URI'];
$curr_post_id = url_to_postid( "https://sharkrayareas.org".$url ); //Too early for $post object
$taxonomy = 'portfolio_entries';
$term_objs = get_the_terms( $curr_post_id->ID, $taxonomy );
foreach ($term_objs as $term_obj)
$term_ids = $term_obj->term_id; // get the id from the WP_Term object
$args = array(
'post_type' => 'portfolio',
'post_status' => 'publish',
'tax_query' => array(
array(
'taxonomy' => 'portfolio_entries',
'terms' => $term_ids,
),
),
'order'=> 'ASC',
'posts_per_page' => -1
);
$portfolio_posts = new WP_Query($args);
if ($portfolio_posts->have_posts()):
echo 'true';
$total_posts = $portfolio_posts->found_posts;
$num = 1;
while ($portfolio_posts->have_posts()) : $portfolio_posts->the_post();
$post_id = get_the_ID();
if ($post_id === $curr_post_id) {
$output= '<div class="count">'. $num . '/' . $total_posts .'</div>';
} else {
$num++;
}
endwhile;
endif;
return $output;
}
I recommend going about that by looping through the posts and checking whether the the page you are on is that point in the loop. I'm including an example that prints it on-page.
add_action('init', 'get_current_post_num');
function get_current_post_num() {
$url = $_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
$curr_post_id = url_to_postid( "https://".$url ); //Too early for $post object so get $curr_post_id from url_to_postid
$args = array(
'post_type' => 'portfolio',
'post_status' => 'publish',
'posts_per_page' => -1
);
$portfolio_posts = new WP_Query($args);
if ($portfolio_posts->have_posts()):
$total_posts = $portfolio_posts->found_posts;
$num = 1;
while ($portfolio_posts->have_posts()) : $portfolio_posts->the_post();
$post_id = get_the_ID();
if ($post_id === $curr_post_id) {
echo $num . '/' . $total_posts;
} else {
$num++;
}
endwhile;
endif;
}

How to auto draft WooCommerce product on specific date?

I have a custom date field, added with ACF, for each of my products at which point the product status should change to draft.
I know there are a bunch of schedular and count time plugins, but it comes with a bell and whistle I don't need. Is there a simple way to achieve this
Thank you
You can use WorsPress CRON. You can use wp_schedule_event. You have to get all products and get your ACF field to compare against today or current date then use the wp_update_post function to update posts status. try the below code.
// Schedule an action if it's not already scheduled
if ( ! wp_next_scheduled( 'check_daily_for_change_product_status' ) ) {
wp_schedule_event( time(), 'daily', 'check_daily_for_change_product_status' );
}
// Hook into that action that'll fire every three minutes
add_action( 'check_daily_for_change_product_status', 'check_daily_for_change_product_status_func' );
function check_daily_for_change_product_status_func() {
$args = array(
'post_type' => 'product',
'posts_per_page' => -1,
'post_status' => 'publish'
);
$get_products = new WP_Query( $args );
if( $get_products->have_posts() ){ while ( $get_products->have_posts() ) { $get_products->the_post();
$draft_date = strtotime( get_field('keyname', get_the_ID() ) );
$current_date = time();
if( $current_date >= $draft_date ){
$my_post = array(
'ID' => get_the_ID(),
'post_status' => 'draft',
);
wp_update_post( $my_post );
}
} wp_reset_postdata(); }
}

Why wc_get_template_part() not working inside wc_get_products()?

Cant find full loop example on the web with wc_get_template_part() and wc_get_products(), so looking for help here:
global $woocommerce;
global $product;
$args = array(
'limit' => 15,
'category' => array('printers', 'laptop')
);
$query_cats = wc_get_products($args);
foreach ($query_cats as $query_cat) {
echo $query_cat->get_id();
echo $query_cat->get_title();
// echo "<pre>";
// var_dump($query_cat);
wc_get_template_part('content', 'product');
}
?>
Titles and ids are displayed, var_dump also, bu wc_get_template_part - no. I have add_theme_support('woocommerce'); and also body_class();
WooCommerce content-product.php template only works only with standard loop(with instance of Wp_Query). May be following solution can help:
$args = array(
'post_type' => 'product',
'post_status' => 'publish',
'tax_query' => [
array(
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => ['printers', 'laptop'],
)
],
);
$product = new WP_Query( $args );
while ( $product->have_posts() ) {
$product->the_post();
wc_get_template_part( 'content', 'product' );
}
wp_reset_postdata();
Thanks
Yes, it can be done (2022 update)
If you want to avoid native WP_Query or using shortcodes, this actually can be done using wc_get_products().
You just need to setup and reset postdata within your foreach loop and setup WooCommerce loop properly.
global $post; // Do not forget this!
$args = array(
'limit' => 15,
'category' => array('printers', 'laptop')
);
$products = wc_get_products( $args );
// Set loop properties
wc_set_loop_prop('columns', 5);
// Start custom WC loop
woocommerce_product_loop_start();
foreach( $products as $product ) {
// Setup postdata
$post = get_post( $product->get_id() );
setup_postdata( $post );
// Get template part
wc_get_template_part( 'content', 'product' );
}
// End loop and reset postdata
woocommerce_product_loop_end();
wp_reset_postdata();
Important note: this will only work if queried products are within any HTML element with the woocommerce class (otherwise WooCommerce CSS won't load for your products). In some templates, the woocommerce class is already part of the DOM (e.g. <body> element), but if it isn't, wrap your loop within an element as such:
<div class="woocommerce my-products-loop">
<?php // Your loop goes here ?>
</div>
TIP: You can set various loop properties using wc_set_loop_prop in the "Set loop properties" part of the code

Exclude children of custom post type from search

I have a custom post type of 'location'. I then have children pages for each of the pages for that cpt. so it looks something like this, "www.example.com/location/location-name/child-page/", each child page is using a post template of "location-product-services.php". So what I am trying to do is exclude from the search results the children of this cpt.
I am trying to do it by checking the meta data to see if it is using that template. I just cant seem to get it working. Any help would be great.
This is what I currently have -
// Exclude local product and services pages from search result.
function location_subpages_exclude_search( $query ) {
if ( is_search() && !is_admin()) {
$query->set('meta_query',
array(
'key' => '_wp_page_template',
'value' => 'location-product-services.php',
'compare' => '!='
)
);
}
}
add_action('pre_get_posts', 'location_subpages_exclude_search');
Thanks in advance.
First, I pretty much exclusively use the Relevanssi plugin any time I want to modify search. But to search programmatically, I think this is what you're after:
$taxonomy = 'location';
$terms = get_terms($taxonomy, array( 'parent' => 0, 'search' => $query ) );
if ( $terms && !is_wp_error( $terms ) ) :
?>
<ul>
<?php foreach ( $terms as $term ) { ?>
<li><?php echo $term->name; ?></li>
<?php } ?>
</ul>
<?php endif;?>
Use the function get_terms to search your CPT, the 'search' is your $query (you might consider wrapping the search string with the SQL wildcard '%') and 'parent'=>0 returns only the top level.
I figured it out.
First I got all parent pages of my post type, used get_pages() grab them all.
Looped through each of the parent pages and ran another get_pages() for children of that parent.
function SearchFilter($query) {
if ($query->is_search) {
$args = array(
'hierarchical' => 0,
'post_type' => 'location',
'parent' => 0, //returns all top level pages
'post_per_page' => -1
);
$parents = get_pages($args);
$excludes = array();
foreach($parents as $parent) :
$args = array(
'post_type' => 'location',
'child_of' => $parent->ID,
'post_per_page' => -1
);
$children = get_pages($args);
foreach($children as $child):
array_push($excludes, $child->ID);
endforeach;
endforeach;
$query->set('post__not_in', $excludes);
}
return $query;
}
add_filter('pre_get_posts','SearchFilter');

Show all terms excluding the current page

The following lists all of the terms, can I get help revising it so that it shows all terms except the the active/current page? Thank you.
$terms = get_terms( 'topics', array(
'orderby' => 'name',
'order' => 'ASC',
));
if ( ! empty( $terms ) ){
foreach ( $terms as $term ) {
$term_thumb = get_field('image', $term);
echo '<li><img src="' .$term_thumb['url']. '"><span class="model">'.$term->name .'</span></li>';
}
}
You can do something like this:
// create an empty array holder
$current_tax_ids = array();
// get the post terms
$current_tax = get_the_terms( $post->ID, 'topics' );
// creating loop to insert ids
foreach( $current_tax as $tax ) {
$current_tax_ids[] = $tax->term_id;
}
$args = array(
'taxonomy' => 'topics',
'hide_empty' => 0,
'exclude' => $current_tax_ids // exclude the terms
);
// get all the terms
$all_terms = get_terms($args);
// now do whatever you want
so if you follow my comments it should be clear, but basically you want to get the current post terms and store the id in an array, then simply exclude the ids when you do get_terms .

Resources