How to hide protected posts from archive?

There are 2 answers depending on where you want to do this. One has good news, the other has bad news

Anywhere That Uses Post Queries

Modifying the SQL is rarely necessary here and usually a sign that something was overlooked. In this case, WP_Query already provides a simple parameter to do this named has_password.

With this in mind, we can modify the main loop to hide passworded/protected posts easily via pre_get_posts:

add_action( 'pre_get_posts', function ( \WP_Query $q ) {
    if ( is_admin() || $q->is_singular() ) {
        return;
    }
    $q->set( 'has_password', false );
} );

Anywhere That uses the get_calendar function

This includes the calendar widget and block, I’m afraid the news is not great.

get_calendar doesn’t use WP_Query, but instead uses raw SQL queries to count posts ( it doesn’t retrieve the posts ). It doesn’t provide filters for the SQL either.

It does provide 2 opportunities however:

  • The get_calendar filter gives you the final HTML, allowing you to make some modifications to the final string. With this you could completely replace the HTML by re-implementing get_calendar and returning new HTML that excludes private posts in the SQL
  • The query filter. Because get_calendar calls WPDB query directly, you could filter on this, however, this filter runs on all queries WP makes, not just those for the calendar. You will need to figure out if a query is coming from get_calendar or not, which may be difficult or impossible. If you modify a query that isn’t from get_calendar you could cause problems that lead to instability. There is also a potential bug, get_calendar does a check if there are any posts and returns an empty string if none are found, if that post is a passworded post then this could lead to a calendar that has no posts. Be careful to make any filter on this run as fast as possible or you’ll slow down WP a lot, this means no new DB queries in the filter, no fetching data, no making http requests or loading files. There’s also the danger of recursive loops.

Note that get_calendar caches by both month and year, so you need to flush caches to see updated output

Why Didn’t posts_where work?

It didn’t work because that filter is a part of WP_Query, which isn’t used by get_calendar. I would advise against using this though as it breaks the post_password parameter of WP_Query and the functions that depend on it ( e.g get_posts or areas of the admin )