[Paid Memberships Pro] Give users 7 day early access to unpublished posts

Hi,

for gooloo.de +PLUS, our new ad-free solution, we wanted to enable that users of specific levels [in this code snippet levels 2 and 3, please change accordingly!] the ability to access all future, unpublished posts, that would be published in 7 days or max. That way, users of specific levels, can access posts 7 days earlier. For this, add the following code snippet to your functions.php, or via a Code Snippets Plugin.

What you have to do:

  • Change the Level IDs accordingly to the levels you want to give access to
  • Change the amount of days to your needs [this snippet uses 7]

Newer Version 1.1

This Version fixes some common issues and is more approached to Page Builders, like Divi. This code is more extensive, yet more reliable. It also fixes the issue where posts couldn’t be previewed anymore by authors and admins.

It seems the key was likely removing the !is_main_query() check in the pre_get_posts function. In some theme or plugin configurations (especially complex ones like Divi potentially), the primary loop displaying posts might not always register as the “main query” in the way WordPress typically defines it. By removing that check, the function could modify the correct query on your homepage/archives, while the is_preview() check still successfully prevented it from interfering with the preview functionality.

function pmpro_show_future_posts_for_members_final_attempt( $query ) {
    // --- Exit Conditions ---
    // 1. Don't run in the admin area.
    if ( is_admin() ) {
        return;
    }
    // 2. IMPORTANT: Don't run if this is a preview query.
    if ( $query->is_preview() ) {
        return;
    }

    // --- Target Identification ---
    // Check if we are on a relevant public-facing view (home, archive, search)
    // Temporarily removed !is_main_query() check for broader compatibility testing with Divi.
    $is_relevant_view = $query->is_home() || $query->is_archive() || $query->is_search();

    // Check if the query is for the 'post' post type (or default).
    $post_type = $query->get('post_type');
    $is_post_query = empty($post_type) || $post_type === 'post' || (is_array($post_type) && in_array('post', $post_type));

    // Only proceed if it's a relevant view AND querying for posts.
    if ( ! ($is_relevant_view && $is_post_query) ) {
        return;
    }

    // --- Membership Check ---
    $has_access = false;
    if ( function_exists('pmpro_hasMembershipLevel') && is_user_logged_in() ) {
        $user_id = get_current_user_id();
        // Check for Level 2 or 3 (as per your original code)
        $has_access = pmpro_hasMembershipLevel( array( 2, 3 ), $user_id );
    }

    // --- Query Modification ---
    if ( $has_access ) {
        // Member has access: Modify query to include future posts within 7 days

        // Calculate 7 days from now (use GMT for consistency)
        $current_time_gmt     = current_time( 'timestamp', true ); // true = GMT
        $seven_days_later_gmt = gmdate( 'Y-m-d H:i:s', $current_time_gmt + ( 7 * DAY_IN_SECONDS ) );

        // Set post status to include 'publish' and 'future'
        // Make sure not to remove other existing statuses if they exist (though unlikely here)
        $statuses = $query->get('post_status');
        if(empty($statuses)){
             $statuses = array('publish', 'future');
        } elseif (is_string($statuses)) {
             $statuses = array($statuses, 'future');
        } elseif (is_array($statuses)) {
             $statuses[] = 'future';
        }
        $query->set( 'post_status', array_unique($statuses) );


        // Add date query: Only posts with date <= 7 days from now
        // Append to existing date query array if one exists
        $date_query = $query->get('date_query');
        if ( ! is_array($date_query) ) {
            $date_query = array(); // Initialize if not an array
        }
        // Add our 'before' clause, ensuring not to duplicate logic if somehow run twice
        $clause_exists = false;
        foreach ($date_query as $clause) {
            if (isset($clause['column']) && $clause['column'] === 'post_date_gmt' && isset($clause['before'])) {
                 $clause_exists = true;
                 break;
            }
        }
        if (!$clause_exists) {
            $date_query[] = array(
                'column'    => 'post_date_gmt',
                'before'    => $seven_days_later_gmt,
                'inclusive' => true,
            );
            // Set relation if there's more than one clause now
            if (count($date_query) > 1 && !isset($date_query['relation'])) {
                 $date_query['relation'] = 'AND';
            }
            $query->set( 'date_query', $date_query );
        }

        // Ensure posts are ordered correctly, defaulting to date descending
        if ( empty( $query->get('orderby') ) ) {
            $query->set('orderby', 'date');
        }
        if ( empty( $query->get('order') ) ) {
            $query->set('order', 'DESC');
        }

    } else {
        // Non-member or logged-out user: Ensure ONLY published posts are shown
        // More robustly remove 'future' if present, otherwise set to 'publish'
        $statuses = $query->get('post_status');
        if (is_string($statuses) && $statuses === 'future') {
            $query->set('post_status', 'publish');
        } elseif (is_array($statuses)) {
            $filtered_statuses = array_diff($statuses, ['future']);
            // If filtering left it empty, default to publish
            $query->set('post_status', !empty($filtered_statuses) ? $filtered_statuses : 'publish');
        } else {
             // If status is empty or already just 'publish', leave it or set explicitly
             $query->set('post_status', 'publish');
        }
        // It's generally safer NOT to clear date_query here for non-members,
        // as other plugins might legitimately use it (e.g., date archives).
        // Filtering by 'publish' status handles the date implicitly.
    }
}
// Use default priority 10 for the hook
add_action( 'pre_get_posts', 'pmpro_show_future_posts_for_members_final_attempt' );


/**
 * Handle direct access to single future posts.
 * Allows users with editing capabilities to bypass checks (for previews).
 * Checks membership and date window for regular members viewing future posts.
 * (This function seems correct and remains unchanged)
 *
 * @return void
 */
function pmpro_check_single_future_post_final_attempt() {
    // Only run this check on single 'post' pages
    if ( ! is_singular( 'post' ) ) {
        return;
    }

    global $post;

    // Basic check for post object and status
    if ( ! $post || ! isset( $post->post_status ) ) {
        return;
    }

    // Allow users who can edit to bypass checks (for previews etc.)
    if ( current_user_can( 'edit_post', $post->ID ) ) {
        return; // Grant access
    }

    // Only apply restrictions if the post is actually 'future'
    if ( $post->post_status === 'future' ) {

        // Verify membership level (Levels 2 & 3)
        $has_access = false;
        if ( function_exists('pmpro_hasMembershipLevel') && is_user_logged_in() ) {
            $has_access = pmpro_hasMembershipLevel( array( 2, 3 ) );
        }

        // Check if the post's date is within the allowed 7-day window (using GMT)
        $post_time_gmt   = strtotime( $post->post_date_gmt );
        $cutoff_time_gmt = current_time( 'timestamp', true ) + ( 7 * DAY_IN_SECONDS ); // true = GMT

        // If access denied (no membership OR post too far in future), trigger 404
        if ( ! $has_access || $post_time_gmt > $cutoff_time_gmt ) {
            global $wp_query;
            $wp_query->set_404();
            status_header( 404 );
            nocache_headers();
            // Optional: include 404 template and exit
            // include( get_query_template( '404' ) );
            // exit;
        }
    }
}
// Hook into 'wp'
add_action( 'wp', 'pmpro_check_single_future_post_final_attempt' );

Initial Version 1.0

This was the initial release version. Although it works still, you might be blocked from accessing planned posts that will be posted more than 7 days ahead.

/**
 * Show future posts up to 7 days ahead for PMPro Levels 2 & 3 members
 * Exclude pages and other post types
 */
function pmpro_show_future_posts_for_members($query) {
    if (is_admin()) return;

    // Get current post type being queried
    $post_type = $query->get('post_type');
    
    // Handle default post type (post)
    if (empty($post_type)) {
        if ($query->is_home() || $query->is_archive() || $query->is_search()) {
            $post_type = 'post';
        } else {
            return;
        }
    }

    // Only affect normal posts
    if ($post_type !== 'post') return;

    // Check membership access
    $has_access = false;
    if (is_user_logged_in()) {
        $has_access = pmpro_hasMembershipLevel(array(2, 3));
    }

    if ($has_access) {
        // Calculate 7 days from now
        $current_time = current_time('timestamp');
        $seven_days_later = date('Y-m-d H:i:s', $current_time + (7 * DAY_IN_SECONDS));
        
        // Modify query for members
        $query->set('post_status', array('publish', 'future'));
        $query->set('date_query', array(
            array(
                'before' => $seven_days_later,
                'inclusive' => true,
            )
        ));
    } else {
        // Non-members see only published posts
        $query->set('post_status', 'publish');
    }
}
add_action('pre_get_posts', 'pmpro_show_future_posts_for_members');

/**
 * Handle single post access for future posts
 */
function pmpro_check_single_future_post() {
    if (!is_singular('post')) return;

    global $post;
    
    // Only check future posts
    if ($post->post_status !== 'future') return;

    // Verify membership and post date
    $has_access = false;
    if (is_user_logged_in()) {
        $has_access = pmpro_hasMembershipLevel(array(2, 3));
    }

    $post_time = strtotime($post->post_date);
    $cutoff_time = current_time('timestamp') + (7 * DAY_IN_SECONDS);

    if (!$has_access || $post_time > $cutoff_time) {
        global $wp_query;
        $wp_query->set_404();
        status_header(404);
        nocache_headers();
    }
}
add_action('wp', 'pmpro_check_single_future_post');

Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *