Attach posts based on category and position - Wordpress - php

I'm working with a wordpress site where posts havn't been attached correctly, visibly it makes sense and all posts are in order but in the database there is no reference of any post connection. There are thousands of posts here, so I'll just show an example of what I'm working with, and what I would like to achieve via an action hook, or function. Or even just an update_post method.
Example list of posts currently
Post Title 1 (Category = Folder)
Post Title 2 (Category = Item)
Post Title 3 (Category = Item)
Post Title 4 (Category = Item)
Post Title 5 (Category = Item)
Post Title 6 (Category = Folder)
Post Title 7 (Category = Item)
Post Title 8 (Category = Item)
Post Title 9 (Category = Item)
Post Title 10 (Category = Item)
What I would like to achieve is:
Any posts in category 'item' that are below a post in category 'folder' to then become children of the above Folder post, so my posts would become something like this:
Post Title 1 (Category = Folder)
- Post Title 2 (Category = Item Post_parent = Post Title 1)
- Post Title 3 (Category = Item Post_parent = Post Title 1)
- Post Title 4 (Category = Item Post_parent = Post Title 1)
- Post Title 5 (Category = Item Post_parent = Post Title 1)
Post Title 6 (Category: Folder)
- Post Title 7 (Category = Item Post_parent = Post Title 6)
- Post Title 8 (Category = Item Post_parent = Post Title 6)
- Post Title 9 (Category = Item Post_parent = Post Title 6)
- Post Title 10 (Category = Item Post_parent = Post Title 6)
I'm wondering if someone can just put me on the right track of how I could do this writing a script myself. Something along the lines of:
For each post in category link > that is below a post in category folder > make this post a child of post in folder category above
What I am struggling to reference is the 'that is a below a post'. Could this be referenced by using published post date?

First of all, wordpresss post aren't hierarchical so you might want to eanble hierarchy support so you can see them on the back-end, you can add something like this
add_action('registered_post_type', '_enable_post_hierarchy', 10, 2 );
function _enable_post_hierarchy( $post_type ) {
if ($post_type !== 'post') return;
global $wp_post_types;
$wp_post_types['post']->hierarchical = 1;
add_post_type_support( 'post', 'page-attributes' );
}
Regarding about your main concern, you can achieve what you want by;
First;
create custom WP_Query, query all the post (though I think you should implement paging as you have a lot of posts) with ITEM category and make sure the order is the same as what you stated above, I believe its orderby=date and order=DESC
Then;
After you get the results, loop through each result and do another query, this time, it must only need one result, with the category FOLDER and/or not in category ITEM, and the most important part is that the POST_DATE must be greater than the current item POST_DATE (from your loop ), you can either create custom SQL query or try using WP_Query
the other method is to use get_adjacent_post built-in function, this is use on wordpress next and previous post, though this won't work if you have other category than ITEM and FOLDER. check it out https://codex.wordpress.org/Function_Reference/get_adjacent_post
Here's some NOT FULLY tested code I've come up with
First lets try getting the data and displaying the array on the front-end to verify if our new data array is built properly
// spit the data to verify the order and hierarchy
add_action('wp_head', function() {
echo '<pre>', print_r( _check_data_post(), 1), '</pre>';
});
function _check_data_post( $display = 'data' ) {
// ID of the item category
$item_id = 29;
//create query argument
$args = [
'post_type' => 'post',
'post_status' => 'publish',
'orderby' => 'date',
'order' => 'DESC',
'cat' => $item_id,
// lets add some limit not to kill the server
'post_per_page' => 50,
];
//build new query
$q = new WP_Query( $args );
//get all query result
$posts = $q->get_posts();
//new data holder
$data = [];
// loop through each result
foreach ($posts as $key => $new_post) {
// the global $post is needed for get_adjacent_post to work
global $post;
// assign each result to $post
$post = $new_post;
// get the next post, the parameters is as follows
// false - must not be only in same terms, meaning its not gonna query in ITEM category only but rather query all category
// $item_id - exclude the specified ID, we must not include the ITEM category
// false - (true means previous) NEXT to the current $post which is the current item of our loop, meaning it must have greater date than our current item
// This will not work if you have categories other then Folder and Item
// You should write your own custom SQL to be more specific and adjust on your need
$parent = get_adjacent_post( false, $item_id , false );
if( $display == 'ids' ) {
// lets return each post ID with additional "parent" ID
$data[$key]['id'] = $post->ID;
$data[$key]['parent'] = $parent->ID;
} else {
// Display Title and Category instead of the ID to verify the result
$data[$key]['cat'] = get_the_category( $post->ID )[0]->name;
$data[$key]['title'] = $post->post_title;
$data[$key]['parent'] = [
'cat' => get_the_category( $parent->ID )[0]->name,
'title' => $parent->post_title,
];
}
}
return $data;
}
Once its verified, we will then loop through each item and update each item with new parent post ID, you can call this function somewhere safe and run only once, its also better to add a bunch of conditional verification and error messages
function _update_data_post() {
// get the all the item, must be IDs only
$list_items = _check_data_post('ids');
// loop through each item
foreach ( $list_items as $items ) {
//assign item data
$data = [
'ID' => $items['id'],
'post_parent' => $items['parent'],
];
//update item with assigning post_parent value
wp_update_post( $data );
}
}

Related

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 ) );

Get the parent product categories ids from a sub product category ID in Woocommerce

For example :
- A = 21
- B = 22
- C = 23
How can I get 21 and 22 IDs using 23 sub Id?
Updated (2020)
To get the parent terms Ids from a product category term ID, try the following (code is commented):
// Get the parent term slugs list
$parent_terms_list = get_term_parents_list( 23, 'product_cat', array('format' => 'slug', 'separator' => ',', 'link' => false, 'inclusive' => false) );
$parent_terms_ids = []; // Initialising variable
// Loop through parent terms slugs array to convert them in term IDs array
foreach( explode(',', $parent_terms_list) as $term_slug ){
if( ! empty($term_slug) ){
// Get the term ID from the term slug and add it to the array of parent terms Ids
$parent_terms_ids[] = get_term_by( 'slug', $term_slug, 'product_cat' )->term_id;
}
}
// Test output of the raw array (just for testing)
print_r($parent_terms_ids);
Tested and works.
Addition:
You can better use Wordpress get_ancestors() dedicated function, like in this recent answer thread or on those other related answers.
In this case the code is going to be:
// Get the parent term ids array
$parent_terms_ids = $parent_ids = get_ancestors( $child_id, 'product_cat' , 'taxonomy');
// Test output of the raw array (just for testing)
print_r($parent_terms_ids);
Related treads:
woocommerce - How do I get the most top level category of the current product category
Get top level parent product category as body class in WooCommerce
List product categories hierarchy from a product id in Woocommerce
Related documented Wordpress functions:
Documented Wordpress function get_term_parents_list()
Documented Wordpress function get_term_by()
Documented Wordpress function get_ancestors()
function parentIDs($sub_category_id)
{
static $parent_ids = [];
if ( $sub_category_id != 0 ) {
$category_parent = get_term( $sub_category_id, 'product_cat' );
$parent_ids[] = $category_parent->term_id;
parentIDs($category_parent->parent);
}
return $parent_ids;
}
$sub_category_id = 23;
$parent_ids_array = parentIDs($sub_category_id);
echo "<pre>";
print_r($parent_ids_array);
echo "</pre>";
The fastest way to get the parents ids is to use the built in function given and documented by Wordpress. See get_ancestors
if you have checked get_term_parents_list you will see that it uses get_ancestors see this link
https://core.trac.wordpress.org/browser/tags/5.4/src/wp-includes/category-template.php#L1362
So the short answer is just below code.
$parent_ids = get_ancestors( $child_id, 'product_cat' , 'taxonomy');

Display Advanced Custom Field for Woocommerce applied to subcategory on parent category

I have an advanced custom field set up to show on a woocommerce subcategory that allows the user to define a colour via the color picker field.
This field will apply that colour to a number of elements related to that sub category (Styling the sub category thumbnail, the product page itself etc).
Im currently using as per the the ACF documentation this code to pull the field in and display it on the subcategory page:
$term = get_queried_object();
$color = get_field('colour', $term); // Get ACF Field
This works fine until it comes to the parent category for the sub pages. I am unable to call the field in for the sub categories of the parent. I understand I need to use get_terms(). I am unable ot get that working though.
This is some code I found which I have added to the loop on content-product_cat.php. However it just breaks the woocommerce loop. What would I need to do to this code to get the parent category page to show all the child subcategories each with its related color field?
// current term
$current_term = get_queried_object();
// child terms
// this returns an array of terms
$args = array(
'taxonomy' => 'YOUR TAXONOMY HERE',
'parent' => $current_term->term_id,
// you may need other arguments depending on your needs
);
$child_terms = get_terms($args);
// you need to maybe loop through the child terms gotte
// to pick which one you want to use
// I'm assuming that you only want to use the first one
$child_term = false; // set it to false to begin with
// we'll use this later
if ($child_terms) {
$child_term = $child_terms[0];
}
// make a decision
if ($child_term) {
// get field value(s) from child term
$color = get_field('color', $child_term);
} else {
// get field value(s) from current term
$color = get_field('color', $current_term);
}
// do something with the values
echo $color;
I found the solution here:
https://wordpress.stackexchange.com/a/341632
add_action('woocommerce_after_subcategory_title', 'wpse_add_custom_text_under_category_title', 10);
function wpse_add_custom_text_under_category_title($category) {
$term_id = 'product_cat_'.$category->term_id;
the_field('krotki_opis_kategorii', $term_id);
}
From Alex Uleberg:
get_queried_object_id on shop archive page it will return the id of the page and not the category. When you use the hook, the $category object will be passed in through the hook.

Manual post order arrangement

My goal is to allow exact, sticky, front-page only, positioning of posts based on user back-end input. All other WP behaviour should remain the default.
The front page displays 9 posts
User can select the position of the post on the front page (Off, 1-9)
Only one post can have an assigned position, the posts with the
latest post date is selected, others with the same positioned are
ignored and remain part of the regular orderby flow
In order to re-arrange my posts I run a second WP_Query to collect all posts that have a position assigned to them through a custom meta field. The meta field shows the option 1-9 on the admin post page but corresponds to the index numbers of an array, just like the WP_Query object posts array.
function get_positioned_posts() {
$positioned_posts = get_posts( array (
'meta_query' => array(
array(
'key' => 'post_position',
'value' => array('0','1','2','3','4','5','6','7','8'),
'compare' => 'IN',
),
),
));
return $positioned_posts;
}
Subsequently I get the MAIN posts array, loop over it and insert a positioned post when the index number matches the positioned_post value.
function mns_post_position( $posts) {
if ( is_home() && is_main_query() && ! is_admin() && ! is_search() && ! is_archive()) {
$positioned_posts = get_positioned_posts();
$positioned_post_ids = wp_list_pluck( $positioned_posts, 'ID' );
$num_posts = count( $positioned_posts );
// Find the positioned posts
for ($i = 0; $i < $num_posts; $i++) {
if ( in_array( $positioned_posts[$i]->ID, $positioned_post_ids ) ) {
$positioned_post = $positioned_posts[$i];
// Remove post from current position
array_splice( $posts, 4, 1 );
// Get post position and put it in the posts array
$position = intval(get_post_meta( $positioned_post->ID, 'post_position', true ));
array_splice($posts, $position, 0, array($positioned_post));
}
}
}
return $posts;
}
add_action('the_posts', 'mns_post_position', 1 );
This works great, but it only works for the homepage and not the paged pages. The "the_posts" action only gets the current X (9) posts and not the entire WP Query posts array. This causes unwanted behaviour:
There can be more than 9 posts on the frontpage if a positioned posts
comes from page > 2.
You can see a positioned posts twice, once on the frontpage and later
one a paged page.
Possible solutions
I need to find a way to build the query correctly on the first try.
I don't see how I can achieve this with pre_get_posts filter.
Using "menu_order" like some plugins is not an option. Ordering by date is required.
Find a hook that returns the ENTIRE WP_Query Posts array, which I
just rearrange with array_splice.

display WordPress category for its first post only

I'm displaying the 3 most recent posts from a parent category ("Where We Serve") on a page. Within that parent category, I have 6 other categories named by regions ("Africa", "Europe", "Asia", etc.). The page displays the region category name, and the post content below it. Here's the catch; of these 3 most recent posts, sometimes there will be 2 from the same region category. When that happens, I need the page to show the region category for ONLY the first post from that category. Hopefully this code explains what I'm trying to do:
<div class="news">
<?php
$args = array( 'numberposts' => '3', 'category' => 9 );
$recent_posts = wp_get_recent_posts( $args );
foreach( $recent_posts as $recent ){
$category = get_the_category($recent["ID"]);
if(
$category == //any previous post's category on this page
){
//echo the post WITHOUT the category name displayed
echo '<h2>'.$recent["post_title"].'</h2><br>'.$recent["post_content"].'<br>';
}
else{
//echo the post WITH the category name displayed
echo '<h1>'.$category[0]->cat_name.'</h1><br><h2>'.$recent["post_title"].'</h2><br>'.$recent["post_content"].'<br>';
}
}
?>
</div>
I don't know how to test for other posts' categories on that page.
As you loop through the posts, save the categories you have used to an array, then check that array to see if the category already exists.
$used_categories = array(); //optional, but for clarity
foreach( $recent_posts as $recent ){
$category = get_the_category($recent["ID"]);
$category_name = $category[0]->cat_name;
if(!isset($used_categories[$category_name])){
//echo the category name displayed
echo '<h1>'.$category_name.'</h1><br />';
//save to used categories. Value assigned doesn't matter
$used_categories[$category_name]=true;
}
//You are outputting this either way, so take it out of the if
echo '<h2>'.$recent["post_title"].'</h2><br />'.$recent["post_content"].'<br />';
}
EDIT: I'm now using Eric G's method.
I wasn't able to get this to work with PHP. I ended up using this javascript at the bottom of the page:
var regionName = document.getElementsByTagName("h1");
if(regionName[1].innerHTML == regionName[0].innerHTML){
regionName[1].style.display="none";
};
if(regionName[2].innerHTML == regionName[1].innerHTML){
regionName[1].style.display="none";
};
Definitely not as clean or "correct" as I was hoping, but it's working for now...

Categories