How to prepend to the_title for admin-side plugin's use

When I do this:

function append_album_review_to_title( $title ) {
    global $post;
    if ( get_post_type( $post->ID ) == 'album_review' && in_the_loop() ){
        return 'Album Review: ' . $title;
    } else {
        return $title;
    }
}
add_filter('the_title', 'append_album_review_to_title');

The title is properly prepended-to, but only when viewing the actual page.

When I don’t test for the loop by removing && in_the_loop(), it properly prepends to the titles which are output by the plugin I use to auto-share my Published posts to Twitter. However, the problem is, if I don’t test for the loop, the string is also prepended to every single nav menu item name.

What conditional statements can I use to test for nav menu items and skip them in the filter?

UPDATE 1

Thank you Ittikorn. I tried it but it doesn’t work. I can see it’s altering the <title> but unfortunately the output with the sharing plugin is still the original title.

UPDATE 2

Much appreciation, and though both would get genuine upvotes from me for the detailed and insightful responses if I could upvote, unfortunately, none of the solutions provided by Sumit or Peter worked.

Sumit’s solution worked in that it didn’t needlessly break nav menu items by applying the filter to them, but it also did not apply the filter to the post-screen auto-social-sharing plugin, which is what I needed.

Peter’s solution also didn’t work and did the exact same thing is Sumit’s. I tried playing around with it, but couldn’t get it to target the post titles for in_the_loop AND for the social sharing plugin’s use but not for nav menu items.

SOLUTION

Peter’s and Sumit’s answers did truly help me tremendously though and lead to me eventually finding the oslution.

Trying both answers I noticed that on the backend, the plugin would return warnings like “missing parameter 2” and “Notice: Trying to get property of non-object…’, and so it occurred to me that unlike the front end (nav menu and the loop), in the backend, apparently an $id is not being passed to the_title and so I can use this as a conditional.

This code accomplishes what I need (ie, modify post title in the loop, do NOT modify nav menu items, and modify the_title for use by a backend social sharing plugin):

function append_album_review_to_title( $title, $id = NULL ) {
    if ($id) {
        if ( get_post_type( $id ) == 'album_review' ){
            return 'Album Review: ' . $title;
        } else {
            return $title;
        }
    } else {
        return 'Album Review: ' . $title;
    };
}
add_filter('the_title', 'append_album_review_to_title', 10, 2);

Let me know if you think I can improve this somehow or if this could potentially cause future problems if unattended. And thank you for all your help.

Solutions Collecting From Web of "How to prepend to the_title for admin-side plugin's use"

The problem is global $post.
As @Pieter suggested global $post object can be alter anytime with some other plugin or code. Menu items are effected because global $post does not contain the correct object.

Instead of $post, the_title filter also provide you ID of current post in action, so use it in this way

function append_album_review_to_title( $title, $id ) {
    if ( get_post_type( $id ) == 'album_review' ){
        return 'Album Review: ' . $title;
    } else {
        return $title;
    }
}
add_filter('the_title', 'append_album_review_to_title', 10, 2);

RE-ARRANGED ANSWER TO AVOID CONFUSION

Although the following solution might work, it is not ideal

add_filter( 'the_title', function ( $title, $post_id )
{
    if ( 'album_review' === get_post_type( $post_id ) )
        $title = 'Album Review: ' . $title;

    return $title;
}, 10, 2 );

The issue is, any post that uses the_title() or get_the_title() will be affected by this filter, and that include all queries and navigation menus. If you have posts from the album_review post type in your navigation menus, this filter will act on those titles as well.

As you correctly pointed out, the in_the_loop() condition only target s the main query, and there is nothing similar to target custom queries and the nav menu items.

The big issue with the template tags like the_title() and the_content() is that they do not know the context in which they are used, this makes them hard to target.

To remove the title filter from only the navigation menus can be quite an issue, because, as I said, the_title() does not know the context in which it is used. The only probable way I can think of is to momentarily remove the title filter within the wp_nav_menu_args filter which runs before the nav menu items’ titles, and then adding it back after the titles is added through either the wp_page_menu or the wp_nav_menu filter, depending on whether a fallback is set or not

You can try the following:

add_filter( 'wp_nav_menu_args', function ( $args )
{
    // Remove the title filter for a while so we do not alter nav menu titles
    remove_filter( 'the_title', 'wpse_219222_customize_title', 10, 2 );

    // Re-apply the title filter after we add titles
    $filter = ( 'wp_page_menu' === $args['fallback_cb'] ) ? 'wp_page_menu' : 'wp_nav_menu';
    add_filter( $filter, 'wpse_219222_add_title_filter_back' );

    return $args;
});

function wpse_219222_add_title_filter_back( $nav_menu )
{
    add_filter( 'the_title', 'wpse_219222_customize_title', 10, 2 );

    return $nav_menu;
}

/**
 * Our custom call back function which will be hooked to the_title filter
 */
function wpse_219222_customize_title( $title, $post_id )
{
    if ( 'album_review' === get_post_type( $post_id ) )
        $title = 'Album Review: ' . $title;

    return $title;
}

ADDITIONAL USEFUL INFO – to remove filter from other queries if needed

If I need to apply a filter to a specific query, I like to pass a custom query var to the query which I then check for in pre_get_posts and then use that to decide if my filter must be applied or not. Something like the following will work in custom query

add_action( 'pre_get_posts', function ( $q )
{
    if ( 1 === $q->get( 'custom_title' ) )
    {
        add_filter( 'the_title', function ( $title, $post_id )
        {
            if ( 'album_review' === get_post_type( $post_id ) )
                $title = 'Album Review: ' . $title;

            return $title;
        }, 10, 2 );
    }
});

You can then simply pass 'custom_title' => 1 to your query args to apply the filter to that specific query only

Just for interest sake, and just to play around, you can also do something like this to target the main query on the home page and any custom query where 'custom_title' => 1 is passed

add_action( 'pre_get_posts', function ( $q )
{
    if (    $q->is_home()
         && $q->is_main_query() 
    ) {
        $q->set( 'custom_title', 1 );
    }

    if ( 1 === $q->get( 'custom_title' ) )
    {
        add_filter( 'the_title', function ( $title, $post_id )
        {
            if ( 'album_review' === get_post_type( $post_id ) )
                $title = 'Album Review: ' . $title;

            return $title;
        }, 10, 2 );
    }
});

Here is another great filter idea by @birgire which you can adapt and modify if you need to target only post titles in a widget

You can use this in conjunction with all the other methods as well as you see fit

Solution

function append_album_review_to_title( $title, $id = NULL ) {
    if ($id) {
        if ( get_post_type( $id ) == 'album_review' ){
            return 'Album Review: ' . $title;
        } else {
            return $title;
        }
    } else {
        return 'Album Review: ' . $title;
    };
}
add_filter('the_title', 'append_album_review_to_title', 10, 2);

Explanation

Though neither solution worked completely, Peter’s and Sumit’s answers did help me tremendously and lead to me eventually finding the oslution.

Trying both Sumit’s and Peter’s answers I noticed that on the backend, the plugin would return warnings like missing parameter 2 and Notice: Trying to get property of non-object..., and so it occurred to me that unlike the front end (nav menu and the loop), in the backend, apparently an $id is not being passed to the_title and so I can use this as a conditional.

This code accomplishes what I need (ie, modify post title in the loop, do NOT modify nav menu items, and modify the_title for use by a backend social sharing plugin):

Try to use wp_title filter instead of the_title to rewrite on title meta tag

add_filter('wp_title', 'append_album_review_to_title');