-
Notifications
You must be signed in to change notification settings - Fork 196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Don't dispatch e-mails related to Temporary Users #6855
Conversation
Codecov Report
@@ Coverage Diff @@
## trunk #6855 +/- ##
============================================
+ Coverage 47.37% 47.47% +0.09%
- Complexity 10124 10142 +18
============================================
Files 499 499
Lines 35995 36017 +22
Branches 283 283
============================================
+ Hits 17054 17098 +44
+ Misses 18729 18707 -22
Partials 212 212
... and 5 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
Or even for e-mails related to the guest_user
b19f3be
to
82538d8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few comments to help with the review process. =)
$subject = $this->get_email_subject( $email_post, $replacement ); | ||
$message = $this->get_email_body( $email_post, $replacement ); | ||
|
||
/* | ||
* For documentation of the filter check class-sensei-emails.php file. | ||
*/ | ||
if ( apply_filters( 'sensei_send_emails', true, $recipient, $subject, $message ) ) { | ||
wp_mail( | ||
$recipient, | ||
$subject, | ||
$message, | ||
$this->get_email_headers(), | ||
null | ||
); | ||
sensei_log_event( 'email_send', [ 'type' => $usage_tracking_type ] ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the "real" fix for this issue.
Basically, the Email Sender added in a recent release wasn't using the filter sensei_send_emails
, which was used by the guest/preview classes to block e-mails from...guest users.
* @param bool $send_email Whether to send the email or not. | ||
* @param mixed $to The email address(es) to send the email to. | ||
* @param mixed $subject The subject of the email. | ||
* @param mixed $message The message of the email. | ||
*/ | ||
if ( apply_filters( 'sensei_send_emails', $send_email, $to, $subject, $message ) ) { | ||
if ( apply_filters( 'sensei_send_emails', true, $to, $subject, $message ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a small change to inline the variable and improve the PHPDoc documentation. It is quite an old filter. :)
/** | ||
* Detect if the e-mail attributes relate to an e-mail from a temporary user. | ||
* | ||
* @access private | ||
* @since $$next-version$$ | ||
* | ||
* @param array $atts Email attributes. | ||
* @param string $email_domain The email domain to search for. | ||
* @return boolean Whether to block the email or not. | ||
*/ | ||
public static function should_block_email( $atts, $email_domain ) { | ||
$emails = $atts['to']; | ||
if ( ! is_array( $emails ) ) { | ||
$emails = explode( ',', $emails ); | ||
} | ||
if ( ! empty( $atts['headers'] ) ) { | ||
$headers = $atts['headers']; | ||
if ( ! is_array( $headers ) ) { | ||
// Explode the headers out, so this function can take | ||
// both string headers and an array of headers. | ||
$temp_headers = explode( "\n", str_replace( "\r\n", "\n", $headers ) ); | ||
} else { | ||
$temp_headers = $headers; | ||
} | ||
// If it's actually got contents. | ||
if ( ! empty( $temp_headers ) ) { | ||
foreach ( $temp_headers as $name => $content ) { | ||
if ( is_int( $name ) && str_contains( $content, ':' ) ) { | ||
list ( $name, $content) = explode( ':', trim( $content ), 2 ); | ||
} | ||
|
||
// Cleanup crew. | ||
$name = trim( $name ); | ||
$content = trim( $content ); | ||
|
||
if ( in_array( strtolower( $name ), [ 'from', 'cc', 'bcc', 'reply-to' ], true ) ) { | ||
$emails = array_merge( (array) $emails, explode( ',', $content ) ); | ||
} | ||
} | ||
} | ||
} | ||
foreach ( $emails as $address ) { | ||
if ( preg_match( '/(.*)<(.+)>/', $address, $matches ) && count( $matches ) === 3 ) { | ||
$address = $matches[2]; | ||
} | ||
if ( str_ends_with( $address, '@' . $email_domain ) ) { | ||
// If this is an e-mail address for a temporary user, don't send it. | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is primarily an attempt on blocking any third party plugins (maybe something related to automation?) from dispatching e-mails (related?) to the temporary user.
We basically look out at the $to
, From
, Cc
, Bcc
and Reply-To
headers to look out for any e-mails used by temporary users, and, if there's any reference there, we consider the e-mail "blocked".
if ( $this->is_current_user_guest() ) { | ||
// If this e-mail is being dispatched while the current user is a guest, just... don't send it. | ||
return false; | ||
} | ||
if ( Sensei_Temporary_User::should_block_email( $atts, self::EMAIL_DOMAIN ) ) { | ||
// If this e-mail is being dispatched to a guest user, don't send it. | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this block, we're returning false
so wp_mail
returns false
too. This is considered an "error". It might not be the most friendly way of doing this (for any third party developer debugging this), but I believe it is better than returning true
and faking a possible success..
if ( $this->is_preview_user_active() ) { | ||
// If this e-mail is being dispatched while the current user is a previwe user, just... don't send it. | ||
return false; | ||
} | ||
if ( Sensei_Temporary_User::should_block_email( $atts, self::EMAIL_DOMAIN ) ) { | ||
// If this e-mail is being dispatched to a preview user, don't send it. | ||
return false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the Guest Users situation, in this block, we're returning false
so wp_mail
returns false
too. This is considered an "error". It might not be the most friendly way of doing this (for any third party developer debugging this), but I believe it is better than returning true
and faking a possible success..
@@ -137,7 +146,8 @@ public function init() { | |||
add_action( 'sensei_is_enrolled', [ $this, 'open_course_always_enrolled' ], 10, 3 ); | |||
add_action( 'sensei_can_access_course_content', [ $this, 'open_course_enable_course_access' ], 10, 2 ); | |||
add_action( 'sensei_can_user_manually_enrol', [ $this, 'open_course_user_can_manualy_enroll' ], 10, 2 ); | |||
add_action( 'sensei_send_emails', [ $this, 'skip_sensei_email' ] ); | |||
add_filter( 'sensei_send_emails', [ $this, 'skip_sensei_email' ] ); | |||
add_filter( 'pre_wp_mail', [ $this, 'skip_wp_mail' ], 10, 2 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Notice that I decided to add a pre_wp_mail
filter for guest users and one for preview users.
The idea is that the user might want - for some reason - to disable one or another. I'm not sure why someone would want to do that. But maybe someone can consider that preview users should receive e-mails as usual, while guest users shouldn't? Not sure.
I can quickly change that to a single pre_wp_mail
filter, but, given that most of the logic is shared, for me it seems OK to leave as it is. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good and tests well.
Really appreciate the detailed explanation! 🙇
if ( preg_match( '/(.*)<(.+)>/', $address, $matches ) && count( $matches ) === 3 ) { | ||
$address = $matches[2]; | ||
} | ||
if ( str_ends_with( $address, '@' . $email_domain ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, the str_ends_with
function was introduced in PHP 8.0 but it looks like the symfony/polyfill-php80
is working. Nice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@m1ro Actually, even without symfony/polyfill-php80
this would work well.
The reason? WordPress also implements some polyfills for functions from PHP 8.0...since WP 5.9. Here's the polyfill for str_ends_with
: https://github.com/WordPress/WordPress/blob/f2d315036b0d4bba651852b4d32ffdd58e97b2f1/wp-includes/compat.php#L467-L489 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh nice, TIL!
Fixes #6860
Proposed Changes
add_filter
instead ofadd_action
for hooking on thesensei_send_emails
hook;sensei_send_emails
hook before dispatching e-mail for the new e-mail project;wp_mail
from being triggered for any e-mail related to the guest/preview user or when the guest/preview user is logged in - The idea is to block third-party plugins from sending e-mails to these users;Testing Instructions
Using a plugin build based on this branch's code, and a plugin to log calls to wp_mail (I recommend the email-log plugin):
Create a course thas has the Open Access setting enabled;
As an anonymous user, visit that course's page and "Take the Course". Make sure you can take the course as usual with the guest user created for you;
Make sure welcome e-mails (and any other transactional e-mail, actually) isn't triggered for guest users;
Now, as an administrator/teacher, visit the page of the course you just created and click on "Preview as Student" on the admin bar:
Make sure that no welcome e-mails are triggered for these guest users;
Also make sure that the added unit tests run as expected
Pre-Merge Checklist