Bulk rewrite post slugs based on custom field value in Wordpress - php

Basically I have a custom post type setup called "Parts" with over 5,000 posts currently in it. There are a number of custom fields associated with each part, including a "part number". Currently, the URL for each part is:
http://site.com/parts/name-of-part/
What I would rather have is:
http://site.com/parts/XXXX-608-AB/ (That's a part number, stored as a custom field "partno".)
I believe I need to do two things:
1) Make a script to bulk edit all the slugs for each existing part, based on the custom field "partno".
2) Hook into a Wordpress function to trigger it to always create the slug for new parts based on the custom field "partno".
Does anyone have any knowledge on how to accomplish one or both of these aspects?
UPDATE: Below is the code I ended up using for changing existing posts
// Set max posts per query
$max = 500;
$total = 5000;
for($i=0;$i<=$total;$i+=$max) {
$parts = get_posts(array('post_type' => 'parts', 'numberposts' => $max, 'offset' => $i));
// loop through every part
foreach ( $parts as $part ) {
// get part number
$partno = get_post_meta( $part->ID, 'partno', true );
$updated_post = array();
$updated_post['ID'] = $part->ID;
$updated_post['post_name'] = $partno;
wp_update_post( $updated_post ); // update existing posts
echo $part->ID;
}
}
UPDATE: Below is the code I used in functions.php to change ongoing posts
(thanks in part to https://wordpress.stackexchange.com/questions/51363/how-to-avoid-infinite-loop-in-save-post-callback)
add_action('save_post', 'my_custom_slug');
function my_custom_slug($post_id) {
//Check it's not an auto save routine
if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE )
return;
//Perform permission checks! For example:
if ( !current_user_can('edit_post', $post_id) )
return;
//If calling wp_update_post, unhook this function so it doesn't loop infinitely
remove_action('save_post', 'my_custom_slug');
//call wp_update_post update, which calls save_post again. E.g:
if($partno != '')
wp_update_post(array('ID' => $post_id, 'post_name' =>get_post_meta($post_id,'partno',true)));
// re-hook this function
add_action('save_post', 'my_custom_slug');
}

1) Create a new page and assign a new page template to it, lets say site.com/update and update.php. Inside of update.php write you bulk mechanism:
<?php // grab all your posts
$parts = get_posts(array('post_type' => 'parts', 'numberposts' => -1,))
// loop through every part
foreach ( $parts as $part ) {
// get part number
$partno = get_post_meta( $part->ID, 'parto', true );
$updated_post = array();
$updated_post['ID'] = $part->ID;
$updated_post['post_name'] = $partno;
wp_update_post( $updated_post ); // update existing posts
} ?>
You could place this anywhere in your theme but I like to create a page for that so I can easily run a cron job with it.
Next the function to change the slug of every newly created post:
<?php function change_default_slug($id) {
// get part number
$partno = get_post_meta( $id, 'parto', true );
$post_to_update = get_post( $id );
// prevent empty slug, running at every post_type and infinite loop
if ( $partno == '' || $post_to_update['post_type'] != 'parts' || $post_to_update['post_name'] == $partno )
return;
$updated_post = array();
$updated_post['ID'] = $id;
$updated_post['post_name'] = $partno;
wp_update_post( $updated_post ); // update newly created post
}
add_action('save_post', 'change_default_slug'); ?>
The code above runs every time a post gets saved (e.g. when published for the first time) and sets a new post_name to the part no.

Related

How to achieve search in code for dynamic-created text using PHP code as shortcode in WordPress?

I need to move certain articles on my website, all of them, to another unique category. I am using specific phrases which are at bottom of every article, but after code execution, only two articles are moved to a new category, not all of them.
Also when I search through all posts using the native Dashboard-All posts option, it returns only those two articles, not all of them. I assume that is because the text is not stored in the database because it shows only when the post renders itself on the front end.
How can I include dynamically created text in my code?
My code, just for reference:
<?php
// Move to the category Breaking News.
$target_category_id = 1982;
// Get all posts that contain the search phrase from Syndication.
$posts = get_posts(
array(
's' => 'and has been published here with permission',
'post_status' => 'any',
'posts_per_page' => -1, // This will retrieve all posts.
)
);
// Loop through the posts and move them to the target category.
foreach ( $posts as $post ) {
// Remove the post from any existing categories.
$current_categories = wp_get_post_categories( $post->ID );
wp_remove_object_terms( $post->ID, $current_categories, 'category' );
// Add the post to the target category.
wp_set_post_categories( $post->ID, array( $target_category_id ), true );
}
That specific phrase is created dynamically because I am using the WP Broadcast plugin to add text at the bottom of every broadcasted article, but I am not sure if that phrase is stored in a database or that text is dynamically populated (created) every time post is rendered on the frontend.
This is the code I am using to generate a message (and has been published here with permission) at the bottom of every article:
<?php
function broadcasted_from() {
// Check that Broadcast is enabled.
if ( ! function_exists( 'ThreeWP_Broadcast' ) ) {
return;
}
// Load the broadcast data for this post.
global $post;
$broadcast_data = ThreeWP_Broadcast()->get_post_broadcast_data( get_current_blog_id(), $post->ID );
// This post must be a child. Check for a parent.
$parent = $broadcast_data->get_linked_parent();
if ( ! $parent ) {
return;
}
// Fetch the permalink
switch_to_blog( $parent['blog_id'] );
$blog_name = get_bloginfo( 'name' );
$permalink = get_post_permalink( $parent['post_id'] );
restore_current_blog();
// And now assemble a text.
$r = sprintf( 'This article appeared in %s and has been published here with permission.', $permalink, $blog_name );
return $r;
}
add_shortcode( 'broadcasted_from', 'broadcasted_from' );
add_filter(
'the_content',
function( $content ) {
// Get the broadcast from the text.
$sc_text = do_shortcode( '[broadcasted_from]' );
// Add the text to the content.
$content .= $sc_text;
// Return the expanded content.
return $content;
}
);

Moving custom field images to product Gallery Woocommerce

I've several custom image fields (ACF) from an old configuration, and would like to move those images in the Product Gallery (Woocommerce), now I've converted all the datas into a Product post type.
I tried to set this function (found in a similar post), but nothing happens, and no errors returned neither :
function upload_all_images_to_product($product_id, $image_id_array) {
//define the array with custom fields images
$image_1 = get_field('images'); // should returns image IDs
$image_2 = get_field('images-2');
$image_3 = get_field('images-3');
$image_4 = get_field('images-4');
$image_5 = get_field('images-5');
$image_6 = get_field('images-6');
$image_id_array = array($image_1, $image_2, $image_3, $image_4, $image_5, $image_6);
//take the first image in the array and set that as the featured image
set_post_thumbnail($product_id, $image_id_array[0]);
//if there is more than 1 image - add the rest to product gallery
if(sizeof($image_id_array) > 1) {
array_shift($image_id_array); //removes first item of the array (because it's been set as the featured image already)
update_post_meta($product_id, '_product_image_gallery', implode(',',$image_id_array)); //set the images id's left over after the array shift as the gallery images
}
}
Could someone help or explain me what's wrong ?
Depending on where you run this function, you should define the $product_id argument in ACF get_field() function.
Questions to clarify: How do you run this function? are you using a hook?
Update: Hooked the function in woocommerce_process_product_meta, so when a product is created or updated, it will trigger the code.
Also your code can be simplified, optimized and compacted as follow:
add_action( 'woocommerce_process_product_meta', 'save_my_custom_settings' );
function upload_all_images_to_product( $product_id, $image_ids = array(); ) {
// Loop from 1 to 6
for ( $i = 1; $i <= 6; $i++ ) {
$field_key = 'images'.( $i == 1 ? '' : '-'.$i );
// Check that the custom field exists
if( $field_value = get_field( $field_key, $product_id ) )
$image_ids[] = $field_value; // Set each ACF field value in the array
}
if( ! empty($image_ids) ) {
// Take the first image (removing it from the array) and set it as the featured image
set_post_thumbnail( $product_id, array_shift($image_ids) );
}
if( ! empty($image_ids) ) {
// Set the remaining array images ids as a coma separated string for gallery images
update_post_meta( $product_id, '_product_image_gallery', implode(',', $image_ids) );
}
}
Code goes in functions.php file of your active child theme (or active theme). untested it could work.

Update all posts in custom post type with wp_cron()

I have made a function to apply a taxonomy term to post, if it has a post meta value set to true. This works as should.
The problem I am facing is that it only updates after I have manually saved/updated the post.
Is there any way to schedule this or do it dynamically for all posts inside the custom post type?
My code for the taxonomy term function:-
function save_cp_term_meta( $post_id, $post, $update ) {
$termshouldbe='new';
$meta_value = get_post_meta( $post->ID, 'new_used_cat', true );
if (!empty( $meta_value ))
{
$termshouldbe='used';
}
else
{
}
wp_set_object_terms($post_id,$termshouldbe,'vehicle_condition',false);
}
add_action( 'save_post', 'save_cp_term_meta', 10, 3 );
You can use WP_Cron() to schedule a task to run at a specific time each day and perform the update.
// check if the next cron is ours, if not schedule it.
if ( ! wp_next_scheduled( 'prefix_save_cp_term_meta_cron' ) ) {
wp_schedule_event( strtotime( '5:15PM' ), 'daily', 'prefix_save_cp_term_meta_cron' );
}
/**
* function to query for posts of a certain post type and update
* some term_meta based on a post_meta value.
*/
function prefix_save_cp_term_meta_runner() {
// make sure to set the correct post type you want here.
$args = array(
'post_type' => array( 'your post type' ),
'meta_key' => 'new_used_cat',
'posts_per_page' => -1,
);
// run the query.
$query = new WP_Query( $args );
// if the query has posts...
if ( $query->have_posts() ) {
// loop through them...
while ( $query->have_posts() ) {
$query->the_post();
// default value to use as term.
$termshouldbe = 'new';
// get the current post_meta if it exists.
$meta_value = get_post_meta( $post->ID, 'new_used_cat', true );
if ( ! empty( $meta_value ) ) {
// update the value for term based on having a value for new_used_cat.
$termshouldbe = 'used';
// NOTE: you may want to delete the post_meta here so that next
// iteration this query doesn't need to work with this post
// again as it's already processed.
}
// set the term with a value (either 'new' or 'used').
wp_set_object_terms( $post->ID, $termshouldbe, 'vehicle_condition', false );
}
// restore the main query.
wp_reset_postdata();
}
} // End if().
add_action( 'prefix_save_cp_term_meta_cron', 'save_cp_term_meta_runner' );
NOTE: You may want to look at adding this in intervals instead of at a fixed time - or run it via a system level cron job instead. This answer details some issues when working with WP_Cron() that might help you decide which method is best for you: https://wordpress.stackexchange.com/a/179774/37158

WooCommerce pagination on single product pages - but only inside parent category

I would like to put pagination on each single product page in WooCommerce so that a user can move between products in that category easier than having to go back out to the main category page every time.
I know it's possible to use the standard WordPress pagination links like…
<?php previous_post_link('« %link'); ?>
<?php next_post_link('%link »'); ?>
This works if I want to page through all products, but I only want to page through products that are within the category I'm in. Does anyone know how I can limit this so products outside this category aren't included?
I've tried using the in_same_term parameter as mentioned in the WordPress codex to get the links only showing if the next/prev product is in the same category, but it's returning an integer for some reason. Here's the code I'm using…
<?php next_post_link( '%link', '%title', TRUE, '' ); ?>
This returns nothing at all even though it follows the Codex structure. I've also tried…
<?php next_post_link( '%link %title', TRUE, '' ); ?>
And this is what I'm getting in return…
1 %title
I'm stumped where to go next.
Here is a function that I have recently written that also does the job and is quite flexible
THE IDEA:
You first need to get the current post id, which I get through get_queried_object_id(). The post ID will be used to retrieve:
The post terms the post belongs to with wp_get_post_terms(). To speed things up, only the ID's of the terms will be returned. The first ID will be used (you can modify the code here to decide which term will be used if a post have more than one term) and this will be used to retrieve all the posts which has this certain term
The post ID's of the posts that is directly adjacent to this post to determine and retrieve the next and previous post from this one
All the info above will be used in a tax_query with get_posts to retrieve all the posts that shares the term from the current post. In the function, the default taxonomy is category and the post_type is set to any to get all the posts that has this specific term
Again, to make the code faster and to safe on resources, we only going to get the post ID's as this is all that is needed
Now comes the important parts of the code. We now need to determine the following:
The current position of the current post in the returned array of post ID's from the custom get_posts query. The function used here is array_search
If there is a post before or after this post (next or previous posts, the definitions are the same as for the build in functions next_post_link() and previous_post_link()), get the ID's of these posts
Use the ID's with get_post to retrieve the next and previous post's titles from the current post
Lastly will be to return the links. I have set messages if the current post is either the first or last post in the array and there are no next or previous post. You can decide what you want to do here, and for all that matters, the rest of the code
To make the code even faster and more efficient, I have made use of the Transient API which you can read further on. I have also used the transition_post_status action hook to hook a function to delete these transients whenever the post status of a post change. This includes new posts being published, post being updated and post deleted/undeleted
THE CODE:
Here is the code. This goes into your functions.php
function get_post_link( $taxonomy = 'category', $post_type = [ 'any' ] ) {
$id = get_queried_object_id(); // Get the current post ID
$transient_id = 'post_number_' . md5( $id . $taxonomy . implode( ',', $post_type ) ); //Create a unique transient id
if ( false === ( $links = get_transient( $transient_id ) ) ) {
// Get the terms a post belongs to
$terms = wp_get_post_terms( $id, $taxonomy, array( 'fields' => 'ids' ) );
// Use a tax_query to get all posts from the given term
// Just retrieve the ids to speed up the query
$post_args = [
'post_type' => $post_type,
'fields' => 'ids',
'posts_per_page' => -1,
'tax_query' => [
[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $terms[0],
'include_children' => false,
],
],
];
// Get all the posts having the given term from all post types
$q = get_posts( $post_args );
//Get the current post position. Will be used to determine next/previous post
$current_post_position = array_search( $id, $q );
// Get the previous/older post ID
if ( array_key_exists( $current_post_position + 1 , $q ) ) {
$previous = $q[$current_post_position + 1];
}
// Get post title link to the previous post
if( isset( $previous ) ) {
$previous_post = get_post( $previous );
$previous_post_link = get_permalink( $previous );
$previous_title = '' . $previous_post->post_title . '</br>';
}
// Get the next/newer post ID
if ( array_key_exists( $current_post_position - 1 , $q ) ) {
$next = $q[$current_post_position - 1];
}
// Get post title link to the next post
if( isset( $next ) ) {
$next_post = get_post( $next );
$next_post_link = get_permalink( $next );
$next_title = '' . $next_post->post_title . '</br>';?><pre><?php var_dump($next_title); ?></pre><?php
}
// The returned post links
if( isset( $previous_title, $next_title ) ) {
$links = [
'previous_post' => $previous_title,
'next_post' => $next_title,
];
}elseif( !isset( $previous_title ) && $next_title ) {
$links = [
'previous_post' => 'You are currently viewing the newest post',
'next_post' => $next_title,
];
}elseif( $previous_title && !isset( $next_title ) ) {
$links = [
'previous_post' => $previous_title,
'next_post' => 'You are currently viewing the last post',
];
}
set_transient( $transient_id, $links, 7 * DAY_IN_SECONDS );
}
return (object)$links;
}
add_action( 'transition_post_status', function ( $new_status, $old_status, $post )
{
global $wpdb;
$wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient%_post_number_%')" );
$wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient_timeout%_post_number_%')" );
}, 10, 3 );
HOW TO USE:
You can now use the code as follows in you single.php. The default taxonomy is category and post type is any. If your custom taxonomy is called mytax, you can use the code like this
if( function_exists( 'get_post_link' ) ) {
$post_links = get_post_link( 'mytax' );
echo $post_links->previous_post . '</br>' . $post_links->next_post;
}

How to count the total number of posts from the selected post types?

I want to make a notification bar in my Wordpress website, that will be displayed when there is a new post published. Unfortunately, I have some problems with getting the code work.
GOAL
What the goal is, is to let the user know that there are new posts available for them on the website with a notification bar. So the code should check if the amount of posts is increased. If yes, I want to show the notification bar on the website for two days.
THE PROBLEM
The following code will only outputs the total number of posts from each post type that I have specified in the $post_types array. The if statement doesn’t work correctly. When I publish a new post, delete it and post another, it will not update the number in the database. Only if I post after I delete an older one, the value will increase.
MY CODE
The code below will now only echos the post type name and number of posts.
$args = array(
'public' => true,
'_builtin' => false
);
$post_types = array( 'post', 'roosters', 'downloads', 'reglements', 'alv' );
foreach ( $post_types as $post_type ) {
// variable
$postCountTotal = wp_count_posts( $post_type )->publish;
echo '<strong>' . $post_type . '</strong>';
echo ' has total posts of : ' . $postCountTotal;
echo '<br>';
// First read the previous post count value from the databse so we can compare the old value with the new one
// EDIT: use 0 as the default value if no data in database - first run
$previousCount = get_option( 'post_count_total', 0 );
if ( $postCountTotal != $previousCount ) {
//echo 'New post detected';
update_option( 'post_count_total', $postCountTotal );
} elseif ( '' == $postCountTotal && $previousCount ) {
delete_option( 'post_count_total', $previousCount );
}
}
echo $postCountTotal;
Counting posts to determine if there is a new post is wasting resources and not accurate as a transition in post status can influence the count. For example, as you said, if a post is deleted and a new one is published, the count will stay the same
To make this work, we need to follow the following worksflow
WORKSFLOW
We need to determine when a post is published. This can be done via the transition_post_status action hook. This hook is fired every time a post's status is changed. Even if a post is updated, it's status is changed.
We should only do something when a new post is published, so we need to check the post's status before and after it is published ( the 'new' status here does not work as I have expected it to work, so I scrapped that idea).
Next will be to save the new post object in the wp_options table where we can get it later in a template and use it to display the notification bar. The functions to use here would be add_option() to create our option if it does not exist and update_option() if the option already exist.
The post object is now saved in the wp_options table. We must now retrieve that option in an function or template file. We will use get_option(). From the post object saved we will need to get the post_date_gmt in order to use it for comaprison and we need to determine the exact time 2 days later
We also need the current time which we can get with current_time()
In the final stretch, we can now compare the dates and if the dates we are comparing is less than two days, we need to make things happen, show the notification bar, if the comparison is more than two days, we either shows nothing or something else
THE CODE
Here is the final code. I have commented it well so that you can follow it
In your functions.php, add the following
add_action( 'transition_post_status', function ( $new_status, $old_status, $post )
{
//Check if our post status then execute our code
if ( $new_status == 'publish' && $old_status != 'publish' ) {
if ( get_option( 'new_post_notification' ) !== false ) {
// The option already exists, so we just update it.
update_option( 'new_post_notification', $post );
} else {
add_option( 'new_post_notification', $post );
}
}
}, 10, 3 );
Now, in your template or in a custom function if you wish, add the following
// Get the new_post_notification which holds the newest post
$notification = get_option( 'new_post_notification' );
if( false != $notification ) {
//Get the post's gmt date. This can be changed to post_date
$post_date = strtotime( $notification->post_date_gmt );
//Get the current gmt time
$todays_date = current_time( 'timestamp', true );
//Set the expiry time to two days after the posts is published
$expiry_date = strtotime('+2 day', $post_date);
if( $expiry_date > $todays_date ) {
// Display your notification if two days has not been passed
}
}
There is built-in WP function for that:
<?php
$count_posts = wp_count_posts();
?>
Good documentation available on the WP Codex:
http://codex.wordpress.org/Function_Reference/wp_count_posts
You should be able to simplify your code if you only want the total of all posts.
I Needed to count the posts in the current language. Here's my code:
$allpost_arg = array(
'numberposts' => -1,
'post_type' => 'post',
'suppress_filters' => 0,
);
$allposts = get_posts( $allpost_arg );
if($allposts){
$count_the_posts = 0;
foreach ($allposts as $allpost){
$count_the_posts++;
}
}
echo $count_the_posts;
Copy and paste the following code to your theme’s functions.php file or a site-specific plugin to create a shortcode:
function wpb_total_posts() {
$total = wp_count_posts()->publish;
return $total;
}
add_shortcode('total_posts','wpb_total_posts');
Now you can use the shortcode [total_posts] to display the total number of posts in any posts, pages, or sidebar widgets.

Categories