Leave a Reply

1 comment

  1. When you set a post as password protected, the protection happen on get_the_content() function. There WordPress check for a post password cookie, and if not set, not valid or expired then show the password form.

    This password form is submitted to wp-login.php, there a cookie is setted according to the password wrote in the form, then the request is redirected to the post again.

    The process can be described like so:

    1. go to post page
    2. call the_content()
    3. check the cookie
    4. if not valid show the password form
    5. submit form to wp_login.php
    6. wp_login.php
    7. set the cookie based on pwd submitted and redirect to post page
    8. start again form #1

    What we can do:

    • at point #4 use the hook 'the_password_form' to edit the output of the form, adding a field for the email and a hidden field with the post id (at this point we are inside get_the_content function so we have access to global post variable)
    • Unluckily at point #3 there we can’t change the result of cookie check (or at least we can’t in a easy and reliable way). But at point #7 WordPress has a filter hook that allow to set the cookie expiration: if we set that time to a past timestamp, then the cookie will not be set (and if existx it will be deleted) and so the validation will fail. So we can use that hook to check the email submitted via the form and thanks to the post id in the hidden field we can compare it with the emails in the meta, if the email si not given or is wrong we return a past timestamp.

    First step:

    /**
     * Customize the form, adding a field for email and a hidden field with the post id
     */
    add_filter( 'the_password_form', function( $output ) {
    
      unset( $GLOBALS['the_password_form'] );
      global $post;
      $submit = '<input type="submit" name="Submit" value="' . esc_attr__('Submit') . '" /></p>';
      $hidden = '<input type="hidden" name="email_res_postid" value="' . $post->ID . '">';
      $email = '</p><p><label for="email_res">' . __( 'Email:' );
      $email .= '<input name="email_res" id="email_res" type="text" size="20" /></label></p><p>';
      return str_replace( $submit, $hidden . $email . $submit, $output );
    
    }, 0 );
    

    And the second:

    /**
     * Set the post password cookie expire time based on the email
     */
    add_filter( 'post_password_expires', function( $valid ) {
    
      $postid = filter_input( INPUT_POST, 'email_res_postid', FILTER_SANITIZE_NUMBER_INT );
      $email = filter_input( INPUT_POST, 'email_res', FILTER_SANITIZE_STRING );
      // a timestamp in the past
      $expired = time() - 10 * DAY_IN_SECONDS;
      if ( empty( $postid ) || ! is_numeric( $postid ) ) {
          // empty or bad post id, return past timestamp
          return $expired;
      }
      if ( empty($email) || ! filter_var($email, FILTER_VALIDATE_EMAIL) ) {
          // empty or bad email id, return past timestamp
          return $expired;
      }
      // get the allowed emails
      $allowed = array_filter( (array)get_post_meta( $postid, 'allow_email' ), function( $e ) {
        if ( filter_var( $e, FILTER_VALIDATE_EMAIL) ) return $e;
      });
      if ( ! empty( $allowed ) ) { // some emails are setted, let's check it
        // if the emails posted is good return the original expire time
        // otherwise  return past timestamp
        return in_array( $email, $allowed ) ? $valid : $expired;
      }
      // no emails are setted, return the original expire time
      return $valid;
    
    }, 0 );
    

    We are done.

    Now create a post, save as password protected and set some allowed emails in custom fields using the key 'allow_email'. There’s no limit on number of the emails you can add…


    Settings:

    Custom fields to allow email post protection


    post protection via password


    Result (TwentyThirteen with no additional styling):

    Result in TwentyThirteen with no additional styling