Wordpress - Apply the_title() filter in post & page title, but not in menu title

Problem Description:

Let me rephrase the question first. You want to:

  1. Set new title to all post and page type from a meta field.

  2. You want this to happen everywhere (home page, single page, widgets etc.)

  3. However, you don't want this title change to happen if the title is on the Navigation Menu.

Solution:

Before I give you the CODE, let me explain a few points first (based on your CODE):

How to change titles of all posts and pages:

You already know the use of the_title filter. However, if you want to target all post and page type titles (but not custom post types), then your condition:

is_singular(array('post','page')) || is_archive() || is_home()

will not work. For example, it'll change custom post type on an archive page or the home page as well. This condition doesn't check if the title we are filtering is a page or post type. Instead, it checks if the page itself is either singular (post or page) or it's an archive (category, tag etc.) page or the home page. So custom post types in these pages also gets affected. Additionally, if there is a widget in a custom post type (singular) page, then by this logic, page or post titles in that widget will not be affected there.

To fix that, we need a different check, like:

$post = get_post( $id );
if ( $post instanceof WP_Post && ( $post->post_type == 'post' || $post->post_type == 'page' ) )

Why Navigation Menu title is also changed & how to stop it:

WordPress applies the_title filter twice on the navigation menu items' title (if the menu items correspond to existing posts or pages).

  1. First as the corresponding post or page title. This happens in the wp_setup_nav_menu_item() function of wp-includes/nav-menu.php file.

  2. Then as the Menu item title itself. This happens in the Walker_Nav_Menu class.

For your requirement, we need to stop the the_title filter both the times.

Fortunately, WordPress has two filters: pre_wp_nav_menu fires before filtering menu titles and wp_nav_menu_items fires after filtering menu titles. So we can use these two filters to first remove the the_title filter for nav menu item titles and then add the the_title filter back again for other titles.

CODE

You may use the following CODE in the theme's functions.php file or as a separate plugin:

function wpse309151_title_update( $title, $id = null ) {
    if ( ! is_admin() && ! is_null( $id ) ) {
        $post = get_post( $id );
        if ( $post instanceof WP_Post && ( $post->post_type == 'post' || $post->post_type == 'page' ) ) {
            $new_titile = get_post_meta( $id, 'pp_new_title', true );
            if( ! empty( $new_titile ) ) {
                return $new_titile;
            }
        }
    }
    return $title;
}
add_filter( 'the_title', 'wpse309151_title_update', 10, 2 );

function wpse309151_remove_title_filter_nav_menu( $nav_menu, $args ) {
    // we are working with menu, so remove the title filter
    remove_filter( 'the_title', 'wpse309151_title_update', 10, 2 );
    return $nav_menu;
}
// this filter fires just before the nav menu item creation process
add_filter( 'pre_wp_nav_menu', 'wpse309151_remove_title_filter_nav_menu', 10, 2 );

function wpse309151_add_title_filter_non_menu( $items, $args ) {
    // we are done working with menu, so add the title filter back
    add_filter( 'the_title', 'wpse309151_title_update', 10, 2 );
    return $items;
}
// this filter fires after nav menu item creation is done
add_filter( 'wp_nav_menu_items', 'wpse309151_add_title_filter_non_menu', 10, 2 );

WordPress navigation editor has ability to change menu title regardless of the title of the post/page.

If you need more automated solution code below will replace title of the post/page everywhere using the_title filter but restore default title of the menu item using nav_menu_item_title filter.

/**
 * Replace post/page title on home, single and archive pages.
 *
 * @param string $title   Post title
 * @param int    $post_id Post ID
 *
 * @return string New post tilte
 */
function wpse_309151_get_replace_default_title_from_meta( $title, $post_id ) {

    $post_type = get_post_type( $post_id );

    if( !is_admin() && ( $post_type === 'post' || $post_type === 'page' ) ) {

        $new_title = get_post_meta( $post_id, 'wpse_309151_post_title', true);

        if( $new_title && !empty( $new_title ) ) {
            return $new_title;
        }
    }

    return $title;
}

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

/**
 * Restore default post/page title in navigation
 *
 * @param string   $title The menu item's title.
 * @param WP_Post  $item  The current menu item.
 * @param stdClass $args  An object of wp_nav_menu() arguments.
 * @param int      $depth Depth of menu item. Used for padding.
 *
 * @return string Restored post title
 */
function wpse_309151_get_restore_default_title_for_navigation( $title, $item, $args, $depth ) {

    // Remove filter to not affect title
    remove_filter( 'the_title', 'wpse_309151_get_replace_default_title_from_meta', 10, 2 );

    $post_id = $item->object_id;
    $title   = get_the_title( $post_id );

    // Add the title filter back
    add_filter( 'the_title', 'wpse_309151_get_replace_default_title_from_meta', 10, 2 );

    return $title;
}

add_filter( 'nav_menu_item_title', 'wpse_309151_get_restore_default_title_for_navigation', 10, 4 );