In a multisite, how can I get posts from one site and display their permalinks in another site?

Let’s take a look at get_blog_permalink:

get_blog_permalink( $blog_id, $post_id ) {
    switch_to_blog( $blog_id );
    $link = get_permalink( $post_id );
    restore_current_blog();

    return $link;
}

As you can see, there’s no magic… But there are some problems, since switch_to_blog isn’t very efficient…

It means that it would be way better if you’d done it this way:

$tp_blog_id = 4;
switch_to_blog( $tp_blog_id );
$posts = get_posts(
            array(
                'post_type' => 'property',
                'posts_per_page' => 100,
                'numberposts' => -1
            )
);

error_log( print_r($posts,true) );
foreach ( $posts as $post ) {
    echo "The URL is: <br>";    
    echo get_permalink( $post->ID ) . "<br>";
}
restore_current_blog();

And if the problem with permalinks still occur, then you’ll have to check the way you register this CPT (especially the with_front part of rewrite param in register_post_type).

And as I expected, the problem lies exactly in that spot. When you register your CPT you use:

'rewrite' => array( 'slug' => $slug ),

If you’ll take a look at register_post_type docs, then you’ll see that this param has multiple fields:

  • slug => string Customize the permalink structure slug. Defaults to
    the $post_type value. Should be translatable.
  • with_front => bool
    Should the permalink structure be prepended with the front base.
    (example: if your permalink structure is /blog/, then your links will
    be: false->/news/, true->/blog/news/). Defaults to true
  • feeds => bool Should a feed permalink structure be built for this post type. Defaults to has_archive value.
  • pages => bool Should the permalink
    structure provide for pagination. Defaults to true
  • ep_mask => const
    As of 3.4 Assign an endpoint mask for this post type. For more info
    see Rewrite API/add_rewrite_endpoint, and Make WordPress Plugins
    summary of endpoints. If not specified, then it inherits from
    permalink_epmask(if permalink_epmask is set), otherwise defaults to
    EP_PERMALINK.

As you can see one of them is with_front and its default value is true. That means that your post type is told to prepend front base to its URLs.

So if the front base is set to blog, then the URL for that post type shuld contain it – so it’s generated correctly…

PS. It’s hard to say why it causes 404 errors and how exactly are these links processed, so it works without front base. But your problem is clear – there is something wrong with registering and processing that CPT.