Get parent category for CPT - wordpress

I'm working on an index page that lists all posts for a custom post type. I've been listing the categories for each post using <?php echo strip_tags(get_the_term_list( $post->ID, 'genre', ' ',' • ')); ?>.
We need to add sub-categories but I don't want these to display on the index page - just the parent category. Have tried using get_term_parents_list and a few other examples from here but can't get anything working.
Can anyone help?

You can use get_the_terms filter to change the terms to return.
add_filter('get_the_terms', 'only_parent_genre', 10, 3);
function only_parent_genre($terms, $post_id, $taxonomy) {
// TODO for you : Add condition to heck if you are not on your custom index too.
if(is_admin() || $taxonomy !== 'genre') {
return $terms;
}
// Loop over terms and if parent is something different than 0, it means that's its a child term
foreach($terms as $key => $term) {
if($term->parent !== 0) {
unset($terms[$key]);
}
}
return $terms;
}

Related

How to Add custom price on single product without affecting to related product?

I have code to changes the custom message on all product with 2 different messages. Please take a look.
add_filter('woocommerce_empty_price_html', 'show_alert_info_if_no_price');
function show_alert_info_if_no_price ($product){
if (is_product()) {
global $product;
$price = $product->get_price();
if ($price == '') {
ob_start();
// return for the product page
return '<div class="alert-info">Produk ini hanya dapat diproses melakukan pemesanan pembelian (PO). Segera hubungi tim kami. Kontak kami</div>';
} else {
// otherwise return short text as kontak kami
return 'Contact us';
}
}
}
Now I have a problem on related product. I need the related product price will appear like :
//short text as kontak kami
Now I am stuck how to add another code when using hooked.
$woocommerce_loop['name'] != 'related').
any help will appreciated!
There is an mistake in your code, because this hook is only executed for empty prices. So going to check in the hook again for an empty price and if it isn't, running an else condition is useless.
To display a different text on the single product page for related products you can use global $woocommerce_loop
So you get:
function filter_woocommerce_empty_price_html( $html, $product ) {
global $woocommerce_loop;
// True on a single product page
if ( is_product() ) {
$html = '<div class="alert-info">Produk ini hanya dapat diproses melakukan pemesanan pembelian (PO). Segera hubungi tim kami. Kontak kami</div>';
// Related
if ( $woocommerce_loop['name'] == 'related' ) {
$html = __( 'Some other text', 'woocommerce' );
}
}
return $html;
}
add_filter( 'woocommerce_empty_price_html', 'filter_woocommerce_empty_price_html', 10, 2 );

Breadcrumbs on product page don't reflect current url/slug when using multiple categories for product

My Wordpress site uses Woocommerce for products to sell. But when you assign multiple product categories to a product and visit the product page (with Product Permalinks set to Shop base with category), something unexpected is happening. The breadcrumb link to the (parent) category is always the same one and does not reflect the navigated url. (This is the same problem as described here.)
Problem in summary:
Navigate to
Breadcrumb link
Expected breadcrumb link
/shop/category-1/sample-product/
category-2
category-1
/shop/category-2/sample-product/
category-2
category-2
I found no real answer on any source that fixes this. (I think it should be fixed, but some might say it's for SEO reasons, preventing duplicate site contents.) So to help anyone searching for the answer, here it is:
What's going (wr)on(g)?
The breadcrumbs on the product page are produced by the woocommerce_breadcrumb() function. Which in turn gets it through WC_Breadcrumb->generate().
Traversing further in the code, you end up inside the add_crumbs_single() function where the woocommerce_breadcrumb_product_terms_args filter is applied to the ordering of the product terms fetch. (See wp_get_post_terms and WP_Term_Query::__construct() for more info on this.)
It's clear from the code, that they prioritize the term with the highest parent value, in other words, the term with the latest added parent in the database.
Solution 1
Since this will always be the same for this product, you might want to add a filter to your theme's function.php (or using a custom plugin) that will overwrite this behavior. I got mine working using this:
function prioritize_current_url_cat_in_breadcrumbs( $args ) {
// Only if we're on a product page..
if ( is_product() ) {
// ..and there's a product category slug in the navigated url..
$product_cat_slug = get_query_var( 'product_cat', '' );
if ( ! empty($product_cat_slug) ) {
// ..which we can find
$product_cat = get_term_by( 'slug', $product_cat_slug, 'product_cat', ARRAY_A );
if ( ! empty($product_cat) ) {
// Then only get that current product category to start the breadcrumb trail
$args['term_taxonomy_id'] = $product_cat['term_taxonomy_id'];
}
}
}
return $args;
}
add_filter( 'woocommerce_breadcrumb_product_terms_args', 'prioritize_current_url_cat_in_breadcrumbs' );
The rest of the add_crumbs_single() function will take care of traversing the category's parents etc up 'till Home.
Solution 2
Alternatively, you could use the woocommerce_breadcrumb_main_term filter to change the 'main term' used for the breadcrumb trail. The filter function receives 2 arguments: the main term (as WP_Term) and an array of WP_Terms with all product categories found. And it returns one term, so you can search through the array and pick the right one you want to start the breadcrumbs with.
function prioritize_current_url_cat( $first_term, $all_terms ) {
if ( is_product() ) {
$product_cat_slug = get_query_var( 'product_cat', '' );
if ( ! empty($product_cat_slug) ) {
// Get the WP_Term with the current slug
$filtered_terms = array_values( array_filter($all_terms, function($v) use ($product_cat_slug) {
return $v->slug === $product_cat_slug;
}) );
if ( ! empty($filtered_terms) ) {
return $filtered_terms[0];
}
}
}
// Fallback to default
return $first_term;
}
add_filter( 'woocommerce_breadcrumb_main_term', 'prioritize_current_url_cat', 10, 2 );
Hope this helps anyone searching for hours and working through lines of source code..!
Extra: Fix permalinks on archive pages as well
For tackling the same problem on the product archive pages, you can hook into the post_type_link filter and pre-emptively replace the %product_cat% part of the permalink with the correct slug like so:
/**
* Set product link slugs to match the page context,
* for products with multiple associated categories.
*
* For example:
* when viewing Category A, use '/category-a/this-product/'
* when viewing Category B, use '/category-b/this-product/'
*/
function my_plugin_product_url_use_current_cat( $post_link, $post, $leavename, $sample ) {
// Get current term slug (used in page url)
$current_product_term_slug = get_query_var( 'product_cat', '' );
if ( empty ( $current_product_term_slug ) ) {
return $post_link;
}
if ( is_product_category() ) {
// Get current term object
$current_product_term = get_term_by( 'slug', $current_product_term_slug, 'product_cat' );
if ( FALSE === $current_product_term ) {
return $post_link;
}
// Get all terms associated with product
$all_product_terms = get_the_terms( $post->ID, 'product_cat' );
// Filter terms, taking only relevant terms for current term
$matching_or_descendant_terms = array_filter( array_map( function( $term ) use ( $current_product_term ) {
// Return term if it is the current term
if ( $term->term_id === $current_product_term->term_id ) {
return [ TRUE, $term ];
}
// Return term if one of its ancestors is the current term (highest hierarchy first)
$parent_terms = array_reverse( get_ancestors( $term->term_id, 'product_cat' ) );
foreach ( $parent_terms as $parent_term_id ) {
if ( $parent_term_id === $current_product_term->term_id ) {
return [ FALSE, $term ];
}
}
// Leave out all others
return NULL;
}, $all_product_terms ) );
if ( count( $matching_or_descendant_terms ) > 0 ) {
// Sort terms (directly associated first, descendants next)
usort( $matching_or_descendant_terms, function( $a, $b ) {
if ( $a[0] === $b[0] ) {
return 0;
} else if ( TRUE === $a[0] ) {
return -1;
} else {
return 1;
}
} );
// Get entire slug (including ancestors)
$slug = get_term_parents_list( $matching_or_descendant_terms[0][1]->term_id, 'product_cat', [
'format' => 'slug',
'separator' => '/',
'link' => false,
'inclusive' => true,
] );
// Set url slug to closest child term of current term
// or current term (if directly associated)
$post_link = str_replace('%product_cat%/', $slug, $post_link);
}
} else if ( is_product() ) {
$post_link = str_replace('%product_cat%', $current_product_term_slug, $post_link);
}
return $post_link;
}
add_filter( 'post_type_link', 'my_plugin_product_url_use_current_cat', 10, 4 );
Explanation
As #Markos mentioned, we tried to figure out the most logical and intuitive way to do this.
Basically, it checks if the current product category (slug from the url) is directly associated with the displayed product. If so, use that slug for the link to the product page. (Using one of the solutions above, the breadcrumbs reflect the url path for intuitive navigation.)
If the current viewed product category is not directly associated with the product, it looks for the first matching descendant (sub) category and uses that slug for the link to the product page.
This way, the user always sees how they came on the current product page and can easily navigate back to the category.
Note: If this snippet doesn't change anything, try an earlier hook priority and/or check whether the $post_link variable contains the %product_cat% template part.
After a lengthy chat with Philip and troubleshooting scenarios when a shop has multiple layers of categories (thank you Philip) I found the following approach for the permalinks working for me.
function my_plugin_product_url_use_current_cat( $post_link, $post, $leavename, $sample ) {
if ( is_product_category() || is_product() ) {
$product_cat_slug = get_query_var( 'product_cat', '' );
// get all the category of products
$terms = get_the_terms ( $post->ID, 'product_cat' );
foreach($terms as $term){
$term_slug = $term->slug;
// check if category page belongs to the category of the product
if($term_slug == $product_cat_slug){
$post_link = str_replace('%product_cat%', $term_slug, $post_link);
return $post_link;
}
// or category page is a parent category of the product
else{
$all_parents = get_ancestors($term->term_id, 'product_cat');
foreach($all_parents as $cat_id){
$cat_term = get_term( $cat_id, 'product_cat' );
if($cat_term->slug == $product_cat_slug){
$post_link = str_replace('%product_cat%', $term_slug, $post_link);
return $post_link;
}
}
}
}
}
return $post_link;
}
add_filter( 'post_type_link', 'my_plugin_product_url_use_current_cat', 9, 4 );

Add featured image to wp_nav_menu items

This is a self Q&A.
How do you modify the text/html that appears in the output of a wp_nav_menu? For example, I wanted to add the featured image for pages and categories.
You see examples of doing this with a custom walker, but the code is very complex to do for small changes. Surely there is a way to do it with a filter?
This is the code I came up with thanks to some help from a Wordpress StackOverflow answer that I can't find anymore (please comment with a link if you find it).
First you need to add the filter to the specific menu (you could add it to all menus if you want - just use the add_filter line by itself).
// Add filter to specific menus
add_filter('wp_nav_menu_args', 'add_filter_to_menus');
function add_filter_to_menus($args) {
// You can test agasint things like $args['menu'], $args['menu_id'] or $args['theme_location']
if( $args['theme_location'] == 'header_menu') {
add_filter( 'wp_setup_nav_menu_item', 'filter_menu_items' );
}
return $args;
}
Then you need to build out the code to get the post or category ID from the $item object passed to the filter. It's not as easy as you'd expect, as $item doesn't contain the underlying post/category ID, just the menu item ID. So I use the URL's to do a reverse lookup of the IDs.
This won't work for tags used in a menu, or custom taxonomys. I only needed it for categories, so this is all I built.
// Filter menu
function filter_menu_items($item) {
if( $item->type == 'taxonomy') {
// For category menu items
$cat_base = get_option('category_base');
if( empty($cat_base) ) {
$cat_base = 'category';
}
// Get the path to the category (excluding the home and category base parts of the URL)
$cat_path = str_replace(home_url().'/'.$cat_base, '', $item->url);
// Get category and image ID
$cat = get_category_by_path($cat_path, true);
$thumb_id = get_term_meta($cat->term_id, '_term_image_id', true); // I'm using the 'Simple Term Meta' plugin to store an attachment ID as the featured image
} else {
// Get post and image ID
$post_id = url_to_postid( $item->url );
$thumb_id = get_post_thumbnail_id( $post_id );
}
if( !empty($thumb_id) ) {
// Make the title just be the featured image.
$item->title = wp_get_attachment_image( $thumb_id, 'poster');
}
return $item;
}
And then you want to remove the filter that you applied at the beginning, so that the next menu processed doesn't use the same HTML as defined above in filter_menu_items().
// Remove filters
add_filter('wp_nav_menu_items','remove_filter_from_menus', 10, 2);
function remove_filter_from_menus( $nav, $args ) {
remove_filter( 'wp_setup_nav_menu_item', 'filter_menu_items' );
return $nav;
}
Modified Drew Baker answer. It works without plugins, also if there is no category with current slug it checks for woocommerce product category ('product_cat').
functions.php
// Add filter to specific menus
add_filter('wp_nav_menu_args', 'add_filter_to_menus');
function add_filter_to_menus($args) {
// You can test agasint things like $args['menu'], $args['menu_id'] or $args['theme_location']
if( $args['theme_location'] == 'menu-header') {
add_filter( 'wp_setup_nav_menu_item', 'filter_menu_items' );
}
return $args;
}
// Filter menu
function filter_menu_items($item) {
if( $item->type == 'taxonomy') {
// Get category and image ID
$slug = pathinfo( $item->url, PATHINFO_BASENAME );
$cat = get_term_by( 'slug', $slug, 'category' );
// If there is no standard category try getting product category
if( !$cat ) {
$cat = get_term_by( 'slug', $slug, 'product_cat' );
}
$thumb_id = get_term_meta($cat->term_id, 'thumbnail_id', true);
} else {
// Get post and image ID
$post_id = url_to_postid( $item->url );
$thumb_id = get_post_thumbnail_id( $post_id );
}
if( !empty($thumb_id) ) {
// Make the title just be the featured image.
$item->title = wp_get_attachment_image( $thumb_id, 'poster');
// Display image + title example
// $item->title = wp_get_attachment_image( $thumb_id, 'poster').$item->title;
}
return $item;
}
// Remove filters
add_filter('wp_nav_menu_items','remove_filter_from_menus', 10, 2);
function remove_filter_from_menus( $nav, $args ) {
remove_filter( 'wp_setup_nav_menu_item', 'filter_menu_items' );
return $nav;
}

Replacing the title of a post with a custom taxonomy on wordpress

Using filters/hooks, how would I replace the title of a wordpress post with whatever term has been selected from a custom taxonomy.
Hopefully the attached image will explain what I'm trying to do.
Let's say I selected 'Powerchrono' - I would like the title of the post to be replaced with the selected term, and it's parent.
Any help would be much appreciated.
I'd obviously like the url of the post to also be updated too.
I can't guarantee this will work straight out of the gate since it's untested. But this should get you started:
functions.php
<?php
add_action('save_post', 'update_term_title');
function update_term_title($post_id)
{
if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
return;
if(!current_user_can('edit_post', $post_id))
return;
//Replace 'manufacturer' with whatever your custom taxonomy slug is
$terms = wp_get_post_terms($post_id, 'manufacturer');
if(empty($terms))
return;
$title = false;
foreach($terms as $term)
{
if($term->parent)
{
$parent = get_term($term->parent, 'manufacturer');
$title = $term->name.' '.$parent->name;
break;
}
}
/*Default to first selected term name if no children were found*/
$title = $title ? $title : $terms[0]->name;
/*We must disable this hook and reenable from within
if we don't want to get caught in a loop*/
remove_action('save_post', 'update_term_title');
$update = array(
'ID'=>$post_id,
'post_name'=>sanitize_title_with_dashes($title),
'post_title'=>$title
);
wp_update_post($update);
add_action('save_post', 'update_term_title');
}
?>

FeedWordPress - Save string as Custom Field

I am using the FeedWordPress plugin http://wordpress.org/extend/plugins/feedwordpress/ to pull posts from one site to another.
I have written a filter that after some help from Stack users successfully scans the $content and extracts the image URL into $new_content
define('FWPASTOPC_AUTHOR_NAME', 'radgeek');
add_filter(
/*hook=*/ 'syndicated_item_content',
/*function=*/ 'fwp_add_source_to_content',
/*order=*/ 10,
/*arguments=*/ 2
);
function fwp_add_source_to_content ($content, $post) {
// Use SyndicatedPost::author() to get author
// data in a convenient array
$content = $post->content();
// Authored by someone else
if( preg_match( '/<img[^>]+src\s*=\s*["\']?([^"\' ]+)[^>]*>/', $content, $matches ) ) {
$new_content .= 'URL IS '.$matches[0].'';
return $new_content;
}
else
{
}
}
What I wanted to do now was save this URL into a custom field instead of just returning it. Has anyone achieved anything similar?
So as I understand it, the plugin grabs content from external RSS feeds and creates them as posts in your website.
If this is the case, using your filter you should be able to grab the post ID within the $post variable.
So all you need is the add_post_meta() function to add a custom field to the specific post.
So including your code above it should look something like:
define('FWPASTOPC_AUTHOR_NAME', 'radgeek');
add_filter(
/*hook=*/ 'syndicated_item_content',
/*function=*/ 'fwp_add_source_to_content',
/*order=*/ 10,
/*arguments=*/ 2
);
function fwp_add_source_to_content ($content, $post) {
// Use SyndicatedPost::author() to get author
// data in a convenient array
$content = $post->content();
// Authored by someone else
if( preg_match( '/<img[^>]+src\s*=\s*["\']?([^"\' ]+)[^>]*>/', $content, $matches ) ) {
$new_content .= 'URL IS '.$matches[0].'';
//Add custom field with author info to post
add_post_meta($post->ID, 'post_author', $new_content);
return $new_content;
}
}

Resources