'; $html .= '
'; $html .= '
'; $html .= '' . esc_html( Settings::get( 'consent_text' ) ) . ''; $html .= '

'; return $field . $html; } /** * Record the subscription intent when a comment is saved. * * Nothing is subscribed (and no email sent) until the comment is approved. * * @param int $comment_id Comment ID. * @param int|string $approved Approval status. */ public function handle_comment( $comment_id, $approved ) { if ( 'spam' === $approved ) { return; } // The core comment form carries no nonce (page-cache friendly); this only records an opt-in tied to the submitted comment. // phpcs:disable WordPress.Security.NonceVerification.Missing $want_replies = ! empty( $_POST['replyping_replies'] ); $want_post = ! empty( $_POST['replyping_post'] ); // phpcs:enable WordPress.Security.NonceVerification.Missing if ( ! $want_replies && ! $want_post ) { return; } $comment = get_comment( $comment_id ); if ( ! $comment || ! comments_open( (int) $comment->comment_post_ID ) ) { return; } $email = strtolower( sanitize_email( $comment->comment_author_email ) ); if ( ! is_email( $email ) || strlen( $email ) > 190 ) { return; } $trusted = false; if ( is_user_logged_in() ) { $user = wp_get_current_user(); $trusted = ( strtolower( $user->user_email ) === $email ); } update_comment_meta( $comment_id, self::INTENT_META, array( 'replies' => $want_replies, 'post' => $want_post, 'trusted' => $trusted, ) ); if ( '1' === (string) $approved ) { $this->process_intent( (int) $comment_id ); } } /** * Comment status changed: act on or drop the stored intent. * * @param string $new_status New status. * @param string $old_status Old status. * @param \WP_Comment $comment Comment. */ public function on_transition( $new_status, $old_status, $comment ) { if ( ! $comment instanceof \WP_Comment ) { return; } if ( 'approved' === $new_status && 'approved' !== $old_status ) { $this->process_intent( (int) $comment->comment_ID ); } elseif ( in_array( $new_status, array( 'spam', 'trash' ), true ) ) { delete_comment_meta( (int) $comment->comment_ID, self::INTENT_META ); } } /** * Turn a stored intent into subscriptions (comment is approved). * * @param int $comment_id Comment ID. */ private function process_intent( $comment_id ) { $intent = get_comment_meta( $comment_id, self::INTENT_META, true ); if ( ! is_array( $intent ) ) { return; } delete_comment_meta( $comment_id, self::INTENT_META ); $comment = get_comment( $comment_id ); if ( ! $comment ) { return; } $email = strtolower( sanitize_email( $comment->comment_author_email ) ); if ( ! is_email( $email ) || strlen( $email ) > 190 ) { return; } $post_id = (int) $comment->comment_post_ID; $trusted = ! empty( $intent['trusted'] ); $ip = (string) $comment->comment_author_IP; if ( ! empty( $intent['replies'] ) ) { self::subscribe( $post_id, $comment_id, $email, 'replies', $trusted, $ip ); } if ( ! empty( $intent['post'] ) ) { self::subscribe( $post_id, 0, $email, 'post', $trusted, $ip ); } } /** * Create or refresh a subscription. * * Guests always confirm. Only a logged-in user using their own account email * may skip confirmation, and only when double opt-in is switched off. * * @param int $post_id Post ID. * @param int $comment_id Parent comment ID (0 for whole post). * @param string $email Email. * @param string $type 'replies' or 'post'. * @param bool $trusted Logged-in user using own account email. * @param string $ip Commenter IP (for rate limiting). * @return void */ public static function subscribe( $post_id, $comment_id, $email, $type, $trusted = false, $ip = '' ) { if ( strlen( $email ) > 190 ) { return; } $needs_confirm = ! ( $trusted && ! Settings::get( 'double_optin' ) ); $status = $needs_confirm ? 'pending' : 'active'; $token = Db::new_token(); $existing = Db::find( $post_id, $comment_id, $email, $type ); if ( $existing ) { if ( 'active' === $existing->status ) { return; // Silent: never reveal existing subscriptions. } if ( 'pending' === $existing->status && $needs_confirm && ( time() - strtotime( $existing->created_at . ' UTC' ) ) < DAY_IN_SECONDS ) { return; // Keep the existing token valid; do not re-send. } Db::update_status( $existing->id, $status, Db::hash_token( $token ) ); $id = (int) $existing->id; } else { $id = Db::insert( array( 'post_id' => $post_id, 'comment_id' => $comment_id, 'email' => $email, 'type' => $type, 'token_hash' => Db::hash_token( $token ), 'status' => $status, 'consent_text' => (string) Settings::get( 'consent_text' ), ) ); } if ( ! $id ) { return; } $sub = Db::get( $id ); if ( ! $sub ) { return; } do_action( 'replyping_subscribed', $sub ); if ( $needs_confirm ) { Mailer::send_confirmation( $sub, $token, $ip ); } else { do_action( 'replyping_confirmed', $sub ); } } }