Skip to content

Commit

Permalink
Merge pull request #7615 from Automattic/fix/prevent-welcome-email-fo…
Browse files Browse the repository at this point in the history
…r-future-access

Prevent sending welcome email immediately on enrole in case their access starts in the future
  • Loading branch information
Imran92 authored Nov 7, 2024
2 parents 745f967 + 5508b2d commit e6db554
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 47 deletions.
18 changes: 10 additions & 8 deletions includes/class-sensei-emails.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ function send( $to, $subject, $message, $headers = "Content-Type: text/html\r\n"
// Set content type
$this->_content_type = $content_type;

// Filters for the email
// Filters for the email.
add_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
add_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
add_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) );
Expand All @@ -189,20 +189,22 @@ function send( $to, $subject, $message, $headers = "Content-Type: text/html\r\n"
*
* @hook sensei_send_emails
*
* @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.
* @param {string} $identifier Unique identifier of the email, not for legacy emails.
* @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.
* @param {string} $identifier Unique identifier of the email, not for legacy emails.
* @param {array} $replacements The replacements values for the email, not for legacy emails.
*
* @return {bool} Whether to send the email or not.
*/
if ( apply_filters( 'sensei_send_emails', true, $to, $subject, $message, 'legacy-email' ) ) {
if ( apply_filters( 'sensei_send_emails', true, $to, $subject, $message, 'legacy-email', [] ) ) {

wp_mail( $to, $subject, $message, $headers, $attachments );

}

// Unhook filters
// Unhook filters.
remove_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
remove_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
remove_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) );
Expand Down
2 changes: 1 addition & 1 deletion includes/internal/emails/class-email-sender.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public function send_email( $email_name, $replacements, $usage_tracking_type ) {
/*
* For documentation of the filter check class-sensei-emails.php file.
*/
if ( apply_filters( 'sensei_send_emails', true, $recipient, $subject, $message, $email_name ) ) {
if ( apply_filters( 'sensei_send_emails', true, $recipient, $subject, $message, $email_name, $replacement ) ) {
wp_mail(
$recipient,
$subject,
Expand Down
24 changes: 20 additions & 4 deletions includes/internal/emails/generators/class-course-welcome.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,39 @@ class Course_Welcome extends Email_Generators_Abstract {
* @return void
*/
public function init() {
$this->maybe_add_action( 'sensei_user_course_start', [ $this, 'welcome_to_course_for_student' ], 10, 2 );
$this->maybe_add_action( 'sensei_course_enrolment_status_changed', [ $this, 'welcome_to_course_for_student' ], 10, 3 );

// Send welcome email on the day the student gets access to the course.
$this->maybe_add_action( 'sensei_pro_course_access_start_student_email_send', [ $this, 'welcome_to_course_for_student' ], 10, 2 );
}

/**
* Send email to student when they are enrolled in a course.
*
* @access private
*
* @param int $student_id The student ID.
* @param int $course_id The course ID.
* @param int $student_id The student ID.
* @param int $course_id The course ID.
* @param bool $is_enrolled Whether the student is enrolled in the course.
*/
public function welcome_to_course_for_student( $student_id, $course_id ) {
public function welcome_to_course_for_student( $student_id, $course_id, $is_enrolled = true ) {
if ( ! $is_enrolled ) {
return;
}

$course = get_post( $course_id );
if ( ! $course || 'publish' !== $course->post_status ) {
return;
}

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
$original_course_id = apply_filters( 'wpml_original_element_id', null, $course_id, 'post_course' );

// Prevent sending emails for the copy courses created by WPML for translations.
if ( $original_course_id && intval( $original_course_id ) !== $course_id ) {
return;
}

$student = new \WP_User( $student_id );
$teacher_id = $course->post_author;
$teacher = new \WP_User( $teacher_id );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,32 +44,11 @@ abstract class Email_Generators_Abstract {
protected $repository;

/**
* Action name.
* All registered actions.
*
* @var string
*/
private $action = '';

/**
* Callback name.
*
* @var callable
*/
private $callback = '';

/**
* Priority.
*
* @var int
* @var array
*/
private $priority = 10;

/**
* Accepted arguments.
*
* @var int
*/
private $accepted_args = 1;
private $actions = [];

/**
* Email_Generators_Abstract constructor.
Expand Down Expand Up @@ -121,10 +100,11 @@ public function is_email_active() {
* @param int $accepted_args Accepted arguments.
*/
protected function maybe_add_action( $action, $callback, $priority = 10, $accepted_args = 1 ) {
$this->action = $action;
$this->callback = $callback;
$this->priority = $priority;
$this->accepted_args = $accepted_args;
$this->actions[ $action ] = [
'callback' => $callback,
'priority' => $priority,
'accepted_args' => $accepted_args,
];

add_action( $action, [ $this, 'add_action_if_email_active' ], 1 );
}
Expand All @@ -137,8 +117,11 @@ protected function maybe_add_action( $action, $callback, $priority = 10, $accept
* @internal
*/
public function add_action_if_email_active() {
if ( $this->is_email_active() ) {
add_action( $this->action, $this->callback, $this->priority, $this->accepted_args );
$current_action_name = current_filter();

if ( $this->is_email_active() && array_key_exists( $current_action_name, $this->actions ) ) {
$action = $this->actions[ $current_action_name ];
add_action( $current_action_name, $action['callback'], $action['priority'], $action['accepted_args'] );
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,13 @@ public function testInit_WhenCalled_AddsHooksForInitializingIndividualEmails() {
$generator->init();

/* Assert. */
do_action( 'sensei_user_course_start', 1, 1 );
$priority = has_action( 'sensei_user_course_start', [ $generator, 'welcome_to_course_for_student' ] );
self::assertSame( 10, $priority );
do_action( 'sensei_course_enrolment_status_changed', 1, 1 );
do_action( 'sensei_pro_course_access_start_student_email_send', 1, 1 );

$priority_for_immediate_start = has_action( 'sensei_course_enrolment_status_changed', [ $generator, 'welcome_to_course_for_student' ] );
$priority_for_access_start = has_action( 'sensei_pro_course_access_start_student_email_send', [ $generator, 'welcome_to_course_for_student' ] );
self::assertSame( 10, $priority_for_immediate_start );
self::assertSame( 10, $priority_for_access_start );
}

public function testWelcomeToCourseForStudent_WhenCalled_CallsSenseiEmailSendFilterWithMatchingArguments() {
Expand Down Expand Up @@ -102,16 +106,98 @@ public function testWelcomeToCourseForStudent_WhenCalled_CallsSenseiEmailSendFil
$generator = new Course_Welcome( $email_repository );

$actual_data = [];
$filter = function( $email, $options ) use ( &$actual_data ) {
$filter = function ( $email, $options ) use ( &$actual_data ) {
$actual_data = [
'email' => $email,
'options' => $options,
];
};
add_filter( 'sensei_email_send', $filter, 10, 2 );

/* Act. */
$generator->welcome_to_course_for_student( $student_id, $course_id );

/* Assert. */
$expected = [
'email' => 'course_welcome',
'options' => [
'test@a.com' => [
'teacher:id' => $teacher_id,
'teacher:displayname' => 'Test Teacher',
'student:id' => $student_id,
'student:displayname' => 'Test Student',
'course:id' => $course_id,
'course:name' => '“Course with Special Characters…?”',
'course:url' => esc_url(
get_permalink( $course_id )
),
],
],
];
self::assertSame( $expected, $actual_data );

/* Cleanup. */
remove_filter( 'sensei_email_send', $filter, 10 );
$factory->tearDown();
}

public function testWelcomeToCourseForStudent_WhenCalledForWPMLCopy_CallsEmailSendActionOnlyForTheRealCourse() {
/* Arrange. */
$factory = new \Sensei_Factory();
$student_id = $factory->user->create(
[
'display_name' => 'Test Student',
'user_email' => 'test@a.com',
]
);
$teacher_id = $factory->user->create(
[
'display_name' => 'Test Teacher',
]
);
$course_id = $factory->course->create(
[
'post_title' => '“Course with Special Characters…?”',
'post_author' => $teacher_id,
]
);

$course_id_translated = $factory->course->create(
[
'post_title' => '“Course with Special Characters…? Translated”',
'post_author' => $teacher_id,
]
);

$email_repository = $this->createMock( Email_Repository::class );
$email_repository->method( 'get' )->with( 'course_welcome' )->willReturn( new \WP_Post( (object) [ 'post_status' => 'publish' ] ) );

$generator = new Course_Welcome( $email_repository );

$actual_data = [];
$filter = function ( $email, $options ) use ( &$actual_data ) {
$actual_data = [
'email' => $email,
'options' => $options,
];
};
add_filter( 'sensei_email_send', $filter, 10, 2 );

add_filter(
'wpml_original_element_id',
function ( $modifiable, $current_course_id ) use ( $course_id_translated, $course_id ) {
if ( $current_course_id === $course_id_translated ) {
return "$course_id";
}
return $current_course_id;
},
10,
3
);

/* Act. */
$generator->welcome_to_course_for_student( $student_id, $course_id );
$generator->welcome_to_course_for_student( $student_id, $course_id_translated );

/* Assert. */
$expected = [
Expand Down

0 comments on commit e6db554

Please sign in to comment.