Skip to content
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

Email notifications #232

Merged
merged 24 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a31eef6
Update the get_attendees method
amieiro Apr 23, 2024
a2609d1
Add the email notifition classes
amieiro Apr 23, 2024
84d4a3e
Update a check to send email notifications
amieiro Apr 23, 2024
a39aac1
Merge branch 'trunk' into email-notifications
amieiro Apr 23, 2024
5f7b40c
Move the email sending to single events
amieiro Apr 25, 2024
ec28aa9
Merge branch 'trunk' into email-notifications
amieiro Apr 25, 2024
5cf8719
Move the create_update_scheduled_notifications call at the end of sav…
amieiro Apr 25, 2024
3704490
Move the event schedule call
amieiro Apr 25, 2024
3dd38b0
Update the scheduled events when the user update an event
amieiro Apr 25, 2024
c77f09c
Delete the scheduled emails when an event is deleted
amieiro Apr 25, 2024
27dd511
Check if the event exists before enqueing the email notifications
amieiro Apr 26, 2024
42aa2df
Add the option to the if the scheduled emails has been deleted
amieiro Apr 26, 2024
057aa86
Update some checks and some strings
amieiro Apr 29, 2024
eb7f0ba
Add a "," in the greetings
amieiro May 6, 2024
a871ce1
Update the email text
amieiro May 6, 2024
c7d2426
Update the email subject
amieiro May 6, 2024
7f53e3f
Update the email message
amieiro May 6, 2024
17177e0
Update email message
amieiro May 6, 2024
05bf80c
Update the event link in the email message
amieiro May 6, 2024
81d71f1
Get attendee-repository.php and attendee.php files from trunk
amieiro May 6, 2024
f411833
Merge branch 'trunk' into email-notifications
amieiro May 6, 2024
a12d9a9
Fix lint
amieiro May 6, 2024
274bb49
Add the Notifications_Schedule object in the event-form-handler const…
amieiro May 6, 2024
0a0a1c5
Check if the event exists before sending emails to its attendees
amieiro May 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions autoload.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
require_once __DIR__ . '/includes/event/event-repository.php';
require_once __DIR__ . '/includes/event/event-repository-cached.php';
require_once __DIR__ . '/includes/event/event-form-handler.php';
require_once __DIR__ . '/includes/notifications/notifications-schedule.php';
require_once __DIR__ . '/includes/notifications/notifications-send.php';
require_once __DIR__ . '/includes/event/event-capabilities.php';
require_once __DIR__ . '/includes/stats/stats-calculator.php';
require_once __DIR__ . '/includes/stats/stats-importer.php';
Expand Down
13 changes: 11 additions & 2 deletions includes/event/event-form-handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
use DateTimeZone;
use Exception;
use WP_Error;
use Wporg\TranslationEvents\Attendee\Attendee_Repository;
use Wporg\TranslationEvents\Notifications\Notifications_Schedule;
use Wporg\TranslationEvents\Stats\Stats_Calculator;
use Wporg\TranslationEvents\Urls;

class Event_Form_Handler {
private Event_Repository_Interface $event_repository;
private Attendee_Repository $attendee_repository;
private Notifications_Schedule $notifications_schedule;

public function __construct( Event_Repository_Interface $event_repository ) {
$this->event_repository = $event_repository;
public function __construct( Event_Repository_Interface $event_repository, Attendee_Repository $attendee_repository ) {
$this->event_repository = $event_repository;
$this->attendee_repository = $attendee_repository;
$this->notifications_schedule = new Notifications_Schedule( $this->event_repository );
}

public function handle( array $form_data ): void {
Expand Down Expand Up @@ -75,6 +81,7 @@ public function handle( array $form_data ): void {
} else {
$response_message = esc_html__( 'Event deleted successfully.', 'gp-translation-events' );
$event_status = 'deleted';
$this->notifications_schedule->delete_scheduled_emails( $event_id );
}
} else {
// Create or update event.
Expand Down Expand Up @@ -115,6 +122,7 @@ public function handle( array $form_data ): void {
return;
}
$response_message = esc_html__( 'Event created successfully.', 'gp-translation-events' );
$this->notifications_schedule->schedule_emails( $result );
}
if ( 'edit_event' === $action ) {
$event = $this->event_repository->get_event( $new_event->id() );
Expand All @@ -139,6 +147,7 @@ public function handle( array $form_data ): void {
return;
}
$response_message = esc_html__( 'Event updated successfully', 'gp-translation-events' );
$this->notifications_schedule->schedule_emails( $result );
}

$event_id = $new_event->id();
Expand Down
72 changes: 72 additions & 0 deletions includes/notifications/notifications-schedule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Wporg\TranslationEvents\Notifications;

use Wporg\TranslationEvents\Event\Event_Repository_Interface;

class Notifications_Schedule {
private Event_Repository_Interface $event_repository;

/**
* Notifications_Schedule constructor.
*
* @param Event_Repository_Interface $event_repository Event repository.
*/
public function __construct( Event_Repository_Interface $event_repository ) {
$this->event_repository = $event_repository;
}

/**
* Schedule emails for events.
*
* @param int $post_id Post ID.
*
* @return void
*/
public function schedule_emails( int $post_id ) {
$event = $this->event_repository->get_event( $post_id );
if ( ! $event ) {
return;
}

$this->delete_scheduled_emails( $post_id );
if ( 'publish' === get_post_status( $post_id ) ) {
$args = array(
'post_id' => $post_id,
);
$now = time();
$new_next_1h_schedule = $event->start()->getTimestamp() - HOUR_IN_SECONDS;
$new_next_24h_schedule = $event->start()->getTimestamp() - 24 * HOUR_IN_SECONDS;
if ( $new_next_1h_schedule > $now ) {
wp_schedule_single_event( $new_next_1h_schedule, 'wporg_gp_translation_events_email_notifications_1h', $args );
}
if ( $new_next_24h_schedule > $now ) {
wp_schedule_single_event( $new_next_24h_schedule, 'wporg_gp_translation_events_email_notifications_24h', $args );
}
}
}

/**
* Delete scheduled emails for events.
*
* @param int $post_id Post ID.
*
* @return void
*/
public function delete_scheduled_emails( int $post_id ): void {
$args = array(
'post_id' => $post_id,
);

$unscheduled_1h = false;
$unscheduled_24h = false;
$next_1h_schedule = wp_next_scheduled( 'wporg_gp_translation_events_email_notifications_1h', $args );
$next_24h_schedule = wp_next_scheduled( 'wporg_gp_translation_events_email_notifications_24h', $args );
if ( $next_1h_schedule ) {
$unscheduled_1h = wp_unschedule_event( $next_1h_schedule, 'wporg_gp_translation_events_email_notifications_1h', $args );
}
if ( $next_24h_schedule ) {
$unscheduled_24h = wp_unschedule_event( $next_24h_schedule, 'wporg_gp_translation_events_email_notifications_24h', $args );
}
}
}
177 changes: 177 additions & 0 deletions includes/notifications/notifications-send.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php

namespace Wporg\TranslationEvents\Notifications;

use DateTime;
use DateTimeZone;
use Wporg\TranslationEvents\Attendee\Attendee;
use Wporg\TranslationEvents\Attendee\Attendee_Repository;
use Wporg\TranslationEvents\Event\Event;
use Wporg\TranslationEvents\Event\Event_Repository_Interface;
use WP_User;
use Wporg\TranslationEvents\Event\Event_Start_Date;

class Notifications_Send {

private Attendee_Repository $attendee_repository;
private Event_Repository_Interface $event_repository;

/**
* Notifications_Send constructor.
*
* @param Event_Repository_Interface $event_repository Event repository.
* @param Attendee_Repository $attendee_repository Attendee repository.
*/
public function __construct(
Event_Repository_Interface $event_repository,
Attendee_Repository $attendee_repository
) {
$this->event_repository = $event_repository;
$this->attendee_repository = $attendee_repository;
add_action( 'wporg_gp_translation_events_email_notifications_1h', array( $this, 'send_notifications' ), 10, 1 );
add_action( 'wporg_gp_translation_events_email_notifications_24h', array( $this, 'send_notifications' ), 10, 1 );
}

/**
* Send notifications to the attendees of the event.
*
* @param int $post_id Post ID.
*
* @return void
* @throws \Exception
*/
public function send_notifications( int $post_id ) {
$event = $this->event_repository->get_event( $post_id );
if ( null === $event ) {
return;
}
$attendees = $this->attendee_repository->get_attendees( $event->id() );
foreach ( $attendees as $attendee ) {
$this->send_email_notification( $event, $attendee );
}
}

/**
* Send an email notification to the attendee of the event.
*
* @param Event $event The event.
* @param Attendee $attendee The attendee.
*
* @return void
*/
public function send_email_notification( Event $event, Attendee $attendee ): void {
$user = get_user_by( 'ID', $attendee->user_id() );
$subject = $this->get_email_subject( $event );
$message = $this->get_email_message( $user, $event );
wp_mail(
$user->user_email,
$subject,
$message,
'Content-Type: text/html'
);
}

/**
* Get the email subject.
*
* @param Event $event The event.
*
* @return string
*/
private function get_email_subject( Event $event ): string {
$subject = sprintf(
// translators: %s: Event title.
esc_html__( 'Translation Event Coming Up: %s', 'gp-translation-events' ),
esc_html( $event->title() )
);

return $subject;
}

/**
* Get the email message.
*
* @param WP_User $user The user.
* @param Event $event The event.
*
* @return string
*/
private function get_email_message( WP_User $user, Event $event ): string {
$start_date = $event->start();
// translators: %s: User display name.
$message = sprintf( esc_html__( 'Hi %s,', 'gp-translation-events' ), $user->display_name );
$message .= '<br><br>';
amieiro marked this conversation as resolved.
Show resolved Hide resolved
$message .= esc_html(
sprintf(
// translators: %s is the event title.
__( 'We are sending you this e-mail because you have signed up for the translation event "%1$s".', 'gp-translation-events' ),
$event->title()
)
);
$message .= ' ';
$message .= esc_html(
sprintf(
// translators: %s: Time until event starts.
__( 'The event will start in %s.', 'gp-translation-events' ),
$this->calculate_time_until_event( $event->start() )
)
);
$message .= ' ';
$message .= esc_html__( "We're looking forward to translating with you!", 'gp-translation-events' );
$message .= '<br>';
amieiro marked this conversation as resolved.
Show resolved Hide resolved
$local_start_date = $event->start()->setTimezone( new DateTimeZone( $event->timezone()->getName() ) );
$message .= sprintf(
// translators: %1$s: Event start date in 'Y-m-d H:i' format. %2$s: Event timezone name.
esc_html__( 'The event will start at %1$s (local %2$s time).', 'gp-translation-events' ),
$local_start_date->format( 'Y-m-d H:i' ),
$local_start_date->getTimezone()->getName()
);
$message .= '<br><br>';
$message .= wp_kses(
sprintf(
// translators: %1$s: Event permalink.
__( 'You can get more information about the event or stop attending go to <a href="%1$s">%1$s</a>.', 'gp-translation-events' ),
esc_url( home_url( gp_url( wp_make_link_relative( get_the_permalink( $event->id() ) ) ) ) )
),
array( 'a' => array( 'href' => array() ) )
);
$message .= '<br><br>';
$message .= esc_html__( 'Have a nice day', 'gp-translation-events' );
$message .= '<br><br>';
$message .= esc_html__( 'The Global Polyglots Team', 'gp-translation-events' );
$message .= '<br>';

return $message;
}

/**
* Calculate the time until the event starts.
*
* @param Event_Start_Date $start_date The start date of the event.
*
* @return string
*/
private function calculate_time_until_event( Event_Start_Date $start_date ): string {
$now = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
$diff = $start_date->diff( $now );
$days_to_start = $diff->days;
$hours_to_start = $diff->h;
$minutes_to_start = $diff->i;
$message = '';
if ( $days_to_start >= 1 ) {
// translators: %d: Number of days.
$message .= sprintf( _n( '%d day', '%d days', $days_to_start, 'gp-translation-events' ), $days_to_start );
} elseif ( $hours_to_start > 1 ) {
// translators: %d: Number of hours.
$message .= sprintf( esc_html__( '%d hours', 'gp-translation-events' ), $hours_to_start );
} elseif ( 1 === $hours_to_start ) {
// translators: %d: Number of minutes.
$message .= sprintf( _n( '1 hour and %d minute', '1 hour and %d minutes', $minutes_to_start, 'gp-translation-events' ), $minutes_to_start );
} else {
// translators: %d: Number of minutes.
$message .= sprintf( _n( '%d minute', '%d minutes', $minutes_to_start, 'gp-translation-events' ), $minutes_to_start );
}

return $message;
}
}
12 changes: 10 additions & 2 deletions wporg-gp-translation-events.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use Wporg\TranslationEvents\Event\Event_Form_Handler;
use Wporg\TranslationEvents\Event\Event_Repository_Cached;
use Wporg\TranslationEvents\Event\Event_Repository_Interface;
use Wporg\TranslationEvents\Notifications\Notifications_Send;
use Wporg\TranslationEvents\Stats\Stats_Calculator;
use Wporg\TranslationEvents\Stats\Stats_Listener;

Expand Down Expand Up @@ -69,6 +70,7 @@ public function __construct() {
add_action( 'wp_ajax_nopriv_submit_event_ajax', array( $this, 'submit_event_ajax' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'register_translation_event_js' ) );
add_action( 'init', array( $this, 'register_event_post_type' ) );
add_action( 'init', array( $this, 'send_notifications' ) );
add_action( 'add_meta_boxes', array( $this, 'event_meta_boxes' ) );
add_action( 'save_post', array( $this, 'save_event_meta_boxes' ) );
add_action( 'transition_post_status', array( $this, 'event_status_transition' ), 10, 3 );
Expand Down Expand Up @@ -183,7 +185,6 @@ public function save_event_meta_boxes( int $post_id ) {
return;
}
}

$fields = array( 'event_start', 'event_end' );
foreach ( $fields as $field ) {
if ( isset( $_POST[ $field ] ) ) {
Expand All @@ -196,7 +197,7 @@ public function save_event_meta_boxes( int $post_id ) {
* Handle the event form submission for the creation, editing, and deletion of events. This function is called via AJAX.
*/
public function submit_event_ajax() {
$form_handler = new Event_Form_Handler( self::get_event_repository() );
$form_handler = new Event_Form_Handler( self::get_event_repository(), self::get_attendee_repository() );
// Nonce verification is done by the form handler.
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$form_handler->handle( $_POST );
Expand Down Expand Up @@ -341,6 +342,13 @@ public function add_active_events_current_user(): void {
);
}

/**
* Send notifications for the events.
*/
public function send_notifications() {
new Notifications_Send( self::get_event_repository(), self::get_attendee_repository() );
}

/**
* Add the event meta keys to the list of meta keys to keep in post revisions.
*
Expand Down
Loading