1

I am using the the_title filter to attempt to prepend some text to a custom post type's title. Our website has several custom post types.

  1. Product - slug is eproduct
  2. Brand - slug is brand-listing
  3. Retail - slug is retail-listing

I am trying to prepend a Product's associated Brand title to the beginning of the Product's title.

The problem I am having is that, the initial arguments passed to the the_title callback function do not belong to the particular Product that is being displayed on the page. I check for this at the very beginning of my callback function, using this debugging statement:

echo "Initial title is {$title} Initial id is {$id}";

This statement returns the title and id of a particular Retail custom post type. This happens no matter what particular Product page I am looking at. The echo statement always echoes the title and id of the exact same Retail custom post type. I expected the echo statement to echo the title and id of the Product being displayed on the page.

I have tried to dig into the actual SQL query to see if I could find something wrong, but I'm a novice at SQL. Below I have cut and pasted the verbatim SQL query. This SQL query seems suspicious to me, because it is querying for multiple custom post types, when I expected it would only be querying for the eproduct custom post type, on a page that is displaying a single Product. To me, it seems like, for some reason, the part of the SQL query that queries for wp_posts.post_type = retail-listing is somehow "winning", because it is a Retail custom post type's Title and ID that are being passed to my callback function.

SELECT   wp_posts.* FROM wp_posts  WHERE 1=1  AND wp_posts.post_type = 'um_role' AND ((wp_posts.post_status = 'publish'))  ORDER BY wp_posts.post_date DESC 

SELECT   wp_posts.* FROM wp_posts  WHERE 1=1  AND wp_posts.post_type = 'um_role' AND ((wp_posts.post_status = 'publish'))  ORDER BY wp_posts.post_date DESC 

SELECT   wp_posts.* FROM wp_posts  WHERE 1=1  AND wp_posts.post_name = 'testing-product-description' AND wp_posts.post_type = 'eproduct'  ORDER BY wp_posts.post_date DESC 

SELECT   wp_posts.* FROM wp_posts  WHERE 1=1  AND wp_posts.post_type = 'retail-listing' AND ((wp_posts.post_status = 'publish'))  ORDER BY wp_posts.post_date DESC 

SELECT   wp_posts.* FROM wp_posts  WHERE 1=1  AND wp_posts.post_type = 'brand-listing' AND ((wp_posts.post_status = 'publish'))  ORDER BY wp_posts.post_date DESC 

SELECT   wp_posts.ID FROM wp_posts  WHERE 1=1  AND wp_posts.post_type = 'um_notice' AND ((wp_posts.post_status = 'publish'))  ORDER BY wp_posts.post_date DESC 

the_title callback function code is below:

/**
* Prepend Brand name to Product post title.
* Use 'the_title" filter.
* Arguments: (string) the post title, (int) the post id
*/

function vendia_prepend_brand_to_product_title( $title, $id ) {
    remove_filter( current_filter(), __FUNCTION__ );

    echo "Initial title is {$title} Initial id is {$id}";

    //Should work on single product pages, product archive pages, and search pages.
    if ( 
        is_post_type_archive( 'eproduct' ) ||
        is_singular ( 'eproduct' ) ||
        is_search()
    )
    {
        $brand_id = get_post_meta( $id, '_wpcf_belongs_brand-listing_id', $single = true);
        echo "Brand ID is {$brand_id}";
        $brand_title = get_the_title( $brand_id );
        $new_title = $brand_title . ' ' . $title;
        return $new_title;
    }

}

add_filter( 'the_title', 'vendia_prepend_brand_to_product_title', 10, 2 );

So essentially my question is : Why is my the_title callback function getting parameters (Title and ID) associated with a specific Retail custom post type (the exact same Retail post every time), instead of getting the Title and ID of the particular Product that is being displayed on the page?

Thank you so much for any help.

  • Why are you using remove_filter? also - All filters need to return something, even if conditions are not met. – shanebp Oct 13 '15 at 18:00
  • I am using remove_filter because I make a call to get_the_title() inside the callback function hooked to the_title filter and it causes an infinite loop if I don't remove the filter. You make a good point though, that the filter should return something, even if the if conditional is not met, which currently my callback is not doing. However, at least for now, the if conditional is being met, because I always see the echo statement. – LeonardShelby Oct 13 '15 at 18:04
  • Sorry, didn't notice the get_the_title call. Try using $wpdb->get_var to retrieve the Brand title. – shanebp Oct 13 '15 at 18:27
  • That's not my issue. My issue is that the filter is passing the wrong arguments to the callback. – LeonardShelby Oct 13 '15 at 18:32
2

You have a few issues here

  • remove_filter( current_filter(), __FUNCTION__ ) is used in wrong context here for what you need to do. remove_filter() unsets the filter from the GLOBALS array once the filter has run once, so your filter is removed after the first run. This happens with spaghetti functions.

  • You should always return the default filter (or some custom default) value the make sure that something is returned as is should your conditions fail

  • get_the_title() simply uses get_post to get the post object to return the $post_title property of the object if no post object is passed to get_the_title(). So, instead of using get_the_title() which is affected by the filter, simply use get_post and from there get the post title

I would simply rewrite your filter as follow:

add_filter( 'the_title', function ( $title, $id ) 
{
    // Make sure we are not in admin, if so, return $title
    if (    is_admin() // Check if we are in admin
    )
        return $title;

    //Should work on single product pages, product archive pages, and search pages.
    if ( 
        is_post_type_archive( 'eproduct' ) ||
        is_singular ( 'eproduct' ) ||
        is_search()
    ) {

        $brand_id    = get_post_meta( $id, '_wpcf_belongs_brand-listing_id', $single = true);
        // Make sure we have a $brand_id to avoid bugs and unexpected output
        if ( $brand_id ) {
            $brand_title = get_post( $brand_id )->post_title;
            $title       = $brand_title . ' ' . $title;
        }
        // Either return title as is or our new modified title
        return $title;
    }
    // Make sure we return $title if our conditions should fail
    return $title;
}, 10, 2 );

Just an extra note, you would probably want to wrap everything in a in_the_loop() condition to only target titles in the main query loop

EDIT

I forgot to say, the code above would require at least PHP 5.4 due to direct array de-referencing. The code is also untested

  • Thanks Pieter. That really solved it. Amazing. I had a question for you. I forgot to add a check in my conditional if statement to make sure that this code does not run on the back end admin screens. I've updated my if statement like so if ( ! is_admin() && ( is_post_type_archive( 'eproduct' ) || is_singular( 'eproduct' ) || is_search() ) ) but it's still running on the admin screen. Is there something wrong with this new if statement? – LeonardShelby Oct 13 '15 at 19:34
  • Check my updated code. The way I do it is just preference, and always works – Pieter Goosen Oct 13 '15 at 19:44
  • 1
    The !in_the_loop() conditional in the if statement makes this not work. if I take that out, it works ok. – LeonardShelby Oct 13 '15 at 19:47
  • Ok, then you have a custom query then, I did remove it from my code. Enjoy :-) – Pieter Goosen Oct 13 '15 at 20:02
  • One more thing. If I don't set $id = null as a default value for the second parameter, I get a PHP error telling me that I'm missing a second argument, and lots of nasty PHP warnings all over my screen. – LeonardShelby Oct 13 '15 at 20:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.